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

9.7 KiB
Raw Blame History

材质上传流程

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

目录

  1. 流程概述
  2. API接口说明
  3. 生成预签名上传URL
  4. 创建材质记录
  5. 错误处理机制
  6. 流程时序图

流程概述

材质上传流程采用分步式设计包含两个核心API接口GenerateTextureUploadURLCreateTexture。该流程遵循安全最佳实践通过预签名URL机制实现文件上传确保文件在上传到存储系统后才在数据库中创建相应记录。

整个流程分为两个阶段:

  1. 准备阶段:客户端调用GenerateTextureUploadURL接口服务器验证权限后返回预签名上传URL和表单数据
  2. 完成阶段:客户端使用返回的凭证上传文件后,调用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
  }
}

实现细节

  1. 文件名验证:检查文件扩展名是否为.png确保只允许上传PNG格式的材质文件
  2. 类型验证:确认材质类型为SKINCAPE之一
  3. 对象名称生成:采用user_{userId}/{textureType}/timestamp_{originalFileName}的格式,确保文件路径的唯一性
  4. 预签名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 : 返回创建结果

时序图来源