refactor: admin 列表筛选纪律全面治理 (PR #1908)

- 后端 changelog: 8 处筛选范式治理, API 行为变化说明
- 前端 BUG 通知: DevTools 实测纠正 07 文件根因, 真实根因为
  3 个并发请求互相 cancel, 需 mmg 收敛 fetch 入口

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot 2026-05-09 11:07:55 +08:00
父节点 4b8735e3cf
当前提交 c935ef7905
共有 2 个文件被更改,包括 197 次插入0 次删除

查看文件

@ -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<String>` + 自定义 setter, 兼容 4 种入参
- 即使前端按重复 key / 逗号分隔 / 单值任一方式发送, 后端能正确接住
后端**不能**修复"3 个请求互相 cancel"问题——这是前端调用频次问题, 必须前端修.

查看文件

@ -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<String>` + 自定义 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<XxxDO> selectPage(... String status, ...) {
return selectPage(page, new LambdaQueryWrapperX<XxxDO>()
.eq(XxxStatus.of(status)) // 字典+1 enum 不+1 → IAE
...);
}
// After (Mapper)
default IPage<XxxDO> selectPage(... String status, ...) {
return selectPage(page, new LambdaQueryWrapperX<XxxDO>()
.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 不受影响 (治理过程中误删已回滚)