diff --git a/internal/handler/relay/sync_image.go b/internal/handler/relay/sync_image.go index 64c93df..4abf93f 100644 --- a/internal/handler/relay/sync_image.go +++ b/internal/handler/relay/sync_image.go @@ -93,7 +93,15 @@ func HandleSyncImageSubmit(r *ghttp.Request, body []byte, rc *relay_handler.Task } // 5. 建 QUEUED 任务行(channel_id=0,dispatch 时绑定) - publicTaskID := generateSyncImagePublicID() + // 生成任务 ID 依赖 crypto/rand,失败时此处尚未建任务行、无法交给超时兜底网结算, + // 必须就地退掉上一步的预扣,避免冻结额被永久占用。 + publicTaskID, err := generateSyncImagePublicID() + if err != nil { + _ = taskBillingProvider.SettleTaskFailed(ctx, rc.TenantID, rc.RequestID, preDeduct) + g.Log().Errorf(ctx, "sync_image: generate task ID failed, refunded pre-deduct: %v", err) + writeSyncImageError(w, http.StatusInternalServerError, "generate task ID failed") + return + } now := time.Now() // 从渠道类型推导实际的供应商平台(ali / gemini / volcengine / openai 等),用于任务日志展示。 @@ -258,12 +266,12 @@ func checkSyncImageIPWhitelist(whitelist, clientIP string) bool { return false } -func generateSyncImagePublicID() string { +func generateSyncImagePublicID() (string, error) { b := make([]byte, 16) if _, err := crand.Read(b); err != nil { - panic("crypto/rand failed: " + err.Error()) + return "", err } - return "task_" + hex.EncodeToString(b) + return "task_" + hex.EncodeToString(b), nil } func writeSyncImageError(w http.ResponseWriter, statusCode int, message string) { diff --git a/internal/handler/relay/sync_image_test.go b/internal/handler/relay/sync_image_test.go index ef981f9..4766826 100644 --- a/internal/handler/relay/sync_image_test.go +++ b/internal/handler/relay/sync_image_test.go @@ -94,7 +94,10 @@ func TestCheckSyncImageIPWhitelist(t *testing.T) { } func TestGenerateSyncImagePublicID(t *testing.T) { - id := generateSyncImagePublicID() + id, err := generateSyncImagePublicID() + if err != nil { + t.Fatalf("generateSyncImagePublicID() error = %v", err) + } if !strings.HasPrefix(id, "task_") { t.Fatalf("id %q should have task_ prefix", id) } @@ -102,7 +105,11 @@ func TestGenerateSyncImagePublicID(t *testing.T) { t.Fatalf("id %q length = %d, want %d", id, len(id), len("task_")+32) } // 唯一性(极小概率碰撞) - if generateSyncImagePublicID() == id { + id2, err := generateSyncImagePublicID() + if err != nil { + t.Fatalf("generateSyncImagePublicID() error = %v", err) + } + if id2 == id { t.Fatal("two ids should differ") } }