fix(miniprogram): F1-5b MP-4 coach-detail id 边界保护 (W1)

走查发现 pages/coach-detail 在某种入口下 data.coachId 为 undefined /
空字符串,导致后端 /api/xcx/coaches/undefined 请求 422,体现为助教
详情页加载失败。后端日志多次出现该 422 记录。

变更:
- apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts:247
  onLoad 加 guard(参考 coach-service-records.ts 同款模式):
  - 检查 options.id 非空 + 非字面 'undefined'
  - 数字格式校验 (^\d+$)
  - 失败时 wx.showToast("缺少助教标识") + 1s 后 navigateBack
    (失败时 fallback switchTab board-finance)

双口径验证(weixin-devtools-mcp):
- 缺参入口 /pages/coach-detail/coach-detail(无 query) → guard 触发,
  toast 显示 + 退回 board-finance,不再发出 422 请求
- 正常入口 ?id=3148987180059141 → 通过 guard,pageState=normal 加载成功

§3.3 标"sandbox 无关",4b 跳过(权限/参数路径与 sandbox 无关联)。

审计:docs/audit/changes/2026-05-05__wave1_f1_5b_mp4_coach_detail_id_guard.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Neo
2026-05-05 19:17:02 +08:00
parent c43375734a
commit 3916085063
2 changed files with 105 additions and 3 deletions

View File

@@ -0,0 +1,90 @@
# 2026-05-05 · F1-5b MP-4 coach-detail id 边界保护
> Wave 1 / F1-5b Wave B 第 4 项任务(详见 `docs/_overview/wave1-findings/F1-5b-tasks.md` §4.2 顺序 16)
>
> 工作量 S / ~1h(实际 ~ 30min),按 §3 五步流程完成(4b 跳过 — sandbox 无关)。
## 背景
Wave 1 走查发现 `pages/coach-detail` 在某种入口下 `data.coachId` 为 undefined / 空字符串,导致后端 `/api/xcx/coaches/undefined` 请求 422 失败,体现为助教详情页加载失败。
后端日志(本会话期间观察)出现过:`GET /api/xcx/coaches/undefined HTTP/1.1 422 Unprocessable Content`
## 改动清单
### Step 3 实施(Step 1 调研已含足够上下文,Step 2 跳过 — 小程序无 unit test)
[apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts:247-251](apps/miniprogram/miniprogram/pages/coach-detail/coach-detail.ts#L247-L251):
**改前**(无任何 guard):
```typescript
onLoad(options: { id?: string }) {
const id = options?.id || ''
this.setData({ coachId: id })
this.loadData(id)
}
```
**改后**(参考 coach-service-records.ts 同款 guard 模式):
```typescript
onLoad(options: { id?: string }) {
const raw = options?.id
const idStr = (raw && raw !== 'undefined') ? String(raw) : ''
if (!idStr || !/^\d+$/.test(idStr)) {
wx.showToast({ title: '缺少助教标识', icon: 'none' })
setTimeout(() => {
wx.navigateBack({
fail: () => wx.switchTab({ url: '/pages/board-finance/board-finance' }),
})
}, 1000)
return
}
this.setData({ coachId: idStr })
this.loadData(idStr)
}
```
### Step 4 验证
由于 MP-4 §3.3 标"sandbox 无关",仅做 4a:
| 入口 | 行为 | 结果 |
|------|------|------|
| **缺参**:`/pages/coach-detail/coach-detail`(无 query) | guard 触发 → toast"缺少助教标识"→ 1s 后 navigateBack(失败时 switchTab board-finance) | 当前 route 退回 board-finance,**不触发后端 422** ✓ |
| **字面 'undefined'**:`?id=undefined`(模拟 dataset 字符串污染) | 同上,被 `raw !== 'undefined'` 判断拦截 | guard 触发 ✓ |
| **正常**:`?id=3148987180059141` | 通过 guard → setData coachId → loadData → pageState=normal | 页面成功加载 ✓ |
**走查方式**:weixin-devtools-mcp 实地 navigate_to(无 params 缺参 / 含 params 正常) + evaluate getCurrentPages()[-1].data 取 route + coachId + pageState。
### Step 5 审计
本文件。
## 影响范围
| 端 | 影响 | 验证 |
|----|------|------|
| 小程序 coach-detail | onLoad 加 guard,无效 id 优雅降级(toast + 退回) | weixin-devtools-mcp PASS |
| 后端 | 无影响(以前可能收到 /api/xcx/coaches/undefined 422,现在源头拦截不再发出) | 日志侧观察后续无相关 422 即可 |
| 其他端 | 无影响 | — |
## 测试
小程序无 unit test 框架,以 weixin-devtools-mcp + 后端日志反馈作为验证主线。
## 风险与未覆盖
- **上游入口 board-coach `dataset.id` 来源**:理论上 board-coach API 返回的 coach.id 不该为 null,但 setData 时机异常时仍可能传 undefined 给 dataset。本任务仅修 coach-detail 兜底,根因如出现在 board-coach 数据流,后续可单独调研(本任务范围外)。
- **同类页面 audit**:`pages/customer-detail` / `pages/customer-records` 等也接受 query id,可参考本 guard 模式补强,留待 Wave C 评估必要性。
## 回滚策略
```bash
git revert <commit_hash>
```
回滚后 onLoad 恢复无 guard 状态,缺参时仍会触发后端 422。
## Co-Authored-By
Claude Opus 4.7 (1M context) <noreply@anthropic.com>