5-26代码优化.md 26.4 KB

5-26 代码优化

本文档说明 2026-05-26 对美国版接口的变更。

  1. /api/app/product-category:新增/编辑 categoryCode 取消必填(见 product-category-categoryCode)。
  2. /api/app/label-template:新增/编辑/列表/详情支持 Region、Location 多选数组;列表 Query 增加 Region/Location 筛选(见 label-template-regionlocation)。
  3. /api/app/rbac-role:修复 accessPermissions JSON 数组(如 manage_labels)无法绑定菜单(见 rbac-role-accesspermissions)。
  4. /api/app/auth-scope:管理员(及按数据范围受限账号)登录后 Company → Region → Location 级联选店(见 auth-scope-登录选店)。
  5. /api/app/us-app-auth:App 管理员 Token 专用 Company / Region / 门店筛选 接口(见 us-app-auth-管理员选店)。

应用服务ProductCategoryAppServiceLabelTemplateAppServiceRbacRoleAppService
命名约定(与 5-17 / 5-18 一致):UI Region = API regionIds / groupIds / groupIdfl_group.Id);UI Location = locationIds / locationIdlocation.Id)。


product-category categoryCode 可选

影响接口

方法 路径
POST /api/app/product-category
PUT /api/app/product-category/{id}

变更说明

变更前 变更后
categoryCode 必填;空则报「类别编码和名称不能为空」 可选;可不传、传 null""
categoryName 必填 仍必填
落库 未填编码时 CategoryCode空字符串
唯一性 编码或名称重复即报错 有编码:编码 名称重复报错;无编码:仅校验 名称 不重复

入参(节选)

