--- 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: 符号引用一致性报告,确保无悬空引用、无未使用导入、编译与静态检查全绿