From 2b0b64248a67079738ff452e580ed979cc3b4f3c Mon Sep 17 00:00:00 2001 From: wx Date: Thu, 7 May 2026 14:55:25 +0800 Subject: [PATCH] =?UTF-8?q?docs(frontend-bug):=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=203=20=E4=B8=AA=E5=B7=A5=E5=8D=95=20/@bug=20?= =?UTF-8?q?=E7=BF=BB=E7=9B=98=E8=BD=AC=E5=89=8D=E7=AB=AF=20(2026-05-07)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /@bug 实测后这 3 个 Gitea 工单从"后端 BUG"翻盘成前端 BUG,已关闭: 1. #1788 HT-001 合同生成"网络错误" → axios interceptor 误把业务码 5xxxxx 当 HTTP 5xx → 雪花 ID schemeId 没字符串化 → JS Number 精度丢失 2. #1790 YY-001 退款原因新增失败 → hl-ui/api/order.js 4 个 API 路径缺 /admin/ 前缀 → http.post(url, null, {params:data}) 把 body 当 querystring 3. #1792 JQ-027/CT-010/BP-013/YW-013 4 模块批量删除失败 → hl-ui/api/{scenic,restaurant,spare,playservice}.js 多包一层 data → 同 PR #1494 时期 anti-pattern 的反转版本 @mmg 看到请修复,每个 changelog 都附了 round-trip 实测证据。 完整分析报告在 wx/HL repo 的 docs/tasks/20260507_bug_*_analysis.md。 --- ..._contract_create_axios_and_snowflake_id.md | 107 ++++++++++++++++++ ...ug_refund_reason_api_path_and_post_body.md | 85 ++++++++++++++ ..._resource_batch_delete_double_wrap_data.md | 95 ++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 changelogs/2026-05/07_frontend_bug_contract_create_axios_and_snowflake_id.md create mode 100644 changelogs/2026-05/07_frontend_bug_refund_reason_api_path_and_post_body.md create mode 100644 changelogs/2026-05/07_frontend_bug_resource_batch_delete_double_wrap_data.md diff --git a/changelogs/2026-05/07_frontend_bug_contract_create_axios_and_snowflake_id.md b/changelogs/2026-05/07_frontend_bug_contract_create_axios_and_snowflake_id.md new file mode 100644 index 0000000..6f5464f --- /dev/null +++ b/changelogs/2026-05/07_frontend_bug_contract_create_axios_and_snowflake_id.md @@ -0,0 +1,107 @@ +--- +date: 2026-05-07 +type: frontend-bug +module: contract-generate +priority: high +notify: ["@mmg"] +status: pending +source: 测试用例汇总.xlsx (2026-05-07) + /@bug 实测 +related_cases: [HT-001] +gitea_issue_closed: 1788 +--- + +# 合同生成显示"网络错误" — 实为前端 axios 误判 + 雪花 ID 精度丢失 + +## 原始现象 + +测试用例 HT-001:合同生成报"网络错误"。 + +## /@bug 实测结论(admin 真 token round-trip) + +后端 `POST /admin/contract/create-by-scheme` HTTP **全部 200**,5 个 case 测试结果: + +| Case | 入参 | 业务 code | 含义 | +|------|------|-----------|------| +| A | DEPOSIT_PAID 订单+schemeId=3 | 510208 | 出行人未补全(业务校验,正确) | +| B | 订单+schemeId=`2046840448906940417`(19 位 Long) | 510205 | **前端把雪花 ID 序列化为 JS Number,后 4 位精度丢失** | +| C | CONFIRMED 订单+schemeId=3 | **200** | **生成成功**,contractId=2052278173260824577 | +| D | 不存在 orderId | 510207 | 订单不存在(正确) | + +**结论**:后端纯净,根因在前端两处。 + +## 根因 1:axios interceptor 误把业务码当 HTTP 5xx + +本项目 P0 铁律:**HTTP 始终 200,业务码在 `Result.code`**。 + +前端 axios interceptor 看到 `code:5xxxxx`(业务码格式 `xxxxxx` 6 位),可能误判为 HTTP 5xx 异常,统一显示"网络错误",**用户根本看不到 body.message** 提示的真实业务原因(如"出行人未补全")。 + +### 修复方向 + +```js +// 错误(推断): +axios.interceptors.response.use(resp => { + if (String(resp.data.code).startsWith('5')) { + showToast('网络错误') // ❌ 业务码 510208 被误判为 5xx + throw new Error() + } + return resp.data +}) + +// 正确: +axios.interceptors.response.use(resp => { + const { code, message, data } = resp.data + if (code !== 200) { + showToast(message || `操作失败 (${code})`) // ✅ 显示后端真实 message + throw new BizError(code, message) + } + return data +}) +``` + +## 根因 2:雪花 ID schemeId 没字符串化 + +后端 schemeId 是 19 位 Long(雪花算法),最大值 `2^63-1 ≈ 9.22e18`。 +JS Number 精度上限 `Number.MAX_SAFE_INTEGER = 2^53-1 ≈ 9.007e15`。 + +19 位 Long 直接被 JS Number 接收会**丢失后 3-4 位精度**,发回后端的 ID 已经不是原值。 + +### 实测证据(case B) + +``` +原值: 2046840448906940417 +JS 后: 2046840448906940000 ← 后 4 位变 0 +后端: schemeId 不存在 → code:510205 +``` + +### 修复方向 + +```js +// 1. axios 配置 transformResponse 用 json-bigint 解析 +import JSONbig from 'json-bigint' +axios.defaults.transformResponse = [data => JSONbig.parse(data)] + +// 2. 或后端 Long 字段 @JsonSerialize(using = ToStringSerializer.class) +// → 但本项目历史决定,前端处理更省事 + +// 3. 或前端拿到 ID 后立即转 String,传回时也按 String 传 +const schemeId = String(resp.schemeId) // 显式字符串化 +``` + +类似已知坑:memory `feedback_http-200-check-business-code.md`、项目其他列表用雪花 ID 的地方都需要复检。 + +## 影响 + +- 合同生成完全无法触发(用户看到"网络错误"放弃) +- 间接影响所有调用合同接口的页面 + +## 验收(前端) + +- [ ] 合同生成失败时 toast 显示后端真实 message(如"出行人未补全") +- [ ] CONFIRMED 订单 + 真实 schemeId → 合同生成成功(HTTP 200 + code:200) +- [ ] axios 全局检查:所有调用涉及雪花 ID 的接口(订单 ID/产品 ID/方案 ID/合同 ID)都按 String 处理 +- [ ] 业务码 5xxxxx 不再被误判为网络错误 + +## 关联工单 + +Gitea #1788 已关闭并留 closing comment(项目规则前端 BUG 不建工单)。 +完整分析报告:`docs/tasks/20260507_bug_contract_analysis.md` diff --git a/changelogs/2026-05/07_frontend_bug_refund_reason_api_path_and_post_body.md b/changelogs/2026-05/07_frontend_bug_refund_reason_api_path_and_post_body.md new file mode 100644 index 0000000..875bd82 --- /dev/null +++ b/changelogs/2026-05/07_frontend_bug_refund_reason_api_path_and_post_body.md @@ -0,0 +1,85 @@ +--- +date: 2026-05-07 +type: frontend-bug +module: refund-reason-api +priority: high +notify: ["@mmg"] +status: pending +source: 测试用例汇总.xlsx (2026-05-07) + /@bug 实测 +related_cases: [YY-001] +gitea_issue_closed: 1790 +--- + +# 退款原因新增失败 — 前端 4 个 API 路径错 + body 当 querystring + +## 原始现象 + +测试用例 YY-001:新增退款原因不通过(保存失败)。 + +## /@bug 实测结论(admin 真 token round-trip) + +后端 `POST /admin/refund-reason/create` 实测: + +``` +Body: {"reasonName":"测试取消原因","reasonType":"CUSTOMER","sortOrder":1} +HTTP: 200 +Response: {"code":200, "data":{"reasonId":2052278650115342338, ...}} +``` + +**后端完全正常,DB 也已落库。问题在前端 API 调用方式。** + +## 根因(hl-ui/src/api/order.js) + +4 个退款原因相关 API 都犯同样 2 个错: + +| 行号 | 函数 | 错误 1 | 错误 2 | +|------|------|--------|--------| +| 393 | `createRefundReason()` | 路径缺 `/admin/` 前缀 | `http.post(url, null, { params: data })` 把 body 当 querystring | +| 404 | `updateRefundReason()` | 同上 | 同上 | +| 416 | `deleteRefundReason()` | 同上 | 同上 | +| 424 | `listRefundReason()` | 同上 | (GET 可能影响小) | + +### 错误后果 + +1. **缺 `/admin/` 前缀** → 网关 9443 拿到 `/refund-reason/create` 没匹配路由 → 404 +2. **`http.post(url, null, { params })`** → axios 把 `{reasonName: '...'}` 拼到 query string `?reasonName=...`,body 是 null。后端 `@Valid @RequestBody RefundReasonSaveReqVO` 拿到 null → 400 必填字段为空 + +### 正确写法 + +```js +// 错 ❌ +export function createRefundReason(data) { + return http.post('/refund-reason/create', null, { params: data }) +} + +// 对 ✅ +export function createRefundReason(data) { + return http.post('/admin/refund-reason/create', data) + // ^^^^^^^ ^^^^ + // 加 /admin/ body 直接传,不用 params +} +``` + +## 同源 anti-pattern + +跟 memory `experience/backend-dev/frontend-http-delete-double-wrap-data-trap.md` 反向: +- 之前:前端正确给 body,但 wrapper 误把第二参数当 config → 后端拿到空 body +- 这次:前端把数据当 query → 后端 @RequestBody 收到 null + +**建议**:mmg 用全局搜 `http.post(.*null.*params` 或 `http.post(.*\{.*params:` 模式扫一下,这是高风险写法,可能 hl-ui 还有其他历史遗留。 + +## 影响 + +- 退款原因 admin 配置无法新增/编辑/删除(YY-001 直接症状) +- 列表查询走 GET 可能不受影响,但路径缺前缀也会 404 + +## 验收(前端) + +- [ ] `api/order.js` 4 个退款原因 API 全部加 `/admin/` 前缀 + 把 data 直接传 body +- [ ] 测试服 round-trip:admin 端能新增/修改/删除/列表退款原因 +- [ ] 全局 grep `http.post.*null.*params` 模式,发现的同类问题一起修 + +## 关联工单 + +Gitea #1790 已关闭并留 closing comment(项目规则前端 BUG 不建工单)。 +完整分析报告:`docs/tasks/20260507_bug_order_analysis.md` diff --git a/changelogs/2026-05/07_frontend_bug_resource_batch_delete_double_wrap_data.md b/changelogs/2026-05/07_frontend_bug_resource_batch_delete_double_wrap_data.md new file mode 100644 index 0000000..98bbde1 --- /dev/null +++ b/changelogs/2026-05/07_frontend_bug_resource_batch_delete_double_wrap_data.md @@ -0,0 +1,95 @@ +--- +date: 2026-05-07 +type: frontend-bug +module: resource-batch-delete +priority: high +notify: ["@mmg"] +status: pending +source: 测试用例汇总.xlsx (2026-05-07) + /@bug 实测 +related_cases: [JQ-027, CT-010, BP-013, YW-013] +gitea_issue_closed: 1792 +--- + +# 4 个资源模块批量删除失败 — 前端多包一层 data + +## 原始现象 + +测试用例 JQ-027 / CT-010 / BP-013 / YW-013:景区/餐厅/备品/游玩项目("物业"实为游玩项目)批量删除时,勾选数据后点删除"系统提示但无法删除"。 + +## /@bug 实测结论(admin 真 token round-trip 双向对照) + +| 模块 | "正确 body" | "前端字面 body" | 结论 | +|------|------------|-----------------|------| +| 景区 | `{"scenicIds":[3001,3002]}` → code:200 | `{"data":{"scenicIds":[...]}}` → code:400 字段【scenicIds】不能为空 | 前端多包 | +| 餐厅 | `{"restaurantIds":[...]}` → code:200 | `{"data":{"restaurantIds":[...]}}` → code:400 | 前端多包 | +| 备品 | `{"suppliesIds":[...]}` → code:200 | `{"data":{"suppliesIds":[...]}}` → code:400 | 前端多包 | +| 游玩项目 | `{"playServiceIds":[...]}` → code:200 | `{"data":{"playServiceIds":[...]}}` → code:400 | 前端多包 | + +**后端纯净,4 个模块前端 api 文件统一多包了一层 `data`**。 + +## 根因详情 + +| 文件:行号 | 现状 | +|----------|------| +| `hl-ui/src/api/scenic.js:421` | `http.delete(url, { data: { scenicIds } })` ❌ | +| `hl-ui/src/api/restaurant.js:78` | `http.delete(url, { data: { restaurantIds } })` ❌ | +| `hl-ui/src/api/spare.js:69` | `http.delete(url, { data: { suppliesIds } })` ❌ | +| `hl-ui/src/api/playservice.js:71` | `http.delete(url, { data: { playServiceIds } })` ❌ | + +### 项目 wrapper 行为(`hl-ui/src/utils/request.js:582-583`) + +项目自定义的 `http.delete(url, data, config)` 第二参数**直接当 body**,不像 axios 原生 `axios.delete(url, { data: body })` 那样从 config.data 取。 + +所以前端写 `http.delete(url, { data: { scenicIds } })` 时: +- 第二参数是 `{ data: { scenicIds } }` +- wrapper 直接当 body 发出去 +- 后端拿到 `{ "data": { "scenicIds": [...] } }` +- 后端 DTO 是 `{ scenicIds: List }`,找不到顶层 `scenicIds` 字段 → @NotEmpty fail + +### 正确写法 + +```js +// 错 ❌ +export function batchDelete(scenicIds) { + return http.delete('/admin/scenic/batch', { data: { scenicIds } }) +} + +// 对 ✅ +export function batchDelete(scenicIds) { + return http.delete('/admin/scenic/batch', { scenicIds }) + // ^^^^^^^^^ + // 第二参数直接是 body 对象,不要再包 { data: ... } +} +``` + +## 同源 anti-pattern(重要更新) + +跟 memory `experience/backend-dev/frontend-http-delete-double-wrap-data-trap.md` 是**同一个坑的反转版本**: + +- **9 天前**(PR #1494 时期):项目 wrapper 第二参数当 body,前端按 axios 原生写 `{ data: payload }`,后端拿到多包一层 → 一样的 code:400 +- **现在**:wrapper 仍然这样行为,但部分模块前端代码似乎曾经改对过又改错回去(或者一开始就错),症状一模一样 + +**建议 mmg**: +1. 修这 4 处当前确认的错误 +2. 全局 grep `http\.delete\(.*\{.*data:` / `http\.put\(.*\{.*data:` 扫一遍,看历史遗留 +3. 考虑把 wrapper 改成兼容 axios 原生用法(即从 config.data 取)减少这种坑 + +## 影响 + +- 4 个资源模块批量删除完全失效 +- 单个删除(非批量)通常不走这个路径,可能正常 + +## 验收(前端) + +- [ ] 4 个文件改完,传第二参数直接是 IDs 对象不要包 `{ data: ... }` +- [ ] 测试服 round-trip:景区/餐厅/备品/游玩项目 4 模块批量删除 5 条 → 列表 deleted_at 已写 +- [ ] 全局 grep 同类 anti-pattern 一起改 + +## 关联工单 + +Gitea #1792 已关闭并留 closing comment(项目规则前端 BUG 不建工单)。 +完整分析报告:`docs/tasks/20260507_bug_resource_analysis.md` + +## 顺手另一个共性问题(建议 PM 复核) + +/@bug 还发现:8 个资源服务(hotel/scenic/activity/restaurant/staff/supplies/cost/vehicle)的 `updateStatus` 方法**全部缺 checkResourceInUse 引用计数检查**(只有 `submitApproval` 路径有)。本批 PR 只修景区一个(JQ-011 #1794),其余 7 个建议 PM 决策是否扩大修复范围。