这是审查报告里标了「可能藏着比注入更严重的问题」却一直没碰的维度。
纪律不变:每条结论带判读依据;自己的误判也写进去。
| 维度 | 结论 |
|---|---|
| 幂等中间件实现 | ✅ 扎实(Stripe 范式、唯一约束、body_hash 校验、失败释放占位、并发 409) |
| 幂等覆盖范围 | ✅ 声明 28 条,实测命中 28/28(无失效) |
| C 端下单防重 | ✅ 比中间件更硬(DB 唯一约束 + 代码层回查) |
| 🔴 覆盖缺口 | 10 个写操作不在幂等范围(商户端确认/退款审批/分账等) |
| 事务边界 | ⚠️ 本轮未查(见文末) |
逐行读了 backend/app/services/idempotency.py(195 行):
| 机制 | 实现 | 评价 |
|---|---|---|
| 去重靠什么 | INSERT INTO idempotency_keys + 唯一约束冲突 | ✅ 靠数据库,不靠应用判断 ⇒ 并发下也成立 |
| 同键不同参 | _hash_body 存 body_hash,不一致则拒绝 | ✅ 防客户端 bug |
| 失败处理 | 异常/4xx/5xx ⇒ _release 删占位 | ✅ 失败不占键,用户可同键重试 |
| 并发同键 | 占位未完成 ⇒ 返回 409 让客户端稍后重试 | ✅ 不阻塞等待 |
| 键的语义 | 刻意不用 url+body(连收两笔同额款项是合法业务) | ✅ 理解业务 |
注释里还留着一条踩坑记录(值得肯定):
本轮实测踩过:模板里的 \\{ 与正则里的 \\{ 混淆,导致 match_scope 对真实 URL 全部返回 None、幂等静默失效
⇒ 这说明「规则看起来对」和「规则真的生效」是两件事,他们踩过并留了记录。
我没有只看 SCOPE_MAP 声明了什么,而是拿真实路由去匹配——新增
scripts/verify_idempotency_real.py:从各router 文件提取真实 POST 路径(含 prefix),
再调 match_scope 验证命中。
真实 POST 路由 249 条
✅ 命中幂等 28 条(与 SCOPE_MAP 声明一致)
🔴 关键写操作未覆盖 44 条
我最初判定「/api/miniapp/orders 不在 SCOPE_MAP ⇒ C 端下单无防重」。
自查后推翻:
-- 生产库实测
uq_orders_idempotency_key → CREATE UNIQUE INDEX ... ON orders (idempotency_key)
# miniapp_orders.py:796 create_order
existing = await pool.fetchrow(
"SELECT id, order_no, batch_no, status FROM orders "
"WHERE idempotency_key=$1 AND user_id=$2", ...)
if existing: ... # 命中已有 ⇒ 直接回传
⇒ 数据库唯一约束 + 代码层回查,比中间件更硬(中间件可被绕过,约束绕不过)。
merchant_console.py /orders/{order_id}/merchant-confirm ← 商户确认收货
merchant_console.py /orders/{order_id}/merchant-reject ← 商户拒收
merchant_console.py /orders/{order_id}/refund/approve ← 退款审批
merchant_console.py /orders/{order_id}/refund/reject ← 退款驳回
merchant_console.py /payments/{payment_id}/confirm ← 收款确认
merchant_console.py /service-fee/prepay
agent.py /delegations/{delegation_id}/pay-merchant ← 代理打款
agent.py /payments/{payment_id}/confirm
miniapp_distribution.py /joint-payment/generate ← 分账生成
admin_distribution.py /protection-pay ← 保护付
风险场景(真实可能发生,不是理论):
merchant-confirm ⇒ 重复确认(若后续有副作用如结算,就是重复入账)approve 被双击 ⇒ 重复退款generate 重试 ⇒ 生成两笔分账⚠️ 这些端点是否有状态机守卫兜底,本轮未逐个判读——
miniapp_orders.py 的状态机守卫(pending_payment → paid 之外拒绝迁移)是存在的,
商户端这些端点大概率也有类似守卫,但我没有验证。
| # | 动作 | 理由 | 风险 |
|---|---|---|---|
| 1 | 把 verify_idempotency_real.py 接进 pre-commit | 它是元检查:验证「幂等规则是否真的生效」,比规则本身更重要 | 极低 |
| 2 | 给 refund/approve、joint-payment/generate 加幂等覆盖 | 这两个是钱相关且可重试的 | 低 |
| 3 | 补查 10 个未覆盖端点的状态机守卫 | 若无守卫则真会重复执行 | 低 |
verify_idempotency_real.py 这个思路:不要只检查「规则写对了」,要检查「规则真的生效」
我看到 /api/miniapp/orders 不在 SCOPE_MAP,就判「C 端下单无防重」。
实际数据库层有唯一约束 + 代码层回查,防护更硬。
⇒ 判「缺失」之前,必须先问「有没有别的层已经防了」。
第一版 verify_idempotency_real.py 只收集 var == 'router' 的路径,
把所有具名 router(c_router / console_router)都漏了
⇒ 输出「命中 0 条」⇒ 我据此写下「幂等完全失效,形同不存在」。
发现方式:那个结论太反常(他们明明踩过这个坑并留了记录)⇒ 我回头核对 SCOPE_MAP 内容,
发现它格式完全正确,是我的提取逻辑错。修好后:命中 28/28。
⇒ 🔴 这是我今晚第三次犯「脚本 bug 当成代码缺陷」的错(前两次:except 正则恒不命中、
--changed 扫到自己)。
这已经是模式,不是意外。
| 未查 | 为什么重要 |
|---|---|
| 🔴 事务边界 | 多表写入是否在同一事务?中途失败会不会留半截数据 |
| 🔴 并发锁 | 库存/房间/名额扣减有没有行锁或乐观锁 ⇒ 超卖 |
| 🔴 10 个未覆盖端点的状态机守卫 | 决定它们是真缺口还是已被兜住 |
| 🔴 金额计算精度 | 有没有浮点混进分 |
**⇒ 这份结论可用于「幂等」维度的决策,
但「并发/事务」这个维度仍未审完——而超卖恰恰在这里**。
优先查「库存/名额扣减有没有锁」 —— 这是这个业务最可能真实出事的点:
酒店房间、车队名额、预售券数量,超卖一次就是真金白银的赔偿。
比继续查幂等覆盖面更值得做。