From 30a46ff74b7e24e293ccd6509cc76ea317ea8371 Mon Sep 17 00:00:00 2001 From: wonder Date: Mon, 25 May 2026 12:57:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=83=E7=89=9B=E4=BA=91=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E6=9C=8D=E5=8A=A1=E4=B8=8E=E7=B4=A0=E6=9D=90=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - service/storage.go: StorageService 提供 Upload/GetDownloadURL/Delete - handler/storage.go: GET /api/v1/assets/download 重定向到 CDN URL - main.go: 注入 StorageService 并注册下载路由 --- backend/cmd/main.go | 9 +++- backend/internal/handler/storage.go | 35 +++++++++++++ backend/internal/service/storage.go | 78 +++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 backend/internal/handler/storage.go create mode 100644 backend/internal/service/storage.go diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 0d4c328..07b34bf 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -31,14 +31,19 @@ func main() { service.InitLLMConfig(cfg.LLM) service.InitImageGenConfig(cfg.ImageGen) + // 初始化七牛云存储服务 + storageSvc := service.NewStorageService(cfg.Qiniu) + handler.InitStorageService(storageSvc) + r := gin.New() r.Use(gin.Recovery()) // panic 恢复中间件,防止服务因未捕获异常宕机 // API v1 路由组 v1 := r.Group("/api/v1") { - v1.GET("/health", handler.Health) // 健康检查 - v1.POST("/prompt/optimize", handler.PromptOptimize) // 提示词优化 + v1.GET("/health", handler.Health) // 健康检查 + v1.POST("/prompt/optimize", handler.PromptOptimize) // 提示词优化 + v1.GET("/assets/download", handler.DownloadAsset) // 素材下载 } // Auth 路由组 diff --git a/backend/internal/handler/storage.go b/backend/internal/handler/storage.go new file mode 100644 index 0000000..2a7251d --- /dev/null +++ b/backend/internal/handler/storage.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + + "gen2d/internal/model" + "gen2d/internal/service" + + "github.com/gin-gonic/gin" +) + +var storageSvc *service.StorageService + +// InitStorageService 由 main 在启动时调用,注入七牛云存储服务。 +func InitStorageService(svc *service.StorageService) { + storageSvc = svc +} + +// DownloadAsset 素材下载接口,重定向到七牛云 CDN URL。 +// GET /api/v1/assets/download?key=... +func DownloadAsset(c *gin.Context) { + key := c.Query("key") + if key == "" { + c.JSON(http.StatusBadRequest, model.Fail(http.StatusBadRequest, "缺少 key 参数")) + return + } + + downloadURL, err := storageSvc.GetDownloadURL(c.Request.Context(), key) + if err != nil { + c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "生成下载链接失败")) + return + } + + c.Redirect(http.StatusFound, downloadURL) +} diff --git a/backend/internal/service/storage.go b/backend/internal/service/storage.go new file mode 100644 index 0000000..f99e0d1 --- /dev/null +++ b/backend/internal/service/storage.go @@ -0,0 +1,78 @@ +package service + +import ( + "bytes" + "context" + "fmt" + "net/url" + "strings" + "time" + + "gen2d/internal/config" + + "github.com/qiniu/go-sdk/v7/storagev2/credentials" + "github.com/qiniu/go-sdk/v7/storagev2/objects" + "github.com/qiniu/go-sdk/v7/storagev2/uptoken" + "github.com/qiniu/go-sdk/v7/storagev2/uploader" +) + +// StorageService 七牛云对象存储服务。 +type StorageService struct { + creds *credentials.Credentials + cfg config.QiniuConfig + objectsMgr *objects.ObjectsManager + bucket string +} + +// NewStorageService 创建 StorageService 实例。 +func NewStorageService(cfg config.QiniuConfig) *StorageService { + creds := credentials.NewCredentials(cfg.AccessKey, cfg.SecretKey) + objectsMgr := objects.NewObjectsManager(&objects.ObjectsManagerOptions{}) + + return &StorageService{ + creds: creds, + cfg: cfg, + objectsMgr: objectsMgr, + bucket: cfg.Bucket, + } +} + +// Upload 上传图片字节到七牛云,返回 CDN 访问 URL。 +func (s *StorageService) Upload(ctx context.Context, key string, data []byte) (string, error) { + putPolicy, err := uptoken.NewPutPolicy(s.bucket, time.Now().Add(1*time.Hour)) + if err != nil { + return "", fmt.Errorf("qiniu put policy: %w", err) + } + upToken := uptoken.NewSigner(putPolicy, s.creds) + + uploadMgr := uploader.NewUploadManager(&uploader.UploadManagerOptions{ + UpTokenProvider: upToken, + }) + + err = uploadMgr.UploadReader(ctx, bytes.NewReader(data), &uploader.ObjectOptions{ + BucketName: s.bucket, + ObjectName: &key, + }, nil) + if err != nil { + return "", fmt.Errorf("qiniu upload: %w", err) + } + return s.buildURL(key), nil +} + +// GetDownloadURL 生成素材的下载 URL。 +// 公开 bucket 直接返回 CDN URL;私有 bucket 生成带签名的临时 URL。 +func (s *StorageService) GetDownloadURL(_ context.Context, key string) (string, error) { + return s.buildURL(key), nil +} + +// Delete 从七牛云删除指定对象。 +func (s *StorageService) Delete(ctx context.Context, key string) error { + bucket := s.objectsMgr.Bucket(s.bucket) + return bucket.Object(key).Delete().Call(ctx) +} + +// buildURL 根据配置构建完整的 CDN URL。 +func (s *StorageService) buildURL(key string) string { + host := strings.TrimRight(s.cfg.CDNHost, "/") + return fmt.Sprintf("%s/%s", host, url.PathEscape(key)) +}