- Updated main.go to initialize email service and include it in the dependency injection container. - Refactored handlers to utilize context in service method calls, improving consistency and error handling. - Introduced new service options for upload, security, and captcha services, enhancing modularity and testability. - Removed unused repository implementations to streamline the codebase. This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
168 lines
4.3 KiB
Go
168 lines
4.3 KiB
Go
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过期时间
|
|
}
|
|
|
|
// uploadService UploadService的实现
|
|
type uploadService struct {
|
|
storage *storage.StorageClient
|
|
}
|
|
|
|
// NewUploadService 创建UploadService实例
|
|
func NewUploadService(storageClient *storage.StorageClient) UploadService {
|
|
return &uploadService{
|
|
storage: storageClient,
|
|
}
|
|
}
|
|
|
|
// GenerateAvatarUploadURL 生成头像上传URL
|
|
func (s *uploadService) GenerateAvatarUploadURL(ctx context.Context, 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 := s.storage.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 (使用存储客户端内置的 PublicURL)
|
|
result, err := s.storage.GeneratePresignedPostURL(
|
|
ctx,
|
|
bucketName,
|
|
objectName,
|
|
uploadConfig.MinSize,
|
|
uploadConfig.MaxSize,
|
|
uploadConfig.Expires,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("生成上传URL失败: %w", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GenerateTextureUploadURL 生成材质上传URL
|
|
func (s *uploadService) GenerateTextureUploadURL(ctx context.Context, 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 := s.storage.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 (使用存储客户端内置的 PublicURL)
|
|
result, err := s.storage.GeneratePresignedPostURL(
|
|
ctx,
|
|
bucketName,
|
|
objectName,
|
|
uploadConfig.MinSize,
|
|
uploadConfig.MaxSize,
|
|
uploadConfig.Expires,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("生成上传URL失败: %w", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// 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
|
|
}
|