字段 类型 必填 说明
categoryCode string 类别编码
categoryName string 类别名称
regionIds / groupIds / locationIds string[] Region·Location 范围(规则见 5-17接口优化.md

请求示例(无编码)

POST /api/app/product-category
Content-Type: application/json
Authorization: Bearer {token}
{
  "categoryName": "Beverages",
  "buttonAppearance": "TEXT",
  "state": true,
  "availabilityType": "ALL",
  "orderNum": 0
}

联调注意

现象 处理
仍报「类别编码和名称不能为空」 确认已部署含本变更的后端;仅需保证 categoryName 非空
无编码时名称重复 正常:仅按 categoryName 判重

Region/Location 多选、列表 region/location 展示等完整说明见 5-17接口优化.md → product-category 章节。


label-template Region·Location 多选

应用服务LabelTemplateAppService
存储表fl_label_template_location(模板 ↔ 门店,无新表
主表字段fl_label_template.AppliedLocationType = ALL / SPECIFIED

变更说明

变更前 变更后
新增/编辑 Body appliedLocation + appliedLocationIds 增加 regionIdsgroupIdslocationIds(与 appliedLocationIds 合并)
列表 Query locationId 增加 groupId(Region);locationId 优先于 groupId(与 product-category 一致)
列表出参 locationText(单条展示) 增加 regionlocation 展示 + regionIdslocationIds 数组
详情出参 appliedLocationIds 同上,并保留 appliedLocationIds(与 locationIds 一致,兼容编辑器)
范围解析 仅显式门店 Id Region 展开为门店后与门店 Id 取并集 落库

影响接口

方法 路径 说明
GET /api/app/label-template?SkipCount=1&MaxResultCount=10 列表支持 groupId/locationId 筛选;items[] 增加 regionlocationregionIdslocationIds
GET /api/app/label-template/{id} 详情增加上述字段
POST /api/app/label-template Body 支持 Region/Location 多选
PUT /api/app/label-template/{id} 同新增

路径参数 id 仍为模板编码 TemplateCode(与编辑器 JSON 的 id 一致)。

新增/编辑入参(Body:LabelTemplateCreateInputVo

字段 JSON 名 类型 必填 说明
TemplateCode id string 模板编码
TemplateName name string 模板名称
AppliedLocationType appliedLocation string ALL / SPECIFIED,默认 ALL
RegionIds regionIds string[] Region 多选(fl_group.Id
GroupIds groupIds string[] regionIds 等价,合并去重
LocationIds locationIds string[] 门店多选(location.Id
AppliedLocationIds appliedLocationIds string[] 兼容旧字段,与 locationIds 合并
Elements elements array 模板组件,全量重建
TemplateProductDefaults templateProductDefaults array 编辑 时显式传入才重建

自动规则

入参 行为
regionIds / groupIds / locationIds / appliedLocationIds 任一有有效 Id appliedLocationSPECIFIED 处理
仅传空数组 []appliedLocationALL 不绑定门店(全部门店)
appliedLocation: "SPECIFIED" 且合并后无有效门店 报错:指定适用区域或门店时,至少需要匹配到一个有效门店
appliedLocation 非法值 报错:适用门店范围不合法(ALL/SPECIFIED)

合并规则:每个 regionIds 展开为该 Region 下全部门店,再与 locationIdsappliedLocationIds 取并集 → 写入 fl_label_template_location

请求示例(Region + 门店多选)

POST /api/app/label-template
Content-Type: application/json
Authorization: Bearer {token}
{
  "id": "TPL_TEST_001",
  "name": "Price Tag 4x6",
  "labelType": "PRICE",
  "unit": "inch",
  "width": 4,
  "height": 6,
  "appliedLocation": "SPECIFIED",
  "regionIds": [
    "fl_group_id_east",
    "fl_group_id_west"
  ],
  "locationIds": [
    "11111111-1111-1111-1111-111111111111"
  ],
  "showRuler": true,
  "showGrid": true,
  "state": true,
  "elements": []
}

请求示例(全部门店,兼容旧版)

{
  "id": "TPL_ALL",
  "name": "Global Template",
  "labelType": "PRICE",
  "unit": "inch",
  "width": 4,
  "height": 6,
  "appliedLocation": "ALL",
  "appliedLocationIds": [],
  "elements": []
}

列表(GET /api/app/label-template

Query 参数

字段 类型 说明
SkipCount / MaxResultCount int 分页(项目约定 SkipCount 从 1 起)
keyword string 模板名称/编码模糊
groupId string 按 Region 筛选fl_group.Id):命中 appliedLocation=ALL 的模板,或在 fl_label_template_location 中绑定了该 Region 下任一门门店的模板
locationId string 按门店筛选location.Id);优先于 groupId
labelType string PRICE
state bool 启用状态
sorting string 排序(可选)

筛选规则(与 product-category / label-type 相同,内部 LocationScopeBindingHelper.ResolveScopedLocationIdsAsync

入参 行为
均未传 groupIdlocationId 不过滤适用范围
groupId 解析该 Region 下全部门店 Id,再筛模板
locationId 按该门店 Id 筛模板
同时传 locationId 为准(忽略 groupId
Region/门店无效或解析结果为空 仅返回 appliedLocation=ALL 的模板

命中条件(满足其一即可出现在列表):

  • fl_label_template.AppliedLocationType = 'ALL'
  • SPECIFIEDfl_label_template_location 中存在 LocationId ∈ 解析得到的门店集合

请求示例

GET /api/app/label-template?SkipCount=1&MaxResultCount=10&groupId=fl_group_id_east HTTP/1.1
Authorization: Bearer {token}
GET /api/app/label-template?SkipCount=1&MaxResultCount=10&locationId=11111111-1111-1111-1111-111111111111 HTTP/1.1
Authorization: Bearer {token}

命名对照:UI Region → Query groupId;UI Location → Query locationId

列表出参

items[] 新增/对齐字段

字段 类型 说明
region string 适用 Region 展示文案
location string 适用门店展示文案
regionIds string[] Region Id 多选;ALL 时为 []
locationIds string[] 门店 Id 多选;ALL 时为 []
locationText string 兼容字段,与 location 相同

其它字段不变:id(= TemplateCode)、templateNamecontentsCountsizeTextversionNolastEdited 等。

列表响应示例片段

{
  "pageIndex": 1,
  "pageSize": 10,
  "totalCount": 2,
  "items": [
    {
      "id": "TPL_ALL",
      "templateCode": "TPL_ALL",
      "templateName": "Global Template",
      "labelType": "PRICE",
      "region": "All Regions",
      "location": "All Locations",
      "locationText": "All Locations",
      "regionIds": [],
      "locationIds": [],
      "contentsCount": 5,
      "sizeText": "4x6inch",
      "versionNo": 1,
      "lastEdited": "2026-05-26T10:00:00"
    },
    {
      "id": "TPL_TEST_001",
      "templateName": "Price Tag 4x6",
      "region": "East Region, West Region",
      "location": "UNCC store, Central Park Store",
      "locationText": "UNCC store, Central Park Store",
      "regionIds": ["fl_group_id_east", "fl_group_id_west"],
      "locationIds": [
        "11111111-1111-1111-1111-111111111111",
        "22222222-2222-2222-2222-222222222222"
      ],
      "contentsCount": 3,
      "sizeText": "4x6inch",
      "versionNo": 2,
      "lastEdited": "2026-05-26T11:30:00"
    }
  ]
}

详情出参(GET /api/app/label-template/{id}

在原有 elementstemplateProductDefaultsappliedLocationType 等基础上增加:

字段 类型 说明
region string 展示文案
location string 展示文案
regionIds string[] Region Id 多选
groupIds string[] regionIds 相同(兼容)
locationIds string[] 门店 Id 多选
appliedLocationIds string[] locationIds 一致(编辑器回显)

展示规则

appliedLocation region location regionIds / locationIds
ALL All Regions All Locations 空数组 []
SPECIFIED 绑定门店 location.GroupName 去重后 , 拼接 门店名(优先 LocationName,否则 LocationCode)拼接 由绑定门店反推 / 直接为绑定 Id
SPECIFIED 无绑定 []

regionIdslocationIds 反查 fl_group 得到(与 product-category、label-type 一致)。

编辑说明

  • PUT Body 字段与 POST 相同;传 regionIds / locationIds全量替换 模板适用门店(先删 fl_label_template_location 再插入)。
  • elements 仍为全量重建;templateProductDefaults 仅当 Body 显式包含 该字段时才重建,避免普通保存误清空。
  • 编辑成功 versionNo +1

联调注意

现象 处理
列表无 regionIds 确认已部署含本变更的后端
groupId 列表仍很多 正常:appliedLocation=ALL 的模板始终可见
groupId 列表为空 检查 Region 是否存在、其下是否有门店;无效 Region 时仅剩 ALL 模板
传了 Region 仍显示 All Locations 检查 Region Id 是否有效、是否能在库中展开到门店
appliedLocationIds 不传 locationIds 仍支持,与 locationIds 合并
前端编辑器仍传 appliedLocation: "ALL" 管理端若需多选,须在 Body 增加 regionIds / locationIds(见 labelTemplateService.ts
指定范围但 0 门店 后端报错,需至少 1 个有效门店

与 product-category / label-type 的关系

逻辑与 5-17接口优化.md 中 product-category、label-type 的 Region·Location 绑定一致,差异仅为:

模块 范围字段名 关联表
product-category availabilityType fl_product_category_location
label-type availabilityType fl_label_type_location
label-template appliedLocation fl_label_template_location

rbac-role accessPermissions 修复

应用服务RbacRoleAppService
影响接口POST / PUT /api/app/rbac-role/{id}GET 列表/详情回显

问题与根因

现象 根因
保存报 accessPermissions 未匹配到任何菜单 前端提交 JSON 数组字符串(如 ["manage_labels",...]),旧逻辑按逗号拆分,解析结果带 [" 引号,无法匹配
manage_labels 等仍无菜单 表单权限码为 UI 编码manage_labels),菜单侧为 menu.labels(由 Menu.Router 推导);二者未做映射
详情 accessPermissionCodes 为空 新增/编辑未写入 Role.AccessPermissionCodes(JSON 列),仅依赖 RoleMenu 反查

变更说明

变更后
入参解析 accessPermissions 支持 JSON 数组字符串、逗号分隔、以及 Body 字段 accessPermissionCodes 数组
菜单绑定 UI 权限码经 RoleAccessPermissionMenuMapping 映射到 Menu.Router,再写入 RoleMenu
落库 同时将勾选的 UI 编码写入 Role.AccessPermissionCodes(JSON 数组),供 GET 回显
PermissionCode 为空 仍可按 Router 推导 menu.xxx(建议执行 menu_backfill_permission_code.sql

UI 权限码 → 菜单 Router 映射(当前库)

accessPermissions(UI) 绑定菜单 Router
manage_labels /labeling/labels/label-categories/label-types/label-templates
manage_people /account-management
edit_settings /menu-management/multiple-options
view_reports /reports
manage_products (当前 Menu 表无 Products 路由,勾选不绑定菜单,不单独报错
approve_batches (当前无对应菜单路由,同上)

至少 1 个 权限码能匹配到菜单即保存成功;若 全部 均无法匹配(例如只勾 manage_products 且库中无对应菜单),仍返回业务错误。

请求示例(与前端一致)

PUT /api/app/rbac-role/3a1f077b-3665-63f2-5fea-0fd7e7044b88
Content-Type: application/json
Authorization: Bearer {token}
{
  "roleName": "Partner Admin",
  "roleCode": "admin",
  "remark": "Admin",
  "dataScope": 0,
  "state": true,
  "orderNum": 999,
  "accessPermissions": "[\"manage_labels\",\"edit_settings\",\"view_reports\",\"manage_people\",\"manage_products\",\"approve_batches\"]"
}

也可使用逗号分隔(旧格式):

{
  "accessPermissions": "manage_labels, view_reports, manage_people"
}

或同时传数组字段(与 accessPermissions 合并去重):

{
  "accessPermissionCodes": ["manage_labels", "view_reports"]
}

入参优先级(与 5-18 一致)

menuIds accessPermissions / accessPermissionCodes 行为
非空数组 任意 以 menuIds 为准
不传 非空 按 UI 权限码映射菜单并覆盖 RoleMenu
不传 "" 或空数组 清空 RoleMenuAccessPermissionCodes
[] 不传 清空绑定

响应回显

字段 说明
accessPermissionCodes 来自 Role.AccessPermissionCodes,如 ["manage_labels","view_reports"]
accessPermissions 已绑定菜单的 menu.xxx 汇总(逗号拼接,只读展示)
menuIds 已绑定菜单 Guid 列表(RoleMenu

数据库准备(推荐)

美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/menu_backfill_permission_code.sql

联调注意

现象 处理
仍报未匹配到菜单 确认已部署本修复;检查 Menu 是否存在上表 Router
只勾 Products/Batches 报错 当前库无对应菜单属预期;请同时勾选 Labels/Reports 等
同时传 menuIds: [] menuIds 优先,会清空绑定并忽略 accessPermissions

更完整的 RBAC 说明见 5-18接口优化.md → rbac-role 章节。


auth-scope 登录后 Company · Region · Location 级联选店

应用服务AuthScopeAppService
适用场景:Web POST /api/app/account/login 或 App POST /api/app/us-app-auth/login 取得 Token 后,管理员userlocation 绑定时需先选工作门店;亦支持非管理员在数据范围内级联选择(须已绑定该门店)。

命名约定(与 5-17 一致):UI Company = partnerIdfl_partner.Id);UI Region = groupIdfl_group.Id);UI Location = locationIdlocation.Id,Guid 字符串)。

接口一览

步骤 方法 路径 说明
1 GET /api/app/auth-scope/companies 可选公司列表
2 GET /api/app/auth-scope/regions?partnerId={partnerId} 指定公司下 Region
3 GET /api/app/auth-scope/locations?partnerId={partnerId}&groupId={groupId} 指定公司+Region 下门店
4 POST /api/app/auth-scope/select-location 确认当前工作门店
GET /api/app/auth-scope/current-scope 查询已选工作门店(未选返回 null

鉴权:均需 Authorization: Bearer {token}

数据范围

角色 Company Region Location
管理员admin / 用户名 admin / 权限 *:*:* 全部未删除公司 该公司下全部 Region 该 Region 下全部门店(location.Partner + location.GroupNamefl_group 一致)
非管理员 userlocation 绑定门店所属公司 绑定门店对应 Region 上述 Region 内且符合 LocationRegionScopeHelper 的门店;选店时须已绑定该 locationId

1)公司列表

GET /api/app/auth-scope/companies HTTP/1.1
Authorization: Bearer {token}

响应AuthScopeCompanyOptionDto[]

[
  { "id": "fl_partner_id_1", "partnerName": "Acme Foods", "state": true }
]

2)Region 列表

GET /api/app/auth-scope/regions?partnerId=fl_partner_id_1 HTTP/1.1
Authorization: Bearer {token}

响应AuthScopeRegionOptionDto[]

[
  { "id": "fl_group_id_east", "groupName": "East Region", "partnerId": "fl_partner_id_1", "state": true }
]

3)门店列表

GET /api/app/auth-scope/locations?partnerId=fl_partner_id_1&groupId=fl_group_id_east HTTP/1.1
Authorization: Bearer {token}

响应AuthScopeLocationOptionDto[](含 fullAddressgroupName 等)

4)确认选店(与现有 App 逻辑对齐)

POST /api/app/auth-scope/select-location HTTP/1.1
Authorization: Bearer {token}
Content-Type: application/json
{
  "partnerId": "fl_partner_id_1",
  "groupId": "fl_group_id_east",
  "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f"
}

响应AuthScopeSelectLocationOutputDto

字段 说明
partnerId / partnerName 所选公司
groupId / groupName 所选 Region
location UsAppBoundLocationDto 相同(idlocationCodelocationNamefullAddressstate

选店后的服务端行为(无需改前端即可对接 App):

能力 行为
工作范围缓存 写入分布式缓存(24h);退出 POST /api/app/auth-session/logout 时清除
GET /api/app/us-app-auth/my-locations 管理员在缓存选店后,列表 合并 该门店(与 userlocation 并集)
GET .../location-detail/{locationId} 管理员可不依赖 userlocation 访问已选门店(UsAppPrintLogScopeHelper.EnsureUserCanAccessLocationAsync
App 打印/报表 仍传 locationId;权限规则不变(见 5-18接口优化.md

5)当前工作范围

GET /api/app/auth-scope/current-scope HTTP/1.1
Authorization: Bearer {token}

未选店时响应体为 null(HTTP 200)。

联调注意

现象 处理
regions 为空 公司下无 fl_group 或当前账号无 Region 数据范围
locations 为空 门店 Partner / GroupName 未与 fl_partnerfl_group 对齐
选店报「门店与所选公司/区域不匹配」 检查 location.Partnerlocation.GroupName
非管理员选店报未绑定 须在 Team Member 中为该账号绑定该门店
选店后 my-locations 仍为空 确认已调 select-location 且 Token 为管理员身份

Web 管理端报表等模块仍可按 Query 传 partnerId / groupId / locationId 收窄;本组接口主要解决 登录后选工作门店App 门店列表 一致性问题。


us-app-auth App 管理员级联选店

应用服务UsAppAuthAppService
适用场景:App 使用 POST /api/app/us-app-auth/login 登录后,持 管理员 身份(admin 角色 / 用户名 admin / 权限 *:*:*)且 JWT 含 client_kind=us-app,按 Company → Region 筛选门店。

auth-scope 关系:查询逻辑共用 AuthScopeQueryHelper;App 侧路径统一在 us-app-auth 下,并 强制 App Token + 管理员,避免误用 Web Token。

接口一览

步骤 方法 路径 说明
0 POST /api/app/us-app-auth/login 获取 App Token(须管理员账号)
1 GET /api/app/us-app-auth/admin-scope-companies 公司列表 → 取 idpartnerId
2 GET /api/app/us-app-auth/admin-scope-regions?partnerId={partnerId} Region 列表 → 取 idgroupId
3 GET /api/app/us-app-auth/admin-scope-locations?partnerId={partnerId}&groupId={groupId} 按公司与 Region Id 筛选门店
4 POST /api/app/us-app-auth/select-admin-scope-location 确认工作门店
GET /api/app/us-app-auth/my-locations 选店后刷新绑定门店(含缓存门店)

鉴权:步骤 1–4 须 Header Authorization: Bearer {App登录返回的token}

前置条件

要求
Token 来源 必须来自 /api/app/us-app-auth/login(非 Web /api/app/account/login
JWT 声明 client_kind = us-app
角色 平台管理员(ReportsRoleHelper.IsAdminRole
违反时 请使用 App 登录令牌调用该接口仅管理员可使用公司/区域/门店筛选接口

1)公司列表

GET /api/app/us-app-auth/admin-scope-companies HTTP/1.1
Authorization: Bearer {app_token}

响应AuthScopeCompanyOptionDto[](与 auth-scope 相同)

[
  { "id": "fl_partner_id_1", "partnerName": "Acme Foods", "state": true }
]

2)Region 列表

GET /api/app/us-app-auth/admin-scope-regions?partnerId=fl_partner_id_1 HTTP/1.1
Authorization: Bearer {app_token}

响应AuthScopeRegionOptionDto[]

[
  { "id": "fl_group_id_east", "groupName": "East Region", "partnerId": "fl_partner_id_1", "state": true }
]

3)门店列表(按 partnerId + groupId 筛选)

GET /api/app/us-app-auth/admin-scope-locations?partnerId=fl_partner_id_1&groupId=fl_group_id_east HTTP/1.1
Authorization: Bearer {app_token}

Query

参数 必填 说明
partnerId 公司 Id(fl_partner.Id
groupId Region Id(fl_group.Id

响应AuthScopeLocationOptionDto[]

[
  {
    "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f",
    "locationCode": "LOC-1",
    "locationName": "Downtown Kitchen",
    "fullAddress": "123 Main St, New York, NY 10001",
    "state": true,
    "partnerId": "fl_partner_id_1",
    "groupId": "fl_group_id_east",
    "groupName": "East Region"
  }
]

筛选规则:location.Partner 匹配该公司(Id 或名称),且 location.GroupName 与所选 fl_group.GroupName 一致。

4)确认选店

POST /api/app/us-app-auth/select-admin-scope-location HTTP/1.1
Authorization: Bearer {app_token}
Content-Type: application/json
{
  "partnerId": "fl_partner_id_1",
  "groupId": "fl_group_id_east",
  "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f"
}

响应AuthScopeSelectLocationOutputDto(含 location 节点,结构同 UsAppBoundLocationDto

推荐调用顺序(App)

POST /api/app/us-app-auth/login
  → GET  admin-scope-companies
  → GET  admin-scope-regions?partnerId=...
  → GET  admin-scope-locations?partnerId=...&groupId=...
  → POST select-admin-scope-location
  → GET  my-locations
  → 后续业务接口传 locationId(打印、报表等,规则不变)

联调注意

现象 处理
报「请使用 App 登录令牌」 勿用 Web account/login 的 Token;须重新 App 登录
报「仅管理员可使用」 换管理员账号或绑定 admin 角色
locations 为空 核对门店 PartnerGroupNamefl_partnerfl_group
与 auth-scope 重复 App 端 优先 使用本节前缀;Web 端用 auth-scope

变更记录

日期 说明
2026-05-26 us-app-auth:App 管理员 admin-scope-companies/regions/locationsselect-admin-scope-location
2026-05-26 auth-scope:登录后 Company/Region/Location 级联选店;选店缓存;my-locations / 门店详情与管理员选店对齐
2026-05-26 product-category:categoryCode 新增/编辑改为可选
2026-05-26 label-template:新增/编辑/列表/详情支持 regionIdslocationIdsregionlocation 展示
2026-05-26 label-template 列表 Query 增加 groupId(Region)、locationId(门店)筛选
2026-05-26 rbac-role:支持 accessPermissions JSON 数组 + UI 权限码映射 Menu;落库 AccessPermissionCodes