并发 / 事务 / 幂等审查 · 2026-10-09 10:35

这是审查报告里标了「可能藏着比注入更严重的问题」却一直没碰的维度。
纪律不变:每条结论带判读依据;自己的误判也写进去。

结论速览

维度结论
幂等中间件实现✅ 扎实(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、幂等静默失效

⇒ 这说明「规则看起来对」和「规则真的生效」是两件事,他们踩过并留了记录。


二、覆盖范围:实测 28/28 全部命中 ✅

我没有只看 SCOPE_MAP 声明了什么,而是拿真实路由去匹配——新增

scripts/verify_idempotency_real.py:从各router 文件提取真实 POST 路径(含 prefix),

再调 match_scope 验证命中。

真实 POST 路由 249 条
✅ 命中幂等 28 条(与 SCOPE_MAP 声明一致)
🔴 关键写操作未覆盖 44 条

三、C 端下单:我误报了,查完发现防护更硬

我最初判定「/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: ...   # 命中已有 ⇒ 直接回传

⇒ 数据库唯一约束 + 代码层回查,比中间件更硬(中间件可被绕过,约束绕不过)。


四、🔴 真正的缺口:10 个写操作无幂等覆盖

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                      ← 保护付

风险场景(真实可能发生,不是理论):

⚠️ 这些端点是否有状态机守卫兜底,本轮未逐个判读——

miniapp_orders.py 的状态机守卫(pending_payment → paid 之外拒绝迁移)是存在的,

商户端这些端点大概率也有类似守卫,但我没有验证。


五、修改建议

🔴 建议改(按优先级)

#动作理由风险
1把 verify_idempotency_real.py 接进 pre-commit它是元检查:验证「幂等规则是否真的生效」,比规则本身更重要极低
2给 refund/approve、joint-payment/generate 加幂等覆盖这两个是钱相关且可重试的低
3补查 10 个未覆盖端点的状态机守卫若无守卫则真会重复执行低

✅ 建议推广为范式

不要只检查「规则写对了」,要检查「规则真的生效」


六、🔴 我在这一轮犯的两个错(必须记录)

错误 ①:把「SCOPE_MAP 里没它」当成「无防护」

我看到 /api/miniapp/orders 不在 SCOPE_MAP,就判「C 端下单无防重」。

实际数据库层有唯一约束 + 代码层回查,防护更硬。

⇒ 判「缺失」之前,必须先问「有没有别的层已经防了」。

错误 ②:我的验证脚本自己有 bug,却差点当成 P0 结论

第一版 verify_idempotency_real.py 只收集 var == 'router' 的路径,

把所有具名 router(c_router / console_router)都漏了

⇒ 输出「命中 0 条」⇒ 我据此写下「幂等完全失效,形同不存在」。

发现方式:那个结论太反常(他们明明踩过这个坑并留了记录)⇒ 我回头核对 SCOPE_MAP 内容,

发现它格式完全正确,是我的提取逻辑错。修好后:命中 28/28。

⇒ 🔴 这是我今晚第三次犯「脚本 bug 当成代码缺陷」的错(前两次:except 正则恒不命中、

--changed 扫到自己)。

这已经是模式,不是意外。


七、🔴 本轮仍未覆盖(诚实标注)

未查为什么重要
🔴 事务边界多表写入是否在同一事务?中途失败会不会留半截数据
🔴 并发锁库存/房间/名额扣减有没有行锁或乐观锁 ⇒ 超卖
🔴 10 个未覆盖端点的状态机守卫决定它们是真缺口还是已被兜住
🔴 金额计算精度有没有浮点混进分

**⇒ 这份结论可用于「幂等」维度的决策,

但「并发/事务」这个维度仍未审完——而超卖恰恰在这里**。

八、下一步建议

优先查「库存/名额扣减有没有锁」 —— 这是这个业务最可能真实出事的点:

酒店房间、车队名额、预售券数量,超卖一次就是真金白银的赔偿。

比继续查幂等覆盖面更值得做。