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:
327
.qoder/repowiki/zh/content/API参考/材质API/材质上传与管理/材质上传流程.md
Normal file
327
.qoder/repowiki/zh/content/API参考/材质API/材质上传与管理/材质上传流程.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# 材质上传流程
|
||||
|
||||
<cite>
|
||||
**本文档引用文件**
|
||||
- [internal/handler/texture_handler.go](file://internal/handler/texture_handler.go)
|
||||
- [internal/service/upload_service.go](file://internal/service/upload_service.go)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go)
|
||||
- [pkg/storage/minio.go](file://pkg/storage/minio.go)
|
||||
- [pkg/config/config.go](file://pkg/config/config.go)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [流程概述](#流程概述)
|
||||
2. [API接口说明](#api接口说明)
|
||||
3. [生成预签名上传URL](#生成预签名上传url)
|
||||
4. [创建材质记录](#创建材质记录)
|
||||
5. [错误处理机制](#错误处理机制)
|
||||
6. [流程时序图](#流程时序图)
|
||||
|
||||
## 流程概述
|
||||
|
||||
材质上传流程采用分步式设计,包含两个核心API接口:`GenerateTextureUploadURL`和`CreateTexture`。该流程遵循安全最佳实践,通过预签名URL机制实现文件上传,确保文件在上传到存储系统后才在数据库中创建相应记录。
|
||||
|
||||
整个流程分为两个阶段:
|
||||
1. **准备阶段**:客户端调用`GenerateTextureUploadURL`接口,服务器验证权限后返回预签名上传URL和表单数据
|
||||
2. **完成阶段**:客户端使用返回的凭证上传文件后,调用`CreateTexture`接口创建材质元数据记录
|
||||
|
||||
这种设计确保了只有成功上传的文件才会在系统中创建记录,避免了数据库中出现孤立的记录。
|
||||
|
||||
## API接口说明
|
||||
|
||||
材质上传相关的API接口定义在路由配置中,主要包含以下两个核心接口:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[客户端] --> B[GenerateTextureUploadURL]
|
||||
A --> C[CreateTexture]
|
||||
B --> D[返回预签名URL和表单数据]
|
||||
C --> E[创建材质记录]
|
||||
```
|
||||
|
||||
**接口来源**
|
||||
- [internal/handler/routes.go](file://internal/handler/routes.go#L53-L54)
|
||||
|
||||
## 生成预签名上传URL
|
||||
|
||||
`GenerateTextureUploadURL`接口负责生成临时的文件上传凭证,使客户端能够直接上传文件到对象存储系统。
|
||||
|
||||
### 接口实现
|
||||
|
||||
该接口的处理流程如下:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([开始]) --> AuthCheck["验证用户身份"]
|
||||
AuthCheck --> ValidateInput["验证请求参数"]
|
||||
ValidateInput --> CheckFileName["验证文件名"]
|
||||
CheckFileName --> CheckTextureType["验证材质类型"]
|
||||
CheckTextureType --> GetStorageConfig["获取存储配置"]
|
||||
GetStorageConfig --> GenerateObjectName["生成对象名称"]
|
||||
GenerateObjectName --> GeneratePresignedURL["生成预签名POST URL"]
|
||||
GeneratePresignedURL --> ReturnResult["返回PostURL和FormData"]
|
||||
ReturnResult --> End([结束])
|
||||
```
|
||||
|
||||
**代码来源**
|
||||
- [internal/handler/texture_handler.go](file://internal/handler/texture_handler.go#L28-L83)
|
||||
- [internal/service/upload_service.go](file://internal/service/upload_service.go#L117-L160)
|
||||
|
||||
### 请求参数
|
||||
|
||||
请求体包含以下字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| fileName | string | 上传的文件名 |
|
||||
| textureType | string | 材质类型(SKIN或CAPE) |
|
||||
|
||||
### 响应格式
|
||||
|
||||
成功响应包含预签名上传所需的所有信息:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"postURL": "https://storage.example.com/textures",
|
||||
"formData": {
|
||||
"key": "user_1/skin/20231201120000_texture.png",
|
||||
"policy": "base64-encoded-policy",
|
||||
"signature": "request-signature",
|
||||
"AWSAccessKeyId": "access-key-id"
|
||||
},
|
||||
"textureURL": "https://storage.example.com/textures/user_1/skin/20231201120000_texture.png",
|
||||
"expiresIn": 900
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 实现细节
|
||||
|
||||
1. **文件名验证**:检查文件扩展名是否为`.png`,确保只允许上传PNG格式的材质文件
|
||||
2. **类型验证**:确认材质类型为`SKIN`或`CAPE`之一
|
||||
3. **对象名称生成**:采用`user_{userId}/{textureType}/timestamp_{originalFileName}`的格式,确保文件路径的唯一性
|
||||
4. **预签名URL生成**:调用存储模块的`GeneratePresignedPostURL`方法创建临时上传凭证
|
||||
|
||||
**存储实现来源**
|
||||
- [pkg/storage/minio.go](file://pkg/storage/minio.go#L82-L120)
|
||||
|
||||
## 创建材质记录
|
||||
|
||||
`CreateTexture`接口在文件上传完成后创建材质的元数据记录,将文件与用户关联起来。
|
||||
|
||||
### 接口实现
|
||||
|
||||
该接口的处理流程包含多个验证步骤:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([开始]) --> AuthCheck["验证用户身份"]
|
||||
AuthCheck --> ValidateInput["验证请求参数"]
|
||||
ValidateInput --> CheckLimit["检查上传数量限制"]
|
||||
CheckLimit --> CheckHash["检查文件Hash是否重复"]
|
||||
CheckHash --> ValidateUser["验证用户存在"]
|
||||
ValidateUser --> ConvertType["转换材质类型"]
|
||||
ConvertType --> CreateRecord["创建材质记录"]
|
||||
CreateRecord --> ReturnResult["返回材质信息"]
|
||||
ReturnResult --> End([结束])
|
||||
style CheckLimit fill:#f9f,stroke:#333
|
||||
style CheckHash fill:#f9f,stroke:#333
|
||||
```
|
||||
|
||||
**代码来源**
|
||||
- [internal/handler/texture_handler.go](file://internal/handler/texture_handler.go#L95-L172)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
|
||||
### 请求参数
|
||||
|
||||
请求体包含材质的元数据信息:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| name | string | 材质名称 |
|
||||
| description | string | 描述信息 |
|
||||
| type | string | 材质类型(SKIN或CAPE) |
|
||||
| url | string | 文件访问URL |
|
||||
| hash | string | 文件SHA-256哈希值 |
|
||||
| size | int | 文件大小(字节) |
|
||||
| isPublic | boolean | 是否公开 |
|
||||
| isSlim | boolean | 是否为细身模型 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
成功创建材质后返回材质的详细信息:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 123,
|
||||
"uploaderID": 1,
|
||||
"name": "My Skin",
|
||||
"description": "A custom skin",
|
||||
"type": "SKIN",
|
||||
"url": "https://storage.example.com/textures/user_1/skin/20231201120000_texture.png",
|
||||
"hash": "sha256-hash-value",
|
||||
"size": 10240,
|
||||
"isPublic": true,
|
||||
"downloadCount": 0,
|
||||
"favoriteCount": 0,
|
||||
"isSlim": false,
|
||||
"status": 1,
|
||||
"createdAt": "2023-12-01T12:00:00Z",
|
||||
"updatedAt": "2023-12-01T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 核心验证逻辑
|
||||
|
||||
#### 上传数量限制检查
|
||||
|
||||
系统通过`CheckTextureUploadLimit`函数检查用户是否达到上传上限:
|
||||
|
||||
```go
|
||||
func CheckTextureUploadLimit(db *gorm.DB, uploaderID int64, maxTextures int) error {
|
||||
count, err := repository.CountTexturesByUploaderID(uploaderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count >= int64(maxTextures) {
|
||||
return fmt.Errorf("已达到最大上传数量限制(%d)", maxTextures)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**代码来源**
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L251)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L223-L231)
|
||||
|
||||
#### 重复上传防止
|
||||
|
||||
通过文件的SHA-256哈希值防止重复上传相同内容的材质:
|
||||
|
||||
```go
|
||||
// 检查Hash是否已存在
|
||||
existingTexture, err := repository.FindTextureByHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingTexture != nil {
|
||||
return nil, errors.New("该材质已存在")
|
||||
}
|
||||
```
|
||||
|
||||
**代码来源**
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L30)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L29-L41)
|
||||
|
||||
## 错误处理机制
|
||||
|
||||
系统针对不同场景提供了详细的错误响应,帮助客户端正确处理各种异常情况。
|
||||
|
||||
### 错误码定义
|
||||
|
||||
| 错误码 | HTTP状态码 | 说明 |
|
||||
|--------|------------|------|
|
||||
| 400 | 400 | 请求参数错误 |
|
||||
| 401 | 401 | 未授权访问 |
|
||||
| 403 | 403 | 无权操作 |
|
||||
| 404 | 404 | 资源不存在 |
|
||||
|
||||
### 常见错误场景
|
||||
|
||||
#### 上传数量达到上限
|
||||
|
||||
当用户上传的材质数量达到系统限制时,返回以下错误:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "已达到最大上传数量限制(100)",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 权限不足
|
||||
|
||||
未登录用户或非上传者尝试操作时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "Unauthorized",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 文件Hash冲突
|
||||
|
||||
上传已存在的材质文件时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "该材质已存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 无效的材质类型
|
||||
|
||||
指定不支持的材质类型时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "无效的材质类型: INVALID",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 流程时序图
|
||||
|
||||
以下是完整的材质上传流程时序图:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant Handler as "处理器"
|
||||
participant Service as "服务层"
|
||||
participant Storage as "存储模块"
|
||||
participant DB as "数据库"
|
||||
Client->>Handler : 调用GenerateTextureUploadURL
|
||||
Handler->>Service : 验证参数并调用GenerateTextureUploadURL
|
||||
Service->>Service : 验证文件名和类型
|
||||
Service->>Storage : 获取存储桶名称
|
||||
Storage-->>Service : 返回存储桶名称
|
||||
Service->>Storage : 生成预签名POST URL
|
||||
Storage-->>Service : 返回PostURL和FormData
|
||||
Service-->>Handler : 返回结果
|
||||
Handler-->>Client : 返回预签名上传信息
|
||||
Client->>Storage : 使用PostURL上传文件
|
||||
Storage-->>Client : 上传成功响应
|
||||
Client->>Handler : 调用CreateTexture
|
||||
Handler->>Service : 验证参数并调用CreateTexture
|
||||
Service->>DB : 检查用户上传数量限制
|
||||
DB-->>Service : 返回数量统计
|
||||
Service->>DB : 检查文件Hash是否重复
|
||||
DB-->>Service : 返回查询结果
|
||||
Service->>DB : 创建材质记录
|
||||
DB-->>Service : 创建成功
|
||||
Service-->>Handler : 返回材质信息
|
||||
Handler-->>Client : 返回创建结果
|
||||
```
|
||||
|
||||
**时序图来源**
|
||||
- [internal/handler/texture_handler.go](file://internal/handler/texture_handler.go)
|
||||
- [internal/service/upload_service.go](file://internal/service/upload_service.go)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go)
|
||||
Reference in New Issue
Block a user