fix(worker): 将 Gemini 同步请求交由异步队列执行

将非流式 Gemini generateContent 改为 River Worker 执行,API 通过批量状态查询等待完成,避免高并发同步请求耗尽 API 数据库连接池。\n\n生成图片在持久化时保存带哈希的资产引用,响应恢复时下载并校验后重建 Base64,数据库不保存媒体原文。请求体物化增加前置内存门禁,并扩展双 API/Worker PostgreSQL 压力测试。\n\n验证:go test ./... -count=1;go vet ./...;pnpm openapi;迁移安全检查;64 与 256 请求双角色异步 Gemini 压力测试。
This commit is contained in:
2026-07-31 05:49:45 +08:00
parent 352e23e099
commit 4af86b22ee
12 changed files with 516 additions and 48 deletions
@@ -2,9 +2,13 @@ package runner
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
@@ -112,6 +116,45 @@ func TestMaterializeAndHydrateLocalBinaryResult(t *testing.T) {
}
}
func TestHydrateGeneratedResultAssetReference(t *testing.T) {
payload := []byte("verified generated image bytes")
digest := sha256.Sum256(payload)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write(payload)
}))
defer server.Close()
reference := func(hash string) map[string]any {
return map[string]any{
"assetRef": map[string]any{
"sha256": hash,
"contentType": "image/png",
"size": len(payload),
"url": server.URL + "/generated.png",
},
"assetStorage": map[string]any{
"scene": store.FileStorageSceneImageResult,
"source": "b64_json",
},
}
}
service := &Service{}
result := map[string]any{"data": []any{map[string]any{"b64_json": reference(hex.EncodeToString(digest[:]))}}}
hydrated, err := service.HydrateTaskResult(context.Background(), "task-remote", result)
if err != nil {
t.Fatalf("hydrate generated result asset: %v", err)
}
item := hydrated["data"].([]any)[0].(map[string]any)
if got, want := item["b64_json"], base64.StdEncoding.EncodeToString(payload); got != want {
t.Fatalf("hydrated Base64=%v, want %v", got, want)
}
result = map[string]any{"data": []any{map[string]any{"b64_json": reference(strings.Repeat("0", 64))}}}
_, err = service.HydrateTaskResult(context.Background(), "task-corrupted", result)
assertClientErrorCode(t, err, "binary_result_corrupted")
}
func TestHydrateLocalBinaryResultReturnsExpiredAndCorruptedErrors(t *testing.T) {
service := newLocalBinaryTestService(t)
service.cfg.LocalResultTTLHours = 1