9.7 KiB
9.7 KiB
材质上传流程
**本文档引用文件** - [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)目录
流程概述
材质上传流程采用分步式设计,包含两个核心API接口:GenerateTextureUploadURL和CreateTexture。该流程遵循安全最佳实践,通过预签名URL机制实现文件上传,确保文件在上传到存储系统后才在数据库中创建相应记录。
整个流程分为两个阶段:
- 准备阶段:客户端调用
GenerateTextureUploadURL接口,服务器验证权限后返回预签名上传URL和表单数据 - 完成阶段:客户端使用返回的凭证上传文件后,调用
CreateTexture接口创建材质元数据记录
这种设计确保了只有成功上传的文件才会在系统中创建记录,避免了数据库中出现孤立的记录。
API接口说明
材质上传相关的API接口定义在路由配置中,主要包含以下两个核心接口:
flowchart TD
A[客户端] --> B[GenerateTextureUploadURL]
A --> C[CreateTexture]
B --> D[返回预签名URL和表单数据]
C --> E[创建材质记录]
接口来源
生成预签名上传URL
GenerateTextureUploadURL接口负责生成临时的文件上传凭证,使客户端能够直接上传文件到对象存储系统。
接口实现
该接口的处理流程如下:
flowchart TD
Start([开始]) --> AuthCheck["验证用户身份"]
AuthCheck --> ValidateInput["验证请求参数"]
ValidateInput --> CheckFileName["验证文件名"]
CheckFileName --> CheckTextureType["验证材质类型"]
CheckTextureType --> GetStorageConfig["获取存储配置"]
GetStorageConfig --> GenerateObjectName["生成对象名称"]
GenerateObjectName --> GeneratePresignedURL["生成预签名POST URL"]
GeneratePresignedURL --> ReturnResult["返回PostURL和FormData"]
ReturnResult --> End([结束])
代码来源
请求参数
请求体包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| fileName | string | 上传的文件名 |
| textureType | string | 材质类型(SKIN或CAPE) |
响应格式
成功响应包含预签名上传所需的所有信息:
{
"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
}
}
实现细节
- 文件名验证:检查文件扩展名是否为
.png,确保只允许上传PNG格式的材质文件 - 类型验证:确认材质类型为
SKIN或CAPE之一 - 对象名称生成:采用
user_{userId}/{textureType}/timestamp_{originalFileName}的格式,确保文件路径的唯一性 - 预签名URL生成:调用存储模块的
GeneratePresignedPostURL方法创建临时上传凭证
存储实现来源
创建材质记录
CreateTexture接口在文件上传完成后创建材质的元数据记录,将文件与用户关联起来。
接口实现
该接口的处理流程包含多个验证步骤:
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
代码来源
请求参数
请求体包含材质的元数据信息:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | string | 材质名称 |
| description | string | 描述信息 |
| type | string | 材质类型(SKIN或CAPE) |
| url | string | 文件访问URL |
| hash | string | 文件SHA-256哈希值 |
| size | int | 文件大小(字节) |
| isPublic | boolean | 是否公开 |
| isSlim | boolean | 是否为细身模型 |
响应格式
成功创建材质后返回材质的详细信息:
{
"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函数检查用户是否达到上传上限:
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
}
代码来源
重复上传防止
通过文件的SHA-256哈希值防止重复上传相同内容的材质:
// 检查Hash是否已存在
existingTexture, err := repository.FindTextureByHash(hash)
if err != nil {
return nil, err
}
if existingTexture != nil {
return nil, errors.New("该材质已存在")
}
代码来源
错误处理机制
系统针对不同场景提供了详细的错误响应,帮助客户端正确处理各种异常情况。
错误码定义
| 错误码 | HTTP状态码 | 说明 |
|---|---|---|
| 400 | 400 | 请求参数错误 |
| 401 | 401 | 未授权访问 |
| 403 | 403 | 无权操作 |
| 404 | 404 | 资源不存在 |
常见错误场景
上传数量达到上限
当用户上传的材质数量达到系统限制时,返回以下错误:
{
"code": 400,
"message": "已达到最大上传数量限制(100)",
"data": null
}
权限不足
未登录用户或非上传者尝试操作时返回:
{
"code": 401,
"message": "Unauthorized",
"data": null
}
文件Hash冲突
上传已存在的材质文件时返回:
{
"code": 400,
"message": "该材质已存在",
"data": null
}
无效的材质类型
指定不支持的材质类型时返回:
{
"code": 400,
"message": "无效的材质类型: INVALID",
"data": null
}
流程时序图
以下是完整的材质上传流程时序图:
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 : 返回创建结果
时序图来源