337 lines
15 KiB
Markdown
337 lines
15 KiB
Markdown
|
|
# 头像管理
|
|||
|
|
|
|||
|
|
<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的API:POST /api/v1/user/avatar/upload-url
|
|||
|
|
- 更新头像URL的API:PUT /api/v1/user/avatar
|
|||
|
|
- 客户端直传对象存储(MinIO/RustFS)的完整流程
|
|||
|
|
- upload_service与storage包的协作机制
|
|||
|
|
- 请求参数、返回字段、错误处理与900秒(15分钟)有效期说明
|
|||
|
|
- 提供可直接参考的curl示例
|
|||
|
|
|
|||
|
|
## 项目结构
|
|||
|
|
围绕头像管理的关键文件组织如下:
|
|||
|
|
- 路由注册:在路由层注册两个端点,分别对应生成上传URL与更新头像URL
|
|||
|
|
- 处理器:用户处理器包含两个方法,分别处理上述两个端点
|
|||
|
|
- 服务层:upload_service负责生成预签名URL;user_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:生成头像上传URL(POST /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:更新头像URL(PUT /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)
|
|||
|
|
|
|||
|
|
### 组件C:upload_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)
|