chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled

This commit is contained in:
lan
2025-11-30 08:33:17 +08:00
parent 4b4980820f
commit a4b6c5011e
58 changed files with 19353 additions and 0 deletions

View File

@@ -0,0 +1,357 @@
# 个人资料管理
<cite>
**本文引用的文件**
- [user_handler.go](file://internal/handler/user_handler.go)
- [routes.go](file://internal/handler/routes.go)
- [auth.go](file://internal/middleware/auth.go)
- [user_service.go](file://internal/service/user_service.go)
- [user_repository.go](file://internal/repository/user_repository.go)
- [common.go](file://internal/types/common.go)
- [response.go](file://internal/model/response.go)
- [user.go](file://internal/model/user.go)
- [jwt.go](file://pkg/auth/jwt.go)
- [password.go](file://pkg/auth/password.go)
- [user_handler_test.go](file://internal/handler/user_handler_test.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向开发者与测试人员系统性梳理“个人资料管理”相关API重点覆盖
- GET /api/v1/user/profile获取当前登录用户的详细信息
- PUT /api/v1/user/profile更新头像与密码邮箱修改请使用独立接口
文档将解释 UserInfo 数据结构各字段及 JSON 序列化规则;详述密码更新的安全机制(旧密码校验与新密码加密);说明头像 URL 的更新流程(预签名上传 URL 生成与最终更新)。同时,结合 user_handler.go 中的 GetUserProfile 与 UpdateUserProfile 函数,说明 JWT 中间件如何注入用户上下文,服务层如何调用仓库层执行持久化操作,并提供完整请求/响应示例(含成功与常见错误场景)。
## 项目结构
围绕个人资料管理的代码组织遵循“控制器-中间件-服务-仓库-模型”的分层设计,路由在 v1 组下统一挂载,用户相关接口均受 JWT 认证保护。
```mermaid
graph TB
Client["客户端"] --> Router["Gin 路由器"]
Router --> GroupV1["/api/v1 用户组<br/>启用 AuthMiddleware()"]
GroupV1 --> GetUserProfile["GET /api/v1/user/profile"]
GroupV1 --> UpdateUserProfile["PUT /api/v1/user/profile"]
GroupV1 --> AvatarUploadURL["POST /api/v1/user/avatar/upload-url"]
GroupV1 --> UpdateAvatar["PUT /api/v1/user/avatar"]
GetUserProfile --> Handler["user_handler.GetUserProfile"]
UpdateUserProfile --> Handler
AvatarUploadURL --> Handler
UpdateAvatar --> Handler
Handler --> Middleware["AuthMiddleware()<br/>注入 user_id/username/role"]
Handler --> Service["user_service.*"]
Service --> Repo["user_repository.*"]
Service --> Model["model.User"]
Handler --> Types["types.UserInfo / UpdateUserRequest"]
Handler --> Resp["model.Response / model.ErrorResponse"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L16-L41)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [user_service.go](file://internal/service/user_service.go#L124-L164)
- [user_repository.go](file://internal/repository/user_repository.go#L17-L69)
- [common.go](file://internal/types/common.go#L42-L47)
- [response.go](file://internal/model/response.go#L1-L86)
章节来源
- [routes.go](file://internal/handler/routes.go#L16-L41)
## 核心组件
- 控制器层Handler
- GetUserProfile从上下文提取 user_id查询用户并返回 UserInfo
- UpdateUserProfile接收 UpdateUserRequest按需更新密码与头像返回最新 UserInfo
- 中间件层AuthMiddleware
- 解析 Authorization: Bearer <token>,校验 JWT 并将用户信息写入上下文
- 服务层Service
- GetUserByID封装仓库查询
- UpdateUserInfo / UpdateUserAvatar封装仓库更新
- ChangeUserPassword校验旧密码并加密新密码后更新
- 仓库层Repository
- FindUserByID / UpdateUser / UpdateUserFields数据库读写
- 类型与模型
- types.UpdateUserRequest请求体定义
- types.UserInfo响应体定义
- model.User数据库映射模型
- model.Response / model.ErrorResponse统一响应结构
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [user_service.go](file://internal/service/user_service.go#L124-L164)
- [user_repository.go](file://internal/repository/user_repository.go#L17-L69)
- [common.go](file://internal/types/common.go#L42-L47)
- [user.go](file://internal/model/user.go#L7-L21)
- [response.go](file://internal/model/response.go#L1-L86)
## 架构总览
以下序列图展示“获取/更新用户资料”的端到端流程,包含 JWT 中间件、控制器、服务与仓库的交互。
```mermaid
sequenceDiagram
participant C as "客户端"
participant R as "Gin 路由"
participant M as "AuthMiddleware"
participant H as "user_handler"
participant S as "user_service"
participant RP as "user_repository"
participant DB as "数据库"
C->>R : "GET /api/v1/user/profile"
R->>M : "鉴权中间件"
M-->>R : "注入 user_id/username/role"
R->>H : "GetUserProfile"
H->>S : "GetUserByID(user_id)"
S->>RP : "FindUserByID(user_id)"
RP->>DB : "SELECT ..."
DB-->>RP : "User"
RP-->>S : "User"
S-->>H : "User"
H-->>C : "200 + UserInfo"
C->>R : "PUT /api/v1/user/profile"
R->>M : "鉴权中间件"
M-->>R : "注入 user_id/username/role"
R->>H : "UpdateUserProfile"
H->>H : "解析请求体 UpdateUserRequest"
alt "更新密码"
H->>S : "ChangeUserPassword(user_id, old, new)"
S->>RP : "FindUserByID(user_id)"
RP->>DB : "SELECT ..."
DB-->>RP : "User"
RP-->>S : "User"
S->>S : "CheckPassword(旧密码)"
S->>S : "HashPassword(新密码)"
S->>RP : "UpdateUserFields(user_id, {password})"
RP->>DB : "UPDATE ..."
DB-->>RP : "OK"
end
alt "更新头像"
H->>S : "UpdateUserInfo(User)"
S->>RP : "UpdateUser(User)"
RP->>DB : "SAVE ..."
DB-->>RP : "OK"
end
H->>S : "GetUserByID(user_id)"
S->>RP : "FindUserByID(user_id)"
RP->>DB : "SELECT ..."
DB-->>RP : "User"
RP-->>S : "User"
S-->>H : "User"
H-->>C : "200 + 最新 UserInfo"
```
图表来源
- [routes.go](file://internal/handler/routes.go#L27-L41)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [user_service.go](file://internal/service/user_service.go#L124-L164)
- [user_repository.go](file://internal/repository/user_repository.go#L17-L69)
## 详细组件分析
### GET /api/v1/user/profile获取当前用户资料
- 功能概述
- 仅限已登录用户访问,通过 Authorization: Bearer <token> 进行鉴权
- 中间件将 user_id 写入上下文,控制器据此查询用户并返回 UserInfo
- 请求与响应
- 请求Authorization: Bearer <token>
- 成功响应200 + model.Response{ data: types.UserInfo }
- 常见错误401 未授权、404 用户不存在
- 数据结构与序列化
- types.UserInfo 字段与 JSON 映射规则详见“附录-数据结构”
- 错误处理
- 未携带或无效的 Authorization 头:返回 401
- 查询不到用户:返回 404
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L17-L68)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [routes.go](file://internal/handler/routes.go#L27-L41)
- [common.go](file://internal/types/common.go#L113-L125)
- [response.go](file://internal/model/response.go#L1-L86)
### PUT /api/v1/user/profile更新头像与密码
- 功能概述
- 支持同时更新头像 URL 与密码;若仅更新头像,可不提供密码字段
- 密码更新安全机制:必须同时提供旧密码与新密码,服务层校验旧密码并通过 bcrypt 加密新密码后更新
- 头像更新流程:先生成预签名上传 URL上传完成后调用更新头像接口最终返回最新 UserInfo
- 请求体 UpdateUserRequest
- avatar可选头像 URL
- old_password可选修改密码时必填
- new_password可选修改密码时必填
- 安全机制与流程
- 旧密码校验:服务层通过 bcrypt 校验
- 新密码加密:服务层使用 bcrypt 生成哈希
- 头像更新:控制器直接更新用户记录;若仅更新头像,服务层保存用户对象
- 错误处理
- 未授权401
- 参数错误400如仅提供新密码或仅提供旧密码
- 用户不存在404
- 服务器错误500
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L70-L193)
- [user_service.go](file://internal/service/user_service.go#L141-L164)
- [password.go](file://pkg/auth/password.go#L1-L21)
- [common.go](file://internal/types/common.go#L42-L47)
- [response.go](file://internal/model/response.go#L27-L53)
### 头像上传与更新流程(补充说明)
- 生成预签名上传 URL
- POST /api/v1/user/avatar/upload-url
- 输入file_name
- 输出post_url、form_data、avatar_url、expires_in
- 更新头像 URL
- PUT /api/v1/user/avatar
- 输入avatar_url查询参数
- 输出:最新 UserInfo
- 注意事项
- 该流程与“更新资料”接口不同,前者用于生成上传凭证,后者用于将最终 URL 写入数据库
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [routes.go](file://internal/handler/routes.go#L34-L39)
- [common.go](file://internal/types/common.go#L68-L80)
### UserInfo 数据结构与 JSON 序列化规则
- 字段说明
- id用户唯一标识
- username用户名
- email邮箱
- avatar头像 URL
- points积分
- role角色如 user
- status账户状态1 正常0 禁用,-1 删除)
- last_login_at最近登录时间可空
- created_at / updated_at创建与更新时间
- JSON 映射
- model.User 中的 Password 字段在 JSON 中不返回(避免泄露)
- 其他字段按 gorm 标签映射到 JSON
章节来源
- [user.go](file://internal/model/user.go#L7-L21)
- [common.go](file://internal/types/common.go#L113-L125)
### 请求/响应示例(含错误场景)
- 获取资料(成功)
- 请求Authorization: Bearer <token>
- 响应200 + { code: 200, message: "操作成功", data: { id, username, email, avatar, points, role, status, last_login_at, created_at, updated_at } }
- 更新资料(仅更新头像)
- 请求体:{ avatar: "https://example.com/new-avatar.png" }
- 响应200 + 最新 UserInfo
- 更新资料(仅更新密码)
- 请求体:{ old_password: "...", new_password: "..." }
- 响应200 + 最新 UserInfo
- 更新资料(参数错误)
- 请求体:{ new_password: "..." }(缺少 old_password
- 响应400 + { code: 400, message: "请求参数错误", error: "修改密码需要提供原密码" }
- 未授权
- 请求:未携带或无效 Authorization
- 响应401 + { code: 401, message: "未授权,请先登录" }
- 用户不存在
- 响应404 + { code: 404, message: "资源不存在" }
- 服务器错误
- 响应500 + { code: 500, message: "服务器内部错误" }
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [response.go](file://internal/model/response.go#L27-L53)
- [user_handler_test.go](file://internal/handler/user_handler_test.go#L52-L106)
## 依赖分析
- 控制器依赖
- user_handler 依赖 gin 上下文中的 user_id来自 AuthMiddleware
- 控制器调用 user_service 的 GetUserByID、UpdateUserInfo、ChangeUserPassword、UpdateUserAvatar
- 服务层依赖
- user_service 依赖 user_repository 的 FindUserByID、UpdateUser、UpdateUserFields
- 使用 pkg/auth 的 HashPassword 与 CheckPassword
- 中间件依赖
- AuthMiddleware 依赖 pkg/auth/jwt 的 ValidateToken将 claims 写入上下文
- 模型与类型
- model.User 作为 ORM 映射
- types.UserInfo / UpdateUserRequest 作为 API 层数据契约
```mermaid
graph LR
Handler["user_handler"] --> Service["user_service"]
Handler --> Middleware["AuthMiddleware"]
Handler --> Types["types.*"]
Handler --> Resp["model.Response / ErrorResponse"]
Service --> Repo["user_repository"]
Service --> Auth["pkg/auth/*"]
Middleware --> JWT["pkg/auth/jwt"]
Repo --> Model["model.User"]
```
图表来源
- [user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [user_service.go](file://internal/service/user_service.go#L124-L164)
- [user_repository.go](file://internal/repository/user_repository.go#L17-L69)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [password.go](file://pkg/auth/password.go#L1-L21)
- [common.go](file://internal/types/common.go#L42-L47)
- [response.go](file://internal/model/response.go#L1-L86)
- [user.go](file://internal/model/user.go#L7-L21)
## 性能考虑
- 数据库查询
- GetUserByID 使用精确主键查询,复杂度 O(1),建议保持索引完善
- 密码处理
- bcrypt 默认成本较高,建议在高并发场景关注 CPU 开销;可通过配置调整成本值
- 缓存策略
- 对频繁读取的用户资料可在应用层引入缓存(如 Redis减少数据库压力
- 日志与可观测性
- 控制器与服务层已记录关键错误日志,建议配合链路追踪与指标监控
## 故障排查指南
- 401 未授权
- 检查 Authorization 头是否为 Bearer <token> 格式
- 确认 token 未过期且签名正确
- 400 参数错误
- 密码更新时必须同时提供 old_password 与 new_password
- 头像 URL 必须为合法 URL
- 404 用户不存在
- 确认 user_id 是否有效;检查用户状态是否被禁用或删除
- 500 服务器错误
- 检查数据库连接与事务执行情况;查看服务层日志定位具体异常
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L27-L193)
- [user_handler_test.go](file://internal/handler/user_handler_test.go#L108-L152)
- [response.go](file://internal/model/response.go#L27-L53)
## 结论
个人资料管理 API 采用清晰的分层架构JWT 中间件负责鉴权并将用户上下文注入控制器,控制器协调服务层完成业务逻辑,服务层通过仓库层与数据库交互。密码更新具备严格的旧密码校验与新密码加密流程,头像更新支持预签名上传与最终 URL 写入。通过统一的响应结构与完善的错误处理,系统在安全性与易用性之间取得平衡。
## 附录
### 数据结构与序列化规则
- types.UserInfo
- 字段id、username、email、avatar、points、role、status、last_login_at、created_at、updated_at
- JSON 映射:与 model.User 字段一致,除 password 不返回
- types.UpdateUserRequest
- 字段avatar、old_password、new_password
- 校验:密码更新时 old_password 与 new_password 必须同时提供
- model.User数据库映射
- 字段id、username、password、email、avatar、points、role、status、properties、last_login_at、created_at、updated_at
- JSONpassword 不返回;其他字段按标签映射
- model.Response / model.ErrorResponse
- 统一响应结构,包含 code、message、data/error
章节来源
- [common.go](file://internal/types/common.go#L42-L47)
- [common.go](file://internal/types/common.go#L113-L125)
- [user.go](file://internal/model/user.go#L7-L21)
- [response.go](file://internal/model/response.go#L1-L86)

View File

@@ -0,0 +1,337 @@
# 头像管理
<cite>
**本文引用的文件**
- [internal/handler/routes.go](file://internal/handler/routes.go)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go)
- [internal/service/upload_service.go](file://internal/service/upload_service.go)
- [internal/service/user_service.go](file://internal/service/user_service.go)
- [pkg/storage/minio.go](file://pkg/storage/minio.go)
- [pkg/config/config.go](file://pkg/config/config.go)
- [internal/types/common.go](file://internal/types/common.go)
- [internal/model/user.go](file://internal/model/user.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向开发者与运维人员,完整说明“头像管理”功能的实现与使用,包括:
- 生成头像上传URL的APIPOST /api/v1/user/avatar/upload-url
- 更新头像URL的APIPUT /api/v1/user/avatar
- 客户端直传对象存储MinIO/RustFS的完整流程
- upload_service与storage包的协作机制
- 请求参数、返回字段、错误处理与900秒15分钟有效期说明
- 提供可直接参考的curl示例
## 项目结构
围绕头像管理的关键文件组织如下:
- 路由注册在路由层注册两个端点分别对应生成上传URL与更新头像URL
- 处理器:用户处理器包含两个方法,分别处理上述两个端点
- 服务层upload_service负责生成预签名URLuser_service负责更新数据库中的头像URL
- 存储层storage封装了S3兼容客户端提供预签名POST策略生成与对象访问URL构造
- 配置层RustFS配置用于对象存储连接参数与桶映射
- 类型定义:请求与响应结构体定义
- 模型:用户模型包含头像字段
```mermaid
graph TB
Routes["路由注册<br/>/api/v1/user/avatar/upload-url<br/>/api/v1/user/avatar"] --> Handler["用户处理器"]
Handler --> UploadSvc["upload_service<br/>生成头像上传URL"]
Handler --> UserSvc["user_service<br/>更新头像URL"]
UploadSvc --> Storage["storage<br/>预签名POST策略生成"]
Storage --> Config["RustFS配置<br/>endpoint/buckets/use_ssl"]
UserSvc --> Model["用户模型<br/>avatar字段"]
```
图表来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
## 核心组件
- 路由注册在v1用户组下注册头像相关端点均受JWT认证保护
- 生成上传URL处理器接收请求体中的文件名调用服务层生成预签名POST策略与表单数据
- 更新头像URL处理器接收查询参数中的头像URL调用服务层更新数据库
- upload_service校验文件名、选择头像配置、生成对象键、调用storage生成预签名POST策略
- storage封装S3兼容客户端生成预签名POST URL与表单数据并构造最终访问URL
- user_service更新用户头像字段
- 配置RustFS配置包含endpoint、use_ssl、buckets映射
- 类型定义:请求与响应结构体
- 模型:用户实体包含头像字段
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
## 架构总览
头像上传采用“服务端签发、客户端直传”的模式:
- 服务端生成预签名POST策略包含目标桶、对象键、有效期、内容长度范围
- 客户端使用返回的PostURL与FormData直接上传至对象存储
- 上传完成后客户端通知服务端更新数据库中的头像URL
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "用户处理器"
participant S as "upload_service"
participant ST as "storage"
participant FS as "对象存储(RustFS/MinIO)"
participant U as "user_service"
participant DB as "数据库"
C->>H : "POST /api/v1/user/avatar/upload-url"<br/>请求体 : {file_name}
H->>S : "GenerateAvatarUploadURL(userID, file_name)"
S->>ST : "GeneratePresignedPostURL(bucket, objectName, limits, expires)"
ST-->>S : "返回 {post_url, form_data, file_url}"
S-->>H : "返回预签名结果"
H-->>C : "返回 {post_url, form_data, avatar_url, expires_in}"
Note over C,FS : "客户端使用post_url与form_data直接上传到FS"
C->>FS : "HTTP POST 到 post_url + form_data"
FS-->>C : "上传成功"
C->>H : "PUT /api/v1/user/avatar?avatar_url={final_url}"
H->>U : "UpdateUserAvatar(userID, avatar_url)"
U->>DB : "UPDATE user SET avatar = ? WHERE id = ?"
DB-->>U : "OK"
U-->>H : "OK"
H-->>C : "返回最新用户信息"
```
图表来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L121)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
## 详细组件分析
### 组件A生成头像上传URLPOST /api/v1/user/avatar/upload-url
- 请求参数
- 请求体包含文件名file_name
- 认证需要JWT
- 处理流程
- 从上下文提取user_id
- 绑定请求体调用upload_service.GenerateAvatarUploadURL
- 生成预签名POST策略与表单数据
- 返回post_url、form_data、avatar_url最终访问URL、expires_in
- 关键点
- 文件名校验:仅允许特定扩展名,且不能为空
- 对象键规则user_{userID}/timestamp_{originalFileName}
- URL有效期15分钟900秒
- 存储桶通过RustFS配置的buckets映射获取avatars桶
```mermaid
flowchart TD
Start(["进入处理器"]) --> Bind["绑定请求体<br/>file_name"]
Bind --> CallSvc["调用upload_service.GenerateAvatarUploadURL"]
CallSvc --> Validate["校验文件名<br/>扩展名与非空"]
Validate --> Bucket["获取avatars桶名"]
Bucket --> ObjKey["生成对象键<br/>user_{userID}/timestamp_{fileName}"]
ObjKey --> Policy["生成预签名POST策略<br/>含有效期与大小限制"]
Policy --> Return["返回post_url/form_data/avatar_url/expires_in"]
Return --> End(["结束"])
```
图表来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L253)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L121)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L253)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)
### 组件B更新头像URLPUT /api/v1/user/avatar
- 请求参数
- 查询参数avatar_url头像最终访问URL
- 认证需要JWT
- 处理流程
- 从上下文提取user_id
- 校验avatar_url非空
- 调用user_service.UpdateUserAvatar更新数据库
- 返回最新用户信息
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "用户处理器"
participant U as "user_service"
participant DB as "数据库"
C->>H : "PUT /api/v1/user/avatar?avatar_url=..."
H->>H : "校验avatar_url非空"
H->>U : "UpdateUserAvatar(userID, avatar_url)"
U->>DB : "UPDATE user SET avatar = ? WHERE id = ?"
DB-->>U : "OK"
U-->>H : "OK"
H-->>C : "返回最新用户信息"
```
图表来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L255-L326)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L255-L326)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
### 组件Cupload_service与storage协作
- upload_service.GenerateAvatarUploadURL
- 校验文件名(扩展名与非空)
- 获取头像上传配置(允许扩展名、最小/最大大小、有效期)
- 解析avatars桶名
- 生成对象键(带时间戳)
- 调用storage.GeneratePresignedPostURL生成预签名POST策略与表单数据
- storage.StorageClient.GeneratePresignedPostURL
- 构建上传策略(桶、键、过期时间、内容长度范围)
- 生成post_url与form_data
- 构造最终访问URL基于endpoint、use_ssl、bucket、objectName
```mermaid
classDiagram
class UploadService {
+GenerateAvatarUploadURL(ctx, storageClient, cfg, userID, fileName) PresignedPostPolicyResult
+ValidateFileName(fileName, fileType) error
+GetUploadConfig(fileType) UploadConfig
}
class StorageClient {
+GeneratePresignedPostURL(ctx, bucketName, objectName, minSize, maxSize, expires, useSSL, endpoint) PresignedPostPolicyResult
+GetBucket(name) string
}
class RustFSConfig {
+Endpoint string
+UseSSL bool
+Buckets map[string]string
}
UploadService --> StorageClient : "调用"
UploadService --> RustFSConfig : "读取配置"
```
图表来源
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
章节来源
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
## 依赖分析
- 路由层依赖处理器层
- 处理器层依赖服务层与配置层
- 服务层依赖存储层与配置层
- 存储层依赖RustFS配置与S3兼容SDK
- 类型定义与模型为上层提供契约
```mermaid
graph LR
Routes["routes.go"] --> Handler["user_handler.go"]
Handler --> UploadSvc["upload_service.go"]
Handler --> UserSvc["user_service.go"]
UploadSvc --> Storage["minio.go"]
UploadSvc --> Cfg["config.go"]
UserSvc --> Model["user.go"]
Handler --> Types["common.go"]
```
图表来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [internal/service/user_service.go](file://internal/service/user_service.go#L134-L140)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L58-L66)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
## 性能考虑
- 预签名URL有效期为15分钟建议客户端在有效期内完成上传避免重复生成
- 上传大小限制与扩展名校验在服务端进行,减少无效上传对存储的压力
- 对象键包含时间戳,便于按用户与时间维度管理与清理
- 存储层使用S3兼容SDK具备良好的并发与连接复用能力
## 故障排查指南
- 生成上传URL失败
- 检查file_name是否为空或扩展名不被允许
- 检查RustFS配置中的endpoint、use_ssl、buckets映射是否正确
- 检查对象存储连通性与权限
- 上传失败
- 确认客户端使用返回的post_url与form_data进行直传
- 确认上传未超过大小限制或超出有效期
- 更新头像URL失败
- 检查avatar_url是否为空
- 检查数据库连接与用户是否存在
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L52-L121)
## 结论
头像管理通过“服务端签发+客户端直传”的方式实现了高效、可控的头像上传流程。upload_service与storage包紧密协作确保上传策略的安全与可靠user_service负责最终的数据库更新。配合明确的请求参数、返回字段与900秒有效期整体方案简洁清晰、易于维护与扩展。
## 附录
### curl示例完整头像上传流程
- 步骤1生成预签名上传URL
- 请求
- 方法POST
- 地址:/api/v1/user/avatar/upload-url
- 请求体包含file_name
- 响应
- 返回post_url、form_data、avatar_url、expires_in
- 步骤2客户端直传到对象存储
- 使用post_url与form_data发起HTTP POST上传
- 上传成功后得到对象存储返回
- 步骤3通知后端更新数据库
- 请求
- 方法PUT
- 地址:/api/v1/user/avatar?avatar_url={最终访问URL}
- 响应
- 返回更新后的用户信息
说明
- expires_in为900秒15分钟在此期间内完成上传与更新
- 上传大小限制与扩展名限制由服务端在生成预签名URL时生效
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L78-L115)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L121)
- [internal/types/common.go](file://internal/types/common.go#L68-L79)

View File

@@ -0,0 +1,384 @@
# 用户API
<cite>
**本文引用的文件**
- [internal/handler/routes.go](file://internal/handler/routes.go)
- [internal/middleware/auth.go](file://internal/middleware/auth.go)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go)
- [internal/service/user_service.go](file://internal/service/user_service.go)
- [internal/service/upload_service.go](file://internal/service/upload_service.go)
- [internal/service/verification_service.go](file://internal/service/verification_service.go)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go)
- [internal/types/common.go](file://internal/types/common.go)
- [internal/model/response.go](file://internal/model/response.go)
- [internal/model/user.go](file://internal/model/user.go)
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go)
- [pkg/storage/minio.go](file://pkg/storage/minio.go)
- [pkg/config/config.go](file://pkg/config/config.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能与安全考量](#性能与安全考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向开发者与集成方系统化梳理用户管理API覆盖以下能力
- 获取与更新用户个人资料
- 更换邮箱(基于验证码)
- 头像上传通过预签名URL
- JWT认证中间件的工作机制
- 数据结构、请求/响应模式、错误处理与常见问题
## 项目结构
用户相关接口位于统一的路由组“/api/v1/user”该组下所有端点均受JWT认证保护。认证中间件负责从请求头中提取并校验Bearer Token并将用户标识写入上下文供业务层使用。
```mermaid
graph TB
Client["客户端"] --> Routes["路由注册<br/>/api/v1/user/*"]
Routes --> AuthMW["认证中间件<br/>AuthMiddleware()"]
AuthMW --> Handlers["用户处理器<br/>user_handler.go"]
Handlers --> Services["用户服务<br/>user_service.go"]
Services --> Repos["用户仓储<br/>user_repository.go"]
Services --> UploadSvc["上传服务<br/>upload_service.go"]
UploadSvc --> Storage["对象存储客户端<br/>minio.go"]
Services --> VeriSvc["验证码服务<br/>verification_service.go"]
Services --> JWT["JWT服务<br/>jwt.go"]
Services --> Types["类型定义<br/>types/common.go"]
Handlers --> Models["响应模型<br/>response.go"]
Handlers --> UserModel["用户模型<br/>model/user.go"]
```
图表来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/middleware/auth.go](file://internal/middleware/auth.go#L12-L56)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
- [internal/model/response.go](file://internal/model/response.go#L1-L86)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
## 核心组件
- 路由与认证
- 路由组“/api/v1/user”下的所有端点均使用认证中间件保护。
- 中间件从Authorization头解析Bearer Token调用JWT服务进行校验并将用户ID、用户名、角色写入上下文。
- 用户处理器
- 提供获取/更新用户资料、生成头像上传URL、更新头像、更换邮箱等接口。
- 服务层
- 用户服务封装业务逻辑(如修改密码、更换邮箱、更新头像),并调用仓储层持久化。
- 上传服务负责生成预签名URLPOST策略并构造最终可访问的文件URL。
- 验证码服务负责生成、发送与校验验证码(更换邮箱场景)。
- 仓储层
- 提供用户查询、更新、软删除等基础操作。
- 类型与模型
- 统一响应结构、用户信息结构、请求参数结构等。
- 用户模型包含基本字段及JSONB属性字段。
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/middleware/auth.go](file://internal/middleware/auth.go#L12-L56)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [internal/service/verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
- [internal/model/response.go](file://internal/model/response.go#L1-L86)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
## 架构总览
用户API采用“路由-中间件-处理器-服务-仓储-存储/外部服务”的分层架构。JWT中间件贯穿所有用户相关端点确保只有合法令牌才能访问。
```mermaid
sequenceDiagram
participant C as "客户端"
participant R as "路由(/api/v1/user/*)"
participant M as "认证中间件"
participant H as "用户处理器"
participant S as "用户服务"
participant U as "上传服务"
participant V as "验证码服务"
participant D as "仓储"
participant T as "JWT服务"
participant O as "对象存储"
C->>R : "携带Authorization : Bearer <token>"
R->>M : "进入中间件"
M->>T : "校验token"
T-->>M : "Claims(用户ID/用户名/角色)"
M->>H : "放行,写入上下文"
alt 获取/更新资料
H->>S : "GetUserByID/UpdateUserInfo"
S->>D : "查询/更新"
D-->>S : "结果"
S-->>H : "用户信息"
H-->>C : "200 成功响应"
else 生成头像上传URL
H->>U : "GenerateAvatarUploadURL"
U->>O : "生成预签名POST策略"
O-->>U : "PostURL+FormData+FileURL"
U-->>H : "结果"
H-->>C : "200 成功响应"
else 更换邮箱
H->>V : "VerifyCode(验证码)"
V-->>H : "校验通过/失败"
H->>S : "ChangeUserEmail"
S->>D : "更新邮箱"
D-->>S : "结果"
S-->>H : "用户信息"
H-->>C : "200 成功响应"
end
```
图表来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/middleware/auth.go](file://internal/middleware/auth.go#L12-L56)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [internal/service/verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
## 详细组件分析
### 认证中间件 AuthMiddleware
- 功能
- 从请求头Authorization中解析Bearer token
- 调用JWT服务校验token有效性
- 校验通过后将用户ID、用户名、角色写入上下文供后续处理器使用
- 异常
- 缺少Authorization头、格式不正确、token无效时返回401
- 适用范围
- 所有“/api/v1/user/*”端点均受此中间件保护
章节来源
- [internal/middleware/auth.go](file://internal/middleware/auth.go#L12-L56)
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
### 用户资料:获取与更新
- 获取用户资料
- 方法GET /api/v1/user/profile
- 认证需要Bearer token
- 成功响应包含用户基本信息ID、用户名、邮箱、头像、积分、角色、状态、时间戳等
- 错误:未授权、用户不存在
- 更新用户资料
- 方法PUT /api/v1/user/profile
- 认证需要Bearer token
- 请求体支持更新头像URL若提供新密码必须同时提供旧密码
- 成功响应:返回更新后的用户信息
- 错误:参数错误、未授权、服务器错误、用户不存在
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L193)
- [internal/types/common.go](file://internal/types/common.go#L42-L47)
- [internal/model/response.go](file://internal/model/response.go#L1-L86)
- [internal/model/user.go](file://internal/model/user.go#L1-L21)
### 头像上传流程预签名URL
- 生成上传URL
- 方法POST /api/v1/user/avatar/upload-url
- 认证需要Bearer token
- 请求体文件名file_name
- 成功响应包含PostURL、FormData、AvatarURL、过期秒数ExpiresIn
- 错误参数错误、未授权、生成URL失败
- 上传与确认
- 客户端使用返回的PostURL与FormData直传至对象存储
- 上传完成后调用PUT /api/v1/user/avatar携带avatar_url查询参数
- 服务端更新用户头像字段并返回最新用户信息
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "用户处理器"
participant U as "上传服务"
participant S as "对象存储客户端"
participant D as "仓储"
C->>H : "POST /api/v1/user/avatar/upload-url {file_name}"
H->>U : "GenerateAvatarUploadURL(userID, file_name)"
U->>S : "生成预签名POST策略"
S-->>U : "PostURL+FormData+FileURL"
U-->>H : "返回结果"
H-->>C : "200 {post_url, form_data, avatar_url, expires_in}"
C->>S : "使用PostURL+FormData直传"
S-->>C : "204/201"
C->>H : "PUT /api/v1/user/avatar?avatar_url=..."
H->>D : "UpdateUserAvatar"
D-->>H : "成功"
H-->>C : "200 用户信息"
```
图表来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L126-L130)
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L195-L326)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L126-L130)
### 更换邮箱(验证码)
- 流程
- 客户端先向验证码服务发送验证码(此处为通用流程,更换邮箱场景使用特定类型)
- 调用POST /api/v1/user/change-email携带新邮箱与验证码
- 服务端校验验证码,通过后更新用户邮箱并返回最新用户信息
- 请求体
- 新邮箱new_email
- 验证码verification_code
- 成功/错误
- 成功:返回用户信息
- 错误:参数错误、未授权、验证码错误、邮箱已被占用等
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [internal/service/verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [internal/service/user_service.go](file://internal/service/user_service.go#L186-L201)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L45-L57)
### 数据结构与请求/响应模式
- 用户信息结构UserInfo
- 字段id、username、email、avatar、points、role、status、last_login_at、created_at、updated_at
- 请求体
- 更新用户UpdateUserRequestavatar、old_password、new_password
- 生成头像上传URLGenerateAvatarUploadURLRequestfile_name
- 更换邮箱ChangeEmailRequestnew_email、verification_code
- 通用响应
- 成功code=200message=“操作成功”data为具体数据
- 失败code为对应HTTP语义码message为错误描述开发环境可带error字段
章节来源
- [internal/types/common.go](file://internal/types/common.go#L42-L47)
- [internal/types/common.go](file://internal/types/common.go#L62-L67)
- [internal/types/common.go](file://internal/types/common.go#L68-L80)
- [internal/model/response.go](file://internal/model/response.go#L1-L86)
- [internal/model/user.go](file://internal/model/user.go#L1-L21)
## 依赖关系分析
- 路由到处理器
- /api/v1/user/profile GET/PUT -> GetUserProfile/UpdateUserProfile
- /api/v1/user/avatar/upload-url POST -> GenerateAvatarUploadURL
- /api/v1/user/avatar PUT -> UpdateAvatar
- /api/v1/user/change-email POST -> ChangeEmail
- 处理器到服务
- 用户处理器调用用户服务、上传服务、验证码服务
- 服务到仓储/存储
- 用户服务调用仓储更新用户信息
- 上传服务调用对象存储客户端生成预签名URL
- 中间件到JWT
- 认证中间件依赖JWT服务校验token并注入用户上下文
```mermaid
graph LR
Routes["routes.go"] --> Handler["user_handler.go"]
Handler --> UserService["user_service.go"]
Handler --> UploadService["upload_service.go"]
Handler --> VeriService["verification_service.go"]
UserService --> Repo["user_repository.go"]
UploadService --> Storage["minio.go"]
Handler --> Types["types/common.go"]
Handler --> Resp["response.go"]
Handler --> ModelUser["model/user.go"]
AuthMW["auth.go"] --> JWT["jwt.go"]
Handler --> AuthMW
```
图表来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L1-L161)
- [internal/service/verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
- [internal/model/response.go](file://internal/model/response.go#L1-L86)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
- [internal/middleware/auth.go](file://internal/middleware/auth.go#L12-L56)
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
## 性能与安全考量
- 性能
- 上传采用预签名POST策略避免服务端中转降低带宽与延迟
- 对象存储客户端连接复用建议合理配置RustFS/Buckets与连接参数
- 安全
- 所有用户相关端点强制JWT认证
- 上传URL带过期时间防止长期有效链接泄露
- 验证码更换邮箱,避免暴力破解
- 密码修改需提供旧密码,防止越权修改
- 可靠性
- 上传URL生成失败、验证码校验失败、仓储更新失败均有明确错误返回
- 建议对高频接口增加限流与熔断策略(可在网关或中间件层实现)
[本节为通用指导,不直接分析具体文件]
## 故障排查指南
- 401 未授权
- 检查Authorization头是否为Bearer token格式
- 确认token未过期且签名正确
- 400 参数错误
- 检查请求体字段是否符合约束(如邮箱格式、验证码长度、文件名等)
- 404 用户不存在
- 确认用户ID有效且未被软删除
- 上传失败
- 检查生成的PostURL与FormData是否完整
- 确认对象存储端点、证书、桶名配置正确
- 验证码错误
- 检查Redis中验证码是否过期或被提前消费
- 确认发送频率限制未触发
章节来源
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/service/verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L1-L121)
- [pkg/config/config.go](file://pkg/config/config.go#L1-L305)
## 结论
本文档系统梳理了用户管理API的路由、认证、数据结构与流程明确了头像上传与邮箱更换的关键步骤与错误处理策略。建议在生产环境中结合限流、监控与日志体系持续优化用户体验与系统稳定性。
[本节为总结,不直接分析具体文件]
## 附录
### 接口一览与示例
- 获取用户资料
- 方法GET /api/v1/user/profile
- 请求Authorization: Bearer <token>
- 成功响应包含UserInfo
- 更新用户资料
- 方法PUT /api/v1/user/profile
- 请求体UpdateUserRequestavatar、old_password、new_password
- 成功响应包含更新后的UserInfo
- 生成头像上传URL
- 方法POST /api/v1/user/avatar/upload-url
- 请求体GenerateAvatarUploadURLRequestfile_name
- 成功响应包含post_url、form_data、avatar_url、expires_in
- 上传头像并确认
- 方法PUT /api/v1/user/avatar?avatar_url=...
- 成功响应包含UserInfo
- 更换邮箱
- 方法POST /api/v1/user/change-email
- 请求体ChangeEmailRequestnew_email、verification_code
- 成功响应包含UserInfo
章节来源
- [internal/handler/routes.go](file://internal/handler/routes.go#L16-L41)
- [internal/handler/user_handler.go](file://internal/handler/user_handler.go#L17-L416)
- [internal/types/common.go](file://internal/types/common.go#L42-L47)
- [internal/types/common.go](file://internal/types/common.go#L62-L67)
- [internal/types/common.go](file://internal/types/common.go#L68-L80)

View File

@@ -0,0 +1,336 @@
# 邮箱变更
<cite>
**本文引用的文件**
- [user_handler.go](file://internal/handler/user_handler.go)
- [routes.go](file://internal/handler/routes.go)
- [auth.go](file://internal/middleware/auth.go)
- [verification_service.go](file://internal/service/verification_service.go)
- [user_service.go](file://internal/service/user_service.go)
- [user_repository.go](file://internal/repository/user_repository.go)
- [common.go](file://internal/types/common.go)
- [response.go](file://internal/model/response.go)
- [redis.go](file://pkg/redis/redis.go)
- [email.go](file://pkg/email/email.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能与可靠性](#性能与可靠性)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
## 简介
本文档围绕“POST /api/v1/user/change-email”端点系统化阐述邮箱变更的完整流程与技术实现细节。该接口要求用户提供新邮箱地址与验证码后端通过验证服务核验Redis中存储的验证码随后由用户服务层执行数据库更新确保邮箱唯一性与安全性。文档还解释了请求体结构、验证码生成与存储机制、错误处理策略并给出成功与失败场景的响应示例强调该操作需要有效的JWT认证。
## 项目结构
- 路由与鉴权
- 路由在 v1 组下用户相关接口均受JWT中间件保护。
- 邮箱变更接口位于 /api/v1/user/change-email采用POST方法。
- 控制器层
- 用户控制器负责接收请求、参数校验、调用服务层、组装响应。
- 服务层
- 验证服务生成、发送、校验验证码验证码以特定键规则存入Redis。
- 用户服务:执行邮箱唯一性检查与数据库更新。
- 数据访问层
- 用户仓库封装GORM操作提供按邮箱查询与字段更新能力。
- 基础设施
- Redis客户端提供键值存取、过期控制、删除等操作。
- 邮件服务:根据类型发送不同主题的验证码邮件。
```mermaid
graph TB
Client["客户端"] --> Routes["路由: /api/v1/user/change-email"]
Routes --> AuthMW["JWT中间件"]
AuthMW --> Handler["用户处理器: ChangeEmail"]
Handler --> VerifySvc["验证服务: VerifyCode"]
Handler --> UserSvc["用户服务: ChangeUserEmail"]
VerifySvc --> Redis["Redis: 验证码存储"]
UserSvc --> Repo["用户仓库: UpdateUserFields"]
Repo --> DB["数据库: user 表"]
VerifySvc --> Email["邮件服务: SendChangeEmail"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L1-L120)
- [auth.go](file://internal/middleware/auth.go#L1-L79)
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [verification_service.go](file://internal/service/verification_service.go#L1-L119)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L47-L55)
章节来源
- [routes.go](file://internal/handler/routes.go#L1-L120)
- [auth.go](file://internal/middleware/auth.go#L1-L79)
## 核心组件
- 请求体结构
- ChangeEmailRequest.new_email新邮箱地址必填且符合邮箱格式。
- ChangeEmailRequest.verification_code验证码必填且长度为6。
- 鉴权要求
- 所有用户相关接口均需携带有效的Bearer JWT。
- 验证码机制
- 验证码类型常量包含“change_email”用于区分不同用途。
- 验证码长度为6有效期10分钟发送频率限制1分钟。
- 验证通过后Redis中的验证码键会被删除。
- 邮箱唯一性校验
- 在更新邮箱前,查询数据库确认新邮箱未被其他用户占用;若被占用则拒绝。
- 错误处理
- 参数错误、未授权、验证码错误、邮箱已被使用、服务器内部错误等均有明确响应码与消息。
章节来源
- [common.go](file://internal/types/common.go#L62-L66)
- [verification_service.go](file://internal/service/verification_service.go#L14-L24)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [response.go](file://internal/model/response.go#L27-L53)
## 架构总览
POST /api/v1/user/change-email 的端到端流程如下:
1. 客户端携带JWT向 /api/v1/user/change-email 发起POST请求请求体包含 new_email 与 verification_code。
2. 路由匹配到用户组JWT中间件校验通过后进入用户处理器。
3. 处理器解析请求体并调用验证服务验证Redis中对应类型的验证码是否匹配。
4. 验证通过后,处理器调用用户服务更新数据库中的邮箱字段。
5. 更新完成后,重新查询用户信息并返回成功响应。
```mermaid
sequenceDiagram
participant C as "客户端"
participant R as "路由"
participant M as "JWT中间件"
participant H as "用户处理器"
participant VS as "验证服务"
participant RS as "Redis"
participant US as "用户服务"
participant UR as "用户仓库"
participant DB as "数据库"
C->>R : "POST /api/v1/user/change-email"
R->>M : "鉴权"
M-->>R : "通过"
R->>H : "进入ChangeEmail"
H->>VS : "VerifyCode(email, code, type=change_email)"
VS->>RS : "Get(verification : code : change_email : new_email)"
RS-->>VS : "验证码"
VS-->>H : "验证通过"
H->>US : "ChangeUserEmail(userID, new_email)"
US->>UR : "FindUserByEmail(new_email)"
UR-->>US : "查询结果"
US->>UR : "UpdateUserFields(userID, {email : new_email})"
UR->>DB : "更新"
DB-->>UR : "成功"
UR-->>US : "成功"
US-->>H : "成功"
H-->>C : "返回用户信息"
```
图表来源
- [routes.go](file://internal/handler/routes.go#L27-L41)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
## 详细组件分析
### 控制器ChangeEmail 端点
- 功能职责
- 从上下文提取用户IDJWT中间件注入
- 解析请求体为 ChangeEmailRequest。
- 调用验证服务核验验证码(类型为 change_email
- 调用用户服务更新邮箱。
- 重新查询用户信息并返回成功响应。
- 错误处理
- 缺少Authorization头或无效token返回401。
- 请求体绑定失败返回400。
- 验证码错误或过期返回400。
- 邮箱已被占用返回400。
- 用户不存在返回404。
- 其他异常返回500。
```mermaid
flowchart TD
Start(["进入ChangeEmail"]) --> GetCtx["获取user_id"]
GetCtx --> CheckAuth{"user_id存在?"}
CheckAuth -- 否 --> Resp401["返回401未授权"]
CheckAuth -- 是 --> BindReq["绑定ChangeEmailRequest"]
BindReq --> BindOK{"绑定成功?"}
BindOK -- 否 --> Resp400["返回400参数错误"]
BindOK -- 是 --> Verify["调用VerifyCode(type=change_email)"]
Verify --> VerifyOK{"验证通过?"}
VerifyOK -- 否 --> Resp400V["返回400验证码错误/过期"]
VerifyOK -- 是 --> Update["调用ChangeUserEmail"]
Update --> UpdateOK{"更新成功?"}
UpdateOK -- 否 --> Resp400E["返回400邮箱已被使用/其他错误"]
UpdateOK -- 是 --> Reload["重新查询用户信息"]
Reload --> Resp200["返回200成功"]
```
图表来源
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [response.go](file://internal/model/response.go#L27-L53)
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [response.go](file://internal/model/response.go#L27-L53)
### 验证服务:验证码生成、发送与校验
- 生成与存储
- 生成6位数字验证码。
- 以键模式 verification:code:{type}:{email} 写入Redis有效期10分钟。
- 发送频率限制键 verification:rate_limit:{type}:{email} 设置1分钟。
- 校验逻辑
- 读取Redis中的验证码并与请求一致进行比对。
- 校验通过后删除该验证码键。
- 邮件发送
- 根据类型选择不同主题与正文,发送更换邮箱验证码邮件。
```mermaid
flowchart TD
Gen["生成6位验证码"] --> Store["写入Redis: code键(10分钟)"]
Store --> Rate["设置rate_limit键(1分钟)"]
Rate --> Mail["发送邮件(ChangeEmail)"]
Mail --> Verify["VerifyCode: 读取code键"]
Verify --> Match{"是否匹配?"}
Match -- 否 --> Err["返回错误(验证码错误/过期)"]
Match -- 是 --> Del["删除code键"]
Del --> Ok["返回成功"]
```
图表来源
- [verification_service.go](file://internal/service/verification_service.go#L26-L77)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [email.go](file://pkg/email/email.go#L47-L55)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
章节来源
- [verification_service.go](file://internal/service/verification_service.go#L14-L24)
- [verification_service.go](file://internal/service/verification_service.go#L26-L77)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [email.go](file://pkg/email/email.go#L47-L55)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
### 用户服务:邮箱唯一性与更新
- 唯一性检查
- 查询新邮箱是否已被其他用户占用若存在且ID不同则报错。
- 更新逻辑
- 使用UpdateUserFields更新邮箱字段。
```mermaid
flowchart TD
Check["FindUserByEmail(new_email)"] --> Found{"是否找到用户?"}
Found -- 是且ID!=userID --> Err["返回错误: 邮箱已被使用"]
Found -- 否或ID==userID --> Update["UpdateUserFields(userID, {email: new_email})"]
Update --> Done["返回成功"]
```
图表来源
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L45-L57)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
章节来源
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L45-L57)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
### 数据模型与响应
- 请求体
- ChangeEmailRequest.new_email新邮箱地址必填且符合邮箱格式。
- ChangeEmailRequest.verification_code验证码必填且长度为6。
- 成功响应
- 返回通用响应结构,包含用户信息(含更新后的邮箱)。
- 错误响应
- 400请求参数错误、验证码错误/过期、邮箱已被使用。
- 401未授权。
- 404用户不存在。
- 500服务器内部错误。
章节来源
- [common.go](file://internal/types/common.go#L62-L66)
- [response.go](file://internal/model/response.go#L27-L53)
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
## 依赖关系分析
- 控制器依赖
- 路由与JWT中间件保证接口受保护。
- 验证服务:负责验证码核验。
- 用户服务:负责邮箱更新与唯一性检查。
- 服务层依赖
- Redis客户端验证码存取与删除。
- 邮件服务:发送验证码邮件。
- 用户仓库:数据库读写。
- 数据库约束
- 用户表的邮箱字段具备唯一索引,配合服务层检查可避免并发冲突。
```mermaid
graph LR
Handler["用户处理器"] --> VerifySvc["验证服务"]
Handler --> UserSvc["用户服务"]
VerifySvc --> Redis["Redis客户端"]
VerifySvc --> Email["邮件服务"]
UserSvc --> Repo["用户仓库"]
Repo --> DB["数据库"]
```
图表来源
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L47-L55)
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [user_repository.go](file://internal/repository/user_repository.go#L65-L69)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L47-L55)
## 性能与可靠性
- 验证码缓存
- Redis键过期时间10分钟避免长期占用内存rate_limit键1分钟限制发送频率降低滥用风险。
- 并发一致性
- 服务层在更新前进行邮箱唯一性检查,结合数据库唯一索引,有效防止并发写入导致的重复。
- 日志与可观测性
- 处理器与服务层在关键路径记录日志,便于定位问题。
- 错误传播
- 明确的错误码与消息,便于前端统一处理。
[本节为通用指导,不直接分析具体文件]
## 故障排查指南
- 401 未授权
- 检查请求头 Authorization 是否为 Bearer 令牌,且令牌有效。
- 400 请求参数错误
- 确认请求体包含 new_email 与 verification_code且格式正确。
- 400 验证码错误/过期
- 检查Redis中 verification:code:change_email:{email} 是否存在且未过期;确认邮件是否送达。
- 400 邮箱已被使用
- 确认新邮箱未被其他用户占用;若被占用请更换邮箱。
- 404 用户不存在
- 检查用户是否被软删除或账户状态异常。
- 500 服务器内部错误
- 查看服务日志关注数据库更新与Redis存取异常。
章节来源
- [user_handler.go](file://internal/handler/user_handler.go#L328-L416)
- [verification_service.go](file://internal/service/verification_service.go#L79-L98)
- [user_service.go](file://internal/service/user_service.go#L186-L201)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [response.go](file://internal/model/response.go#L27-L53)
## 结论
POST /api/v1/user/change-email 通过严格的JWT鉴权、Redis验证码校验与数据库唯一性检查实现了安全可靠的邮箱变更流程。请求体简洁明确错误处理清晰适合在生产环境中稳定运行。建议在部署时确保Redis与邮件服务可用并合理配置验证码有效期与发送频率限制以兼顾用户体验与安全。