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