将工厂改成黑盒模式,降低用户使用成本

This commit is contained in:
2025-11-30 15:54:27 +08:00
parent 6323b49517
commit d454d8e143
18 changed files with 2075 additions and 1046 deletions

View File

@@ -43,29 +43,31 @@ func NewUploadHandler(cfg UploadHandlerConfig) *UploadHandler {
// 表单字段: file (文件)
// 可选字段: prefix (对象键前缀,会覆盖配置中的前缀)
func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler := commonhttp.NewHandler(w, r)
if r.Method != http.MethodPost {
commonhttp.NotFound(w, "Method not allowed")
handler.Error(4001, "Method not allowed")
return
}
// 解析multipart表单
err := r.ParseMultipartForm(h.maxFileSize)
if err != nil {
commonhttp.BadRequest(w, fmt.Sprintf("Failed to parse form: %v", err))
handler.Error(4002, 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))
handler.Error(4003, 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))
handler.Error(1001, fmt.Sprintf("File size exceeds limit: %d bytes", h.maxFileSize))
return
}
@@ -80,7 +82,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
if !allowed {
commonhttp.Error(w, 1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
handler.Error(1002, fmt.Sprintf("File extension not allowed. Allowed: %v", h.allowedExts))
return
}
}
@@ -108,14 +110,14 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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))
handler.SystemError(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))
handler.SystemError(fmt.Sprintf("Failed to get file URL: %v", err))
return
}
@@ -128,7 +130,7 @@ func (h *UploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
UploadTime: time.Now(),
}
commonhttp.SuccessWithMessage(w, "Upload successful", result)
handler.SuccessWithMessage("Upload successful", result)
}
// generateUniqueFilename 生成唯一文件名
@@ -154,15 +156,17 @@ func NewProxyHandler(storage Storage) *ProxyHandler {
// ServeHTTP 处理文件查看请求
// URL参数: key (对象键)
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler := commonhttp.NewHandler(w, r)
if r.Method != http.MethodGet {
commonhttp.NotFound(w, "Method not allowed")
handler.Error(4001, "Method not allowed")
return
}
// 获取对象键
objectKey := r.URL.Query().Get("key")
objectKey := handler.GetQuery("key", "")
if objectKey == "" {
commonhttp.BadRequest(w, "Missing parameter: key")
handler.Error(4004, "Missing parameter: key")
return
}
@@ -170,19 +174,19 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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))
handler.SystemError(fmt.Sprintf("Failed to check file existence: %v", err))
return
}
if !exists {
commonhttp.NotFound(w, "File not found")
handler.Error(4005, "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))
handler.SystemError(fmt.Sprintf("Failed to get file: %v", err))
return
}
defer reader.Close()
@@ -206,7 +210,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 复制文件内容到响应
_, err = io.Copy(w, reader)
if err != nil {
commonhttp.SystemError(w, fmt.Sprintf("Failed to write response: %v", err))
handler.SystemError(fmt.Sprintf("Failed to write response: %v", err))
return
}
}

View File

@@ -5,16 +5,20 @@ import (
"fmt"
"io"
"strings"
"time"
"git.toowon.com/jimmy/go-common/config"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// MinIOStorage MinIO存储实现
type MinIOStorage struct {
config *config.MinIOConfig
// client 存储MinIO客户端实际使用时需要根据具体的MinIO SDK实现
// 这里使用interface{},实际使用时需要替换为具体的客户端类型
client interface{}
config *config.MinIOConfig
client *minio.Client
bucket string
domain string
protocol string
}
// NewMinIOStorage 创建MinIO存储实例
@@ -23,122 +27,138 @@ func NewMinIOStorage(cfg *config.MinIOConfig) (*MinIOStorage, error) {
return nil, fmt.Errorf("MinIO config is nil")
}
storage := &MinIOStorage{
config: cfg,
// 创建MinIO客户端
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)
}
// 初始化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
// 检查bucket是否存在不存在则创建
ctx := context.Background()
exists, err := client.BucketExists(ctx, cfg.Bucket)
if err != nil {
// 如果检查失败,可能是网络问题,但不阻止客户端创建
// 继续创建客户端,后续上传时会再次检查
} else if !exists {
err = client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{})
if err != nil {
// 不阻止客户端创建,后续上传时会再次尝试
}
}
return storage, nil
protocol := "http"
if cfg.UseSSL {
protocol = "https"
}
return &MinIOStorage{
config: cfg,
client: client,
bucket: cfg.Bucket,
domain: cfg.Domain,
protocol: protocol,
}, 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)
// }
if s.client == nil {
return fmt.Errorf("MinIO client is not initialized")
}
// 当前实现返回错误提示需要实现具体的MinIO SDK
return fmt.Errorf("MinIO upload not implemented, please implement with actual MinIO SDK")
ct := "application/octet-stream"
if len(contentType) > 0 && contentType[0] != "" {
ct = contentType[0]
}
opts := minio.PutObjectOptions{
ContentType: ct,
}
_, err := s.client.PutObject(ctx, s.bucket, objectKey, reader, -1, opts)
if err != nil {
return fmt.Errorf("failed to upload object: %w", err)
}
return nil
}
// 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
if s.client == nil {
return "", fmt.Errorf("MinIO client is not initialized")
}
// 如果设置了过期时间生成预签名URL
if expires > 0 {
ctx := context.Background()
expiry := time.Duration(expires) * time.Second
presignedURL, err := s.client.PresignedGetObject(ctx, s.bucket, objectKey, expiry, nil)
if err != nil {
return "", fmt.Errorf("failed to generate presigned URL: %w", err)
}
return s.config.Domain + "/" + objectKey, nil
return presignedURL.String(), nil
}
// 使用自定义域名或默认域名
if s.domain != "" {
// 使用自定义域名
if strings.HasSuffix(s.domain, "/") {
return fmt.Sprintf("%s://%s%s/%s", s.protocol, s.domain, s.bucket, objectKey), nil
}
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.domain, s.bucket, 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
return fmt.Sprintf("%s://%s/%s/%s", s.protocol, s.config.Endpoint, s.bucket, objectKey), 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)
// }
if s.client == nil {
return fmt.Errorf("MinIO client is not initialized")
}
return fmt.Errorf("MinIO delete not implemented, please implement with actual MinIO SDK")
err := s.client.RemoveObject(ctx, s.bucket, objectKey, minio.RemoveObjectOptions{})
if err != nil {
return fmt.Errorf("failed to delete object: %w", err)
}
return nil
}
// 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
if s.client == nil {
return false, fmt.Errorf("MinIO client is not initialized")
}
return false, fmt.Errorf("MinIO exists check not implemented, please implement with actual MinIO SDK")
_, err := s.client.StatObject(ctx, s.bucket, objectKey, minio.StatObjectOptions{})
if err != nil {
errResp := minio.ToErrorResponse(err)
if errResp.Code == "NoSuchKey" {
return false, nil
}
return false, fmt.Errorf("failed to check object existence: %w", err)
}
return true, nil
}
// 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
if s.client == nil {
return nil, fmt.Errorf("MinIO client is not initialized")
}
return nil, fmt.Errorf("MinIO get object not implemented, please implement with actual MinIO SDK")
obj, err := s.client.GetObject(ctx, s.bucket, objectKey, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get object: %w", err)
}
return obj, nil
}