2025-11-28 23:30:49 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"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过期时间
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
// uploadService UploadService的实现
|
|
|
|
|
type uploadService struct {
|
|
|
|
|
storage *storage.StorageClient
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
// NewUploadService 创建UploadService实例
|
|
|
|
|
func NewUploadService(storageClient *storage.StorageClient) UploadService {
|
|
|
|
|
return &uploadService{
|
|
|
|
|
storage: storageClient,
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
// GenerateAvatarUploadURL 生成头像上传URL
|
|
|
|
|
func (s *uploadService) GenerateAvatarUploadURL(ctx context.Context, userID int64, fileName string) (*storage.PresignedPostPolicyResult, error) {
|
2025-11-28 23:30:49 +08:00
|
|
|
// 1. 验证文件名
|
|
|
|
|
if err := ValidateFileName(fileName, FileTypeAvatar); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 2. 获取上传配置
|
|
|
|
|
uploadConfig := GetUploadConfig(FileTypeAvatar)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 3. 获取存储桶名称
|
2025-12-02 22:52:33 +08:00
|
|
|
bucketName, err := s.storage.GetBucket("avatars")
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("获取存储桶失败: %w", err)
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 4. 生成对象名称(路径)
|
|
|
|
|
// 格式: user_{userId}/timestamp_{originalFileName}
|
|
|
|
|
timestamp := time.Now().Format("20060102150405")
|
|
|
|
|
objectName := fmt.Sprintf("user_%d/%s_%s", userID, timestamp, fileName)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-12-02 11:22:14 +08:00
|
|
|
// 5. 生成预签名POST URL (使用存储客户端内置的 PublicURL)
|
2025-12-02 22:52:33 +08:00
|
|
|
result, err := s.storage.GeneratePresignedPostURL(
|
2025-11-28 23:30:49 +08:00
|
|
|
ctx,
|
|
|
|
|
bucketName,
|
|
|
|
|
objectName,
|
|
|
|
|
uploadConfig.MinSize,
|
|
|
|
|
uploadConfig.MaxSize,
|
|
|
|
|
uploadConfig.Expires,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("生成上传URL失败: %w", err)
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
// GenerateTextureUploadURL 生成材质上传URL
|
|
|
|
|
func (s *uploadService) GenerateTextureUploadURL(ctx context.Context, userID int64, fileName, textureType string) (*storage.PresignedPostPolicyResult, error) {
|
2025-11-28 23:30:49 +08:00
|
|
|
// 1. 验证文件名
|
|
|
|
|
if err := ValidateFileName(fileName, FileTypeTexture); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 2. 验证材质类型
|
|
|
|
|
if textureType != "SKIN" && textureType != "CAPE" {
|
|
|
|
|
return nil, fmt.Errorf("无效的材质类型: %s", textureType)
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 3. 获取上传配置
|
|
|
|
|
uploadConfig := GetUploadConfig(FileTypeTexture)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 4. 获取存储桶名称
|
2025-12-02 22:52:33 +08:00
|
|
|
bucketName, err := s.storage.GetBucket("textures")
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("获取存储桶失败: %w", err)
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
// 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)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-12-02 11:22:14 +08:00
|
|
|
// 6. 生成预签名POST URL (使用存储客户端内置的 PublicURL)
|
2025-12-02 22:52:33 +08:00
|
|
|
result, err := s.storage.GeneratePresignedPostURL(
|
2025-11-28 23:30:49 +08:00
|
|
|
ctx,
|
|
|
|
|
bucketName,
|
|
|
|
|
objectName,
|
|
|
|
|
uploadConfig.MinSize,
|
|
|
|
|
uploadConfig.MaxSize,
|
|
|
|
|
uploadConfig.Expires,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("生成上传URL失败: %w", err)
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
2025-11-28 23:30:49 +08:00
|
|
|
return result, nil
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
},
|
2025-12-04 20:07:30 +08:00
|
|
|
MinSize: 512, // 512B
|
2025-12-02 22:52:33 +08:00
|
|
|
MaxSize: 5 * 1024 * 1024, // 5MB
|
|
|
|
|
Expires: 15 * time.Minute,
|
|
|
|
|
}
|
|
|
|
|
case FileTypeTexture:
|
|
|
|
|
return &UploadConfig{
|
|
|
|
|
AllowedExts: map[string]bool{
|
|
|
|
|
".png": true,
|
|
|
|
|
},
|
2025-12-04 20:07:30 +08:00
|
|
|
MinSize: 512, // 512B
|
2025-12-02 22:52:33 +08:00
|
|
|
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
|
|
|
|
|
}
|