Files
backend/.qoder/repowiki/zh/content/API参考/用户API/个人资料管理.md
lan a4b6c5011e
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
chore(git): 更新.gitignore以忽略新的本地文件
2025-11-30 08:33:17 +08:00

357 lines
15 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.

# 个人资料管理
<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)