139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
|
|
package utils
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"io"
|
|||
|
|
"mime/multipart"
|
|||
|
|
"os"
|
|||
|
|
"path/filepath"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// StorageType 存储类型
|
|||
|
|
type StorageType string
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
StorageLocal StorageType = "local"
|
|||
|
|
StorageQCloud StorageType = "qcloud"
|
|||
|
|
StorageAliyun StorageType = "aliyun"
|
|||
|
|
StorageQiniu StorageType = "qiniu"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// OSSConfig OSS配置
|
|||
|
|
type OSSConfig struct {
|
|||
|
|
StorageType string
|
|||
|
|
AccessKey string
|
|||
|
|
SecretKey string
|
|||
|
|
Bucket string
|
|||
|
|
Region string
|
|||
|
|
Domain string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// OSSUploader OSS上传接口
|
|||
|
|
type OSSUploader interface {
|
|||
|
|
Upload(file multipart.File, filename string, size int64) (string, string, error) // 返回 filePath, fileURL, error
|
|||
|
|
Delete(filePath string) error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetOSSUploader 根据存储类型获取上传器
|
|||
|
|
func GetOSSUploader(config *OSSConfig) (OSSUploader, error) {
|
|||
|
|
switch StorageType(config.StorageType) {
|
|||
|
|
case StorageLocal:
|
|||
|
|
return &LocalUploader{
|
|||
|
|
BasePath: "./uploads",
|
|||
|
|
BaseURL: "/uploads",
|
|||
|
|
}, nil
|
|||
|
|
case StorageQCloud:
|
|||
|
|
// TODO: 实现腾讯云COS上传
|
|||
|
|
return nil, fmt.Errorf("qcloud storage not implemented yet")
|
|||
|
|
case StorageAliyun:
|
|||
|
|
// TODO: 实现阿里云OSS上传
|
|||
|
|
return nil, fmt.Errorf("aliyun storage not implemented yet")
|
|||
|
|
case StorageQiniu:
|
|||
|
|
// TODO: 实现七牛云上传
|
|||
|
|
return nil, fmt.Errorf("qiniu storage not implemented yet")
|
|||
|
|
default:
|
|||
|
|
return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LocalUploader 本地存储上传器
|
|||
|
|
type LocalUploader struct {
|
|||
|
|
BasePath string
|
|||
|
|
BaseURL string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Upload 上传文件到本地
|
|||
|
|
func (l *LocalUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
|
|||
|
|
// 生成唯一文件名
|
|||
|
|
ext := filepath.Ext(filename)
|
|||
|
|
timestamp := time.Now().Unix()
|
|||
|
|
randomStr := fmt.Sprintf("%d", timestamp)
|
|||
|
|
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
|
|||
|
|
|
|||
|
|
// 按日期创建目录
|
|||
|
|
dateDir := time.Now().Format("2006/01/02")
|
|||
|
|
uploadDir := filepath.Join(l.BasePath, dateDir)
|
|||
|
|
|
|||
|
|
// 创建目录
|
|||
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|||
|
|
return "", "", fmt.Errorf("failed to create upload directory: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 完整文件路径
|
|||
|
|
filePath := filepath.Join(uploadDir, newFilename)
|
|||
|
|
|
|||
|
|
// 创建目标文件
|
|||
|
|
dst, err := os.Create(filePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", "", fmt.Errorf("failed to create file: %v", err)
|
|||
|
|
}
|
|||
|
|
defer dst.Close()
|
|||
|
|
|
|||
|
|
// 复制文件内容
|
|||
|
|
if _, err := io.Copy(dst, file); err != nil {
|
|||
|
|
return "", "", fmt.Errorf("failed to copy file: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成访问URL
|
|||
|
|
fileURL := fmt.Sprintf("%s/%s/%s", l.BaseURL, dateDir, newFilename)
|
|||
|
|
|
|||
|
|
return filePath, fileURL, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Delete 删除本地文件
|
|||
|
|
func (l *LocalUploader) Delete(filePath string) error {
|
|||
|
|
// 确保文件路径在BasePath内(安全措施)
|
|||
|
|
absBasePath, err := filepath.Abs(l.BasePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
absFilePath, err := filepath.Abs(filePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !strings.HasPrefix(absFilePath, absBasePath) {
|
|||
|
|
return fmt.Errorf("invalid file path: outside base directory")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return os.Remove(filePath)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetFileType 根据MIME类型判断文件类型
|
|||
|
|
func GetFileType(mimeType string) string {
|
|||
|
|
if strings.HasPrefix(mimeType, "image/") {
|
|||
|
|
return "image"
|
|||
|
|
} else if strings.HasPrefix(mimeType, "video/") {
|
|||
|
|
return "video"
|
|||
|
|
} else if strings.HasPrefix(mimeType, "application/pdf") ||
|
|||
|
|
strings.HasPrefix(mimeType, "application/msword") ||
|
|||
|
|
strings.HasPrefix(mimeType, "application/vnd.openxmlformats") ||
|
|||
|
|
strings.HasPrefix(mimeType, "text/") {
|
|||
|
|
return "document"
|
|||
|
|
}
|
|||
|
|
return "other"
|
|||
|
|
}
|