chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
This commit is contained in:
357
.qoder/repowiki/zh/content/API参考/用户API/个人资料管理.md
Normal file
357
.qoder/repowiki/zh/content/API参考/用户API/个人资料管理.md
Normal 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
|
||||
- JSON:password 不返回;其他字段按标签映射
|
||||
- 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)
|
||||
Reference in New Issue
Block a user