6-23 代码优化
本文档说明 2026-06-23 对美国版标签(Label)接口的多门店绑定改造:一个标签可绑定多个门店(locationIds 数组),关联表为 fl_label_location。
一、背景与目标
| 项目 | 说明 |
|---|---|
| 原行为 | 标签仅通过 fl_label.LocationId 绑定单门店 |
| 新行为 | locationIds 为主字段,落库 fl_label_location;fl_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_location(LabelId + 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 展开门店范围内locationId与locationIds合并去重,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_region、fl_label_location 或 AppliedRegionType=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.LocationId←locationIds[0]fl_label_location← 全量locationIdsfl_label_region←regionIds(SPECIFIED 时)
4. 编辑标签
| 项目 | 内容 |
|---|---|
| 方法 | PUT |
| 路径 | /api/app/label/{id} |
| Body | 同 POST;locationIds 全量覆盖关联表 |
5. 删除标签
逻辑删除 fl_label,并删除 fl_label_region、fl_label_location、fl_label_product 关联行(表存在时)。
五、App 打印校验
UsAppLabelingAppService.PrintAsync / PreviewAsync 使用 LabelRegionScopeHelper.EnsureLabelAppliesToLocationAsync / LabelAppliesToLocationAsync,支持:
AppliedRegionType=ALL且配置了fl_label_location:须在关联门店内SPECIFIED:命中fl_label_location、LocationId或fl_label_region
六、代码改动摘要
| 文件 | 改动 |
|---|---|
LabelRegionScopeHelper.cs |
多门店解析/保存/列表筛选/展示;ApplyLabelRegionListFilter 用 SQL EXISTS 查 fl_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 |
详情/列表返回首项,不影响旧页面 |
八、部署注意
- 测试/生产库执行
fl_label_location_scope.sql dotnet build编译FoodLabeling.Application后重启 Web 进程- 管理端表单改为多选
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 库中不存在的列:AppliedPartnerType、AppliedRegionType、BorderType。
MCP 查库(当前测试库):fl_label_template 仅有 AppliedLocationType、PrintOrientation 等基础列;无 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) DESC;Sorting 白名单 |
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"
部署注意
- 必须重新编译
FoodLabeling.Application并完全重启 Web 进程;若 Yi-SQL 仍出现AppliedPartnerType,说明旧 DLL 未替换。 - 可选:执行
scripts/fl_label_template_scope.sql启用 Company/Region 主表列与关联表(非列表 500 的必要条件)。 - 可选:为
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.AccessPermissionCodes(VARCHAR,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_people、manage_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": [] } ]
}
部署注意
- 重新编译并重启 Web 进程;
- 若仍报
AccessPermissionCodesJson,说明旧 DLL 未替换; - 无
AccessPermissionCodes列时接口可正常返回菜单,仅accessPermissionCodes为空。