6.6 KiB
6.6 KiB
name, overview, todos
| name | overview | todos | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| fix-14-issues-except-8-and-15 | 修复代码审查发现的 14 个问题(跳过第 8 项「公开内容自动审核」与第 15 项「CORS 放开」),覆盖:配置加载导致启动崩溃、支付/退款金额校验缺失、点赞收藏越权、CDN 签名弱化、示例配置不一致、删衣不回退额度、穿搭更新清空字段、AI 锁 TTL 过短、收藏列表内存分页、异步 goroutine 无并发上限、单号随机段过短、运势生成无防重、OTel 中间件 nil 防御。 |
|
修复范围
基于前序代码审查发现的 14 项问题,按用户指令修复除第 8 项(公开内容自动审核)与第 15 项(CORS 放开)之外的全部问题。
核心修复项
- P0 启动崩溃:
main.go的setupSetting()漏加载Upload配置段,导致global.UploadSetting为 nil、启动即 panic。 - P1 安全:
- 微信支付回调
PayNotify未校验回调金额是否等于订单应付金额; - 退款回调
RefundNotify未校验回调退款金额是否等于流水退款金额; - 点赞/收藏未校验目标穿搭可见性(非本人可操作私有穿搭);
- CDN 图片 URL 使用 md5 签名 + http 明文传输;
config.example.yaml字段名与代码不一致且缺Cos段。
- P2 业务一致性:
- 删除衣物不回退上传额度;
- 穿搭更新会无条件用空串清空字段;
- AI 推荐防并发锁 TTL 短于下游 120s 超时;
- 收藏列表全量加载内存分页导致分页口径错乱;
- 异步抠图 goroutine 无并发上限。
- 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.goGetByUUID: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(keyztcloset:lock:fortune:daily:<YYYYMMDD>,TTL 5 分钟),避免 pkg→internal 反向依赖。
验证方式
go build ./...与go vet ./...通过;重点回归启动加载、支付回调、点赞/收藏可见性、收藏分页。