fix(storage): 补齐嵌套结果二进制转存
生产真实 Gemini 图片任务可能在标准 data 之外保留嵌套 Base64,导致最终持久化关口拒绝任务成功。\n\n在所有生成结果返回前统一执行递归转存,并对仍未被常规媒体转换识别的二进制回退到本地结果物化,确保 PostgreSQL 不接收原始二进制。\n\n验证:go test ./... -count=1;go vet ./...;使用生产 Gemini Flash Image 平台配置完成本地真实图片编辑请求,文件哈希一致且持久化结果无内联二进制。
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user