Files
zt-closet-backend/.codebuddy/plans/用户头像上传接口_eb17021f.md
T
2026-09-11 01:41:52 +08:00

133 lines
6.6 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.
---
name: 用户头像上传接口
overview: 新增服务端中转的头像上传接口 POST /api/v1/user/avatar/upload,复用现有衣物上传的图片校验与 COS 上传能力,校验通过后上传 COS 并将头像 key 写入 users.avatar,最后失效用户缓存。
todos:
- id: avatar-service
content: 实现 UserService.AvatarUpload(校验、COS 上传、更新 avatar、失效缓存)与 buildUserInfoResponse 头像 URL 转换
status: completed
- id: avatar-api
content: 新增头像上传 DTO 与 UserHandler.AvatarUpload,注册 POST /user/avatar/upload 路由
status: completed
dependencies:
- avatar-service
- id: avatar-docs
content: 同步更新接口文档.md 与 README.md 的头像上传接口说明
status: completed
dependencies:
- avatar-api
---
## 产品概述
为当前用户新增"头像文件上传"能力。此前后端只允许通过 `PUT /user/profile` 传入头像 URL 字符串,现需提供方案 A(服务端中转):客户端以 multipart 上传头像图片,后端完成校验、存储到 COS,并更新用户头像字段,返回可直接访问的头像 URL。
## 核心功能
- 新增鉴权接口 `POST /api/v1/user/avatar/upload`,multipart 字段名 `file`
- 后端校验上传文件:非空、扩展名白名单、大小上限、图片 magic bytes
- 上传成功后写入 COS,并将 COS 对象键保存到 `users.avatar`
- 更新成功后失效用户信息缓存,返回可访问的头像 URL 供前端立即展示
- 用户信息查询接口对头像做兼容转换:历史 URL 直接透传,新存 COS key 自动转可访问 URL
- 头像上传不消耗衣物 COS 上传额度
## 技术栈
- 语言/框架:Go + Gin(沿用现有项目栈)
- 存储:腾讯云 COS(复用 `global.CosClient`)
- 缓存:Redis(复用 `redisdao.UserInfoDel` 失效用户缓存)
- 持久层:GORM(复用 `mysqldao.UserDao`)
- 工具函数:复用 `pkg/upload` 的文件校验、落盘、COS 上传、URL 生成能力
- 无新增第三方依赖
## 实施方案
### 整体策略
仿照现有衣物上传 `ClothesService.Upload` 的同步链路,在用户模块新增头像上传能力:Handler 解析 multipart → Service 完成校验、落盘、上传 COS、更新数据库、失效缓存 → 返回头像 URL。
### 关键决策
1. **复用现有校验链路与错误码**:扩展名白名单(`global.UploadSetting.AllowedExtensions`)、大小上限(`global.UploadSetting.MaxSizeMB`)、magic bytes 校验,以及 `UploadFileEmpty` / `UploadFileExtNotAllow` / `UploadFileTooLarge` / `UploadFileSaveFailed` 错误码,保持与衣物上传一致。
2. **avatar 存储 COS 对象键而非完整 URL**:与衣物 `processed_image` 存 key 的语义一致,读取时统一经 `upload.GetCosUrl` 转换;对历史通过 `PUT /user/profile` 传入的 http(s) URL 直接透传,保证向后兼容。
3. **COS key 规则**:`{user.UserUUID}/{userID}/avatar/{随机名+原扩展名}`,复用 `FileGenName` 防冲突,且按用户维度隔离,避免跨用户覆盖。
4. **不扣 COS 上传额度**:头像属基础资料,与衣物图片处理(抠图/分类)成本不同,不调用 `consumeCosQuota`。
5. **上传后失效缓存**:调用 `redisdao.UserInfoDel(ctx, userID)`,使 `InfoGetBySelf` / `WxLogin` 下次回读最新头像。
### 数据流
```mermaid
sequenceDiagram
participant C as 客户端
participant H as UserHandler.AvatarUpload
participant S as UserService.AvatarUpload
participant U as pkg/upload
participant COS as 腾讯云 COS
participant D as UserDao
participant R as Redis
C->>H: POST /user/avatar/upload (multipart file)
H->>H: JWT 鉴权 + MaxBytesReader 限流
H->>S: AvatarUpload(ctx, userID, fileHeader)
S->>S: 扩展名/大小/magic bytes 校验 + 读取字节
S->>D: UserGetByID 获取 UserUUID
S->>U: FileGenName + FileSaveTmpBytes
S->>U: FileUploadCos(cosKey)
U->>COS: 上传对象
S->>D: UserUpdateByID(avatar=cosKey)
S->>R: UserInfoDel 失效缓存
S-->>H: 返回头像可访问 URL
H-->>C: 统一响应
```
## 实现细节
### 目录结构
```
project-root/
├── internal/
│ ├── dto/
│ │ └── user.go # [MODIFY] 新增 UserAvatarUploadRequest(空)/ UserAvatarUploadResponse
│ ├── service/
│ │ └── user.go # [MODIFY] 新增 AvatarUpload 方法;buildUserInfoResponse 头像 URL 转换;补充 imports
│ ├── routers/
│ │ ├── router.go # [MODIFY] 注册 POST /user/avatar/upload
│ │ └── api/v1/
│ │ └── user.go # [MODIFY] 新增 UserHandler.AvatarUpload;补充 imports
├── 接口文档.md # [MODIFY] 新增头像上传接口说明
└── README.md # [MODIFY] 同步用户接口说明
```
### 关键代码结构
```
// internal/dto/user.go
type UserAvatarUploadRequest struct{}
type UserAvatarUploadResponse struct {
Avatar string `json:"avatar"` // 头像可访问 URL
}
```
```
// internal/service/user.go
func (s *UserService) AvatarUpload(ctx context.Context, userID int64, fileHeader *multipart.FileHeader) (*dto.UserAvatarUploadResponse, *errcode.Error)
```
`buildUserInfoResponse` 中头像转换逻辑:
```
avatar := user.Avatar
if avatar != "" && !strings.HasPrefix(avatar, "http://") && !strings.HasPrefix(avatar, "https://") {
avatar = upload.GetCosUrl(avatar)
}
```
### 执行要点
- **imports 补充**:`internal/service/user.go` 需新增 `io`、`mime/multipart`、`pkg/upload`;`internal/routers/api/v1/user.go` 需新增 `net/http`、`global`、`pkg/errcode`(若缺)。
- **错误映射**:`FileSaveTmpBytes`、`FileUploadCos` 返回原生 error 时统一映射为 `errcode.UploadFileSaveFailed`;`UserGetByID` 未命中映射 `UserNotFound`,其他 DB 错误映射 `DatabaseError`;`UserUpdateByID` 失败映射 `UserUpdateFailed`。
- **同步读取字节**:multipart 临时文件在请求返回后会被清理,必须在 Service 内同步 `io.ReadAll` 后再异步/后续处理(本方案全同步,无异步环节)。
- **`buildUserInfoResponse` 影响面**:该函数同时被 `InfoGetBySelf` 与 `WxLogin` 使用,头像 URL 转换对两处一并生效,属预期行为,无需额外改动。
- **缓存失效**:复用 `ProfileUpdate` 既有模式 `_ = redisdao.UserInfoDel(ctx, userID)`,失败仅忽略(best-effort),下次读取会回落 DB 回填。
- **兼容性保障**:不修改 `PUT /user/profile` 的现有 avatar 传 URL 语义,避免破坏已有客户端;仅扩展用户信息读取时的 URL 转换。