253 lines
6.6 KiB
Go
253 lines
6.6 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
|
|
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
|
"github.com/qiniu/go-sdk/v7/storage"
|
|
)
|
|
|
|
// StorageService 存储服务接口
|
|
type StorageService interface {
|
|
Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error)
|
|
GetPublicURL(key string) string
|
|
Delete(key string) error
|
|
}
|
|
|
|
// UploadResult 上传结果
|
|
type UploadResult struct {
|
|
URL string // 完整访问URL
|
|
Key string // 存储key/路径
|
|
Size int64 // 文件大小
|
|
MD5 string // 文件MD5
|
|
MimeType string // 文件类型
|
|
}
|
|
|
|
// LocalStorage 本地存储实现
|
|
type LocalStorage struct {
|
|
BaseDir string // 基础目录,默认 "uploads"
|
|
BaseURL string // 基础URL,默认 "/"
|
|
}
|
|
|
|
// NewLocalStorage 创建本地存储服务
|
|
func NewLocalStorage() *LocalStorage {
|
|
return &LocalStorage{
|
|
BaseDir: "uploads",
|
|
BaseURL: "/",
|
|
}
|
|
}
|
|
|
|
// Upload 上传文件到本地
|
|
func (s *LocalStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) {
|
|
// 生成存储路径
|
|
ext := filepath.Ext(header.Filename)
|
|
datePath := time.Now().Format("2006/01/02")
|
|
fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
|
|
savePath := filepath.Join(datePath, fileName)
|
|
|
|
// 创建目录
|
|
destDir := filepath.Join(s.BaseDir, filepath.FromSlash(datePath))
|
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("创建目录失败: %w", err)
|
|
}
|
|
|
|
// 保存文件
|
|
destPath := filepath.Join(s.BaseDir, filepath.FromSlash(savePath))
|
|
dst, err := os.Create(destPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建文件失败: %w", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
// 计算MD5并复制文件
|
|
hash := md5.New()
|
|
size, err := io.Copy(io.MultiWriter(dst, hash), file)
|
|
if err != nil {
|
|
_ = os.Remove(destPath)
|
|
return nil, fmt.Errorf("保存文件失败: %w", err)
|
|
}
|
|
|
|
md5Sum := hex.EncodeToString(hash.Sum(nil))
|
|
webURL := s.BaseURL + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/")
|
|
|
|
return &UploadResult{
|
|
URL: webURL,
|
|
Key: savePath,
|
|
Size: size,
|
|
MD5: md5Sum,
|
|
MimeType: header.Header.Get("Content-Type"),
|
|
}, nil
|
|
}
|
|
|
|
// GetPublicURL 获取公开访问URL
|
|
func (s *LocalStorage) GetPublicURL(key string) string {
|
|
return s.BaseURL + filepath.ToSlash(filepath.Join(s.BaseDir, key))
|
|
}
|
|
|
|
// Delete 删除本地文件
|
|
func (s *LocalStorage) Delete(key string) error {
|
|
filePath := filepath.Join(s.BaseDir, filepath.FromSlash(key))
|
|
return os.Remove(filePath)
|
|
}
|
|
|
|
// QiniuStorage 七牛云存储实现
|
|
type QiniuStorage struct {
|
|
AccessKey string
|
|
SecretKey string
|
|
Bucket string
|
|
Domain string
|
|
Region string
|
|
}
|
|
|
|
// NewQiniuStorage 创建七牛云存储服务
|
|
func NewQiniuStorage(cfg *models.StorageConfig) *QiniuStorage {
|
|
return &QiniuStorage{
|
|
AccessKey: cfg.QiniuAccessKey,
|
|
SecretKey: cfg.QiniuSecretKey,
|
|
Bucket: cfg.QiniuBucket,
|
|
Domain: cfg.QiniuDomain,
|
|
Region: cfg.QiniuRegion,
|
|
}
|
|
}
|
|
|
|
// getZone 根据区域代码获取存储区域
|
|
func (s *QiniuStorage) getZone() *storage.Region {
|
|
switch s.Region {
|
|
case "z0":
|
|
return &storage.ZoneHuadong
|
|
case "z1":
|
|
return &storage.ZoneHuabei
|
|
case "z2":
|
|
return &storage.ZoneHuanan
|
|
case "na0":
|
|
return &storage.ZoneBeimei
|
|
case "as0":
|
|
return &storage.ZoneXinjiapo
|
|
case "cn-east-2":
|
|
return &storage.ZoneHuadongZheJiang2
|
|
default:
|
|
return &storage.ZoneHuadong // 默认华东
|
|
}
|
|
}
|
|
|
|
// Upload 上传文件到七牛云
|
|
func (s *QiniuStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) {
|
|
// 生成存储key
|
|
ext := filepath.Ext(header.Filename)
|
|
datePath := time.Now().Format("2006/01/02")
|
|
fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
|
|
key := filepath.ToSlash(filepath.Join(datePath, fileName))
|
|
|
|
// 创建上传凭证
|
|
mac := qbox.NewMac(s.AccessKey, s.SecretKey)
|
|
putPolicy := storage.PutPolicy{
|
|
Scope: s.Bucket,
|
|
}
|
|
upToken := putPolicy.UploadToken(mac)
|
|
|
|
// 配置上传参数
|
|
cfg := storage.Config{
|
|
Region: s.getZone(),
|
|
UseHTTPS: true,
|
|
UseCdnDomains: false,
|
|
}
|
|
|
|
// 创建表单上传器
|
|
formUploader := storage.NewFormUploader(&cfg)
|
|
ret := storage.PutRet{}
|
|
putExtra := storage.PutExtra{}
|
|
|
|
// 计算文件大小和MD5
|
|
tmpFile, err := os.CreateTemp("", "qiniu_upload_*")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建临时文件失败: %w", err)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
defer tmpFile.Close()
|
|
|
|
hash := md5.New()
|
|
size, err := io.Copy(io.MultiWriter(tmpFile, hash), file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取文件失败: %w", err)
|
|
}
|
|
md5Sum := hex.EncodeToString(hash.Sum(nil))
|
|
|
|
// 重置文件指针
|
|
if _, err := tmpFile.Seek(0, 0); err != nil {
|
|
return nil, fmt.Errorf("重置文件指针失败: %w", err)
|
|
}
|
|
|
|
// 执行上传
|
|
err = formUploader.Put(context.Background(), &ret, upToken, key, tmpFile, size, &putExtra)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("上传到七牛云失败: %w", err)
|
|
}
|
|
|
|
// 构建完整URL
|
|
domain := strings.TrimRight(s.Domain, "/")
|
|
url := fmt.Sprintf("%s/%s", domain, ret.Key)
|
|
|
|
return &UploadResult{
|
|
URL: url,
|
|
Key: ret.Key,
|
|
Size: size,
|
|
MD5: md5Sum,
|
|
MimeType: header.Header.Get("Content-Type"),
|
|
}, nil
|
|
}
|
|
|
|
// GetPublicURL 获取七牛云公开访问URL
|
|
func (s *QiniuStorage) GetPublicURL(key string) string {
|
|
domain := strings.TrimRight(s.Domain, "/")
|
|
return fmt.Sprintf("%s/%s", domain, key)
|
|
}
|
|
|
|
// Delete 删除七牛云文件
|
|
func (s *QiniuStorage) Delete(key string) error {
|
|
mac := qbox.NewMac(s.AccessKey, s.SecretKey)
|
|
cfg := storage.Config{
|
|
Region: s.getZone(),
|
|
UseHTTPS: true,
|
|
}
|
|
|
|
bucketManager := storage.NewBucketManager(mac, &cfg)
|
|
err := bucketManager.Delete(s.Bucket, key)
|
|
if err != nil {
|
|
return fmt.Errorf("删除七牛云文件失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetStorageService 根据配置获取存储服务
|
|
func GetStorageService() (StorageService, error) {
|
|
cfg, err := models.GetStorageConfig()
|
|
if err != nil {
|
|
// 默认使用本地存储
|
|
return NewLocalStorage(), nil
|
|
}
|
|
|
|
switch cfg.StorageType {
|
|
case "qiniu":
|
|
if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" ||
|
|
cfg.QiniuBucket == "" || cfg.QiniuDomain == "" {
|
|
return nil, fmt.Errorf("七牛云配置不完整")
|
|
}
|
|
return NewQiniuStorage(cfg), nil
|
|
case "local":
|
|
return NewLocalStorage(), nil
|
|
default:
|
|
return NewLocalStorage(), nil
|
|
}
|
|
}
|