From c935ef79054b5e00172658370e1ce23f5fb1c6ac Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 9 May 2026 11:07:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20admin=20=E5=88=97=E8=A1=A8=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E7=BA=AA=E5=BE=8B=E5=85=A8=E9=9D=A2=E6=B2=BB=E7=90=86?= =?UTF-8?q?=20(PR=20#1908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 changelog: 8 处筛选范式治理, API 行为变化说明 - 前端 BUG 通知: DevTools 实测纠正 07 文件根因, 真实根因为 3 个并发请求互相 cancel, 需 mmg 收敛 fetch 入口 Co-Authored-By: Claude Opus 4.7 (1M context) --- ...d_notice_admin_topic-filter-dup-request.md | 89 +++++++++++++++ ...9_refactor_admin_list_filter_governance.md | 108 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 changelogs/2026-05/09_frontend_notice_admin_topic-filter-dup-request.md create mode 100644 changelogs/2026-05/09_refactor_admin_list_filter_governance.md diff --git a/changelogs/2026-05/09_frontend_notice_admin_topic-filter-dup-request.md b/changelogs/2026-05/09_frontend_notice_admin_topic-filter-dup-request.md new file mode 100644 index 0000000..a468676 --- /dev/null +++ b/changelogs/2026-05/09_frontend_notice_admin_topic-filter-dup-request.md @@ -0,0 +1,89 @@ +# 前端 BUG 通知 — 主题列表筛选触发并发 3 请求互相 cancel (DevTools 实测纠正 07 文件根因) + +**日期**: 2026-05-09 +**类型**: 前端 BUG (admin 后台 hl-ui) +**模块**: 主题管理 (产品线 / Product Line) +**通知**: @mmg +**后端是否需改动**: 否(本日已 PR #1908 做防御性加固) +**关联**: `07_frontend_notice_topic-filter-empty.md` (07 文件的根因推断 A/B 已被本次 DevTools 证据**否定**) + +--- + +## 现象 + +用户 2026-05-09 复现, DevTools Network 截图证实: + +打开「主题列表」页 → 选「主题类型 = 小蒙马」点搜索 → 列表显示「暂无主题数据」, 但顶部仍显示「共 15 条」 + +**Network 关键发现**: +- 同一时刻并发发出 **3 个请求**, productType 分别是 `CORE` / `CUSTOM` / `GROUP` +- 所有 3 个请求 status 全部 `(canceled)` (size 0.0kB / time 8-27ms) +- 因为请求都被 cancel, 列表 store 收不到任何 data → 列表空 +- 「共 15 条」是上一次请求成功的缓存值, 没刷 + +``` +list?keyword=&productType=CORE&page=1&pageSize... (canceled) 27 ms +list?keyword=&productType=CUSTOM&page=1&pageSize... (canceled) 10 ms +list?keyword=&productType=GROUP&page=1&pageSize... (canceled) 8 ms +``` + +--- + +## 根因 (推翻 07 文件的 A/B 推断) + +07 文件原推断: +- (A) 字典 store localStorage 老数据 → ❌ 不成立, 请求里 productType 字段是正确的字典 value +- (B) 前端把 label 当 value 传给后端 → ❌ 不成立, 请求里清楚是 `GROUP` 而非 `小蒙马` + +**真实根因 (推断)**: + +页面进入时 URL 带 `?productType=GROUP`, 触发了**多个 watch/computed 同时联动**, 各自独立调 `getProductLinePage()` 拼接不同的 productType: +- 一个 watch 监听 URL query → 发 `productType=GROUP` +- 另一个 watch 监听筛选下拉 model → 看到 dict store 拉好后默认 option 又触发一次, 可能是 CORE +- 又一个 watch 监听某个状态切换 → 发 `productType=CUSTOM` + +axios 默认会把同一接口的并发请求按 `cancelToken` 互相 cancel (项目 axios 拦截器很可能有 dedupe / cancel-on-new 逻辑), 结果**3 个全被 cancel** (互相 cancel 死锁). + +--- + +## 复现取证 + +请 mmg 在 DevTools 里: +1. Network 抓页面进入时刻所有 `GET /admin/product/line/list` 请求, 看是否真的有 3 个并发 +2. Sources 里搜 `getProductLinePage` 找所有调用点, 看是否多个 watch/onMounted/init 各自触发了一次 +3. axios 拦截器 (`hl-ui/src/utils/request.js` 或 `hl-ui/src/api/index.js`) 是否有 cancel-on-duplicate 逻辑 + +--- + +## 建议修复方向 + +**优先级 1**: 把 `getProductLinePage` 调用收敛到**单一入口**: +- 页面 onMounted 触发一次 fetch +- 用户点搜索 / reset / 改筛选 → 只有点搜索按钮才 fetch (不要 watch 字段直接 fetch) +- URL query 同步只更新 store / 表单 model, 不直接 fetch + +**优先级 2**: 检查 axios 拦截器 cancel 逻辑: +- 如果有 dedupe by url, `productType=GROUP` 与 `productType=CORE` URL 不同应当不算 dup, 但仍被 cancel → 拦截器逻辑 bug +- 临时缓解: 关掉这个 dedupe / cancel-on-new + +**优先级 3**: 后端兼容性 (本次 PR #1908 已加固) +- 即使前端真传了 `productType=GROUP,CORE,CUSTOM` 拼接形式, 后端也能 setter 兼容 +- 即使前端某次传了字典里没有的 status, 后端不再 500 + +--- + +## 涉及文件 (前端排查参考) + +- 页面: `hl-ui/src/views/product/line/index.vue` +- API: `hl-ui/src/api/product/core.js:49-51` `getProductLinePage` +- axios 配置: `hl-ui/src/utils/request.js` 或 `hl-ui/src/api/index.js` + +--- + +## 后端关联 + +后端今日 PR #1908 已落地防御性加固 (详见 `09_refactor_admin_list_filter_governance.md`): +- `ProductLinePageReqVO.productType` 改 `List` + 自定义 setter, 兼容 4 种入参 +- 即使前端按重复 key / 逗号分隔 / 单值任一方式发送, 后端能正确接住 + +后端**不能**修复"3 个请求互相 cancel"问题——这是前端调用频次问题, 必须前端修. diff --git a/changelogs/2026-05/09_refactor_admin_list_filter_governance.md b/changelogs/2026-05/09_refactor_admin_list_filter_governance.md new file mode 100644 index 0000000..bc588d7 --- /dev/null +++ b/changelogs/2026-05/09_refactor_admin_list_filter_governance.md @@ -0,0 +1,108 @@ +# admin 列表筛选纪律全面治理 (8 处) + +> **服务**: hl-product-service-v2 (8093) + hl-order-service-v2 (8094) +> **PR**: #1908 (Closes #1907) +> **日期**: 2026-05-09 +> **影响范围**: 管理端列表分页接口 + +--- + +## ⚠️ 关键变化 + +admin 端 8 个列表分页接口的「下拉筛选」行为统一治理。**API 路径/请求参数/响应字段不变**,仅治理两类范式不健壮: + +| 类别 | Before | After | +|------|--------|-------| +| A. ProductLine 筛选字段类型 | `productType` 单值 String 派生 `ProductTypeList` 不通 → 筛选不生效 | `List` + 自定义 setter, 兼容 4 种入参 | +| B. status/type/productType/platform 筛选 | Service 调 `XxxEnum.of(req.getStatus())`, 字典+1 enum 不+1 时抛 IAE → 500 | String 直接透传 + `eqIfPresent + StrUtil.isNotBlank`, 字典已有合法值 → 正常筛选; 非法/未知值 → 视同未筛选返回全量 | + +--- + +## 一、影响接口清单 + +### A. ProductLine 筛选字段不通 (1 处) + +| 接口 | 方法 | 路径 | 变更 | +|------|------|------|------| +| 主题列表 | GET | `/admin/product/line/list` | `productType` 后端接收**兼容性扩大**到 4 种入参形式 | + +**4 种兼容入参** (前端任一方式都能正确接住): +- `?productType=GROUP`(单值) +- `?productType=CORE,GROUP`(逗号分隔) +- `?productType=CORE&productType=GROUP`(重复 key) +- POST body `{"productType":["CORE","GROUP"]}`(JSON 数组) + +### B. enum.of() IAE 隐患 → String 透传 (7 处) + +| # | 接口 | 方法 | 路径 | 治理字段 | +|---|------|------|------|----------| +| 1 | admin 订单列表 | GET | `/admin/order/list` | status / processStatus / productType | +| 2 | designer 订单列表 | GET | `/admin/order/designer-orders` | status | +| 3 | 工单列表 | GET | `/admin/workorder/page` | status / type | +| 4 | 保险单列表 | GET | `/admin/insurance-order/page` | status | +| 5 | 定制需求列表 | GET | `/admin/customize-request/page` | status | +| 6 | 合同列表 | GET | `/admin/contract/page` | status / platform | +| 7 | 发票列表 | GET | `/admin/invoice/page` | status | + +--- + +## 二、行为变化(前端关注) + +| 场景 | Before | After | +|------|--------|-------| +| 字典已有的合法值, 如 `status=PAID` | 正常筛选 | 正常筛选(行为不变) | +| 字典新增了值 enum 还没同步, 如未来加 `status=AFTER_SALES` | **HTTP 500 IllegalArgumentException** | 正常筛选(直接 SQL eq) | +| 传字典里完全没有的值, 如 `status=XXX_NOT_EXIST` | HTTP 500 | HTTP 200 但返回空列表 | +| 不传 / 空字符串 | 不筛选 | 不筛选(行为不变) | + +**前端不需要任何改动**, 但建议: +- 已有字典 fallback 逻辑可保留也可移除(后端不再 500) +- 主题列表前端的「重复请求互相 cancel」BUG 详见同日 `09_frontend_notice_admin_topic-filter-dup-request.md` + +--- + +## 三、内部范式 + +参照 `RefundApplicationMapper.selectPage` 既有正确范式: + +```java +// Before (Mapper) +default IPage selectPage(... String status, ...) { + return selectPage(page, new LambdaQueryWrapperX() + .eq(XxxStatus.of(status)) // 字典+1 enum 不+1 → IAE + ...); +} + +// After (Mapper) +default IPage selectPage(... String status, ...) { + return selectPage(page, new LambdaQueryWrapperX() + .eqIfPresent(XxxDO::getStatus, StrUtil.isNotBlank(status) ? status : null) + ...); +} +``` + +--- + +## 四、不变化 + +- DB schema / 字典表 / Flyway: **不动** +- enum 类型本身: **不删**, 仍可作字面量使用 (如 `if (PAID.name().equals(status))`) +- 前端 VO 字段名/类型契约: **不变** +- 排序/分页逻辑: **不变** +- 数据权限/角色控制: **不变** + +--- + +## 五、测试 + +- hl-product-service-v2: `ProductLineMapperTest 13 / ProductLinePageReqVOTest 9 / ProductPricingServiceTest 64 / ProductPricingServiceMpQuoteTest 5 / MpProductServiceTest 69 = 160 全绿` +- hl-order-service-v2: `ContractQueryServiceTest 27 / OrderInvoiceServiceTest 31 / OrderListQueryServiceTest 19 / OrderInfoMapperTest 13 / GroupOrderStrategyTest 10 = 100 全绿` +- 新增治理范式 case (字典+enum 不一致 / null 透传 / 4 种入参兼容性) 共 13 个 + +--- + +## 六、关联 + +- 前端 BUG 通知: `09_frontend_notice_admin_topic-filter-dup-request.md` (同日新建) +- 历史前端 BUG 通知: `07_frontend_notice_topic-filter-empty.md` (本日 DevTools 新证据已纠正其根因推断) +- 单房差功能: 完全解耦, PR #1900 / #1901 / #1904 不受影响 (治理过程中误删已回滚)