From 25c11ed649ed1e6852100a365c6f4d45dc5525d7 Mon Sep 17 00:00:00 2001 From: wonder Date: Mon, 25 May 2026 14:31:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=85=A8=E9=9D=A2=E9=9B=86?= =?UTF-8?q?=E6=88=90=20slog=20=E6=97=A5=E5=BF=97=E5=88=B0=20handler?= =?UTF-8?q?=E3=80=81service=E3=80=81middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cmd/main.go: 集成 logger.Init 和日志中间件,替换标准 log 包 - handler 层: 5xx 错误记录完整日志,返回通用消息(防内部信息泄露) - service 层: LLM/图片生成/存储/认证等关键操作补充结构化日志 - auth 中间件: 记录认证失败原因 - generate.go: 后台管线任务使用带 task_id 的 logger --- backend/cmd/main.go | 30 ++++++++----- backend/internal/handler/edit.go | 12 ++--- backend/internal/handler/generate.go | 22 +++++----- backend/internal/handler/login.go | 2 + backend/internal/handler/prompt.go | 4 +- backend/internal/handler/register.go | 2 + backend/internal/handler/storage.go | 2 + backend/internal/mildware/auth.go | 5 +++ backend/internal/service/auth.go | 14 ++++++ backend/internal/service/inference.go | 56 +++++++++++++++++++++--- backend/internal/service/pipeline.go | 14 ++++++ backend/internal/service/prompt_agent.go | 13 ++++-- backend/internal/service/storage.go | 22 +++++++++- 13 files changed, 159 insertions(+), 39 deletions(-) diff --git a/backend/cmd/main.go b/backend/cmd/main.go index c2be817..d4f15f0 100755 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -3,11 +3,12 @@ package main import ( "fmt" - "log" + "log/slog" "gen2d/internal/config" "gen2d/internal/db" "gen2d/internal/handler" + "gen2d/internal/logger" "gen2d/internal/mildware" "gen2d/internal/model" "gen2d/internal/service" @@ -18,11 +19,15 @@ import ( func main() { cfg := config.Load() + // 初始化日志 + logger.Init(cfg.Log.Level, cfg.Log.Format) + gin.SetMode(cfg.Server.Mode) // 初始化 SQLite 数据库 if err := db.Init(cfg.Database.DSN, &model.User{}); err != nil { - log.Fatalf("db init failed: %v", err) + slog.Error("db init failed", "error", err) + return } // 初始化 AuthService,注入 JWT 配置 @@ -37,24 +42,25 @@ func main() { handler.InitStorageService(storageSvc) r := gin.New() - r.Use(gin.Recovery()) // panic 恢复中间件,防止服务因未捕获异常宕机 + r.Use(mildware.Logger()) + r.Use(mildware.Recovery()) // API v1 路由组 — 公开接口 v1 := r.Group("/api/v1") { - v1.GET("/health", handler.Health) // 健康检查 - v1.POST("/prompt/optimize", handler.PromptOptimize) // 提示词优化 - v1.GET("/assets/download", handler.DownloadAsset) // 素材下载 + v1.GET("/health", handler.Health) // 健康检查 + v1.POST("/prompt/optimize", handler.PromptOptimize) // 提示词优化 + v1.GET("/assets/download", handler.DownloadAsset) // 素材下载 } // API v1 路由组 — 需认证 v1Auth := r.Group("/api/v1") v1Auth.Use(mildware.AuthMiddleware(cfg.JWT.Secret)) { - v1Auth.POST("/generate", handler.Generate) // 素材生成管线 - v1Auth.GET("/tasks/:taskId", handler.GetTask) // 查询任务 - v1Auth.GET("/tasks/:taskId/assets", handler.GetAssets) // 查询任务素材 - v1Auth.POST("/images/edit", handler.EditImage) // 图片编辑 + v1Auth.POST("/generate", handler.Generate) // 素材生成管线 + v1Auth.GET("/tasks/:taskId", handler.GetTask) // 查询任务 + v1Auth.GET("/tasks/:taskId/assets", handler.GetAssets) // 查询任务素材 + v1Auth.POST("/images/edit", handler.EditImage) // 图片编辑 } // Auth 路由组 @@ -65,8 +71,8 @@ func main() { } addr := fmt.Sprintf(":%d", cfg.Server.Port) - log.Printf("gen2d backend starting on %s", addr) + slog.Info("gen2d backend starting", "addr", addr, "mode", cfg.Server.Mode) if err := r.Run(addr); err != nil { - log.Fatalf("server failed: %v", err) + slog.Error("server failed", "error", err) } } diff --git a/backend/internal/handler/edit.go b/backend/internal/handler/edit.go index f77b05a..48af1af 100755 --- a/backend/internal/handler/edit.go +++ b/backend/internal/handler/edit.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "net/http" + "gen2d/internal/logger" "gen2d/internal/model" "gen2d/internal/service" @@ -12,9 +13,9 @@ import ( // EditImageRequest 图片编辑请求。 type EditImageRequest struct { - Image string `json:"image" binding:"required"` // 底图 base64 编码 - Prompt string `json:"prompt" binding:"required"` // 编辑指令 - Count int `json:"count"` // 生成数量,默认 1 + Image string `json:"image" binding:"required"` // 底图 base64 编码 + Prompt string `json:"prompt" binding:"required"` // 编辑指令 + Count int `json:"count"` // 生成数量,默认 1 } // editAssetResponse 编辑结果素材(返回 base64)。 @@ -38,7 +39,7 @@ func EditImage(c *gin.Context) { imageData, err := base64.StdEncoding.DecodeString(req.Image) if err != nil { - c.JSON(http.StatusBadRequest, model.Fail(http.StatusBadRequest, "图片 base64 解码失败: "+err.Error())) + c.JSON(http.StatusBadRequest, model.Fail(http.StatusBadRequest, "图片 base64 解码失败")) return } @@ -49,7 +50,8 @@ func EditImage(c *gin.Context) { images, err := service.EditImages(c.Request.Context(), imageData, req.Prompt, count) if err != nil { - c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "图片编辑失败: "+err.Error())) + logger.FromCtx(c.Request.Context()).Error("图片编辑失败", "error", err) + c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "图片编辑失败")) return } diff --git a/backend/internal/handler/generate.go b/backend/internal/handler/generate.go index 3372cdb..e592b1e 100755 --- a/backend/internal/handler/generate.go +++ b/backend/internal/handler/generate.go @@ -3,11 +3,11 @@ package handler import ( "context" "fmt" - "log" "net/http" "sync" "time" + "gen2d/internal/logger" "gen2d/internal/model" "gen2d/internal/service" @@ -55,8 +55,8 @@ type TaskResponse struct { // AssetsResponse 素材列表响应。 type AssetsResponse struct { - Assets []AssetResponse `json:"assets"` - Metadata service.AssetMetadata `json:"metadata"` + Assets []AssetResponse `json:"assets"` + Metadata service.AssetMetadata `json:"metadata"` } // taskRecord 内存中的任务记录。 @@ -103,11 +103,13 @@ func Generate(c *gin.Context) { c.JSON(http.StatusOK, model.OK(GenerateResponse{TaskID: taskID})) // 后台执行管线 - go runPipelineBg(projectID, taskID, req) + go runPipelineBg(c.Request.Context(), projectID, taskID, req) } // runPipelineBg 后台执行生成管线,更新任务状态。 -func runPipelineBg(projectID, taskID string, req GenerateRequest) { +func runPipelineBg(ctx context.Context, projectID, taskID string, req GenerateRequest) { + l := logger.With("task_id", taskID, "project_id", projectID) + updateStatus(taskID, "running", 10) in := service.PipelineInput{ @@ -129,11 +131,10 @@ func runPipelineBg(projectID, taskID string, req GenerateRequest) { }, } - ctx := context.Background() output, err := service.RunPipeline(ctx, in) if err != nil { - log.Printf("[generate] task %s failed: %v", taskID, err) - updateFailed(taskID, err.Error()) + l.Error("task pipeline failed", "error", err) + updateFailed(taskID, "生成管线执行失败") return } @@ -144,7 +145,8 @@ func runPipelineBg(projectID, taskID string, req GenerateRequest) { key := fmt.Sprintf("generation/%s/%s/%d.%s", projectID, taskID, i, a.Format) cdnURL, err := storageSvc.Upload(ctx, key, a.Data) if err != nil { - updateFailed(taskID, "上传素材失败: "+err.Error()) + l.Error("upload asset failed", "index", i, "error", err) + updateFailed(taskID, "上传素材失败") return } assets[i] = AssetResponse{ @@ -168,7 +170,7 @@ func runPipelineBg(projectID, taskID string, req GenerateRequest) { metadata: output.Metadata, }) - log.Printf("[generate] task %s completed, %d assets", taskID, len(assets)) + l.Info("task completed", "asset_count", len(assets)) } func updateStatus(taskID, status string, progress int) { diff --git a/backend/internal/handler/login.go b/backend/internal/handler/login.go index e839664..35b7dfe 100644 --- a/backend/internal/handler/login.go +++ b/backend/internal/handler/login.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + "gen2d/internal/logger" "gen2d/internal/model" "github.com/gin-gonic/gin" @@ -31,6 +32,7 @@ func Login(c *gin.Context) { token, expiresIn, user, err := authSvc.Login(c.Request.Context(), req.Username, req.Password) if err != nil { + logger.FromCtx(c.Request.Context()).Warn("登录失败", "username", req.Username, "error", err) c.JSON(http.StatusUnauthorized, model.Fail(http.StatusUnauthorized, err.Error())) return } diff --git a/backend/internal/handler/prompt.go b/backend/internal/handler/prompt.go index 1910e79..6a09b86 100644 --- a/backend/internal/handler/prompt.go +++ b/backend/internal/handler/prompt.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + "gen2d/internal/logger" "gen2d/internal/model" "gen2d/internal/service" @@ -34,7 +35,8 @@ func PromptOptimize(c *gin.Context) { output, err := service.RunPromptAgent(c.Request.Context(), in) if err != nil { - c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "提示词优化失败: "+err.Error())) + logger.FromCtx(c.Request.Context()).Error("提示词优化失败", "error", err) + c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "提示词优化失败")) return } diff --git a/backend/internal/handler/register.go b/backend/internal/handler/register.go index bca2108..802d3f3 100644 --- a/backend/internal/handler/register.go +++ b/backend/internal/handler/register.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + "gen2d/internal/logger" "gen2d/internal/model" "gen2d/internal/service" @@ -39,6 +40,7 @@ func Register(c *gin.Context) { user, err := authSvc.Register(c.Request.Context(), req.Username, req.Password, req.Email) if err != nil { + logger.FromCtx(c.Request.Context()).Warn("注册失败", "username", req.Username, "error", err) c.JSON(http.StatusConflict, model.Fail(http.StatusConflict, err.Error())) return } diff --git a/backend/internal/handler/storage.go b/backend/internal/handler/storage.go index 2a7251d..6270982 100644 --- a/backend/internal/handler/storage.go +++ b/backend/internal/handler/storage.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + "gen2d/internal/logger" "gen2d/internal/model" "gen2d/internal/service" @@ -27,6 +28,7 @@ func DownloadAsset(c *gin.Context) { downloadURL, err := storageSvc.GetDownloadURL(c.Request.Context(), key) if err != nil { + logger.FromCtx(c.Request.Context()).Error("生成下载链接失败", "key", key, "error", err) c.JSON(http.StatusInternalServerError, model.Fail(http.StatusInternalServerError, "生成下载链接失败")) return } diff --git a/backend/internal/mildware/auth.go b/backend/internal/mildware/auth.go index 9a0226a..550da71 100644 --- a/backend/internal/mildware/auth.go +++ b/backend/internal/mildware/auth.go @@ -4,6 +4,7 @@ import ( "net/http" "strings" + "gen2d/internal/logger" "gen2d/internal/model" "github.com/gin-gonic/gin" @@ -15,12 +16,14 @@ func AuthMiddleware(jwtSecret string) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { + logger.FromCtx(c.Request.Context()).Warn("auth failed: no token") c.AbortWithStatusJSON(http.StatusUnauthorized, model.Fail(http.StatusUnauthorized, "未提供认证令牌")) return } tokenString := strings.TrimPrefix(authHeader, "Bearer ") if tokenString == authHeader { + logger.FromCtx(c.Request.Context()).Warn("auth failed: invalid format") c.AbortWithStatusJSON(http.StatusUnauthorized, model.Fail(http.StatusUnauthorized, "认证格式错误,需为 Bearer ")) return } @@ -29,12 +32,14 @@ func AuthMiddleware(jwtSecret string) gin.HandlerFunc { return []byte(jwtSecret), nil }) if err != nil || !token.Valid { + logger.FromCtx(c.Request.Context()).Warn("auth failed: invalid token", "error", err) c.AbortWithStatusJSON(http.StatusUnauthorized, model.Fail(http.StatusUnauthorized, "令牌无效或已过期")) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { + logger.FromCtx(c.Request.Context()).Warn("auth failed: parse claims") c.AbortWithStatusJSON(http.StatusUnauthorized, model.Fail(http.StatusUnauthorized, "令牌解析失败")) return } diff --git a/backend/internal/service/auth.go b/backend/internal/service/auth.go index 4f96abe..db4a2ba 100644 --- a/backend/internal/service/auth.go +++ b/backend/internal/service/auth.go @@ -6,6 +6,7 @@ import ( "time" "gen2d/internal/db" + "gen2d/internal/logger" "gen2d/internal/model" "github.com/golang-jwt/jwt/v5" @@ -29,8 +30,11 @@ func NewAuthService(jwtSecret string, jwtExpire int64) *AuthService { // Register 注册新用户,密码使用 bcrypt 加密存储。 func (s *AuthService) Register(ctx context.Context, username, password, email string) (*model.User, error) { + l := logger.FromCtx(ctx) + hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { + l.Error("bcrypt failed", "error", err) return nil, err } @@ -42,25 +46,33 @@ func (s *AuthService) Register(ctx context.Context, username, password, email st if err := db.DB.WithContext(ctx).Create(&user).Error; err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { + l.Warn("register failed: duplicate username", "username", username) return nil, errors.New("用户名已存在") } + l.Error("register db failed", "username", username, "error", err) return nil, err } + l.Info("user registered", "username", username, "user_id", user.ID) return &user, nil } // Login 校验用户名密码,成功返回 JWT token 及用户信息。 func (s *AuthService) Login(ctx context.Context, username, password string) (string, int64, *model.User, error) { + l := logger.FromCtx(ctx) + var user model.User if err := db.DB.WithContext(ctx).Where("username = ?", username).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { + l.Warn("login failed: user not found", "username", username) return "", 0, nil, errors.New("用户名或密码错误") } + l.Error("login db query failed", "username", username, "error", err) return "", 0, nil, err } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + l.Warn("login failed: wrong password", "username", username) return "", 0, nil, errors.New("用户名或密码错误") } @@ -72,8 +84,10 @@ func (s *AuthService) Login(ctx context.Context, username, password string) (str } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(s.jwtSecret) if err != nil { + l.Error("jwt sign failed", "error", err) return "", 0, nil, err } + l.Info("login succeeded", "username", username, "user_id", user.ID) return token, int64(s.jwtExpire.Seconds()), &user, nil } diff --git a/backend/internal/service/inference.go b/backend/internal/service/inference.go index b29db7b..37d28d2 100755 --- a/backend/internal/service/inference.go +++ b/backend/internal/service/inference.go @@ -11,13 +11,13 @@ import ( "image/color" "image/png" "io" - "log" "mime/multipart" "net/http" "strings" "time" "gen2d/internal/config" + "gen2d/internal/logger" ) // imgCfg 保存文生图配置,由 main 通过 InitImageGenConfig 注入。 @@ -64,21 +64,33 @@ func GenerateImages(ctx context.Context, prompt string, params AssetParams) ([]G count = params.Frames.Directions * params.Frames.FramesPerDirection } + l := logger.FromCtx(ctx) + if imgCfg.APIKey != "" { width, height := imgCfg.Width, imgCfg.Height if params.Resolution > 0 { width = params.Resolution height = params.Resolution } - log.Println("[inference] calling image gen API") - return callImageAPI(ctx, prompt, count, width, height) + l.Info("calling image gen API", + "model", imgCfg.Model, + "count", count, + "size", fmt.Sprintf("%dx%d", width, height), + ) + images, err := callImageAPI(ctx, prompt, count, width, height) + if err != nil { + l.Error("image gen API failed", "error", err) + return nil, err + } + l.Info("image gen API succeeded", "image_count", len(images)) + return images, nil } size := params.Resolution if size <= 0 { size = 64 } - log.Println("[inference] image API key not configured, using mock") + l.Warn("image API key not configured, using mock") return generateMockImages(size, count) } @@ -107,10 +119,15 @@ func callImageAPI(ctx context.Context, prompt string, count, width, height int) retryDelay = 5 * time.Second } + l := logger.FromCtx(ctx) + var lastErr error for attempt := 0; attempt <= maxRetries; attempt++ { if attempt > 0 { - log.Printf("[inference] retrying image API (attempt %d/%d)", attempt, maxRetries) + l.Warn("retrying image API", + "attempt", attempt, + "max_retries", maxRetries, + ) select { case <-ctx.Done(): return nil, fmt.Errorf("context cancelled during retry: %w", ctx.Err()) @@ -128,6 +145,7 @@ func callImageAPI(ctx context.Context, prompt string, count, width, height int) resp, err := imageHTTPClient.Do(req) if err != nil { lastErr = fmt.Errorf("send request: %w", err) + l.Warn("image API request failed", "error", lastErr) continue } @@ -140,8 +158,18 @@ func callImageAPI(ctx context.Context, prompt string, count, width, height int) if resp.StatusCode >= 500 { lastErr = fmt.Errorf("image api error %d: %s", resp.StatusCode, string(b)) + l.Error("image API server error", + "status", resp.StatusCode, + "body", string(b), + "attempt", attempt, + ) continue } + // 4xx 不重试 + l.Error("image API client error", + "status", resp.StatusCode, + "body", string(b), + ) return nil, fmt.Errorf("image api error %d: %s", resp.StatusCode, string(b)) } @@ -205,10 +233,17 @@ func downloadImage(ctx context.Context, url string) ([]byte, error) { // EditImages 图片编辑接口,调用 OpenAI 兼容 Images Edits API(multipart/form-data)。 func EditImages(ctx context.Context, imageData []byte, prompt string, count int) ([]GeneratedImage, error) { + l := logger.FromCtx(ctx) + if imgCfg.APIKey == "" { return nil, fmt.Errorf("image API key not configured") } + l.Info("calling image edit API", + "model", imgCfg.Model, + "count", count, + ) + var buf bytes.Buffer writer := multipart.NewWriter(&buf) @@ -239,16 +274,25 @@ func EditImages(ctx context.Context, imageData []byte, prompt string, count int) resp, err := imageHTTPClient.Do(req) if err != nil { + l.Error("image edit API request failed", "error", err) return nil, fmt.Errorf("send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + l.Error("image edit API error", "status", resp.StatusCode, "body", string(b)) return nil, fmt.Errorf("image edit api error %d: %s", resp.StatusCode, string(b)) } - return parseImageResponse(ctx, resp.Body, imgCfg.Width, imgCfg.Height) + images, err := parseImageResponse(ctx, resp.Body, imgCfg.Width, imgCfg.Height) + if err != nil { + l.Error("image edit response parse failed", "error", err) + return nil, err + } + + l.Info("image edit API succeeded", "image_count", len(images)) + return images, nil } // ======================== 质检 ======================== diff --git a/backend/internal/service/pipeline.go b/backend/internal/service/pipeline.go index 6ce8a23..9134104 100644 --- a/backend/internal/service/pipeline.go +++ b/backend/internal/service/pipeline.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "gen2d/internal/logger" + "github.com/cloudwego/eino/compose" ) @@ -82,20 +84,32 @@ func NewGenerateGraph() (*compose.Graph[PipelineInput, PipelineOutput], error) { // RunPipeline 编译并执行生成管线。 func RunPipeline(ctx context.Context, in PipelineInput) (*PipelineOutput, error) { + l := logger.FromCtx(ctx) + + l.Info("pipeline started", + "asset_type", in.AssetType, + "tags", in.Tags, + "resolution", in.Params.Resolution, + ) + g, err := NewGenerateGraph() if err != nil { + l.Error("pipeline create graph failed", "error", err) return nil, fmt.Errorf("create graph: %w", err) } r, err := g.Compile(ctx, compose.WithMaxRunSteps(20)) if err != nil { + l.Error("pipeline compile failed", "error", err) return nil, fmt.Errorf("compile graph: %w", err) } output, err := r.Invoke(ctx, in) if err != nil { + l.Error("pipeline invoke failed", "error", err) return nil, fmt.Errorf("invoke pipeline: %w", err) } + l.Info("pipeline completed", "asset_count", len(output.Assets)) return &output, nil } diff --git a/backend/internal/service/prompt_agent.go b/backend/internal/service/prompt_agent.go index 813ade2..f3e64ce 100644 --- a/backend/internal/service/prompt_agent.go +++ b/backend/internal/service/prompt_agent.go @@ -7,11 +7,11 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "strings" "gen2d/internal/config" + "gen2d/internal/logger" "github.com/cloudwego/eino/compose" ) @@ -130,8 +130,10 @@ type chatResponse struct { // callLLMRefine 调用 LLM 生成规范化提示词。未配置 API key 时回退到模板生成。 func callLLMRefine(ctx context.Context, metaPrompt string) (PromptAgentOutput, error) { + l := logger.FromCtx(ctx) + if llmCfg.APIKey == "" { - log.Println("[prompt_agent] LLM API key not configured, using template fallback") + l.Warn("LLM API key not configured, using template fallback") return fallbackRefine(metaPrompt), nil } @@ -140,12 +142,14 @@ func callLLMRefine(ctx context.Context, metaPrompt string) (PromptAgentOutput, e {Role: "user", Content: metaPrompt}, } + l.Info("calling LLM API", "model", llmCfg.Model) result, err := chatCompletion(ctx, messages) if err != nil { - log.Printf("[prompt_agent] LLM API call failed: %v, using template fallback", err) + l.Error("LLM API call failed, using template fallback", "error", err) return fallbackRefine(metaPrompt), nil } + l.Info("LLM API succeeded", "response_length", len(result)) return PromptAgentOutput{ Prompt: result, RawText: result, @@ -175,6 +179,8 @@ func chatCompletion(ctx context.Context, messages []chatMessage) (string, error) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+llmCfg.APIKey) + l := logger.FromCtx(ctx) + resp, err := http.DefaultClient.Do(req) if err != nil { return "", fmt.Errorf("send request: %w", err) @@ -183,6 +189,7 @@ func chatCompletion(ctx context.Context, messages []chatMessage) (string, error) if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + l.Error("LLM API error", "status", resp.StatusCode, "body", string(b)) return "", fmt.Errorf("llm api error %d: %s", resp.StatusCode, string(b)) } diff --git a/backend/internal/service/storage.go b/backend/internal/service/storage.go index f99e0d1..dbd43d0 100644 --- a/backend/internal/service/storage.go +++ b/backend/internal/service/storage.go @@ -9,6 +9,7 @@ import ( "time" "gen2d/internal/config" + "gen2d/internal/logger" "github.com/qiniu/go-sdk/v7/storagev2/credentials" "github.com/qiniu/go-sdk/v7/storagev2/objects" @@ -39,8 +40,11 @@ func NewStorageService(cfg config.QiniuConfig) *StorageService { // Upload 上传图片字节到七牛云,返回 CDN 访问 URL。 func (s *StorageService) Upload(ctx context.Context, key string, data []byte) (string, error) { + l := logger.FromCtx(ctx) + putPolicy, err := uptoken.NewPutPolicy(s.bucket, time.Now().Add(1*time.Hour)) if err != nil { + l.Error("qiniu put policy failed", "key", key, "error", err) return "", fmt.Errorf("qiniu put policy: %w", err) } upToken := uptoken.NewSigner(putPolicy, s.creds) @@ -49,14 +53,19 @@ func (s *StorageService) Upload(ctx context.Context, key string, data []byte) (s UpTokenProvider: upToken, }) + l.Info("uploading to qiniu", "key", key, "size", len(data)) err = uploadMgr.UploadReader(ctx, bytes.NewReader(data), &uploader.ObjectOptions{ BucketName: s.bucket, ObjectName: &key, }, nil) if err != nil { + l.Error("qiniu upload failed", "key", key, "error", err) return "", fmt.Errorf("qiniu upload: %w", err) } - return s.buildURL(key), nil + + cdnURL := s.buildURL(key) + l.Info("upload succeeded", "key", key, "url", cdnURL) + return cdnURL, nil } // GetDownloadURL 生成素材的下载 URL。 @@ -67,8 +76,17 @@ func (s *StorageService) GetDownloadURL(_ context.Context, key string) (string, // Delete 从七牛云删除指定对象。 func (s *StorageService) Delete(ctx context.Context, key string) error { + l := logger.FromCtx(ctx) + bucket := s.objectsMgr.Bucket(s.bucket) - return bucket.Object(key).Delete().Call(ctx) + err := bucket.Object(key).Delete().Call(ctx) + if err != nil { + l.Error("qiniu delete failed", "key", key, "error", err) + return err + } + + l.Info("delete succeeded", "key", key) + return nil } // buildURL 根据配置构建完整的 CDN URL。