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

337 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>
**本文引用的文件**
- [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)