// 上传文件到cloudflare R2 package handler import ( "bytes" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) type UploadHandler struct { accessKey string secretKey string bucket string endpoint string customDomain string } type UploadRequest struct { File string `json:"file" binding:"required"` APIKey string `json:"apikey" binding:"required"` } type UploadResponse struct { ImageURL string `json:"image_url"` Success bool `json:"success"` } func NewUploadHandler(accessKey, secretKey, bucket, endpoint, customDomain string) *UploadHandler { return &UploadHandler{ accessKey: accessKey, secretKey: secretKey, bucket: bucket, endpoint: endpoint, customDomain: customDomain, } } // 上传文件到cloudflare R2。判断文件是否是图片,如果是图片,则上传到R2,并返回图片的url,如果不是图片,则返回错误。 // 图片大小限制为10M,图片格式为jpg, jpeg, png, gif, bmp, tiff, webp // HandleUpload 上传文件到Cloudflare R2 func (h *UploadHandler) HandleUpload(c *gin.Context) { // 解析请求体 file, header, err := c.Request.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read file from request"}) return } defer file.Close() // 读取文件内容 fileBuffer := make([]byte, header.Size) _, err = file.Read(fileBuffer) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read file content"}) return } // 验证文件类型 contentType := http.DetectContentType(fileBuffer) if !isImage(contentType) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file type. Only images are allowed"}) return } // 验证文件大小 if header.Size > 10<<20 { // 10MB c.JSON(http.StatusBadRequest, gin.H{"error": "File size exceeds the limit of 10MB"}) return } // 上传文件到R2 imageURL, err := h.uploadToR2(fileBuffer, header.Filename, contentType) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to upload file to R2: %v", err)}) return } // 返回结果 response := UploadResponse{ ImageURL: imageURL, Success: true, } c.JSON(http.StatusOK, response) } // uploadToR2 上传文件到Cloudflare R2 func (h *UploadHandler) uploadToR2(file []byte, fileName, contentType string) (string, error) { // 创建S3会话 sess, err := session.NewSession(&aws.Config{ Endpoint: aws.String(h.endpoint), Region: aws.String("auto"), Credentials: credentials.NewStaticCredentials(h.accessKey, h.secretKey, ""), }) if err != nil { return "", fmt.Errorf("failed to create S3 session: %v", err) } // 创建S3服务客户端 svc := s3.New(sess) // 上传文件到R2 _, err = svc.PutObject(&s3.PutObjectInput{ Bucket: aws.String(h.bucket), Key: aws.String(fileName), Body: bytes.NewReader(file), ContentType: aws.String(contentType), ACL: aws.String("public-read"), // 设置文件为公开可读 }) if err != nil { return "", fmt.Errorf("failed to upload file to R2: %v", err) } // 生成文件的URL imageURL := fmt.Sprintf("https://%s/%s", h.customDomain, fileName) return imageURL, nil } // isImage 检查文件是否是图片 func isImage(contentType string) bool { allowedTypes := []string{"image/jpeg", "image/png", "image/gif", "image/bmp", "image/tiff", "image/webp"} for _, t := range allowedTypes { if contentType == t { return true } } return false }