6-23代码优化.md 14.2 KB

6-23 代码优化

本文档说明 2026-06-23 对美国版标签(Label)接口的多门店绑定改造:一个标签可绑定多个门店(locationIds 数组),关联表为 fl_label_location


一、背景与目标

项目 说明
原行为 标签仅通过 fl_label.LocationId 绑定单门店
新行为 locationIds 为主字段,落库 fl_label_locationfl_label.LocationId 保留为首项兼容列
兼容 未建 fl_label_location 时仍读/写 LocationId;已建表时列表/筛选/App 打印校验均命中关联表

二、数据库迁移

脚本路径:美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/fl_label_location_scope.sql

步骤 内容
1 创建 fl_label_locationLabelId + LocationId
2 若关联表为空,将现有 fl_label.LocationId 迁移写入关联表

执行前请备份数据库。


三、locationIds 字段规范

入参(POST / PUT)

字段 类型 说明
locationIds string[] 主字段。适用门店 Id(location.Id),全量写入 fl_label_location
locationId string 兼容。与 locationIds 合并;合并后首项写入 fl_label.LocationId
appliedRegionType string ALL / SPECIFIED
regionIds / groupIds string[] Region(fl_group.Id),落库 fl_label_region;可与 locationIds 组合校验

解析规则(ResolveScopeForSaveAsync

  • appliedRegionType=ALL 且未传 region:仅校验/保存 locationIds(可为空表示全部门店)
  • appliedRegionType=SPECIFIED:须传 regionIds 和/或 locationIds;显式 locationIds 须在 Region 展开门店范围内
  • locationIdlocationIds 合并去重,locationId 排在数组首位

出参(GET 列表 / 详情)

字段 说明
locationIds 门店 Id 数组(来自 fl_label_location,无关联表时回退 LocationId
location / locationName 门店名称逗号拼接展示
locationId 兼容:等于 locationIds[0],无门店时为空

四、接口说明

1. 标签列表

项目 内容
方法 GET
路径 /api/app/label
鉴权 Bearer Token

筛选(Query / data)

参数 说明
SkipCount 页码,从 1 起
MaxResultCount 每页条数
GroupId 按 Region 筛选:命中 fl_label_regionfl_label_locationAppliedRegionType=ALL
LocationId 按门店筛选:命中 fl_label_location 或兼容列 LocationId
PartnerId 按 Company 下门店集合筛选(同 Location 逻辑)
ProductId / Keyword / LabelCategoryId / LabelTypeId / TemplateCode / State 其它原有筛选

出参 items[] 新增/变更

字段 说明
locationIds 适用门店 Id 数组
locationName 门店展示名(逗号拼接)
region / regionIds / appliedRegionType Region 范围(逻辑不变)

2. 标签详情

项目 内容
方法 GET
路径 /api/app/label/{id}
路径参数 id = 标签 LabelCode

出参要点

字段 说明
locationIds 适用门店数组
location 门店展示名
locationId 兼容首项
regionIds / groupIds / appliedRegionType Region 范围

3. 新增标签

项目 内容
方法 POST
路径 /api/app/label

Body 示例

{
  "labelName": "Price Label A",
  "templateCode": "TPL_PRICE_2X2",
  "labelCategoryId": "...",
  "labelTypeId": "...",
  "productIds": ["..."],
  "appliedRegionType": "SPECIFIED",
  "regionIds": ["group-id-1"],
  "locationIds": ["loc-id-1", "loc-id-2"],
  "state": true
}

落库

  • fl_label.LocationIdlocationIds[0]
  • fl_label_location ← 全量 locationIds
  • fl_label_regionregionIds(SPECIFIED 时)

4. 编辑标签

项目 内容
方法 PUT
路径 /api/app/label/{id}
Body 同 POST;locationIds 全量覆盖关联表

5. 删除标签

逻辑删除 fl_label,并删除 fl_label_regionfl_label_locationfl_label_product 关联行(表存在时)。


五、App 打印校验

UsAppLabelingAppService.PrintAsync / PreviewAsync 使用 LabelRegionScopeHelper.EnsureLabelAppliesToLocationAsync / LabelAppliesToLocationAsync,支持:

  • AppliedRegionType=ALL 且配置了 fl_label_location:须在关联门店内
  • SPECIFIED:命中 fl_label_locationLocationIdfl_label_region

六、代码改动摘要

文件 改动
LabelRegionScopeHelper.cs 多门店解析/保存/列表筛选/展示;ApplyLabelRegionListFilter 用 SQL EXISTSfl_label_location
LabelAppService.cs CRUD 读写 locationIds;列表筛选走 Helper
LabelCreateInputVo / LabelGetOutputDto / LabelGetListOutputDto 增加 locationIds 等字段
UsAppLabelingAppService.cs 打印/预览门店校验对齐多门店
ILabelAppService.cs XML 注释补充 locationIds

七、向后兼容

场景 行为
仅传 locationId 合并为单元素 locationIds
未执行 SQL 迁移 fl_label_location 时读写 fl_label.LocationId
前端仍读 locationId 详情/列表返回首项,不影响旧页面

八、部署注意

  1. 测试/生产库执行 fl_label_location_scope.sql
  2. dotnet build 编译 FoodLabeling.Application 后重启 Web 进程
  3. 管理端表单改为多选 locationIds;旧单选 locationId 可逐步下线

九、请求示例

登录获取 Token

curl -X POST "http://flus-test.3ffoodsafety.com/api/oauth/Login" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "userName=admin&password=123456"

标签列表(第一页)

curl -G "http://flus-test.3ffoodsafety.com/api/app/label" \
  -H "Authorization: Bearer {token}" \
  --data-urlencode "SkipCount=1" \
  --data-urlencode "MaxResultCount=10"

按门店筛选

curl -G "http://flus-test.3ffoodsafety.com/api/app/label" \
  -H "Authorization: Bearer {token}" \
  --data-urlencode "SkipCount=1" \
  --data-urlencode "MaxResultCount=10" \
  --data-urlencode "LocationId={location.Id}"

新增标签(多门店)

curl -X POST "http://flus-test.3ffoodsafety.com/api/app/label" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "labelName": "Price Label A",
    "templateCode": "TPL_PRICE_2X2",
    "labelCategoryId": "...",
    "labelTypeId": "...",
    "productIds": ["..."],
    "appliedRegionType": "SPECIFIED",
    "regionIds": ["group-id-1"],
    "locationIds": ["loc-id-1", "loc-id-2"],
    "state": true
  }'

编辑标签(全量覆盖 locationIds)

curl -X PUT "http://flus-test.3ffoodsafety.com/api/app/label/{labelCode}" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "labelName": "Price Label A",
    "templateCode": "TPL_PRICE_2X2",
    "labelCategoryId": "...",
    "labelTypeId": "...",
    "productIds": ["..."],
    "locationIds": ["loc-id-1", "loc-id-3"],
    "state": true
  }'

