package fileStorage import ( "context" "encoding/base64" "fmt" "strings" "time" "backend/internal/svc" "backend/internal/types" "github.com/google/uuid" "github.com/zeromicro/go-zero/core/logx" ) type UploadImgLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } // 上傳圖片檔案 func NewUploadImgLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImgLogic { return &UploadImgLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *UploadImgLogic) UploadImg(req *types.UploadImgReq) (resp *types.UploadResp, err error) { // 驗證 base64 格式 content := req.Content if strings.HasPrefix(content, "data:image") { // 移除 data URL 前綴 (例如: data:image/png;base64,) parts := strings.SplitN(content, ",", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid base64 image format") } content = parts[1] } // 解碼 base64 imgData, err := base64.StdEncoding.DecodeString(content) if err != nil { return nil, fmt.Errorf("failed to decode base64: %w", err) } // 驗證文件大小(10MB 限制) const maxImgSize = 10 << 20 // 10MB if len(imgData) > maxImgSize { return nil, fmt.Errorf("image size exceeds 10MB limit") } // 驗證圖片格式(檢查 magic bytes) mimeType, err := l.detectImageType(imgData) if err != nil { return nil, fmt.Errorf("invalid image format: %w", err) } // 生成唯一文件名 fileExt := l.getExtensionFromMimeType(mimeType) fileName := fmt.Sprintf("%s%s", uuid.New().String(), fileExt) objectPath := fmt.Sprintf("images/%d/%s", time.Now().Year(), fileName) // 上傳到 S3 fileStorageUC := l.svcCtx.FileStorageUC if err := fileStorageUC.UploadFromData(l.ctx, imgData, objectPath, mimeType); err != nil { return nil, fmt.Errorf("failed to upload image: %w", err) } // 獲取公開 URL fileUrl := fileStorageUC.GetPublicURL(l.ctx, objectPath) return &types.UploadResp{ FileUrl: fileUrl, FileSize: int64(len(imgData)), MimeType: mimeType, }, nil } // detectImageType 檢測圖片類型 func (l *UploadImgLogic) detectImageType(data []byte) (string, error) { if len(data) < 4 { return "", fmt.Errorf("file too small") } // 檢查常見圖片格式的 magic bytes if len(data) >= 2 && data[0] == 0xFF && data[1] == 0xD8 { return "image/jpeg", nil } if len(data) >= 8 && string(data[0:8]) == "\x89PNG\r\n\x1a\n" { return "image/png", nil } if len(data) >= 6 && (string(data[0:6]) == "GIF87a" || string(data[0:6]) == "GIF89a") { return "image/gif", nil } if len(data) >= 12 && string(data[8:12]) == "WEBP" { return "image/webp", nil } return "", fmt.Errorf("unsupported image format") } // getExtensionFromMimeType 根據 MIME 類型獲取文件擴展名 func (l *UploadImgLogic) getExtensionFromMimeType(mimeType string) string { switch mimeType { case "image/jpeg": return ".jpg" case "image/png": return ".png" case "image/gif": return ".gif" case "image/webp": return ".webp" default: return ".jpg" } }