Files
zt-closet-backend/.codebuddy/plans/fix-14-issues-except-8-and-15_dc316bf3.md
T
2026-09-11 01:41:52 +08:00

6.6 KiB
Raw Blame History

name, overview, todos
name overview todos
fix-14-issues-except-8-and-15 修复代码审查发现的 14 个问题(跳过第 8 项「公开内容自动审核」与第 15 项「CORS 放开」),覆盖:配置加载导致启动崩溃、支付/退款金额校验缺失、点赞收藏越权、CDN 签名弱化、示例配置不一致、删衣不回退额度、穿搭更新清空字段、AI 锁 TTL 过短、收藏列表内存分页、异步 goroutine 无并发上限、单号随机段过短、运势生成无防重、OTel 中间件 nil 防御。
id content status
config-upload-load 修复配置加载:main.go 补 Upload 段读取、config.example.yaml 修正字段并补 Cos 段、otel.go 加 nil 兜底 completed
id content status
order-pay-security order.go 增加支付/退款回调金额校验,并加长订单号/退款单号随机段 completed
id content status
interact-visibility 新增穿搭可见性辅助函数,修复点赞/收藏越权校验 completed
id content status
upload-clothes-fix upload.go 升级 https+HMAC-SHA256 签名;clothes.go 删除回退额度并加异步并发限流 completed
id content status
outfit-airecommend-fix outfit.go 更新防清空字段;airecommend.go 锁 TTL 提高到 180s completed
id content status
collect-pagination 收藏列表改 DB 分页:新增 DAO 分页方法并改造 collect.go List completed
id content status
fortune-dedupe 运势兜底生成加防重(进程内标记 + Redis SetNX) completed
id content status dependencies
build-vet-verify 运行 go build ./... 与 go vet ./... 验证全部修复 completed
config-upload-load
order-pay-security
interact-visibility
upload-clothes-fix
outfit-airecommend-fix
collect-pagination
fortune-dedupe

修复范围

基于前序代码审查发现的 14 项问题,按用户指令修复除第 8 项(公开内容自动审核)与第 15 项(CORS 放开)之外的全部问题。

核心修复项

  1. P0 启动崩溃:main.go 的 setupSetting() 漏加载 Upload 配置段,导致 global.UploadSetting 为 nil、启动即 panic。
  2. P1 安全:
  • 微信支付回调 PayNotify 未校验回调金额是否等于订单应付金额;
  • 退款回调 RefundNotify 未校验回调退款金额是否等于流水退款金额;
  • 点赞/收藏未校验目标穿搭可见性(非本人可操作私有穿搭);
  • CDN 图片 URL 使用 md5 签名 + http 明文传输;
  • config.example.yaml 字段名与代码不一致且缺 Cos 段。
  1. P2 业务一致性:
  • 删除衣物不回退上传额度;
  • 穿搭更新会无条件用空串清空字段;
  • AI 推荐防并发锁 TTL 短于下游 120s 超时;
  • 收藏列表全量加载内存分页导致分页口径错乱;
  • 异步抠图 goroutine 无并发上限。
  1. P3 健壮性:
  • 订单号/退款单号随机段过短;
  • 运势兜底生成无防重;
  • OTel 中间件直接解引用 global.OtelSetting 缺 nil 防御。

技术方案

总体策略

沿用现有分层架构(main → routers → internal/service → internal/mysqldao / internal/redisdao → pkg/*),复用已有的 consumeQuota/refundQuota、redisdao.TryLock/Unlock、setIfNotEmpty 等封装,不做架构变更,以最小侵入方式修复。

关键决策

1. 配置修复(P0-1 / P1-6 / P3-16)

  • 在 main.go 的 setupSetting() 中,于 ReadSection("Cos", ...) 后补 setting.ReadSection("Upload", &global.UploadSetting),保留 MaxSizeMB<=0 兜底为 20。
  • config.example.yaml 的 Upload.SavePath 改为 TmpSavePath,并补 Cos 占位段(字段对齐 CosSettingS)。
  • middleware/otel.go 的 GinOTelMiddleware 增加 nil/空判断,fallback 服务名为 zt-closet-backend。

2. 支付安全(P1-2 / P1-3 / P3-13)

  • PayNotify:在幂等锁内改为「先 OrderGetByOrderNo 查单 → 校验 data.Amount.Total == order.PayAmount(单位均为分)→ 再 OrderUpdateStatusByOrderNo 条件流转 → 发放权益」。金额不符仅记 error 日志并 return nil,不流转、不发放。
  • RefundNotify:拿到 flow 后校验 data.Amount.Refund == flow.RefundAmount,不符告警 return nil。
  • genOrderNo/genRefundNo 随机段由 [:8] 加长为 [:16](结果 32 字符,未超微信单号上限)。

3. 越权修复(P1-4)

  • 在 service 包新增共享辅助函数 isOutfitPublicVisible(o *mysqlmodel.Outfit) bool(语义同 outfit.go GetByUUID:IsPublic==OutfitPublic && AuditStatus==OutfitAuditPass && Status==OutfitStatusNormal)。
  • like.go 的 getPublicOutfit 增加 userID 入参,非本人且不可见时返回 errcode.OutfitNoPermission;同步更新 LikeCreate、GetByTargetID 两处调用点。
  • collect.go 的 Collect 对非本人校验可见性。

4. 图片链路与额度(P1-5 / P2-7 / P2-12)

  • upload.GetCosUrl:http → https;签名由 md5(secret+"/"+file+ts) 升级为 HMAC-SHA256(secret, "/"+file+ts),保持 ?sign=xxx&t=ts。风险:CDN 边缘鉴权算法需同步升级为 HMAC-SHA256,否则图片 403。
  • clothes.DeleteByUUID:删除成功后,仅当 clothes.ImageProcessStatus == ImageProcessSuccess 时回退 1 个 COS 额度(refundQuota),避免与 markProcessFailed 重复回退。
  • clothes.go 增加包级信号量 imageProcessSem = make(chan struct{}, 10),在 processImageAsync 入口获取、defer 释放,限制异步并发。

5. 更新与并发(P2-9 / P2-10)

  • outfit.UpdateByUUID:改用 setIfNotEmpty 仅写非空字段;updates 为空时跳过 update(明细重建逻辑保持不变)。
  • airecommend.Create 锁 TTL 由 60s 提高到 180s(大于 tokenhub 客户端 120s 超时,留余量)。

6. 收藏分页(P2-11)

  • mysqldao/collect.go 新增 CollectListTargetIDsByUserPage(ctx, collectType, userID, page, pageSize) ([]int64, int64, error),用 Order("id DESC") + Count + Offset/Limit。
  • service/collect.go 的 List 改用该分页方法,total 取 DAO 计数,消除内存分页与口径错乱。

7. 运势防重(P3-14)

  • tokenhub/scheduler.go 的 EnsureTodayFortunes 与 runFortuneGeneration 共用「当日」防重:进程内用 sync.Mutex/atomic 标记 + 日期判断,跨实例用 global.RedisEngine.SetNX(key ztcloset:lock:fortune:daily:<YYYYMMDD>,TTL 5 分钟),避免 pkg→internal 反向依赖。

验证方式

  • go build ./... 与 go vet ./... 通过;重点回归启动加载、支付回调、点赞/收藏可见性、收藏分页。