初始版本,工具基础类
This commit is contained in:
212
storage/handler.go
Normal file
212
storage/handler.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
commonhttp "github.com/go-common/http"
|
||||
)
|
||||
|
||||
// UploadHandler 文件上传处理器
|
||||
type UploadHandler struct {
|
||||
storage Storage
|
||||
maxFileSize int64 // 最大文件大小(字节),0表示不限制
|
||||
allowedExts []string // 允许的文件扩展名,空表示不限制
|
||||
objectPrefix string // 对象键前缀
|
||||
}
|
||||
|
||||
// UploadHandlerConfig 上传处理器配置
|
||||
type UploadHandlerConfig struct {
|
||||
Storage Storage
|
||||
MaxFileSize int64 // 最大文件大小(字节),0表示不限制
|
||||
AllowedExts []string // 允许的文件扩展名,空表示不限制
|
||||
ObjectPrefix string // 对象键前缀(如 "images/", "files/")
|
||||
}
|
||||
|
||||
// NewUploadHandler 创建文件上传处理器
|
||||
func NewUploadHandler(cfg UploadHandlerConfig) *UploadHandler {
|
||||
return &UploadHandler{
|
||||
storage: cfg.Storage,
|
||||
maxFileSize: cfg.MaxFileSize,
|
||||
allowedExts: cfg.AllowedExts,
|
||||
objectPrefix: cfg.ObjectPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP 处理文件上传请求
|
||||
// 请求方式: POST
|
||||
// 表单字段: file (文件)
|
||||
// 可选字段: prefix (对象键前缀,会覆盖配置中的前缀)
|
||||
func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
commonhttp.NotFound(w, "Method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析multipart表单
|
||||
err := r.ParseMultipartForm(h.maxFileSize)
|
||||
if err != nil {
|
||||
commonhttp.BadRequest(w, fmt.Sprintf("Failed to parse form: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
commonhttp.BadRequest(w, fmt.Sprintf("Failed to get file: %v", err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 检查文件大小
|
||||
if h.maxFileSize > 0 && header.Size > h.maxFileSize {
|
||||
commonhttp.Error(w, 1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
if len(h.allowedExts) > 0 {
|
||||
ext := strings.ToLower(filepath.Ext(header.Filename))
|
||||
allowed := false
|
||||
for _, allowedExt := range h.allowedExts {
|
||||
if strings.ToLower(allowedExt) == ext {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
commonhttp.Error(w, 1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 生成对象键
|
||||
prefix := h.objectPrefix
|
||||
if r.FormValue("prefix") != "" {
|
||||
prefix = r.FormValue("prefix")
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
filename := generateUniqueFilename(header.Filename)
|
||||
objectKey := GenerateObjectKey(prefix, filename)
|
||||
|
||||
// 获取文件类型
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
contentType = mime.TypeByExtension(filepath.Ext(header.Filename))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
ctx := r.Context()
|
||||
err = h.storage.Upload(ctx, objectKey, file, contentType)
|
||||
if err != nil {
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to upload file: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件URL
|
||||
fileURL, err := h.storage.GetURL(objectKey, 0)
|
||||
if err != nil {
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to get file URL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
result := UploadResult{
|
||||
ObjectKey: objectKey,
|
||||
URL: fileURL,
|
||||
Size: header.Size,
|
||||
ContentType: contentType,
|
||||
UploadTime: time.Now(),
|
||||
}
|
||||
|
||||
commonhttp.SuccessWithMessage(w, "Upload successful", result)
|
||||
}
|
||||
|
||||
// generateUniqueFilename 生成唯一文件名
|
||||
func generateUniqueFilename(originalFilename string) string {
|
||||
ext := filepath.Ext(originalFilename)
|
||||
name := strings.TrimSuffix(originalFilename, ext)
|
||||
timestamp := time.Now().UnixNano()
|
||||
return fmt.Sprintf("%s_%d%s", name, timestamp, ext)
|
||||
}
|
||||
|
||||
// ProxyHandler 文件代理查看处理器
|
||||
type ProxyHandler struct {
|
||||
storage Storage
|
||||
}
|
||||
|
||||
// NewProxyHandler 创建文件代理查看处理器
|
||||
func NewProxyHandler(storage Storage) *ProxyHandler {
|
||||
return &ProxyHandler{
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP 处理文件查看请求
|
||||
// URL参数: key (对象键)
|
||||
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
commonhttp.NotFound(w, "Method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对象键
|
||||
objectKey := r.URL.Query().Get("key")
|
||||
if objectKey == "" {
|
||||
commonhttp.BadRequest(w, "Missing parameter: key")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
ctx := r.Context()
|
||||
exists, err := h.storage.Exists(ctx, objectKey)
|
||||
if err != nil {
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to check file existence: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
commonhttp.NotFound(w, "File not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取文件内容
|
||||
reader, err := h.storage.GetObject(ctx, objectKey)
|
||||
if err != nil {
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to get file: %v", err))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// 设置响应头
|
||||
ext := filepath.Ext(objectKey)
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// 如果是图片,设置适当的Content-Type
|
||||
if strings.HasPrefix(contentType, "image/") {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000") // 缓存1年
|
||||
} else {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filepath.Base(objectKey)))
|
||||
}
|
||||
|
||||
// 复制文件内容到响应
|
||||
_, err = io.Copy(w, reader)
|
||||
if err != nil {
|
||||
commonhttp.SystemError(w, fmt.Sprintf("Failed to write response: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
144
storage/minio.go
Normal file
144
storage/minio.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/go-common/config"
|
||||
)
|
||||
|
||||
// MinIOStorage MinIO存储实现
|
||||
type MinIOStorage struct {
|
||||
config *config.MinIOConfig
|
||||
// client 存储MinIO客户端(实际使用时需要根据具体的MinIO SDK实现)
|
||||
// 这里使用interface{},实际使用时需要替换为具体的客户端类型
|
||||
client interface{}
|
||||
}
|
||||
|
||||
// NewMinIOStorage 创建MinIO存储实例
|
||||
func NewMinIOStorage(cfg *config.MinIOConfig) (*MinIOStorage, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("MinIO config is nil")
|
||||
}
|
||||
|
||||
storage := &MinIOStorage{
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
// 初始化MinIO客户端
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 例如使用MinIO Go SDK:
|
||||
// client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||||
// Creds: credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
|
||||
// Secure: cfg.UseSSL,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to create MinIO client: %w", err)
|
||||
// }
|
||||
// storage.client = client
|
||||
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
// Upload 上传文件到MinIO
|
||||
func (s *MinIOStorage) Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error {
|
||||
// 实现MinIO上传逻辑
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 示例(使用MinIO Go SDK):
|
||||
// ct := "application/octet-stream"
|
||||
// if len(contentType) > 0 && contentType[0] != "" {
|
||||
// ct = contentType[0]
|
||||
// }
|
||||
//
|
||||
// _, err := s.client.PutObject(ctx, s.config.Bucket, objectKey, reader, -1, minio.PutObjectOptions{
|
||||
// ContentType: ct,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to upload object: %w", err)
|
||||
// }
|
||||
|
||||
// 当前实现返回错误,提示需要实现具体的MinIO SDK
|
||||
return fmt.Errorf("MinIO upload not implemented, please implement with actual MinIO SDK")
|
||||
}
|
||||
|
||||
// GetURL 获取MinIO文件访问URL
|
||||
func (s *MinIOStorage) GetURL(objectKey string, expires int64) (string, error) {
|
||||
if s.config.Domain != "" {
|
||||
// 使用自定义域名
|
||||
if strings.HasSuffix(s.config.Domain, "/") {
|
||||
return s.config.Domain + objectKey, nil
|
||||
}
|
||||
return s.config.Domain + "/" + objectKey, nil
|
||||
}
|
||||
|
||||
// 使用MinIO默认域名
|
||||
protocol := "http"
|
||||
if s.config.UseSSL {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
// 构建MinIO URL
|
||||
// 格式: http://endpoint/bucket/objectKey
|
||||
url := fmt.Sprintf("%s://%s/%s/%s", protocol, s.config.Endpoint, s.config.Bucket, objectKey)
|
||||
|
||||
// 如果设置了过期时间,需要生成预签名URL
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 示例(使用MinIO Go SDK):
|
||||
// if expires > 0 {
|
||||
// expiry := time.Duration(expires) * time.Second
|
||||
// presignedURL, err := s.client.PresignedGetObject(ctx, s.config.Bucket, objectKey, expiry, nil)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return presignedURL.String(), nil
|
||||
// }
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Delete 删除MinIO文件
|
||||
func (s *MinIOStorage) Delete(ctx context.Context, objectKey string) error {
|
||||
// 实现MinIO删除逻辑
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 示例(使用MinIO Go SDK):
|
||||
// err := s.client.RemoveObject(ctx, s.config.Bucket, objectKey, minio.RemoveObjectOptions{})
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to delete object: %w", err)
|
||||
// }
|
||||
|
||||
return fmt.Errorf("MinIO delete not implemented, please implement with actual MinIO SDK")
|
||||
}
|
||||
|
||||
// Exists 检查MinIO文件是否存在
|
||||
func (s *MinIOStorage) Exists(ctx context.Context, objectKey string) (bool, error) {
|
||||
// 实现MinIO存在性检查逻辑
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 示例(使用MinIO Go SDK):
|
||||
// _, err := s.client.StatObject(ctx, s.config.Bucket, objectKey, minio.StatObjectOptions{})
|
||||
// if err != nil {
|
||||
// if minio.ToErrorResponse(err).Code == "NoSuchKey" {
|
||||
// return false, nil
|
||||
// }
|
||||
// return false, fmt.Errorf("failed to check object existence: %w", err)
|
||||
// }
|
||||
// return true, nil
|
||||
|
||||
return false, fmt.Errorf("MinIO exists check not implemented, please implement with actual MinIO SDK")
|
||||
}
|
||||
|
||||
// GetObject 获取MinIO文件内容
|
||||
func (s *MinIOStorage) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) {
|
||||
// 实现MinIO获取对象逻辑
|
||||
// 注意:这里需要根据实际的MinIO SDK实现
|
||||
// 示例(使用MinIO Go SDK):
|
||||
// obj, err := s.client.GetObject(ctx, s.config.Bucket, objectKey, minio.GetObjectOptions{})
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to get object: %w", err)
|
||||
// }
|
||||
// return obj, nil
|
||||
|
||||
return nil, fmt.Errorf("MinIO get object not implemented, please implement with actual MinIO SDK")
|
||||
}
|
||||
|
||||
155
storage/oss.go
Normal file
155
storage/oss.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/go-common/config"
|
||||
)
|
||||
|
||||
// OSSStorage OSS存储实现
|
||||
type OSSStorage struct {
|
||||
config *config.OSSConfig
|
||||
// client 存储OSS客户端(实际使用时需要根据具体的OSS SDK实现)
|
||||
// 这里使用interface{},实际使用时需要替换为具体的客户端类型
|
||||
client interface{}
|
||||
}
|
||||
|
||||
// NewOSSStorage 创建OSS存储实例
|
||||
func NewOSSStorage(cfg *config.OSSConfig) (*OSSStorage, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("OSS config is nil")
|
||||
}
|
||||
|
||||
storage := &OSSStorage{
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
// 初始化OSS客户端
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 例如使用阿里云OSS SDK:
|
||||
// client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to create OSS client: %w", err)
|
||||
// }
|
||||
// storage.client = client
|
||||
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
// Upload 上传文件到OSS
|
||||
func (s *OSSStorage) Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error {
|
||||
// 实现OSS上传逻辑
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 示例(使用阿里云OSS SDK):
|
||||
// bucket, err := s.client.Bucket(s.config.Bucket)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to get bucket: %w", err)
|
||||
// }
|
||||
//
|
||||
// options := []oss.Option{}
|
||||
// if len(contentType) > 0 && contentType[0] != "" {
|
||||
// options = append(options, oss.ContentType(contentType[0]))
|
||||
// }
|
||||
//
|
||||
// err = bucket.PutObject(objectKey, reader, options...)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to upload object: %w", err)
|
||||
// }
|
||||
|
||||
// 当前实现返回错误,提示需要实现具体的OSS SDK
|
||||
return fmt.Errorf("OSS upload not implemented, please implement with actual OSS SDK")
|
||||
}
|
||||
|
||||
// GetURL 获取OSS文件访问URL
|
||||
func (s *OSSStorage) GetURL(objectKey string, expires int64) (string, error) {
|
||||
if s.config.Domain != "" {
|
||||
// 使用自定义域名
|
||||
if strings.HasSuffix(s.config.Domain, "/") {
|
||||
return s.config.Domain + objectKey, nil
|
||||
}
|
||||
return s.config.Domain + "/" + objectKey, nil
|
||||
}
|
||||
|
||||
// 使用OSS默认域名
|
||||
protocol := "http"
|
||||
if s.config.UseSSL {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
// 构建OSS URL
|
||||
// 格式: https://bucket.endpoint/objectKey
|
||||
url := fmt.Sprintf("%s://%s.%s/%s", protocol, s.config.Bucket, s.config.Endpoint, objectKey)
|
||||
|
||||
// 如果设置了过期时间,需要生成签名URL
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 示例(使用阿里云OSS SDK):
|
||||
// if expires > 0 {
|
||||
// bucket, err := s.client.Bucket(s.config.Bucket)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, expires)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return signedURL, nil
|
||||
// }
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Delete 删除OSS文件
|
||||
func (s *OSSStorage) Delete(ctx context.Context, objectKey string) error {
|
||||
// 实现OSS删除逻辑
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 示例(使用阿里云OSS SDK):
|
||||
// bucket, err := s.client.Bucket(s.config.Bucket)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to get bucket: %w", err)
|
||||
// }
|
||||
// err = bucket.DeleteObject(objectKey)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to delete object: %w", err)
|
||||
// }
|
||||
|
||||
return fmt.Errorf("OSS delete not implemented, please implement with actual OSS SDK")
|
||||
}
|
||||
|
||||
// Exists 检查OSS文件是否存在
|
||||
func (s *OSSStorage) Exists(ctx context.Context, objectKey string) (bool, error) {
|
||||
// 实现OSS存在性检查逻辑
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 示例(使用阿里云OSS SDK):
|
||||
// bucket, err := s.client.Bucket(s.config.Bucket)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("failed to get bucket: %w", err)
|
||||
// }
|
||||
// exists, err := bucket.IsObjectExist(objectKey)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("failed to check object existence: %w", err)
|
||||
// }
|
||||
// return exists, nil
|
||||
|
||||
return false, fmt.Errorf("OSS exists check not implemented, please implement with actual OSS SDK")
|
||||
}
|
||||
|
||||
// GetObject 获取OSS文件内容
|
||||
func (s *OSSStorage) GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error) {
|
||||
// 实现OSS获取对象逻辑
|
||||
// 注意:这里需要根据实际的OSS SDK实现
|
||||
// 示例(使用阿里云OSS SDK):
|
||||
// bucket, err := s.client.Bucket(s.config.Bucket)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to get bucket: %w", err)
|
||||
// }
|
||||
// body, err := bucket.GetObject(objectKey)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to get object: %w", err)
|
||||
// }
|
||||
// return body, nil
|
||||
|
||||
return nil, fmt.Errorf("OSS get object not implemented, please implement with actual OSS SDK")
|
||||
}
|
||||
105
storage/storage.go
Normal file
105
storage/storage.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/go-common/config"
|
||||
)
|
||||
|
||||
// Storage 存储接口
|
||||
type Storage interface {
|
||||
// Upload 上传文件
|
||||
// ctx: 上下文
|
||||
// objectKey: 对象键(文件路径)
|
||||
// reader: 文件内容
|
||||
// contentType: 文件类型(可选)
|
||||
Upload(ctx context.Context, objectKey string, reader io.Reader, contentType ...string) error
|
||||
|
||||
// GetURL 获取文件访问URL
|
||||
// objectKey: 对象键
|
||||
// expires: 过期时间(秒),0表示永久有效
|
||||
GetURL(objectKey string, expires int64) (string, error)
|
||||
|
||||
// Delete 删除文件
|
||||
Delete(ctx context.Context, objectKey string) error
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
Exists(ctx context.Context, objectKey string) (bool, error)
|
||||
|
||||
// GetObject 获取文件内容
|
||||
GetObject(ctx context.Context, objectKey string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// StorageType 存储类型
|
||||
type StorageType string
|
||||
|
||||
const (
|
||||
StorageTypeOSS StorageType = "oss"
|
||||
StorageTypeMinIO StorageType = "minio"
|
||||
)
|
||||
|
||||
// NewStorage 创建存储实例
|
||||
// storageType: 存储类型(oss或minio)
|
||||
// cfg: 配置对象
|
||||
func NewStorage(storageType StorageType, cfg *config.Config) (Storage, error) {
|
||||
switch storageType {
|
||||
case StorageTypeOSS:
|
||||
ossConfig := cfg.GetOSS()
|
||||
if ossConfig == nil {
|
||||
return nil, fmt.Errorf("OSS config is nil")
|
||||
}
|
||||
return NewOSSStorage(ossConfig)
|
||||
case StorageTypeMinIO:
|
||||
minioConfig := cfg.GetMinIO()
|
||||
if minioConfig == nil {
|
||||
return nil, fmt.Errorf("MinIO config is nil")
|
||||
}
|
||||
return NewMinIOStorage(minioConfig)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported storage type: %s", storageType)
|
||||
}
|
||||
}
|
||||
|
||||
// UploadResult 上传结果
|
||||
type UploadResult struct {
|
||||
// ObjectKey 对象键(文件路径)
|
||||
ObjectKey string `json:"objectKey"`
|
||||
|
||||
// URL 文件访问URL
|
||||
URL string `json:"url"`
|
||||
|
||||
// Size 文件大小(字节)
|
||||
Size int64 `json:"size"`
|
||||
|
||||
// ContentType 文件类型
|
||||
ContentType string `json:"contentType"`
|
||||
|
||||
// UploadTime 上传时间
|
||||
UploadTime time.Time `json:"uploadTime"`
|
||||
}
|
||||
|
||||
// GenerateObjectKey 生成对象键
|
||||
// prefix: 前缀(如 "images/", "files/")
|
||||
// filename: 文件名
|
||||
func GenerateObjectKey(prefix, filename string) string {
|
||||
if prefix == "" {
|
||||
return filename
|
||||
}
|
||||
if prefix[len(prefix)-1] != '/' {
|
||||
prefix += "/"
|
||||
}
|
||||
return prefix + filename
|
||||
}
|
||||
|
||||
// GenerateObjectKeyWithDate 生成带日期的对象键
|
||||
// prefix: 前缀
|
||||
// filename: 文件名
|
||||
// 格式: prefix/YYYY/MM/DD/filename
|
||||
func GenerateObjectKeyWithDate(prefix, filename string) string {
|
||||
now := time.Now()
|
||||
datePath := fmt.Sprintf("%d/%02d/%02d", now.Year(), now.Month(), now.Day())
|
||||
return GenerateObjectKey(prefix+"/"+datePath, filename)
|
||||
}
|
||||
Reference in New Issue
Block a user