# 个人资料管理 **本文引用的文件** - [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) ## 目录 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 用户组
启用 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()
注入 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 ,校验 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 进行鉴权 - 中间件将 user_id 写入上下文,控制器据此查询用户并返回 UserInfo - 请求与响应 - 请求:Authorization: Bearer - 成功响应: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 - 响应: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 未过期且签名正确 - 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)