详情

curl -G "http://flus-test.3ffoodsafety.com/api/app/label/{labelCode}" \
  -H "Authorization: Bearer {token}"

响应片段(详情)

{
  "id": "LBL0001",
  "labelName": "Price Label A",
  "locationIds": ["loc-id-1", "loc-id-2"],
  "locationId": "loc-id-1",
  "location": "Store A, Store B",
  "appliedRegionType": "SPECIFIED",
  "regionIds": ["group-id-1"],
  "region": "West Region"
}

十、标签模板列表 500 修复(GET /api/app/label-template

现象

GET /api/app/label-template?SkipCount=1&MaxResultCount=10 返回 500,日志:

Unknown column 'AppliedPartnerType' in 'field list'

对应 SQL 会 SELECT 库中不存在的列:AppliedPartnerTypeAppliedRegionTypeBorderType

MCP 查库(当前测试库)fl_label_template 仅有 AppliedLocationTypePrintOrientation 等基础列; scope 列与 BorderType fl_label_template_partner / fl_label_template_region 表(仅有 fl_label_template_location)。

根因

FlLabelTemplateDbEntity 仍映射 ORM 属性到不存在的列,SqlSugar 生成 SELECT 时全部拼入,未执行 fl_label_template_scope.sql 的库直接报错。

修复说明

改动 说明
FlLabelTemplateDbEntity 删除 AppliedPartnerType / AppliedRegionType / BorderType 属性
LabelTemplateQueryHelper.ProjectListColumns 列表/详情只读查询 显式 Select 真实列
LabelTemplateQueryHelper.ApplyListSorting 默认 IFNULL(LastModificationTime, CreationTime) DESCSorting 白名单
LabelTemplateScopeSchemaHelper 探测 scope / border 列;已迁移时用 raw SQL 读写
LabelTemplateScopeHelper.SaveTemplateScopeAsync 保存关联表后调用 SetAppliedScopeTypesAsync
LabelTemplateAppService.GetListAsync 排序后 ProjectListColumns 再分页

Company/Region 展示与 Id 数组仍由 fl_label_template_location(及已迁移时的 partner/region 关联表)推断,不依赖主表 scope 列。

接口说明

项目 内容
方法 GET
路径 /api/app/label-template
鉴权 Bearer Token

入参(Query)

参数 说明
SkipCount 页码,从 1 起
MaxResultCount 每页条数
Keyword 模板名称/编码模糊搜索
PartnerId / GroupId / LocationId 适用范围筛选
LabelType / State / Sorting 其它筛选与排序(Sorting 白名单字段)

出参 items[] 要点

字段 说明
id / templateCode 模板编码
company / region / location 适用范围展示
partnerIds / regionIds / locationIds 对应 Id 数组
printOrientation 打印方向 vertical / horizontal
sizeText / contentsCount / lastEdited 尺寸、控件数、最近编辑

请求示例

curl -G "http://flus-test.3ffoodsafety.com/api/app/label-template" \
  -H "Authorization: Bearer {token}" \
  --data-urlencode "SkipCount=1" \
  --data-urlencode "MaxResultCount=10"

部署注意

  1. 必须重新编译 FoodLabeling.Application完全重启 Web 进程;若 Yi-SQL 仍出现 AppliedPartnerType,说明旧 DLL 未替换。
  2. 可选:执行 scripts/fl_label_template_scope.sql 启用 Company/Region 主表列与关联表(非列表 500 的必要条件)。
  3. 可选:为 BorderType 增加列后,SetBorderTypeAsync / GetBorderTypeForTemplateAsync 会自动读写;未迁移时详情/预览 border 默认为 none

十一、我的菜单 500 修复(GET /api/app/auth-session/my-menus

现象

GET /api/app/auth-session/my-menus 返回 500,日志:

Unknown column 'r.AccessPermissionCodesJson' in 'field list'

对应 SQL 错误地 SELECT 了 Role.AccessPermissionCodesJson

根因

层级 名称
C# 属性 RoleAggregateRoot.AccessPermissionCodesJson
数据库列 Role.AccessPermissionCodesVARCHAR,JSON 数组字符串)

UserRoleAccessPermissionHelper 直写 SQL 时误用了 C# 属性名作为列名,导致 MySQL 报列不存在。

MCP 查库Role AccessPermissionCodes 列, AccessPermissionCodesJson 列。

修复说明

文件 改动
RoleAccessPermissionSchemaHelper.cs 新增:探测 Role.AccessPermissionCodes 列是否存在
UserRoleAccessPermissionHelper.cs SQL 改为读 AccessPermissionCodes;列不存在时返回空数组
DTO 注释 统一说明库字段名为 AccessPermissionCodes

未执行列迁移的环境(无 AccessPermissionCodes)接口仍可返回菜单树,accessPermissionCodes 为空数组,不 500。

接口说明

项目 内容
方法 GET
路径 /api/app/auth-session/my-menus
鉴权 Bearer Token(须已登录)

出参要点(CurrentUserMenuPermissionsOutputDto

字段 说明
user 当前用户摘要(id、userName、nick、email 等)
roleCodes JWT 中的角色编码列表
role 角色展示名
menus 树形菜单(admin 返回全部未删除菜单;其它用户按 RoleMenu 关联)
permissionCodes 可见菜单的 PermissionCode 去重列表
accessPermissionCodes 绑定角色 AccessPermissionCodes JSON 合并(如 manage_peoplemanage_labels
lastUpdated 用户最后修改时间

accessPermissionCodes 与菜单的关系

  • 菜单可见性仍由 RoleMenu + Menu 决定;
  • accessPermissionCodes 用于前端/Account Management 等「访问权限」勾选项(Region/Location 管理等能力开关);
  • 列未迁移时该数组为空,不影响基础菜单加载。

数据库(可选迁移)

若库中尚无 AccessPermissionCodes 列,可执行:

scripts/repair_after_clear_keep_admin_menu_role.sql 第 1 段,或:

ALTER TABLE `Role`
  ADD COLUMN `AccessPermissionCodes` VARCHAR(2000) NULL
  COMMENT '访问权限 JSON 数组,如 ["manage_labels","manage_people"]';

请求示例

curl -G "http://flus-test.3ffoodsafety.com/api/app/auth-session/my-menus" \
  -H "Authorization: Bearer {token}"

响应片段

{
  "user": { "id": "...", "userName": "admin", "nick": "Admin" },
  "roleCodes": ["admin"],
  "permissionCodes": ["menu.labels", "menu.reports"],
  "accessPermissionCodes": ["manage_labels", "manage_people"],
  "menus": [ { "id": "...", "menuName": "Labels", "router": "/labels", "children": [] } ]
}

部署注意

  1. 重新编译并重启 Web 进程;
  2. 若仍报 AccessPermissionCodesJson,说明旧 DLL 未替换;
  3. AccessPermissionCodes 列时接口可正常返回菜单,仅 accessPermissionCodes 为空。