153 lines
9.7 KiB
Markdown
153 lines
9.7 KiB
Markdown
---
|
||
name: api-v1层完整实现
|
||
overview: 实现 internal/routers/api/v1 全部 HTTP handler 并在 router.go 完成中间件与路由接线,调用已完成的 service 层;同时修复阻断运行的中间件/启动初始化问题,形成可跑通的 RESTful 接口。
|
||
todos:
|
||
- id: fix-infra
|
||
content: 修复 middleware(requestid uuid 导入、JWTAuth 封禁判断反转与缓存回落 DB)、main.go 初始化 global.Tracer/CosClient 与运势任务、dto 列表字段补 form tag
|
||
status: completed
|
||
- id: handlers-user-basic
|
||
content: 实现 user/quota/horoscope/goods 四个 handler(登录/刷新/信息/额度/运势/商品)
|
||
status: completed
|
||
dependencies:
|
||
- fix-infra
|
||
- id: handlers-clothes-outfit
|
||
content: 实现 clothes(含 multipart 上传)与 outfit 两个 handler,覆盖增删改查与公开
|
||
status: completed
|
||
dependencies:
|
||
- fix-infra
|
||
- id: handlers-community
|
||
content: 实现 closet/collect/like/notification 四个 handler(分享、收藏、点赞、通知)
|
||
status: completed
|
||
dependencies:
|
||
- fix-infra
|
||
- id: handlers-order-ai
|
||
content: 实现 order(含微信支付/退款回调按规范返回)与 airecommend 两个 handler
|
||
status: completed
|
||
dependencies:
|
||
- fix-infra
|
||
- id: wire-router
|
||
content: 在 router.go 接线中间件链与 /api/v1 公开/鉴权路由,添加 /ping,go build 与 go vet 全量验证
|
||
status: completed
|
||
dependencies:
|
||
- handlers-user-basic
|
||
- handlers-clothes-outfit
|
||
- handlers-community
|
||
- handlers-order-ai
|
||
---
|
||
|
||
## 产品概述
|
||
|
||
为"衣柜/穿搭"微信小程序后端补齐 **HTTP 接口层(api/v1)**:将已完成的 service 业务逻辑通过 Gin handler 暴露为 RESTful 接口,并完成路由与中间件接线,形成可跑通的完整后端服务。
|
||
|
||
## 核心功能
|
||
|
||
- **用户与登录**:微信登录、刷新 Token、获取/修改个人信息
|
||
- **衣物管理**:上传(含图片处理)、状态查询、详情、修改、删除、列表
|
||
- **穿搭管理**:创建、详情、修改、删除、列表、公开设置
|
||
- **衣柜分享**:创建、详情(访客/属主)、我的分享列表、取消
|
||
- **社区互动**:点赞/取消、点赞状态与计数、收藏/取消、收藏列表
|
||
- **消息通知**:列表、详情、标记已读、未读数
|
||
- **商品与订单**:商品查询/列表、下单、订单列表、取消、退款申请、微信支付/退款回调
|
||
- **额度与运势**:额度概览与流水、今日运势
|
||
- **AI 推荐**:发起 AI 穿搭推荐、保存到我的搭配
|
||
|
||
## 接口约定
|
||
|
||
- 统一前缀 `/api/v1`;成功返回 `{code:0,msg,data}`,失败按业务错误码返回。
|
||
- 登录、刷新 Token、商品查询、微信回调为公开接口;其余需登录鉴权。
|
||
- 微信支付/退款回调按微信规范返回受理结果。
|
||
|
||
## 技术栈
|
||
|
||
- 语言/框架:Go 1.27 + Gin(沿用现有项目),复用 `pkg/app`(统一响应)、`pkg/errcode`(业务错误码)、`internal/middleware`(JWT/CORS/日志/追踪/RequestID)。
|
||
- 依赖注入:handler 结构体持有对应 `internal/service`,构造函数注入,与 service/dao 现有风格一致。
|
||
|
||
## 实现思路
|
||
|
||
新增 `internal/routers/api/v1` 下按业务域拆分的 handler,并在 `router.go` 完成中间件与路由接线。handler 只负责:请求绑定与校验、path param 覆盖标识字段、从 context 取 userID、调用 service、统一响应转换;业务逻辑与归属校验(AuthZ)保持在 service 层,handler 不重复实现。
|
||
|
||
统一 handler 模式:
|
||
|
||
1. `resp := app.NewResponse(c)`
|
||
2. 绑定:JSON 用 `ShouldBindJSON`,GET query 用 `ShouldBindQuery`,失败返回 `errcode.InvalidParams.WithDetails(err.Error())`
|
||
3. REST 路径标识(uuid/code/orderNo 等)用 `c.Param(...)` 覆盖 req 对应字段
|
||
4. `middleware.GetUserID(c.Request.Context())` 取 userID
|
||
5. 调 service,错误 `resp.ToErrorResponse(e)`,成功 `resp.ToResponse(out)`
|
||
|
||
### 关键技术决策
|
||
|
||
- **同层静态路由与参数路由共存**:Gin v1.12 支持 `/notification/unread` 与 `/notification/:uuid`、`/order/refund` 与 `/order/:orderNo/cancel` 共存,无需拆分前缀,保持 URL 语义清晰。
|
||
- **微信回调独立处理**:回调不走 JWT,handler 读 `c.GetRawData()` 原始报文传 service(service 已做 V3 验签/GCM 解密),按微信规范返回 `{"code":"SUCCESS"}`(200)或 `{"code":"FAIL","message":...}`(非 200),避免微信重复回调。
|
||
- **文件上传**:`/clothes/upload` 用 `c.FormFile("file")` 取 `*multipart.FileHeader` 传 service。
|
||
|
||
## 实现要点(防回归)
|
||
|
||
本次需一并修复阻断 api 层编译/运行的既有问题,否则接口无法跑通:
|
||
|
||
- **middleware/requestid.go**:修复非法导入 `import "uuid"` → `github.com/google/uuid`,调用 `uuid.New().String()`(当前该文件导致 middleware 包无法编译)。
|
||
- **middleware/lzcwyjwt.go**:
|
||
- 修复封禁判断反转:`if userInfo.Status == 1` → 应为 `!= 1` 时拒绝(1=正常),避免正常用户被拦、禁用用户放行。
|
||
- 缓存未命中(`redis.Nil`)回落 DB(`mysqldao.UserDao.UserGetByID`),成功回填 `redisdao.UserInfoSet`,仍失败再拒绝,避免缓存过期导致合法用户被锁死。
|
||
- **main.go 运行期初始化**(当前缺失,直接运行会 nil panic):
|
||
- `global.Tracer = otel.Tracer(global.OtelSetting.ServiceName)`(otel 默认 no-op provider 返回合法非 nil tracer,DAO 的 `global.Tracer.Start` 依赖它)。
|
||
- `global.CosClient, _ = cos.NewCosClient()`(衣物上传 `upload.FileUploadCos` 依赖)。
|
||
- 启动运势兜底:`tokenhub.EnsureTodayFortunes()` 与 `tokenhub.StartFortuneScheduler()`。
|
||
- **dto 补 form tag**(GET query 筛选可用的最小调整):`OutfitListRequest.IsPublic/Source`、`GoodsListRequest.GoodsType`、`ClosetShareListRequest.Status` 当前仅 `json` tag,需补 `form` tag 才能 `ShouldBindQuery` 绑定。
|
||
- 不打印 token/密钥;绑定错误文本作为 details 返回可接受,不泄露内部实现。
|
||
|
||
## 架构与数据流
|
||
|
||
请求 → Gin 中间件链(Recovery → RequestID → OTel → Slog → CORS)→ [公开路由 / JWTAuth 鉴权组] → v1 handler(绑定+取 userID)→ service(业务+AuthZ)→ dao/redis/pkg → 统一响应。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
zt-closet-backend/
|
||
├── main.go # [MODIFY] 初始化 global.Tracer / global.CosClient;启动运势定时任务与启动兜底
|
||
├── internal/
|
||
│ ├── dto/
|
||
│ │ ├── outfit.go # [MODIFY] OutfitListRequest.IsPublic/Source 补 form tag
|
||
│ │ ├── goods.go # [MODIFY] GoodsListRequest.GoodsType 补 form tag
|
||
│ │ └── closet.go # [MODIFY] ClosetShareListRequest.Status 补 form tag
|
||
│ ├── middleware/
|
||
│ │ ├── requestid.go # [MODIFY] 修复 uuid 导入路径
|
||
│ │ └── lzcwyjwt.go # [MODIFY] 修复封禁状态判断反转 + 缓存未命中回落 DB 回填
|
||
│ └── routers/
|
||
│ ├── router.go # [MODIFY] 中间件链接线 + /api/v1 公开与鉴权路由注册 + /ping
|
||
│ └── api/v1/
|
||
│ ├── user.go # [NEW] UserHandler:WxLogin/RefreshToken/InfoGetBySelf/ProfileUpdate
|
||
│ ├── quota.go # [NEW] QuotaHandler:Info/FlowList
|
||
│ ├── horoscope.go # [NEW] HoroscopeHandler:Today
|
||
│ ├── clothes.go # [NEW] ClothesHandler:Upload/List/Get/Status/Update/Delete
|
||
│ ├── outfit.go # [NEW] OutfitHandler:Create/Get/Update/Delete/List/Public
|
||
│ ├── closet.go # [NEW] ClosetShareHandler:Create/Detail/List/Cancel
|
||
│ ├── collect.go # [NEW] CollectHandler:Collect/List
|
||
│ ├── like.go # [NEW] LikeHandler:Like/GetByTarget
|
||
│ ├── notification.go # [NEW] NotificationHandler:List/Unread/Get/Read
|
||
│ ├── goods.go # [NEW] GoodsHandler:GetByCode/List(公开)
|
||
│ ├── order.go # [NEW] OrderHandler:Create/List/Cancel/RefundApply/PayNotify/RefundNotify
|
||
│ └── airecommend.go # [NEW] AIRecommendHandler:Create/Save
|
||
```
|
||
|
||
## 路由清单(前缀 /api/v1)
|
||
|
||
公开:POST /user/wxlogin、POST /user/refresh-token、GET /goods、GET /goods/:code、POST /order/pay/notify、POST /order/refund/notify
|
||
鉴权(JWTAuth):
|
||
|
||
- GET /user/info、PUT /user/profile
|
||
- GET /quota/info、GET /quota/flow
|
||
- GET /horoscope/today
|
||
- POST /clothes/upload、GET /clothes、GET /clothes/:uuid、GET /clothes/:uuid/status、PUT /clothes/:uuid、DELETE /clothes/:uuid
|
||
- POST /outfit、GET /outfit、GET /outfit/:uuid、PUT /outfit/:uuid、DELETE /outfit/:uuid、PUT /outfit/:uuid/public
|
||
- POST /closet-share、GET /closet-share、GET /closet-share/:uuid、POST /closet-share/:uuid/cancel
|
||
- POST /collect、GET /collect
|
||
- POST /like、GET /like/:targetUUID
|
||
- GET /notification、GET /notification/unread、GET /notification/:uuid、POST /notification/:uuid/read
|
||
- POST /order、GET /order、POST /order/:orderNo/cancel、POST /order/refund
|
||
- POST /ai-recommend、POST /ai-recommend/save
|
||
|
||
## SubAgent
|
||
|
||
- **code-explorer**
|
||
- Purpose:实现阶段如需核对个别 service 方法签名或 dto 字段(如回调 body 读取、multipart 字段名、path param 与 dto 字段映射),快速跨文件确认,避免签名臆测。
|
||
- Expected outcome:handler 与 service 签名、dto 绑定一次性对齐,减少编译期返工。 |