Files
backend/internal/pkg/s3/s3.go
lafay 28a45caad3
All checks were successful
Build Backend / build (push) Successful in 13m35s
Build Backend / build-docker (push) Successful in 1m11s
feat(upload): integrate upload service for chat image validation and S3 uploads
- Added UploadService to handle image uploads and validation for chat messages.
- Updated ChatService and MessageService to utilize UploadService for validating image segments in messages.
- Enhanced wire generation to inject UploadService into relevant services.
- Introduced UploadImageBytes method in UploadService for uploading images directly from memory.
- Added TrustedPublicURLPrefix method in S3 Client for generating public URLs for uploaded images.
2026-03-25 03:57:40 +08:00

132 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package s3
import (
"bytes"
"context"
"fmt"
"time"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"carrot_bbs/internal/config"
)
// Client S3客户端
type Client struct {
client *minio.Client
bucket string
domain string
}
// New 创建S3客户端
func New(cfg *config.S3Config) (*Client, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
client, err := minio.New(cfg.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
Secure: cfg.UseSSL,
})
if err != nil {
return nil, fmt.Errorf("failed to create S3 client: %w", err)
}
// 检查bucket是否存在
exists, err := client.BucketExists(ctx, cfg.Bucket)
if err != nil {
return nil, fmt.Errorf("failed to check bucket: %w", err)
}
if !exists {
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
Region: cfg.Region,
}); err != nil {
return nil, fmt.Errorf("failed to create bucket: %w", err)
}
}
// 如果没有配置domain则使用默认的endpoint
domain := cfg.Domain
if domain == "" {
domain = cfg.Endpoint
}
return &Client{
client: client,
bucket: cfg.Bucket,
domain: domain,
}, nil
}
// Upload 上传文件
func (c *Client) Upload(ctx context.Context, objectName string, filePath string, contentType string) (string, error) {
_, err := c.client.FPutObject(ctx, c.bucket, objectName, filePath, minio.PutObjectOptions{
ContentType: contentType,
})
if err != nil {
return "", fmt.Errorf("failed to upload file: %w", err)
}
return fmt.Sprintf("%s/%s", c.bucket, objectName), nil
}
// UploadData 上传数据
func (c *Client) UploadData(ctx context.Context, objectName string, data []byte, contentType string) (string, error) {
_, err := c.client.PutObject(ctx, c.bucket, objectName, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{
ContentType: contentType,
})
if err != nil {
return "", fmt.Errorf("failed to upload data: %w", err)
}
// 返回完整URL包含bucket名称
scheme := "https"
if c.domain == c.bucket || c.domain == "" {
scheme = "http"
}
return fmt.Sprintf("%s://%s/%s/%s", scheme, c.domain, c.bucket, objectName), nil
}
// TrustedPublicURLPrefix 本站公开对象 URL 前缀scheme://domain/bucket/),用于校验聊天图片等仅引用本地上传资源。
func (c *Client) TrustedPublicURLPrefix() string {
if c == nil || c.domain == "" || c.bucket == "" {
return ""
}
scheme := "https"
if c.domain == c.bucket {
scheme = "http"
}
return fmt.Sprintf("%s://%s/%s/", scheme, c.domain, c.bucket)
}
// GetURL 获取文件URL - 使用自定义域名
func (c *Client) GetURL(ctx context.Context, objectName string) (string, error) {
// 使用自定义域名构建URL包含bucket名称
scheme := "https"
if c.domain == c.bucket || c.domain == "" {
scheme = "http"
}
return fmt.Sprintf("%s://%s/%s/%s", scheme, c.domain, c.bucket, objectName), nil
}
// GetPresignedURL 获取预签名URL用于私有桶
func (c *Client) GetPresignedURL(ctx context.Context, objectName string) (string, error) {
url, err := c.client.PresignedGetObject(ctx, c.bucket, objectName, time.Hour*24, nil)
if err != nil {
return "", fmt.Errorf("failed to get presigned URL: %w", err)
}
return url.String(), nil
}
// Delete 删除文件
func (c *Client) Delete(ctx context.Context, objectName string) error {
return c.client.RemoveObject(ctx, c.bucket, objectName, minio.RemoveObjectOptions{})
}
// GetClient 获取原生客户端
func (c *Client) GetClient() *minio.Client {
return c.client
}