Files
zt-closet-backend/.codebuddy/plans/service层完整实现_3a88b729.md
2026-09-11 01:41:52 +08:00

147 lines
13 KiB
Markdown
Raw Permalink 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: service层完整实现
overview: 从零实现 internal/service 全部业务逻辑(用户/衣物/穿搭/衣柜分享/收藏/点赞/通知/商品/订单退款/额度/运势/AI推荐),复用已就绪的 dao/dto/redis/pkg,并接入真实微信登录与微信支付/退款(含配置与依赖),公开内容自动审核通过,保证 service 层可独立编译。
todos:
- id: setup-wechat-infra
content: 新增微信小程序/支付配置项与依赖,实现 pkg/wechat(code2session、JSAPI下单、回调验签解密、退款),并在 setting/global/main.go 接线
status: completed
- id: service-foundation
content: 创建 constants.go 常量与 service.go 公共工具(错误映射、redis.Nil 判定、分页默认、consumeQuota/refundQuota 额度封装)
status: completed
- id: user-quota-horoscope
content: 实现 user/quota/horoscope 三个 service:微信登录注册+初始化额度、用户信息缓存、Token刷新、额度概览与流水、按生日算星座读取今日运势
status: completed
dependencies:
- setup-wechat-infra
- service-foundation
- id: clothes-pipeline
content: 实现 ClothesService:上传扣额度+落COS+建记录,goroutine 异步抠图分类回填状态与失败回退,状态查询与增删改查列表
status: completed
dependencies:
- service-foundation
- id: community-and-closet
content: 实现 outfit/like/collect/notification/closet 五个 service:穿搭增删改查与公开(自动审核通过+扣额度)、点赞收藏与作者通知、通知读写未读数、衣柜分享创建/详情/列表/取消
status: completed
dependencies:
- clothes-pipeline
- id: order-refund
content: 实现 goods 与 OrderService:商品查询、微信JSAPI下单、支付回调发放会员/额度权益+通知、取消订单、订单列表、申请退款与退款回调(幂等+状态流转)
status: completed
dependencies:
- setup-wechat-infra
- user-quota-horoscope
- id: ai-recommend
content: 实现 AIRecommendService:防并发扣AI额度、基于衣橱与运势调用 tokenhub 生成推荐穿搭、幂等流水、保存到我的搭配,失败回退额度
status: completed
dependencies:
- community-and-closet
- user-quota-horoscope
---
## 产品概述
为"衣柜/穿搭"微信小程序后端补齐 **service 业务逻辑层**(`internal/service`),将已就绪的 DTO、MySQL DAO、Redis DAO 与 pkg 能力(大模型推荐、抠图、分类、COS、JWT、错误码)编排为完整可用的业务服务,覆盖 `internal/dto` 中全部请求/响应。本次仅实现 service 层(保证可独立编译),不实现 handler/router。
## 核心功能
- **用户与登录**:微信小程序 code2session 真实登录、注册即初始化默认额度、资料修改、Token 刷新、用户信息缓存。
- **衣物管理**:上传(扣额度、落 COS)+ 异步抠图/分类回填、状态查询、增删改查、列表分页。
- **穿搭管理**:创建/编辑/删除、明细组装、公开设置(公开即自动审核通过并写审核日志、消耗社区分享额度)。
- **衣柜分享**:部分/全量快照分享、访客详情(含过期与状态校验、访问计数)、我的分享列表、取消;创建即自动审核通过。
- **社区互动**:点赞/收藏及其取消、点赞给作者发通知、点赞数/收藏数统计(Redis 优先)。
- **通知**:列表、详情、标记已读、未读数(Redis 优先回落 MySQL)。
- **商品与订单**:商品查询/列表;下单(真实微信 JSAPI 支付)、支付回调发放会员与额度权益、取消、订单列表;申请退款与退款回调(真实微信支付退款)。
- **额度与运势**:额度概览与流水;按用户生日计算星座返回今日运势(Redis→MySQL 多级读取)。
- **AI 穿搭推荐**:防并发扣 AI 额度、基于衣橱与星座运势调用大模型生成推荐穿搭、幂等流水、保存到"我的搭配"。
## 关键约束
- 所有"我的"资源操作必须校验归属(用户/衣物/穿搭/分享/通知/订单)。
- 微信密钥仅来自配置,不硬编码;支付/退款回调必须验签解密并做幂等;额度扣减以数据库原子条件更新为准,Redis 作缓存加速。
## 技术栈
- 语言/框架:Go 1.27、Gin(沿用现有项目);ORM 为 GORM(含 `soft_delete` 软删除);缓存 go-redis/v9。
- 复用能力:`internal/mysqldao`(结构体 DAO + `global.MysqlEngine`)、`internal/redisdao`(**包级函数**,未命中返回 `redis.Nil`)、`pkg/tokenhub`(大模型运势/推荐)、`pkg/matting`(抠图)、`pkg/classification`(分类)、`pkg/upload` + `pkg/cos`(落盘/上传/CDN URL)、`pkg/lzcwyjwt`(JWT)、`pkg/errcode`(业务错误码)、`pkg/app`(响应封装,供后续 handler 用)、`global.Tracer`(OTel)。
- 新增依赖:`github.com/wechatpay-apiv3/wechatpay-go`(微信支付 v3 官方 SDK,用于 JSAPI 下单/回调验签解密/退款);微信登录 code2session 用标准库 `net/http` 直接请求固定域名 `api.weixin.qq.com`(无需额外依赖)。已具备 `github.com/google/uuid` 生成各类 UUID。
## 实现思路
采用与 DAO 一致的**结构体 Service + 构造函数**风格(`NewXxxService()` 内部持有所需 DAO 实例),按业务域分文件放置于 `internal/service`,统一方法签名约定:`func (s *XxxService) Method(ctx context.Context, userID int64, req *dto.XxxRequest) (*dto.XxxResponse, *errcode.Error)`,将底层 `error` 映射为语义化 `*errcode.Error`(DB→`DatabaseError`、缓存→`CacheError`、`redis.Nil` 视为未命中而非错误)。service 层不依赖 gin,保证 `go build ./internal/service/...` 可独立编译。
关键决策与权衡:
- **枚举常量集中化**:mysqlmodel 状态/类型仅有注释、无 Go 常量。新增 `internal/service/constants.go` 定义可读常量(状态、类型、额度类型、变更类型等),消除魔法数字,降低跨域协作出错概率。
- **额度扣减一致性**:以 MySQL `QuotaIncreaseUsed`(`used+n<=total` 条件原子更新)为**权威扣减**,保证不超发(乐观并发);Redis `QuotaRemain*`/每日计数作为读取加速与限流,二者不一致时以 MySQL 为准。封装 `consumeQuota/refundQuota` 复用于衣物上传、公开分享、AI 推荐。
- **异步图片处理**:`ClothesUpload` 同步完成校验/扣额度/落原图/建记录后立即返回,抠图+分类放入 goroutine(派生独立 `context`、`recover` 兜底),逐步更新 `image_process_status`;任一步失败置状态 5 并写 `fail_reason` 且回退额度,避免阻塞请求、控制外部 RPC 时延影响。
- **支付幂等与状态机**:下单/支付回调/退款回调分别用 `redisdao.TryLock`(`KeyLockOrderCreate/KeyLockPayCallback/KeyLockRefundCallback`)+ `OrderUpdateStatusByOrderNo(from→to)` 条件流转双保险,防重复回调重复发放权益。
- **多级读取**:运势、未读数、点赞/收藏计数走 Redis 优先、未命中回落 MySQL 并回填,降低热点查询压力。
## 实现要点(防回归)
- redisdao 是包级函数,未命中返回 `redis.Nil`,必须 `errors.Is(err, redis.Nil)` 判断,不可当错误抛。
- mysqlmodel 均内嵌 `Model`,`clothes` 表主色列名为 `color`(非 `color_main`),且 dto `ClothesUpdateByUUIDRequest.ColorMain` 的 tag 为 `json:"color"`、`ClothesGetByUUIDResponse.ColorMain` 为 `json:"color_main"`——映射一律以 model 字段为准,`updates` map 的 key 用真实列名 `color`。
- dto `GoodsGetByCodeResponse.UploadQuota` 对应 model `Goods.CosQuota`;`AIRecommendQuota` 无对应 dto 字段(发放额度时仍需处理)。注意 `UserDailyUsage.CLosetShareCount` 原始拼写。
- 复用现有 logger(`log/slog`,已接入 OTel context handler);错误日志不打印密钥/回调原文明文,避免 PII/密钥泄露。
- 归属校验统一在 service 层做(AuthZ);所有 SQL 走 DAO 参数绑定(防 SQLi 已满足);微信回调仅访问微信固定域名(无 SSRF 面)。
## 目录结构
```
zt-closet-backend/
├── configs/
│ └── config.example.yaml # [MODIFY] 新增 WechatMini(AppID/AppSecret) 与 WechatPay(AppID/MchID/APIv3Key/SerialNo/PrivateKeyPath/NotifyURL/RefundNotifyURL) 示例段
├── pkg/
│ ├── setting/
│ │ └── section.go # [MODIFY] 新增 WechatMiniSettingS、WechatPaySettingS 结构体
│ └── wechat/
│ ├── mini.go # [NEW] 微信小程序登录:Code2Session(ctx,code)->(openid,sessionKey,err),net/http 请求 jscode2session,errcode 映射
│ └── pay.go # [NEW] 微信支付客户端:初始化 wechatpay-go(加载商户私钥/证书),封装 JsapiPrepay/BuildPaySign、VerifyAndDecryptPayNotify、Refund、VerifyRefundNotify
├── global/
│ └── setting.go # [MODIFY] 新增 WechatMiniSetting、WechatPaySetting 全局变量
├── main.go # [MODIFY] setupSetting 增 ReadSection("WechatMini"/"WechatPay");新增 setupWechatPayClient 初始化支付客户端(可选,失败仅告警不 panic)
└── internal/service/
├── service.go # [NEW] 公共工具:error->errcode 映射、redis.Nil 判定、分页默认值(page/pageSize)、时间/日期工具、consumeQuota/refundQuota 额度封装
├── constants.go # [NEW] 各状态/类型 int8 常量(用户/衣物处理状态/穿搭/分享/审核/额度类型/变更类型/订单/退款/通知/点赞收藏类型等)
├── user.go # [NEW] UserService:WxLogin(真实code2session+注册+初始化额度+发Token)、InfoGetBySelf(缓存)、ProfileUpdate、RefreshToken
├── quota.go # [NEW] QuotaService:InfoGetBySelf(remain=total-used)、FlowList;ensureDefaultQuota 新用户默认额度初始化
├── horoscope.go # [NEW] HoroscopeService:由生日算星座+当日日期,Redis→MySQL 读取今日运势,回填缓存
├── clothes.go # [NEW] ClothesService:Upload(扣额度+落COS+建记录+异步抠图分类)、ProcessStatusGet、GetByUUID(CDN URL)、UpdateByUUID、DeleteByUUID、List
├── outfit.go # [NEW] OutfitService:Create/GetByUUID/Update/List/Delete/Public(公开自动审核通过+写审核日志+扣社区分享额度)
├── closet.go # [NEW] ClosetShareService:Create(自动审核通过+扣衣柜分享额度)、Detail(过期/状态校验+访问计数)、List、Cancel
├── collect.go # [NEW] CollectService:Collect(收藏/取消+Redis集合+target校验)、List(收藏穿搭列表)
├── like.go # [NEW] LikeService:LikeCreate(点赞/取消+Redis+给作者发通知)、GetByTargetID(Redis优先计数)
├── notification.go # [NEW] NotificationService:List/GetByUUID/Read/Unread;内部 sendNotification 供 like/order 复用
├── goods.go # [NEW] GoodsService:GetByCode(下架校验)、List(仅上架+类型过滤)
├── order.go # [NEW] OrderService:Create(微信JSAPI下单)、PayNotify(发放会员/额度权益+通知)、Cancel、List、RefundApply、RefundNotify
└── airecommend.go # [NEW] AIRecommendService:Create(防并发+扣AI额度+大模型推荐+幂等流水+生成待保存穿搭)、Save(置为正常)
```
## 关键代码结构
```
// internal/service/service.go —— service 层统一错误映射约定
// 将底层 error 归一为语义化业务错误;redis.Nil 由调用方按未命中处理,不进入此函数。
func toErr(err error, biz *errcode.Error) *errcode.Error
// 额度封装(供 clothes/outfit/closet/airecommend 复用):MySQL 原子扣减为准 + 写流水 + 同步 Redis
func (s *QuotaService) consumeQuota(ctx context.Context, userID int64, quotaType int8, n int64, changeType int8, reason, orderNo string) *errcode.Error
func (s *QuotaService) refundQuota(ctx context.Context, userID int64, quotaType int8, n int64, reason string) *errcode.Error
```
```
// pkg/wechat/pay.go —— 微信支付封装对外接口(签名以官方 SDK 实际类型为准,实现时对齐)
func InitPayClient(s *setting.WechatPaySettingS) error
func JsapiPrepay(ctx context.Context, openid, orderNo, description string, amount int64, notifyURL string) (prepayID string, err error)
func BuildMiniPayParams(prepayID string) *dto.WechatPayParams
func VerifyAndDecryptPayNotify(req *http.Request) (orderNo, transactionID string, paid bool, err error)
func Refund(ctx context.Context, orderNo, refundNo string, total, refund int64, notifyURL string) error
```
## Agent Extensions
### SubAgent
- **code-explorer**
- Purpose:实现阶段按需核对 DAO/Redis/pkg 的精确方法签名与字段(如 wechatpay-go 实际 API、`ClassifyResult` 字段到 `Clothes` 列的映射),避免签名臆测。
- Expected outcome:跨文件签名/字段映射一次性确认到位,减少编译期返工。