116 lines
6.6 KiB
Markdown
116 lines
6.6 KiB
Markdown
---
|
||
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 ./...` 通过;重点回归启动加载、支付回调、点赞/收藏可见性、收藏分页。 |