补齐 HTTP 层:实现用户模块、路由与中间件接线、COS 上传及优雅启动
- 新增用户与上传错误码文件及 Recovery 中间件 - 修复 router.go 导入与中间件链,注册用户路由并补 handler 错误码导入 - 补充用户状态常量与 UserGetByWechatOpenID 查询,修订刷新令牌 dto - 移除 upload 包 OTel 调用,新增 FileUploadCos 上传封装 - 实现 UserService 微信登录/刷新令牌/我的信息/改资料/上传头像 - 裁剪 main.go 配置段并移除 OTel 桥接,补启动与优雅退出 - 新增 configs 示例配置、global 配置结构及 go.mod 依赖
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
---
|
||||
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: 符号引用一致性报告,确保无悬空引用、无未使用导入、编译与静态检查全绿
|
||||
Reference in New Issue
Block a user