Files
photography/backend/pkg/utils/file/file.go
xujiang 010fe2a8c7
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 5m8s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped
fix
2025-07-10 18:09:11 +08:00

204 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package file
import (
"fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
"github.com/disintegration/imaging"
"github.com/google/uuid"
)
// Config 文件上传配置
type Config struct {
MaxSize int64 // 最大文件大小 (bytes)
UploadDir string // 上传目录
AllowedTypes []string // 允许的文件类型
}
// FileInfo 文件信息
type FileInfo struct {
OriginalName string `json:"original_name"`
FileName string `json:"file_name"`
FilePath string `json:"file_path"`
FileSize int64 `json:"file_size"`
ContentType string `json:"content_type"`
URL string `json:"url"`
}
// UploadResult 上传结果
type UploadResult struct {
Original FileInfo `json:"original"`
Thumbnail FileInfo `json:"thumbnail"`
}
// IsAllowedType 检查文件类型是否允许
func IsAllowedType(contentType string, allowedTypes []string) bool {
for _, allowedType := range allowedTypes {
if contentType == allowedType {
return true
}
}
return false
}
// GenerateFileName 生成唯一文件名
func GenerateFileName(originalName string) string {
ext := filepath.Ext(originalName)
name := strings.TrimSuffix(originalName, ext)
timestamp := time.Now().Unix()
uuid := uuid.New().String()[:8]
return fmt.Sprintf("%s_%d_%s%s", name, timestamp, uuid, ext)
}
// SaveFile 保存文件
func SaveFile(file multipart.File, header *multipart.FileHeader, config Config) (*FileInfo, error) {
// 检查文件大小
if header.Size > config.MaxSize {
return nil, fmt.Errorf("文件大小超过限制: %d bytes", config.MaxSize)
}
// 检查文件类型
contentType := header.Header.Get("Content-Type")
if !IsAllowedType(contentType, config.AllowedTypes) {
return nil, fmt.Errorf("不支持的文件类型: %s", contentType)
}
// 生成文件名
fileName := GenerateFileName(header.Filename)
// 创建上传目录
uploadPath := filepath.Join(config.UploadDir, "photos")
if err := os.MkdirAll(uploadPath, 0755); err != nil {
return nil, fmt.Errorf("创建上传目录失败: %v", err)
}
// 完整文件路径
filePath := filepath.Join(uploadPath, fileName)
// 创建目标文件
dst, err := os.Create(filePath)
if err != nil {
return nil, fmt.Errorf("创建文件失败: %v", err)
}
defer dst.Close()
// 复制文件内容
file.Seek(0, 0) // 重置文件指针
_, err = io.Copy(dst, file)
if err != nil {
return nil, fmt.Errorf("保存文件失败: %v", err)
}
// 返回文件信息
return &FileInfo{
OriginalName: header.Filename,
FileName: fileName,
FilePath: filePath,
FileSize: header.Size,
ContentType: contentType,
URL: "/uploads/photos/" + fileName,
}, nil
}
// CreateThumbnail 创建缩略图
func CreateThumbnail(originalPath string, config Config) (*FileInfo, error) {
// 打开原图
src, err := imaging.Open(originalPath)
if err != nil {
return nil, fmt.Errorf("打开图片失败: %v", err)
}
// 生成缩略图文件名
ext := filepath.Ext(originalPath)
baseName := strings.TrimSuffix(filepath.Base(originalPath), ext)
thumbnailName := baseName + "_thumb" + ext
// 创建缩略图目录
thumbnailDir := filepath.Join(config.UploadDir, "thumbnails")
if err := os.MkdirAll(thumbnailDir, 0755); err != nil {
return nil, fmt.Errorf("创建缩略图目录失败: %v", err)
}
thumbnailPath := filepath.Join(thumbnailDir, thumbnailName)
// 调整图片大小 (最大宽度 300px保持比例)
thumbnail := imaging.Resize(src, 300, 0, imaging.Lanczos)
// 保存缩略图
err = imaging.Save(thumbnail, thumbnailPath)
if err != nil {
return nil, fmt.Errorf("保存缩略图失败: %v", err)
}
// 获取文件大小
stat, err := os.Stat(thumbnailPath)
if err != nil {
return nil, fmt.Errorf("获取缩略图信息失败: %v", err)
}
return &FileInfo{
OriginalName: thumbnailName,
FileName: thumbnailName,
FilePath: thumbnailPath,
FileSize: stat.Size(),
ContentType: "image/jpeg",
URL: "/uploads/thumbnails/" + thumbnailName,
}, nil
}
// UploadPhoto 上传照片(包含原图和缩略图)
func UploadPhoto(file multipart.File, header *multipart.FileHeader, config Config) (*UploadResult, error) {
// 保存原图
original, err := SaveFile(file, header, config)
if err != nil {
return nil, err
}
// 创建缩略图
thumbnail, err := CreateThumbnail(original.FilePath, config)
if err != nil {
return nil, err
}
return &UploadResult{
Original: *original,
Thumbnail: *thumbnail,
}, nil
}
// DeleteFile 删除文件
func DeleteFile(filePath string) error {
if filePath == "" {
return nil
}
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return nil // 文件不存在,认为删除成功
}
return os.Remove(filePath)
}
// GetImageDimensions 获取图片尺寸
func GetImageDimensions(filePath string) (width, height int, err error) {
file, err := os.Open(filePath)
if err != nil {
return 0, 0, err
}
defer file.Close()
img, _, err := image.DecodeConfig(file)
if err != nil {
return 0, 0, err
}
return img.Width, img.Height, nil
}