# 5-27 代码优化 本文档说明 **2026-05-27** 对美国版接口的变更。 1. **修复** App 管理员级联选店接口 **404**(`admin-scope-regions` 等 Query 传参无法命中路由,见 [us-app-auth-路由修复](#us-app-auth-管理员选店路由-404-修复))。 2. 同步修复 Web **`auth-scope`** 同类路由(`regions` / `locations` 使用 Query 传 `partnerId`、`groupId`)。 3. **App Label Report 页**(`pages/more/label-report`):完善 **`get-label-report`**,支持 **7d / 30d / 90d / 自定义日期**(见 [app-label-report](#app-label-report-标签报表))。 4. **泰额版** `POST /api/app/th-app-auth/login`:修复登录成功后因 **主库无 LoginLog 表** 导致 500(见 [th-app-auth-login](#泰额版-th-app-auth-登录))。 > 级联选店业务见 `5-26代码优化.md` → **us-app-auth** / **auth-scope** 章节。 --- ## us-app-auth 管理员选店路由 404 修复 ### 问题现象 ```http GET /api/app/us-app-auth/admin-scope-regions?partnerId=3a217015-4def-3813-01bf-05e4c8c09be6 Authorization: Bearer {app_token} ``` 返回 **404 Not Found**(未进入业务逻辑,非 400 权限错误)。 ### 根因 项目使用 **`YiConventionalRouteBuilder`**:当 Action **仅有一个** 以 `Id` 结尾的参数(如 `partnerId`)时,约定路由会生成 **路径参数**,例如: ```text GET /api/app/us-app-auth/admin-scope-regions/{partnerId} ``` 客户端若用 **Query** `?partnerId=...` 调用,路径不匹配 → **404**。 `admin-scope-locations` 同时有 `partnerId` + `groupId` 两个 `*Id` 参数时,约定路由通常 **不会** 把 Id 放进路径,故 Query 方式有时能通;为统一行为,已改为显式路由。 ### 修复方式 在 `UsAppAuthAppService` / `AuthScopeAppService` 上为级联选店方法增加 **显式** `[HttpGet]` / `[HttpPost]` 模板(与 `forgot-password` 相同写法),**强制** `partnerId`、`groupId` 走 **Query**。 ### 正确接口地址(修复后) | 步骤 | 方法 | 路径 | Query | |------|------|------|-------| | 公司 | GET | `/api/app/us-app-auth/admin-scope-companies` | 无 | | Region | GET | `/api/app/us-app-auth/admin-scope-regions` | **`partnerId`**(必填) | | 门店 | GET | `/api/app/us-app-auth/admin-scope-locations` | **`partnerId`**、**`groupId`**(必填) | | 确认选店 | POST | `/api/app/us-app-auth/select-admin-scope-location` | Body JSON | **鉴权**:App 登录 Token(`client_kind=us-app`)+ 平台管理员。 ### Region 列表 — 正确示例 ```http GET /api/app/us-app-auth/admin-scope-regions?partnerId=3a217015-4def-3813-01bf-05e4c8c09be6 HTTP/1.1 Host: localhost:19001 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应**:`AuthScopeRegionOptionDto[]` ```json [ { "id": "fl_group_id_east", "groupName": "East Region", "partnerId": "3a217015-4def-3813-01bf-05e4c8c09be6", "state": true } ] ``` ### 门店列表 — 正确示例 ```http GET /api/app/us-app-auth/admin-scope-locations?partnerId=3a217015-4def-3813-01bf-05e4c8c09be6&groupId=fl_group_id_east HTTP/1.1 Authorization: Bearer {app_token} ``` ### 兼容说明(不推荐) 修复前若误按约定路由调用,Region 可能需写成路径参数: ```http GET /api/app/us-app-auth/admin-scope-regions/3a217015-4def-3813-01bf-05e4c8c09be6 ``` **请统一使用 Query `?partnerId=`**,与文档及 App 封装一致。 ### Web auth-scope(同步修复) | 方法 | 路径 | Query | |------|------|-------| | GET | `/api/app/auth-scope/companies` | 无 | | GET | `/api/app/auth-scope/regions` | `partnerId` | | GET | `/api/app/auth-scope/locations` | `partnerId`、`groupId` | | POST | `/api/app/auth-scope/select-location` | Body | | GET | `/api/app/auth-scope/current-scope` | 无 | ### 联调注意 | 现象 | 处理 | |------|------| | 仍 404 | 确认已部署含本修复的后端;路径勿多写 `/us-app-auth` 前缀(完整路径见上表) | | 400「请使用 App 登录令牌」 | 使用 `POST /api/app/us-app-auth/login` 返回的 Token | | 400「仅管理员可使用」 | 换管理员账号 | | 200 但 `[]` 空数组 | 该公司下无 `fl_group` 或数据未录入 | ### 推荐调用顺序(App 管理员) ```text POST /api/app/us-app-auth/login → GET /api/app/us-app-auth/admin-scope-companies → GET /api/app/us-app-auth/admin-scope-regions?partnerId={公司Id} → GET /api/app/us-app-auth/admin-scope-locations?partnerId={公司Id}&groupId={区域Id} → POST /api/app/us-app-auth/select-admin-scope-location → GET /api/app/us-app-auth/my-locations ``` --- ## App Label Report 标签报表 **应用服务**:`UsAppLabelingAppService` **接口**:`POST /api/app/us-app-labeling/get-label-report` **页面**:App `Label Report`(指标卡 + Labels by Category + Print Volume Trends + Most Used Products) ### 鉴权与数据范围 | 项 | 说明 | |----|------| | Token | App 登录 `POST /api/app/us-app-auth/login` | | 门店 | Body **`locationId`**(当前选中门店;管理员可先 `select-admin-scope-location`) | | 管理员 / Partner | 当前门店 **全部** 打印任务 | | 其它角色 | 仅 **`CreatedBy == 当前用户`** 的任务 | ### 请求体(UsAppLabelReportQueryInputVo) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | locationId | string | **是** | 门店 Guid | | **period** | string | 否 | **`7d`**(默认)/ **`30d`** / **`90d`** / **`custom`** | | startDate | date | custom 时必填 | 含当天,如 `2026-05-20` | | endDate | date | custom 时必填 | 含当天,默认今天 | | keyword | string | 否 | 产品名 / 标签分类名模糊匹配 | **周期与 App UI 对应** | App 按钮 | period | |----------|--------| | Last 7 Days | `7d` | | Last 30 Days | `30d` | | Last 90 Days | `90d` | | Custom + Start/End | `custom` + `startDate` + `endDate` | **区间规则**(含起止日当天): | period | 起止(结束日 = 今天或 endDate) | |--------|--------------------------------| | 7d | 共 7 个自然日(今天往前 6 天) | | 30d | 共 30 个自然日 | | 90d | 共 90 个自然日 | | custom | `startDate` ~ `endDate`(最长 366 天) | ### 请求示例 **最近 7 天** ```http POST /api/app/us-app-labeling/get-label-report HTTP/1.1 Authorization: Bearer {app_token} Content-Type: application/json ``` ```json { "locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8", "period": "7d" } ``` **最近 30 天** ```json { "locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8", "period": "30d" } ``` **自定义日期**(对应 UI Start / End) ```json { "locationId": "3a21220f-db37-3e32-7390-d55f64cd62a8", "period": "custom", "startDate": "2026-05-20", "endDate": "2026-05-27" } ``` ### 响应体(ReportsLabelReportOutputDto) #### appliedRange(本次实际统计区间) | 字段 | 说明 | |------|------| | period | `7d` / `30d` / `90d` / `custom` | | startDate | `yyyy-MM-dd` | | endDate | `yyyy-MM-dd` | | dayCount | 自然日天数 | | trendDescription | 趋势图副标题(英文,如 `Daily label printing volume for the last 7 days.`) | #### summary(四张指标卡) | 字段 | UI 对应 | |------|---------| | totalLabelsPrinted | Total Labels Printed | | totalLabelsPrintedChangeRate | 相对 **上一同长度周期** 变化率(%,如 `20.1` 表示 +20.1%) | | mostPrintedCategoryName / mostPrintedCategoryCount | Most Printed Category | | topProductName / topProductCount | Top Product | | avgDailyPrints | Avg. Daily Prints | | avgDailyPrintsChangeRate | 日均相对上一周期变化率 | #### labelsByCategory(Labels by Category 柱状图) `{ categoryId, categoryName, count }[]`,按 `count` 降序。分类来自 **标签分类**(`fl_label_category`)。 #### printVolumeTrend(Print Volume Trends) `{ date, count }[]`:**整个查询区间内每日**打印量(7d 返回 7 个点,30d 返回 30 个点,以此类推;无数据日为 `0`)。 #### mostUsedProducts(Most Used Products 表格) | 字段 | UI 列 | |------|-------| | productName | Product Name | | categoryName | Category(产品分类名) | | totalPrinted | Total Printed | | usagePercent | Usage %(占当期总打印量百分比) | ### 响应示例(节选) ```json { "appliedRange": { "period": "7d", "startDate": "2026-05-21", "endDate": "2026-05-27", "dayCount": 7, "trendDescription": "Daily label printing volume for the last 7 days." }, "summary": { "totalLabelsPrinted": 2543, "totalLabelsPrintedPrevPeriod": 2118, "totalLabelsPrintedChangeRate": 20.06, "mostPrintedCategoryName": "Dairy", "mostPrintedCategoryCount": 450, "topProductName": "Whole Milk", "topProductCount": 182, "avgDailyPrints": 363.29, "avgDailyPrintsPrevPeriod": 302.57, "avgDailyPrintsChangeRate": 20.07 }, "labelsByCategory": [ { "categoryName": "Dairy", "count": 450 }, { "categoryName": "Meat", "count": 380 } ], "printVolumeTrend": [ { "date": "2026-05-21", "count": 72 }, { "date": "2026-05-22", "count": 85 } ], "mostUsedProducts": [ { "productName": "Whole Milk", "categoryName": "Dairy", "totalPrinted": 182, "usagePercent": 7.16 } ] } ``` ### 与旧版差异(本次优化) | 项 | 优化前 | 优化后 | |----|--------|--------| | period | 仅 `startDate`/`endDate`,默认约 30 天 | 显式 **`7d`/`30d`/`90d`/`custom`** | | printVolumeTrend | 仅区间内 **最后 7 天** | **整个查询区间**按日汇总 | | appliedRange | 无 | 回显实际区间与趋势副标题 | | 路由 | 仅约定路由 | 增加 **`[HttpPost("us-app-labeling/get-label-report")]`** 显式路径 | ### 联调注意 | 现象 | 处理 | |------|------| | 404 | 确认路径为 **`POST .../get-label-report`**(非 GET) | | custom 报错 | 须同时传 `startDate`、`endDate` | | 趋势只有 7 个点但选了 30d | 部署含本修复的后端 | | 指标全 0 | 该门店/角色在区间内无打印任务;检查 `locationId` | | 分类名为空 | 打印任务未关联标签分类 | > 更完整的权限说明见 **`5-18接口优化.md`** → us-app-print-log / get-label-report 章节。 --- ## 泰额版 th-app-auth 登录 **应用服务**:`ThAppAuthAppService`(泰额模块 `FoodLabeling.Th.Application`) **路径**:`POST /api/app/th-app-auth/login`(已加显式路由 `[HttpPost("th-app-auth/login")]`) ### 问题现象 日志显示登录逻辑已执行成功(查租户、用户、`userlocation` 均有 SQL),但请求最终 **500**: ```text MySqlException: Table 'antis-foodlabeling-host.loginlog' doesn't exist ``` 浏览器还可能提示: ```text Request origin http://saas-test.3ffoodsafety.com does not have permission to access the resource. ``` ### 根因 1. **LoginLog**:泰额 App 在**租户库**校验用户,但登录后发布了 `LoginEvent`,在 **UoW 提交阶段** 向**当前连接(主库 host)** 插入 `LoginLog`;主库 `antis-foodlabeling-host` 未建该表 → 整单失败。 2. **CORS**:`App:CorsOrigins` 未包含 `http://saas-test.3ffoodsafety.com`。 ### 修复说明 | 项 | 处理 | |----|------| | 泰额 App 登录 | **不再发布** `LoginEvent`,避免写 host 库 `LoginLog` | | `LoginEventHandler` | 外层 try/catch + `autoSave: true`;写库失败仅打 Warning,不影响 Web 登录 | | CORS | `appsettings.json` 的 `App:CorsOrigins` 增加 `saas-test.3ffoodsafety.com`(http/https) | | 路由 | `th-app-auth/login`、`th-app-auth/my-locations` 显式 `[HttpPost]` / `[HttpGet]` | ### 请求示例 ```http POST /api/app/th-app-auth/login Content-Type: application/json ``` ```json { "tenantId": "11111111-1111-1111-1111-111111111111", "email": "admin@example.com", "password": "YourPassword1!" } ``` ### 部署注意 1. 重新发布 **泰额版** `Yi.Abp.Web`(含 `FoodLabeling.Th.Application`、`Yi.Framework.Rbac.Domain`)。 2. 生产环境若域名不同,在 **`App:CorsOrigins`**(或 `appsettings.Production.json`)中追加前端 Origin,分号分隔。 3. 若需在**主库**记录 Web 管理员登录日志,可在 host 库执行 RBAC 初始化脚本创建 `LoginLog` 表(泰额 App 登录不依赖该表)。 > 多租户登录完整说明见 **`5-19泰额版.md`**。 --- ## 变更记录 | 日期 | 说明 | |------|------| | 2026-05-27 | 泰额 th-app-auth/login:取消 LoginEvent;修复 host 无 LoginLog 导致 500;CORS 增加 saas-test 域名 | | 2026-05-27 | App Label Report:`get-label-report` 支持 7d/30d/90d/custom;趋势按全区间按日汇总;出参 `appliedRange` | | 2026-05-27 | 修复 us-app-auth / auth-scope 级联选店 404:显式路由,partnerId/groupId 使用 Query | | 2026-05-27 | 补充 `5-27代码优化.md` 接口说明与 curl 示例 |