数据结构优化
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
||||
"github.com/qiniu/go-sdk/v7/storage"
|
||||
cos "github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
|
||||
// StorageType 存储类型
|
||||
@@ -23,11 +32,30 @@ const (
|
||||
// OSSConfig OSS配置
|
||||
type OSSConfig struct {
|
||||
StorageType string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
Region string
|
||||
Domain string
|
||||
// 通用字段(向后兼容)
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Bucket string
|
||||
Region string
|
||||
Domain string
|
||||
// 阿里云OSS专用字段
|
||||
OSSAccessKeyID string
|
||||
OSSAccessKeySecret string
|
||||
OSSEndpoint string
|
||||
OSSBucket string
|
||||
OSSDomain string
|
||||
// 腾讯云COS专用字段
|
||||
QCloudSecretID string
|
||||
QCloudSecretKey string
|
||||
QCloudRegion string
|
||||
QCloudBucket string
|
||||
QCloudDomain string
|
||||
// 七牛云专用字段
|
||||
QiniuAccessKey string
|
||||
QiniuSecretKey string
|
||||
QiniuBucket string
|
||||
QiniuRegion string
|
||||
QiniuDomain string
|
||||
}
|
||||
|
||||
// OSSUploader OSS上传接口
|
||||
@@ -45,14 +73,173 @@ func GetOSSUploader(config *OSSConfig) (OSSUploader, error) {
|
||||
BaseURL: "/uploads",
|
||||
}, nil
|
||||
case StorageQCloud:
|
||||
// TODO: 实现腾讯云COS上传
|
||||
return nil, fmt.Errorf("qcloud storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
secretID := config.QCloudSecretID
|
||||
secretKey := config.QCloudSecretKey
|
||||
region := config.QCloudRegion
|
||||
bucket := config.QCloudBucket
|
||||
domain := config.QCloudDomain
|
||||
|
||||
if secretID == "" {
|
||||
secretID = config.AccessKey
|
||||
}
|
||||
if secretKey == "" {
|
||||
secretKey = config.SecretKey
|
||||
}
|
||||
if region == "" {
|
||||
region = config.Region
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if secretID == "" || secretKey == "" || bucket == "" || region == "" {
|
||||
return nil, fmt.Errorf("qcloud config incomplete: secretID, secretKey, bucket, region are required")
|
||||
}
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid qcloud config: %v", err)
|
||||
}
|
||||
b := &cos.BaseURL{BucketURL: u}
|
||||
client := cos.NewClient(b, &http.Client{
|
||||
Transport: &cos.AuthorizationTransport{
|
||||
SecretID: secretID,
|
||||
SecretKey: secretKey,
|
||||
},
|
||||
})
|
||||
return &QCloudUploader{
|
||||
Client: client,
|
||||
Bucket: bucket,
|
||||
Domain: domain,
|
||||
}, nil
|
||||
case StorageAliyun:
|
||||
// TODO: 实现阿里云OSS上传
|
||||
return nil, fmt.Errorf("aliyun storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
accessKeyID := config.OSSAccessKeyID
|
||||
accessKeySecret := config.OSSAccessKeySecret
|
||||
endpoint := config.OSSEndpoint
|
||||
bucket := config.OSSBucket
|
||||
domain := config.OSSDomain
|
||||
|
||||
if accessKeyID == "" {
|
||||
accessKeyID = config.AccessKey
|
||||
}
|
||||
if accessKeySecret == "" {
|
||||
accessKeySecret = config.SecretKey
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if accessKeyID == "" || accessKeySecret == "" || bucket == "" {
|
||||
return nil, fmt.Errorf("aliyun config incomplete: accessKeyID, accessKeySecret, bucket are required")
|
||||
}
|
||||
|
||||
// 如果endpoint为空,从region构建(向后兼容)
|
||||
if endpoint == "" {
|
||||
region := config.Region
|
||||
if region == "" {
|
||||
return nil, fmt.Errorf("aliyun config incomplete: endpoint or region is required")
|
||||
}
|
||||
endpoint = fmt.Sprintf("https://oss-%s.aliyuncs.com", region)
|
||||
}
|
||||
|
||||
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create aliyun oss client: %v", err)
|
||||
}
|
||||
ossBucket, err := client.Bucket(bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get aliyun bucket: %v", err)
|
||||
}
|
||||
return &AliyunUploader{
|
||||
Bucket: ossBucket,
|
||||
Domain: domain,
|
||||
}, nil
|
||||
case StorageQiniu:
|
||||
// TODO: 实现七牛云上传
|
||||
return nil, fmt.Errorf("qiniu storage not implemented yet")
|
||||
// 优先使用专用字段,如果为空则使用通用字段(向后兼容)
|
||||
accessKey := config.QiniuAccessKey
|
||||
secretKey := config.QiniuSecretKey
|
||||
bucket := config.QiniuBucket
|
||||
region := config.QiniuRegion
|
||||
domain := config.QiniuDomain
|
||||
|
||||
if accessKey == "" {
|
||||
accessKey = config.AccessKey
|
||||
}
|
||||
if secretKey == "" {
|
||||
secretKey = config.SecretKey
|
||||
}
|
||||
if bucket == "" {
|
||||
bucket = config.Bucket
|
||||
}
|
||||
if domain == "" {
|
||||
domain = config.Domain
|
||||
}
|
||||
|
||||
if accessKey == "" || secretKey == "" || bucket == "" {
|
||||
return nil, fmt.Errorf("qiniu config incomplete: accessKey, secretKey, bucket are required")
|
||||
}
|
||||
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
|
||||
// 根据region选择Zone(如果region为空,使用通用字段的region,否则默认华东)
|
||||
var zone *storage.Zone
|
||||
if region != "" {
|
||||
switch region {
|
||||
case "z0", "华东":
|
||||
zone = &storage.ZoneHuadong
|
||||
case "z1", "华北":
|
||||
zone = &storage.ZoneHuabei
|
||||
case "z2", "华南":
|
||||
zone = &storage.ZoneHuanan
|
||||
case "na0", "北美":
|
||||
zone = &storage.ZoneBeimei
|
||||
case "as0", "东南亚":
|
||||
zone = &storage.ZoneXinjiapo
|
||||
default:
|
||||
zone = &storage.ZoneHuadong // 默认华东
|
||||
}
|
||||
} else if config.Region != "" {
|
||||
// 使用通用字段的region
|
||||
switch config.Region {
|
||||
case "z0", "华东":
|
||||
zone = &storage.ZoneHuadong
|
||||
case "z1", "华北":
|
||||
zone = &storage.ZoneHuabei
|
||||
case "z2", "华南":
|
||||
zone = &storage.ZoneHuanan
|
||||
case "na0", "北美":
|
||||
zone = &storage.ZoneBeimei
|
||||
case "as0", "东南亚":
|
||||
zone = &storage.ZoneXinjiapo
|
||||
default:
|
||||
zone = &storage.ZoneHuadong
|
||||
}
|
||||
} else {
|
||||
zone = &storage.ZoneHuadong // 默认华东
|
||||
}
|
||||
|
||||
cfg := storage.Config{
|
||||
Zone: zone,
|
||||
UseHTTPS: true,
|
||||
UseCdnDomains: false,
|
||||
}
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
return &QiniuUploader{
|
||||
FormUploader: formUploader,
|
||||
BucketManager: bucketManager,
|
||||
Bucket: bucket,
|
||||
Domain: domain,
|
||||
Mac: mac,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType)
|
||||
}
|
||||
@@ -122,6 +309,160 @@ func (l *LocalUploader) Delete(filePath string) error {
|
||||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// AliyunUploader 阿里云OSS上传器
|
||||
type AliyunUploader struct {
|
||||
Bucket *oss.Bucket
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Upload 上传文件到阿里云OSS
|
||||
func (a *AliyunUploader) 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")
|
||||
objectKey := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 上传文件
|
||||
err := a.Bucket.PutObject(objectKey, file)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to aliyun oss: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if a.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(a.Domain, "/"), objectKey)
|
||||
} else {
|
||||
// 从endpoint中提取region,格式为 https://oss-region.aliyuncs.com
|
||||
endpoint := a.Bucket.Client.Config.Endpoint
|
||||
fileURL = fmt.Sprintf("%s/%s", endpoint, objectKey)
|
||||
}
|
||||
|
||||
return objectKey, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除阿里云OSS文件
|
||||
func (a *AliyunUploader) Delete(objectKey string) error {
|
||||
err := a.Bucket.DeleteObject(objectKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from aliyun oss: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QCloudUploader 腾讯云COS上传器
|
||||
type QCloudUploader struct {
|
||||
Client *cos.Client
|
||||
Bucket string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Upload 上传文件到腾讯云COS
|
||||
func (q *QCloudUploader) 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")
|
||||
objectKey := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 上传文件
|
||||
_, err := q.Client.Object.Put(context.Background(), objectKey, file, nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to qcloud cos: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if q.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(q.Domain, "/"), objectKey)
|
||||
} else {
|
||||
// 使用BucketURL生成URL
|
||||
fileURL = fmt.Sprintf("%s/%s", q.Client.BaseURL.BucketURL.String(), objectKey)
|
||||
}
|
||||
|
||||
return objectKey, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除腾讯云COS文件
|
||||
func (q *QCloudUploader) Delete(objectKey string) error {
|
||||
_, err := q.Client.Object.Delete(context.Background(), objectKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from qcloud cos: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QiniuUploader 七牛云上传器
|
||||
type QiniuUploader struct {
|
||||
FormUploader *storage.FormUploader
|
||||
BucketManager *storage.BucketManager
|
||||
Bucket string
|
||||
Domain string
|
||||
Mac *qbox.Mac
|
||||
}
|
||||
|
||||
// Upload 上传文件到七牛云
|
||||
func (q *QiniuUploader) 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")
|
||||
key := fmt.Sprintf("%s/%s", dateDir, newFilename)
|
||||
|
||||
// 生成上传凭证
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: q.Bucket,
|
||||
}
|
||||
upToken := putPolicy.UploadToken(q.Mac)
|
||||
|
||||
// 读取文件内容
|
||||
fileData := make([]byte, size)
|
||||
_, err := file.Read(fileData)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
ret := storage.PutRet{}
|
||||
err = q.FormUploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(fileData), size, nil)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to qiniu: %v", err)
|
||||
}
|
||||
|
||||
// 生成访问URL
|
||||
var fileURL string
|
||||
if q.Domain != "" {
|
||||
fileURL = fmt.Sprintf("%s/%s", strings.TrimSuffix(q.Domain, "/"), key)
|
||||
} else {
|
||||
// 七牛云需要配置域名,如果没有配置则返回key
|
||||
fileURL = key
|
||||
}
|
||||
|
||||
return key, fileURL, nil
|
||||
}
|
||||
|
||||
// Delete 删除七牛云文件
|
||||
func (q *QiniuUploader) Delete(key string) error {
|
||||
err := q.BucketManager.Delete(q.Bucket, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete from qiniu: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFileType 根据MIME类型判断文件类型
|
||||
func GetFileType(mimeType string) string {
|
||||
if strings.HasPrefix(mimeType, "image/") {
|
||||
|
||||
Reference in New Issue
Block a user