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

116 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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:<YYYYMMDD>`,TTL 5 分钟),避免 pkg→internal 反向依赖。
### 验证方式
- `go build ./...` 与 `go vet ./...` 通过;重点回归启动加载、支付回调、点赞/收藏可见性、收藏分页。