Files
zt-online-game-backend/.codebuddy/plans/http-layer-completion_952ece43.md
T
zhengchiliu ff9add8655 补齐 HTTP 层:实现用户模块、路由与中间件接线、COS 上传及优雅启动
- 新增用户与上传错误码文件及 Recovery 中间件
- 修复 router.go 导入与中间件链,注册用户路由并补 handler 错误码导入
- 补充用户状态常量与 UserGetByWechatOpenID 查询,修订刷新令牌 dto
- 移除 upload 包 OTel 调用,新增 FileUploadCos 上传封装
- 实现 UserService 微信登录/刷新令牌/我的信息/改资料/上传头像
- 裁剪 main.go 配置段并移除 OTel 桥接,补启动与优雅退出
- 新增 configs 示例配置、global 配置结构及 go.mod 依赖
2026-09-27 15:02:01 +08:00

151 lines
12 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: http-layer-completion
overview: 补齐 zt-online-game-backend 的 HTTP 层:修复路由与中间件接线、实现 user 模块(微信登录/刷新 token/我的信息/改资料/上传头像)service 与 COS 上传能力、补 Recovery 中间件与错误码、完成 main.go 启动与优雅退出;不含 Redis、OTel、WebSocket。
todos:
- id: errcode-recovery
content: 新增用户/上传错误码文件与 Recovery 中间件,镜像兄弟项目范式
status: completed
- id: router-wiring
content: 修复 router.go 导入与中间件链并注册 user 路由,补 handler 的 errcode 导入
status: completed
dependencies:
- errcode-recovery
- id: dao-model-dto
content: 补 UserGetByWechatOpenID 与用户状态常量,按纯 JWT 语义修订刷新令牌 dto
status: completed
- id: upload-cos
content: 移除 upload 包的 OTel Tracer 调用,新增 FileUploadCos 上传封装
status: completed
- id: user-service
content: 用 [subagent:code-explorer] 核对兄弟项目细节后实现 UserService 五个方法与辅助函数
status: completed
dependencies:
- dao-model-dto
- upload-cos
- errcode-recovery
- id: main-boot
content: 裁剪 main.go 配置段并去 OTel 桥接,补启动/优雅退出与 lumberjack 依赖
status: completed
dependencies:
- router-wiring
- user-service
- id: verify-build
content: 执行 go mod tidy/build/vet 与启动自检,用 [skill:lsp-code-analysis] 核查无 OTel/Redis/WS 残留
status: completed
dependencies:
- main-boot
---
## 用户需求
把 zt-online-game-backend 项目现有的 HTTP 层面代码补充完善,使其可编译、可启动并可对外提供用户相关接口。明确排除:Redis、链路追踪/可观测(OTel)、WebSocket 相关代码;users 表由使用者自行准备;刷新令牌采用纯令牌校验换取新令牌(不吊销旧令牌)。
## 产品概述
纯后端 JSON 接口服务,无界面:请求经统一中间件链处理,响应统一为「业务码 + 提示 + 数据」结构,错误按业务码区分参数错误、未授权、令牌过期、账号禁用、上传失败等场景;服务启动时自动校验关键配置,运行中 panic 不再导致进程崩溃。
## 核心功能
1. 服务启动与生命周期:读取配置并校验必填项(令牌密钥、过期时间等)、初始化日志/数据库/对象存储、启动 HTTP 服务、收到退出信号后优雅关闭;端口、读写超时来自配置
2. 健康检查:GET /ping 返回 pong,用于存活探测
3. 微信登录:用小程序 code 换取 openid,未注册自动创建账号(随机用户标识、默认昵称、正常状态);并发注册冲突时自动兜底回查;签发 access/refresh 双令牌并返回用户信息
4. 刷新令牌:校验 refresh token,确认用户存在且状态正常后重新签发双令牌,返回新的过期时间
5. 获取我的信息:返回昵称、头像(统一转为可访问地址)、账号状态、用户标识
6. 修改资料:更新昵称/头像地址,含长度校验;无需更新的字段不提交
7. 上传头像:校验文件非空、扩展名白名单、大小上限、文件头真实性(防伪造图片),落临时文件后上传对象存储,更新用户头像并返回可访问地址,临时文件必定清理
8. 鉴权保护:除健康检查、登录、刷新令牌外,其余接口需携带有效令牌;缺失/格式错误/过期/类型不符均返回明确错误
9. 统一异常处理:服务端 panic 统一记录结构化日志(含请求标识与堆栈)并返回 500;客户端断开类 panic 仅记录不误写响应
## 技术栈
- 语言/框架:Go 1.26.1、Gin v1.12.0(已是现有依赖)
- 数据层:GORM + MySQL 驱动(现有)、gorm.io/plugin/soft_delete(现有)
- 配置:spf13/viper(现有,读取 configs/config.yaml)
- 鉴权:golang-jwt/jwt/v5(现有)、google/uuid(现有)
- 对象存储:腾讯云 cos-go-sdk-v5(现有)
- 日志:log/slog + gopkg.in/natefinch/lumberjack.v2 轮转(lumberjack 尚不在 go.mod/go.sum,需 go mod tidy 拉取)
- 明确不引入:任何 OTel 依赖、Redis 业务代码、WebSocket 相关代码
- 实施参照:同组织兄弟项目 zt-closet-backend(同模板、同目录结构),按需裁剪移植
## 实现策略与关键决策
1. 以兄弟项目为同构模板,移植范围仅限:路由注册方式、Recovery 中间件、错误码段位、service 错误映射与日志风格、头像上传链路
2. 中间件链固定为 Recovery → RequestID → GinSlog → CORS(删除 GinOTelMiddleware;router.go 的 import 现指向 zt-closet-backend,必须改回本项目)
3. 路由路径对齐兄弟项目:公开区 POST /api/v1/user/wxlogin、POST /api/v1/user/refresh-token;鉴权区(JWTAuth)GET /user/info、PUT /user/profile、POST /user/avatar/upload;健康检查 GET /ping
4. RefreshToken 纯 JWT 无状态:解析 refresh token → 校验用户存在且状态正常 → 重新签发双令牌;移除 dto 中仅服务于 Redis 吊销的 access_token 字段并改写注释
5. ExpireTime 语义与兄弟项目一致,取「当前 Unix 秒 + AccessTokenExpire」的绝对时间戳(同步澄清 dto 注释)
6. 错误码段位对齐兄弟项目:用户模块 20000-29999(UserDisabled 21001、UserWxLoginFailed 21002、UserWxCodeInvalid 21003、UserNotFound 22000、UserCreateFailed 29000、UserUpdateFailed 29001);上传模块 30000 段(UploadFileEmpty、UploadFileExtNotAllow、UploadFileTooLarge)+ 39000(UploadFileSaveFailed)。新增码默认走 200 业务码通道,保持现有 StatusCode 设计,不改 errcode.go
7. 头像上传链路:空文件/扩展名白名单/大小上限/文件头 magic 校验(仅读文件头不超过 12 字节,避免全量读内存)→ UserGetByID 取 userUUID → FileGenName 随机名 → FileSaveTmp 落盘 → 上传 COS(key 采用 {userUUID}/{userID}/avatar/{随机名},与兄弟项目一致)→ 更新 users.avatar 存 COS key → 返回 CDN 签名 URL;defer 删除临时文件(防循环上传写满磁盘)
8. 新用户注册:32 位随机 UUID(uuid 去横线,匹配 varchar(32))、默认昵称「微信用户」、状态正常;openid 唯一键并发冲突(MySQL 1062)时回查已存在用户继续登录
9. COS 初始化失败降级:仅告警、CosClient 置空,上传时返回明确错误而非 panic(沿用现有 FileDeleteCos 的 nil 保护约定)
10. main.go 现状为兄弟项目半成品粘贴:读取了本项目未声明的配置段(Otel/Tokenhub/MattingModel/ClassificationModel/WechatPay/UserDefaultQuota/NewUserGift/AdReward/WechatVirtualPay),并引用 otelslog/tracing 桥接与 lumberjack;需裁剪为实际存在的 7 个段(Server/Log/Mysql/Redis/JWT/Upload/Cos/WechatMini,Redis 仅读配置不建连接),删除 OTel 桥接,保留用户已写好的 lumberjack + JSON slog 方案
11. main 启动流程:init 中依次 setupSetting → setupSlogLogger → setupMysqlEngine → setupCosClient(失败 panic);main 中 gin.SetMode、构建路由、http.Server(ReadTimeout/WriteTimeout 按配置秒、MaxHeaderBytes 1MB)、后台 ListenAndServe + 错误 channel、监听 SIGINT/SIGTERM、5 秒超时优雅关闭
## 性能与可靠性
- 上传请求体已在 handler 侧用 MaxBytesReader 限制(MaxSizeMB + 1MB 边界开销),service 侧再校验大小与类型,双层防护
- 文件头校验只读取头部字节;上传成功后立即删除临时文件;日志脱敏沿用现有 sanitizeQuery;panic 日志含 request_id 与堆栈,连接断开类 panic 不回写响应
- 复杂度不变:登录/刷新为 O(1) 数据库访问 + 令牌签发;头像上传为 O(文件大小) 流式复制
## 架构设计
分层保持现状:HTTP 请求 → 中间件链 → /api/v1 路由分组(公开/鉴权)→ handler(参数绑定 + 统一响应)→ service(业务校验与编排,返回 *errcode.Error)→ mysqldao(GORM)→ MySQL;文件经 pkg/upload 校验落盘、pkg/cos 客户端上传至对象存储;配置经 viper 写入 global;不引入 Redis/OTel/WS 任何组件。
## 目录结构
```
zt-online-game-backend/
├── main.go [MODIFY] 裁剪配置段读取(删除未声明段)、移除 OTel 日志桥接,
│ 保留 lumberjack+JSON slog 方案;补 init()(配置→日志→MySQL→COS)
│ 与 main()(gin 模式、路由、http.Server、错误 channel、信号优雅退出)
├── go.mod / go.sum [MODIFY] go mod tidy 引入 lumberjack,整理依赖(移除 main.go 中 OTel 引用后无需新增 OTel 依赖)
├── internal/
│ ├── middleware/
│ │ └── recovery.go [NEW] Recovery 中间件:镜像兄弟项目实现,panic 结构化日志(含 request_id/堆栈)、
│ │ broken pipe 判定(连接断开仅记录、Abort),其余返回 500
│ ├── routers/
│ │ ├── router.go [MODIFY] import 改回本项目、中间件链去掉 GinOTelMiddleware、
│ │ │ 注册 /ping 与公开(wxlogin/refresh-token)+ 鉴权(JWTAuth)用户路由
│ │ └── api/v1/user.go [MODIFY] 补 pkg/errcode import(当前引用 errcode.UploadFileEmpty 等符号但未导入,编译不过)
│ ├── service/user.go [MODIFY] 实现 WxLogin/RefreshToken/InfoGetBySelf/ProfileUpdate/AvatarUpload
│ │ 及辅助函数(isNotFound/isDuplicateKey/genUUID/nowUnix),
│ │ 错误经 errcode 映射、关键路径 slog 记录
│ ├── mysqldao/user.go [MODIFY] 新增 UserGetByWechatOpenID(按 wechat_open_id 查询,登录必需)
│ ├── mysqlmodel/user.go [MODIFY] 新增用户状态常量(1 正常/2 临时禁用/3 永久禁用/4 已注销)
│ └── dto/user.go [MODIFY] 刷新令牌请求移除仅用于 Redis 吊销的 access_token 字段并改写注释;
│ 澄清 ExpireTime 为绝对 Unix 秒时间戳
└── pkg/
├── errcode/
│ ├── user_code.go [NEW] 用户模块错误码(20000-29999 段所选子集:禁用/登录失败/Code 无效/不存在/创建更新失败)
│ └── upload_code.go [NEW] 上传错误码(UploadFileEmpty 30000、UploadFileExtNotAllow 30001、
│ UploadFileTooLarge 30002、UploadFileSaveFailed 39000)
└── upload/upload.go [MODIFY] FileDeleteCos 移除 global.Tracer/span 调用;
新增 FileUploadCos(ctx, key, localPath):nil 客户端保护 +
Object.Upload(ctx, key, localPath, nil) 上传
```
## 关键契约(供实现对照)
- service 方法签名与 handler 已声明的调用一致,错误类型为 *errcode.Error,返回 nil 表示成功
- 更新用户资料/头像复用现有 DAO 签名 UserUpdateByID(ctx, *mysqlmodel.User),传入仅含待更新字段的对象(GORM Updates 忽略零值,主键不参与更新)
- FileUploadCos 内部调用 COS SDK 的 Object.Upload(ctx, key, localPath, nil)(三返回值,取 error)
## 验证方式
- 在项目根目录执行 go mod tidy、go build ./...、go vet ./...,确保零编译告警
- 若本地 MySQL 与 configs/config.yaml 可用,启动服务验证 /ping 返回 pong,未带令牌访问 /api/v1/user/info 返回 401,带伪造令牌返回令牌错误
- 静态核查:全仓无 Tracer/otel/otelslog/tracing/zt-closet-backend 残留引用;无 Redis/WS 新增代码
## Agent Extensions
### SubAgent
- **code-explorer**
- Purpose: 在实现 UserService 前,核对兄弟项目 zt-closet-backend 的 isDuplicateKey/genUUID/nowUnix 等辅助函数与 RefreshToken、头像上传的原始实现细节,确保同构移植不失真
- Expected outcome: 产出可对照的辅助函数与流程清单,UserService 五个方法按同组织既有范式一次写对
### Skill
- **lsp-code-analysis**
- Purpose: 在收尾阶段对修改涉及的关键符号做引用级核查(如确认 FileDeleteCos/新 errcode 符号/路由注册的引用面完整,且不再有 OTel/Tracer 残留引用)
- Expected outcome: 符号引用一致性报告,确保无悬空引用、无未使用导入、编译与静态检查全绿