Files
zt-closet-backend/.codebuddy/plans/修复中等与轻微问题6项_599caa55.md
T
2026-09-11 01:41:52 +08:00

5.9 KiB

name, overview, todos
name overview todos
修复中等与轻微问题6项 按二次审查报告修复 6 个问题:M1(退款推送 RefundStatus 空串误回收)、M2(发货校验失败返回成功应答吞资损线索)、M5(补偿任务 Failed 被回调重置)、M14(衣物失败回退与删除并发漏退)、M10(refresh-token access_token 文档矛盾)、M13(Outfit.Public 并发语义冲突)。限流(M11)与上传防护(M12)本次不修复。
id content status
fix-xpay-notify 修复虚拟支付推送链路:新增 XPayRefundStatusSuccess 常量,handleRefundNotify 改白名单判断,handleGoodsDeliver 五个校验失败分支返回 70009 触发微信重试 completed
id content status
fix-benefit-reset 修复补偿任务重置:BenefitGrantTaskReset 保留 retry_count,失败任务增加 5 分钟冷却期后才允许回调重新触发 completed
id content status
fix-clothes-refund-tx 修复衣物回退漏退:markProcessFailed 重写为行锁单事务,删除 refundIfExists 与 ClothesMarkProcessFailedIfNotFailed completed
id content status
fix-outfit-public-lock 修复穿搭公开竞态:redismodel 新增 KeyLockOutfitPublic,OutfitService.Public 全流程加 Redis 锁串行化 completed
id content status dependencies
sync-docs 同步文档:README 修正 refresh-token access_token 为必填,审查报告 M1/M2/M5/M10/M13/M14 标注已修复 completed
fix-xpay-notify
fix-benefit-reset
fix-clothes-refund-tx
fix-outfit-public-lock
id content status dependencies
verify 验证 go build 与 go test 通过,read_lints 无新增告警 completed
sync-docs

用户需求

根据二次代码审查报告,修复用户确认的 6 个问题(最新调整:限流缺失 M11、上传防护 M12 暂不修复):

  1. M1 退款推送误回收:RefundStatus 字段为空串或未知值时被误判为「退款成功」并回收权益,修复为仅当明确等于 SUCCESS 才回退
  2. M2 发货校验失败静默:发货推送本地校验失败(订单不存在/渠道不符/道具不符/金额不符/环境不符)返回成功应答,微信不重试且资损线索被吞掉,修复为返回失败应答触发重试
  3. M5 补偿任务重试上限失效:失败任务被支付回调重置(retry_count 清零),绕过重试上限与人工介入机制
  4. M14 衣物额度漏退窗口:异步处理失败回退与用户删除并发时,可能两个「应回退」事件叠加却零回退,用户损失额度
  5. M10 refresh-token 文档矛盾:README 标注 access_token 为可选,但代码要求必填,统一为必填
  6. M13 穿搭公开并发语义冲突:并发公开时,一个请求回退公开态后,另一请求已误报成功

约束

  • 保持现有代码风格(slog 结构化日志、errcode 错误码、Redis fail-open 降级)
  • 修复后 go build ./... 与 go test ./pkg/wechat/ 必须通过,read_lints 无新增告警
  • 不修改普通微信支付 V3 回调链路;M3/M4/M6-M9/M11/M12/M15 及未点名问题不在本次范围

技术方案

1. M1 + M2:虚拟支付推送链路修复

M1 退款推送白名单

  • pkg/wechat/virtualpay.go:新增常量 XPayRefundStatusSuccess = "SUCCESS"
  • internal/service/virtualpay.go 的 handleRefundNotify:改为 if refund.RefundStatus != wechat.XPayRefundStatusSuccess { slog.WarnContext(...); return nil },仅成功状态走 settleRefundSuccess;订单不存在分支保持 return nil

M2 发货校验失败应答

  • handleGoodsDeliver 中以下五个分支由 return nil 改为 return errcode.OrderRealityCheckFailed(70009 已存在),使 XPayNotify 回 ErrCode:1 让微信重试:
  • 订单不存在
  • pay_channel != PayChannelVirtual
  • 道具 ID 不匹配
  • 实付金额不匹配
  • 环境不匹配
  • 锁竞争失败分支保持 return nil(并发回调交由持锁者处理,防重试风暴)

2. M5:补偿任务重置保护

  • internal/mysqldao/benefit_grant_task.go 的 BenefitGrantTaskReset:删除 retry_count: 0,保留原重试计数
  • internal/service/order.go 的 grantBenefitIdempotent Failed 分支:新增冷却常量 benefitRetryCooldownSeconds = 300;当 nowUnix()-existing.UpdatedAt < 300 时仅打告警跳过,冷却期外才 Reset + executeGrant,恢复重试上限与人工介入机制

3. M14:衣物处理失败回退事务化

  • internal/service/clothes.go 重写 markProcessFailed 为单事务(复用 DeleteByUUID 的 FOR UPDATE + 回退模式):
  1. tx.Clauses(clause.Locking{Strength: "UPDATE"}) 按 clothes_uuid 查衣物;不存在/已软删(RecordNotFound)或已是失败态则提交并跳过
  2. 置 image_process_status=5 + fail_reason
  3. 同事务回退额度:条件更新 used - 1(used>=1),回读真实值,写回退流水(Reason="衣物处理失败回退",OrderNo=流水 UUID 兜底)
  • 删除 refundIfExists 方法与 internal/mysqldao/clothes.go 的 ClothesMarkProcessFailedIfNotFailed(已确认仅此一处调用)
  • DeleteByUUID 保持不变:行锁读到失败态即跳过回退,与失败事务通过行锁互斥,两路径恰好回退一次

4. M13:穿搭公开加 Redis 锁

  • internal/redismodel/lock.go:新增 KeyLockOutfitPublic = RedisPrefix + "lock:outfit:public:%s"(outfit_uuid)
  • internal/service/outfit.go 的 Public:进入后先 redisdao.TryLock(fmtLockKey(KeyLockOutfitPublic, request.OutfitUUID), 10s),失败返回 errcode.TooManyRequests;成功 defer 解锁;锁内保留现有「条件公开 → 扣额 → 失败回退」流程,串行化后并发请求语义正确

5. M10 + 文档同步

  • README.md 686 行:access_token 由「否(可选,向前兼容)」改为「是(必填)」,说明缺少返回 10000
  • docs/代码审查报告-二次check.md:M1/M2/M5/M10/M13/M14 标题后标注「已修复」及一行修复说明;M11/M12 保持未修复状态

验证

  • go build ./... 全量编译通过
  • go test ./pkg/wechat/ 通过
  • read_lints 检查修改文件无新增告警