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

12 KiB
Raw Blame History

name, overview, todos
name overview todos
http-layer-completion 补齐 zt-online-game-backend 的 HTTP 层:修复路由与中间件接线、实现 user 模块(微信登录/刷新 token/我的信息/改资料/上传头像)service 与 COS 上传能力、补 Recovery 中间件与错误码、完成 main.go 启动与优雅退出;不含 Redis、OTel、WebSocket。
id content status
errcode-recovery 新增用户/上传错误码文件与 Recovery 中间件,镜像兄弟项目范式 completed
id content status dependencies
router-wiring 修复 router.go 导入与中间件链并注册 user 路由,补 handler 的 errcode 导入 completed
errcode-recovery
id content status
dao-model-dto 补 UserGetByWechatOpenID 与用户状态常量,按纯 JWT 语义修订刷新令牌 dto completed
id content status
upload-cos 移除 upload 包的 OTel Tracer 调用,新增 FileUploadCos 上传封装 completed
id content status dependencies
user-service 用 [subagent:code-explorer] 核对兄弟项目细节后实现 UserService 五个方法与辅助函数 completed
dao-model-dto
upload-cos
errcode-recovery
id content status dependencies
main-boot 裁剪 main.go 配置段并去 OTel 桥接,补启动/优雅退出与 lumberjack 依赖 completed
router-wiring
user-service
id content status dependencies
verify-build 执行 go mod tidy/build/vet 与启动自检,用 [skill:lsp-code-analysis] 核查无 OTel/Redis/WS 残留 completed
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: 符号引用一致性报告,确保无悬空引用、无未使用导入、编译与静态检查全绿