fix(storage): 补齐嵌套结果二进制转存

生产真实 Gemini 图片任务可能在标准 data 之外保留嵌套 Base64,导致最终持久化关口拒绝任务成功。\n\n在所有生成结果返回前统一执行递归转存,并对仍未被常规媒体转换识别的二进制回退到本地结果物化,确保 PostgreSQL 不接收原始二进制。\n\n验证:go test ./... -count=1;go vet ./...;使用生产 Gemini Flash Image 平台配置完成本地真实图片编辑请求,文件哈希一致且持久化结果无内联二进制。
This commit is contained in:
2026-07-24 21:57:52 +08:00
parent 4f163ea6d7
commit a2ed32447a
2 changed files with 103 additions and 6 deletions
+57 -6
View File
@@ -98,8 +98,7 @@ func (s *Service) uploadGeneratedAssets(ctx context.Context, taskID string, task
return next, nil
}
if len(data) == 0 && !rawNeedsUpload {
redactGeneratedResultRawData(result)
return result, nil
return s.finalizeGeneratedAssets(ctx, taskID, taskKind, result, policy, nil, false, 0)
}
// Topaz download URLs are short-lived. Persist them before the task can be
// marked succeeded even when the global policy normally keeps URL media.
@@ -128,15 +127,16 @@ func (s *Service) uploadGeneratedAssets(ctx context.Context, taskID string, task
}
rawNeedsUpload = rawNeedsUpload && policy.UploadInlineMedia
if !needsUpload && !changed && !rawNeedsUpload {
redactGeneratedResultRawData(result)
return result, nil
return s.finalizeGeneratedAssets(ctx, taskID, taskKind, result, policy, nil, false, 0)
}
var channels []store.FileStorageChannel
channelsLoaded := false
if needsUpload || rawNeedsUpload {
channels, err = s.activeFileStorageChannels(ctx, store.FileStorageSceneImageResult)
if err != nil {
return nil, &clients.ClientError{Code: "upload_config_failed", Message: err.Error(), Retryable: true}
}
channelsLoaded = true
}
next := map[string]any{}
for key, value := range result {
@@ -217,8 +217,59 @@ func (s *Service) uploadGeneratedAssets(ctx context.Context, taskID string, task
next["raw"] = raw
}
}
redactGeneratedResultRawData(next)
return next, nil
return s.finalizeGeneratedAssets(ctx, taskID, taskKind, next, policy, channels, channelsLoaded, len(nextData))
}
func (s *Service) finalizeGeneratedAssets(
ctx context.Context,
taskID string,
taskKind string,
result map[string]any,
policy generatedAssetUploadPolicy,
channels []store.FileStorageChannel,
channelsLoaded bool,
startIndex int,
) (map[string]any, error) {
redactGeneratedResultRawData(result)
if !TaskResultHasInlineBinary(result) {
return result, nil
}
next := result
if policy.UploadInlineMedia {
var err error
if !channelsLoaded {
channels, err = s.activeFileStorageChannels(ctx, store.FileStorageSceneImageResult)
if err != nil {
return nil, &clients.ClientError{Code: "upload_config_failed", Message: err.Error(), Retryable: true}
}
}
index := startIndex
uploaded, changed, err := s.uploadGeneratedRawMediaValue(ctx, taskID, taskKind, next, "", nil, policy, channels, &index)
if err != nil {
return nil, err
}
if changed {
mapped, ok := uploaded.(map[string]any)
if !ok {
return nil, &clients.ClientError{
Code: "result_binary_not_materialized",
Message: "generated result is not a JSON object",
StatusCode: 500,
Retryable: false,
}
}
next = mapped
}
}
if !TaskResultHasInlineBinary(next) {
return next, nil
}
persistent, _, err := s.materializeLocalBinaryResult(ctx, taskID, next)
if err != nil {
return nil, err
}
redactGeneratedResultRawData(persistent)
return persistent, nil
}
func generatedRawValueHasInlineMedia(value any, key string, siblings map[string]any) bool {