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
@@ -217,6 +217,38 @@ func (s *Server) acquireMediaRequestBodySlot(ctx context.Context) (func(), error
}
}
type mediaRequestBodyLeaseContextKey struct{}
type mediaRequestBodyLease struct {
release func()
}
func (l *mediaRequestBodyLease) Release() {
if l != nil && l.release != nil {
l.release()
}
}
func (s *Server) withMediaRequestBodySlot(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
release, err := s.acquireMediaRequestBodySlot(r.Context())
if err != nil {
return
}
lease := &mediaRequestBodyLease{release: release}
defer lease.Release()
ctx := context.WithValue(r.Context(), mediaRequestBodyLeaseContextKey{}, lease)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (s *Server) requestMediaBodyRelease(ctx context.Context) (func(), error) {
if lease, ok := ctx.Value(mediaRequestBodyLeaseContextKey{}).(*mediaRequestBodyLease); ok && lease != nil {
return lease.Release, nil
}
return s.acquireMediaRequestBodySlot(ctx)
}
func requestAssetFromValue(key string, path []string, value any, siblings map[string]any) (decodedRequestAsset, bool, error) {
text, ok := value.(string)
if !ok {