package service import ( "carrotskin/pkg/config" "carrotskin/pkg/storage" "context" "fmt" "path/filepath" "strings" "time" ) // FileType 文件类型枚举 type FileType string const ( FileTypeAvatar FileType = "avatar" FileTypeTexture FileType = "texture" ) // UploadConfig 上传配置 type UploadConfig struct { AllowedExts map[string]bool // 允许的文件扩展名 MinSize int64 // 最小文件大小(字节) MaxSize int64 // 最大文件大小(字节) Expires time.Duration // URL过期时间 } // GetUploadConfig 根据文件类型获取上传配置 func GetUploadConfig(fileType FileType) *UploadConfig { switch fileType { case FileTypeAvatar: return &UploadConfig{ AllowedExts: map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, }, MinSize: 1024, // 1KB MaxSize: 5 * 1024 * 1024, // 5MB Expires: 15 * time.Minute, } case FileTypeTexture: return &UploadConfig{ AllowedExts: map[string]bool{ ".png": true, }, MinSize: 1024, // 1KB MaxSize: 10 * 1024 * 1024, // 10MB Expires: 15 * time.Minute, } default: return nil } } // ValidateFileName 验证文件名 func ValidateFileName(fileName string, fileType FileType) error { if fileName == "" { return fmt.Errorf("文件名不能为空") } uploadConfig := GetUploadConfig(fileType) if uploadConfig == nil { return fmt.Errorf("不支持的文件类型") } ext := strings.ToLower(filepath.Ext(fileName)) if !uploadConfig.AllowedExts[ext] { return fmt.Errorf("不支持的文件格式: %s", ext) } return nil } // GenerateAvatarUploadURL 生成头像上传URL func GenerateAvatarUploadURL(ctx context.Context, storageClient *storage.StorageClient, cfg config.RustFSConfig, userID int64, fileName string) (*storage.PresignedPostPolicyResult, error) { // 1. 验证文件名 if err := ValidateFileName(fileName, FileTypeAvatar); err != nil { return nil, err } // 2. 获取上传配置 uploadConfig := GetUploadConfig(FileTypeAvatar) // 3. 获取存储桶名称 bucketName, err := storageClient.GetBucket("avatars") if err != nil { return nil, fmt.Errorf("获取存储桶失败: %w", err) } // 4. 生成对象名称(路径) // 格式: user_{userId}/timestamp_{originalFileName} timestamp := time.Now().Format("20060102150405") objectName := fmt.Sprintf("user_%d/%s_%s", userID, timestamp, fileName) // 5. 生成预签名POST URL result, err := storageClient.GeneratePresignedPostURL( ctx, bucketName, objectName, uploadConfig.MinSize, uploadConfig.MaxSize, uploadConfig.Expires, cfg.UseSSL, cfg.Endpoint, ) if err != nil { return nil, fmt.Errorf("生成上传URL失败: %w", err) } return result, nil } // GenerateTextureUploadURL 生成材质上传URL func GenerateTextureUploadURL(ctx context.Context, storageClient *storage.StorageClient, cfg config.RustFSConfig, userID int64, fileName, textureType string) (*storage.PresignedPostPolicyResult, error) { // 1. 验证文件名 if err := ValidateFileName(fileName, FileTypeTexture); err != nil { return nil, err } // 2. 验证材质类型 if textureType != "SKIN" && textureType != "CAPE" { return nil, fmt.Errorf("无效的材质类型: %s", textureType) } // 3. 获取上传配置 uploadConfig := GetUploadConfig(FileTypeTexture) // 4. 获取存储桶名称 bucketName, err := storageClient.GetBucket("textures") if err != nil { return nil, fmt.Errorf("获取存储桶失败: %w", err) } // 5. 生成对象名称(路径) // 格式: user_{userId}/{textureType}/timestamp_{originalFileName} timestamp := time.Now().Format("20060102150405") textureTypeFolder := strings.ToLower(textureType) objectName := fmt.Sprintf("user_%d/%s/%s_%s", userID, textureTypeFolder, timestamp, fileName) // 6. 生成预签名POST URL result, err := storageClient.GeneratePresignedPostURL( ctx, bucketName, objectName, uploadConfig.MinSize, uploadConfig.MaxSize, uploadConfig.Expires, cfg.UseSSL, cfg.Endpoint, ) if err != nil { return nil, fmt.Errorf("生成上传URL失败: %w", err) } return result, nil }