docs(frontend-bug): 测试报告 3 个工单 /@bug 翻盘转前端 (2026-05-07)
/@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。
这个提交包含在:
父节点
6f1e0f0b98
当前提交
2b0b64248a
@ -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`
|
||||
@ -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`
|
||||
@ -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<Long> }`,找不到顶层 `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 决策是否扩大修复范围。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户