Files
photography/backend-old/internal/service/upload/upload_service.go
xujiang 604b9e59ba fix
2025-07-10 18:09:11 +08:00

187 lines
4.5 KiB
Go

package upload
import (
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
"photography-backend/internal/config"
)
// UploadService 文件上传服务
type UploadService struct {
config *config.Config
uploadDir string
baseURL string
}
// UploadResult 上传结果
type UploadResult struct {
Filename string `json:"filename"`
OriginalName string `json:"original_name"`
FilePath string `json:"file_path"`
FileURL string `json:"file_url"`
FileSize int64 `json:"file_size"`
MimeType string `json:"mime_type"`
}
// NewUploadService 创建文件上传服务
func NewUploadService(cfg *config.Config) *UploadService {
uploadDir := cfg.Storage.Local.BasePath
if uploadDir == "" {
uploadDir = "./uploads"
}
baseURL := cfg.Storage.Local.BaseURL
if baseURL == "" {
baseURL = fmt.Sprintf("http://localhost:%d/uploads", cfg.App.Port)
}
// 确保上传目录存在
os.MkdirAll(uploadDir, 0755)
// 创建子目录
subdirs := []string{"photos", "thumbnails", "temp"}
for _, subdir := range subdirs {
os.MkdirAll(filepath.Join(uploadDir, subdir), 0755)
}
return &UploadService{
config: cfg,
uploadDir: uploadDir,
baseURL: baseURL,
}
}
// UploadPhoto 上传照片
func (s *UploadService) UploadPhoto(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) {
// 验证文件类型
if !s.isValidImageType(header.Header.Get("Content-Type")) {
return nil, fmt.Errorf("不支持的文件类型: %s", header.Header.Get("Content-Type"))
}
// 验证文件大小
if header.Size > s.config.Upload.MaxFileSize {
return nil, fmt.Errorf("文件大小超过限制: %d bytes", header.Size)
}
// 生成唯一文件名
filename := s.generateUniqueFilename(header.Filename)
// 保存文件到 photos 目录
photoPath := filepath.Join(s.uploadDir, "photos", filename)
// 创建目标文件
dst, err := os.Create(photoPath)
if err != nil {
return nil, fmt.Errorf("创建文件失败: %w", err)
}
defer dst.Close()
// 复制文件内容
_, err = io.Copy(dst, file)
if err != nil {
return nil, fmt.Errorf("保存文件失败: %w", err)
}
// 构造文件URL
fileURL := fmt.Sprintf("%s/photos/%s", s.baseURL, filename)
return &UploadResult{
Filename: filename,
OriginalName: header.Filename,
FilePath: photoPath,
FileURL: fileURL,
FileSize: header.Size,
MimeType: header.Header.Get("Content-Type"),
}, nil
}
// DeletePhoto 删除照片
func (s *UploadService) DeletePhoto(filename string) error {
// 删除原图
photoPath := filepath.Join(s.uploadDir, "photos", filename)
if err := os.Remove(photoPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("删除文件失败: %w", err)
}
// 删除缩略图
thumbnailPath := filepath.Join(s.uploadDir, "thumbnails", filename)
os.Remove(thumbnailPath) // 忽略错误,因为缩略图可能不存在
return nil
}
// GetPhotoURL 获取照片URL
func (s *UploadService) GetPhotoURL(filename string) string {
return fmt.Sprintf("%s/photos/%s", s.baseURL, filename)
}
// isValidImageType 验证是否为有效的图片类型
func (s *UploadService) isValidImageType(mimeType string) bool {
allowedTypes := s.config.Upload.AllowedTypes
if len(allowedTypes) == 0 {
// 默认允许的图片类型
allowedTypes = []string{
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
"image/webp",
"image/tiff",
}
}
for _, allowedType := range allowedTypes {
if mimeType == allowedType {
return true
}
}
return false
}
// generateUniqueFilename 生成唯一文件名
func (s *UploadService) generateUniqueFilename(originalName string) string {
ext := filepath.Ext(originalName)
timestamp := time.Now().Unix()
// 清理原文件名
baseName := strings.TrimSuffix(originalName, ext)
baseName = strings.ReplaceAll(baseName, " ", "_")
return fmt.Sprintf("%s_%d%s", baseName, timestamp, ext)
}
// GetUploadStats 获取上传统计信息
func (s *UploadService) GetUploadStats() (map[string]interface{}, error) {
photosDir := filepath.Join(s.uploadDir, "photos")
var totalFiles int
var totalSize int64
err := filepath.Walk(photosDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
totalFiles++
totalSize += info.Size()
}
return nil
})
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_files": totalFiles,
"total_size": totalSize,
"upload_dir": s.uploadDir,
"base_url": s.baseURL,
}, nil
}