From 17f54855226d41b5ec98d2b70faf86a6839b1299 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 7 Jul 2026 23:40:34 +0800 Subject: [PATCH] fix: harden scale adjust product lock uploads --- app/api/canvas/local-edit/route.ts | 205 +++++++++++++++-------------- lib/storage/service.ts | 86 ++++++++++++ 2 files changed, 189 insertions(+), 102 deletions(-) diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 760f49063..1aa59b6db 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -3828,57 +3828,66 @@ async function createScaleAdjustPatchReference(params: { }); const targetDraftBuffer = targetDraftReference.buffer; const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const targetDraftUploadBuffer = await sharp(targetDraftBuffer, { failOn: "none" }) + .flatten({ background: "#ffffff" }) + .jpeg({ quality: 90, mozjpeg: true }) + .toBuffer(); + const backgroundPlateUploadBuffer = targetDraftReference.backgroundPlateBuffer + ? await sharp(targetDraftReference.backgroundPlateBuffer, { failOn: "none" }) + .flatten({ background: "#ffffff" }) + .jpeg({ quality: 90, mozjpeg: true }) + .toBuffer() + : null; + const uploadOptionalScaleAdjustAsset = async ( + filename: string, + buffer: Buffer | null | undefined + ) => { + if (!buffer) return null; + try { + return await uploadFile(filename, toArrayBuffer(buffer)); + } catch (error) { + console.warn("[Canvas Local Edit] Optional scale adjust asset upload failed:", { + error: error instanceof Error ? error.message : String(error), + filename, + }); + return null; + } + }; if (params.fullFrameReferenceOnly && structureLockAppliedRef.value) { - const [ - targetDraftFullUpload, - backgroundPlateFullUpload, - productLockOverlayUpload, - productLockProtectMaskUpload, - compositeClipMaskUpload, - productLockEditMaskUpload, - productLockFalEditMaskUpload, - ] = await Promise.all([ - uploadFile( - `canvas-scale-adjust-full-target-draft-${suffix}.png`, - toArrayBuffer(targetDraftBuffer) - ), - targetDraftReference.backgroundPlateBuffer - ? uploadFile( - `canvas-scale-adjust-background-plate-${suffix}.png`, - toArrayBuffer(targetDraftReference.backgroundPlateBuffer) - ) - : Promise.resolve(null), + const targetDraftFullUpload = await uploadFile( + `canvas-scale-adjust-full-target-draft-${suffix}.jpg`, + toArrayBuffer(targetDraftUploadBuffer) + ); + const productLockOverlayUpload = await uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-overlay-${suffix}.png`, targetDraftReference.productLockOverlayBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-overlay-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockOverlayBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockProtectMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-protect-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockProtectMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.compositeClipMaskBuffer - ? uploadFile( - `canvas-scale-adjust-composite-clip-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.compositeClipMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockEditMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-edit-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockEditMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockFalEditMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-fal-edit-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockFalEditMaskBuffer) - ) - : Promise.resolve(null), - ]); + ); + const [productLockEditMaskUpload, productLockFalEditMaskUpload] = + await Promise.all([ + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-edit-mask-${suffix}.png`, + targetDraftReference.productLockEditMaskBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-fal-edit-mask-${suffix}.png`, + targetDraftReference.productLockFalEditMaskBuffer + ), + ]); + const [backgroundPlateFullUpload, productLockProtectMaskUpload, compositeClipMaskUpload] = + await Promise.all([ + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-background-plate-${suffix}.jpg`, + backgroundPlateUploadBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-protect-mask-${suffix}.png`, + targetDraftReference.productLockProtectMaskBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-composite-clip-mask-${suffix}.png`, + targetDraftReference.compositeClipMaskBuffer + ), + ]); return { backgroundPlateFullUrl: backgroundPlateFullUpload?.url || null, @@ -3907,10 +3916,22 @@ async function createScaleAdjustPatchReference(params: { .png({ compressionLevel: 6 }) .toBuffer(), ]); + const [sourceCropUpload, targetDraftUpload, targetDraftFullUpload] = + await Promise.all([ + uploadFile( + `canvas-scale-adjust-workspace-source-${suffix}.png`, + toArrayBuffer(sourceCropBuffer) + ), + uploadFile( + `canvas-scale-adjust-workspace-target-draft-${suffix}.png`, + toArrayBuffer(targetDraftCropBuffer) + ), + uploadFile( + `canvas-scale-adjust-full-target-draft-${suffix}.jpg`, + toArrayBuffer(targetDraftUploadBuffer) + ), + ]); const [ - sourceCropUpload, - targetDraftUpload, - targetDraftFullUpload, backgroundPlateFullUpload, productLockOverlayUpload, productLockProtectMaskUpload, @@ -3918,54 +3939,30 @@ async function createScaleAdjustPatchReference(params: { productLockEditMaskUpload, productLockFalEditMaskUpload, ] = await Promise.all([ - uploadFile( - `canvas-scale-adjust-workspace-source-${suffix}.png`, - toArrayBuffer(sourceCropBuffer) + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-background-plate-${suffix}.jpg`, + backgroundPlateUploadBuffer ), - uploadFile( - `canvas-scale-adjust-workspace-target-draft-${suffix}.png`, - toArrayBuffer(targetDraftCropBuffer) + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-overlay-${suffix}.png`, + targetDraftReference.productLockOverlayBuffer ), - uploadFile( - `canvas-scale-adjust-full-target-draft-${suffix}.png`, - toArrayBuffer(targetDraftBuffer) + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-protect-mask-${suffix}.png`, + targetDraftReference.productLockProtectMaskBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-composite-clip-mask-${suffix}.png`, + targetDraftReference.compositeClipMaskBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-edit-mask-${suffix}.png`, + targetDraftReference.productLockEditMaskBuffer + ), + uploadOptionalScaleAdjustAsset( + `canvas-scale-adjust-product-lock-fal-edit-mask-${suffix}.png`, + targetDraftReference.productLockFalEditMaskBuffer ), - targetDraftReference.backgroundPlateBuffer - ? uploadFile( - `canvas-scale-adjust-background-plate-${suffix}.png`, - toArrayBuffer(targetDraftReference.backgroundPlateBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockOverlayBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-overlay-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockOverlayBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockProtectMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-protect-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockProtectMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.compositeClipMaskBuffer - ? uploadFile( - `canvas-scale-adjust-composite-clip-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.compositeClipMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockEditMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-edit-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockEditMaskBuffer) - ) - : Promise.resolve(null), - targetDraftReference.productLockFalEditMaskBuffer - ? uploadFile( - `canvas-scale-adjust-product-lock-fal-edit-mask-${suffix}.png`, - toArrayBuffer(targetDraftReference.productLockFalEditMaskBuffer) - ) - : Promise.resolve(null), ]); return { @@ -5349,14 +5346,18 @@ export async function POST(request: NextRequest) { regions.some((region) => isScaleAdjustFurnitureProductLikePrompt(region.prompt) ); - const useScaleAdjustProductLockComposite = + const canUseScaleAdjustProductLockMaskEdit = useScaleAdjustFullFrameStructurePatch && - Boolean(scaleAdjustPatchReference?.backgroundPlateFullUrl) && - Boolean(scaleAdjustPatchReference?.productLockOverlayUrl); - const useScaleAdjustProductLockMaskEdit = - useScaleAdjustProductLockComposite && isScaleAdjustMaskCapableModel(resolvedScaleAdjustModelEntry) && Boolean(scaleAdjustPatchReference?.productLockEditMaskUrl); + const useScaleAdjustProductLockComposite = + useScaleAdjustFullFrameStructurePatch && + Boolean(scaleAdjustPatchReference?.productLockOverlayUrl) && + (Boolean(scaleAdjustPatchReference?.backgroundPlateFullUrl) || + canUseScaleAdjustProductLockMaskEdit); + const useScaleAdjustProductLockMaskEdit = + useScaleAdjustProductLockComposite && + canUseScaleAdjustProductLockMaskEdit; const useScaleAdjustPatchTargetDraftPrimary = Boolean(scaleAdjustPatchReference); const includeScaleAdjustPatchSourceReference = Boolean(scaleAdjustPatchReference); diff --git a/lib/storage/service.ts b/lib/storage/service.ts index 96ae55bea..b10224424 100644 --- a/lib/storage/service.ts +++ b/lib/storage/service.ts @@ -9,6 +9,7 @@ import { } from "@/lib/db/queries"; import { enqueueStorageMigrationTask, + getEnabledS3StorageConfigs, getPreferredAliyunStorageConfig, isAliyunStorageConfig, } from "@/lib/storage/migration-db"; @@ -491,6 +492,43 @@ async function uploadWithBackupStorageConfig( } } +async function uploadWithAnyEnabledStorageConfig( + filename: string, + buffer: ArrayBuffer, + skipStorageConfigIds: Array +): Promise< + | { + uploaded: { url: string; pathname: string; contentType: string }; + storageConfig?: StorageConfig; + } + | null +> { + const skipped = new Set(skipStorageConfigIds.filter(Boolean) as string[]); + try { + const configs = await getEnabledS3StorageConfigs(); + for (const storageConfig of configs) { + if (skipped.has(storageConfig.id)) continue; + const s3Config = toDbS3UploadConfig(storageConfig); + if (!s3Config) continue; + + try { + logger.debug( + `[Upload] Retrying with enabled S3 config: ${storageConfig.name ?? storageConfig.id}` + ); + const uploaded = await uploadToS3(filename, buffer, s3Config); + lastKnownGoodDbS3Config = s3Config; + return { uploaded, storageConfig }; + } catch (error) { + logger.error("[Upload] Enabled S3 config retry failed:", error); + } + } + } catch (error) { + logger.error("[Upload] Failed to list enabled S3 configs for retry:", error); + } + + return null; +} + // 上传文件到存储服务 export async function uploadFile( filename: string, @@ -546,6 +584,22 @@ export async function uploadFile( return uploadedFromBackup.uploaded; } + const uploadedFromEnabledConfig = await uploadWithAnyEnabledStorageConfig( + filename, + buffer, + [primaryStorageConfigId] + ); + if (uploadedFromEnabledConfig) { + await maybeEnqueueAliyunMigrationTask({ + uploaded: uploadedFromEnabledConfig.uploaded, + storageConfig: uploadedFromEnabledConfig.storageConfig, + migrationConfig, + }).catch((enqueueError) => { + logger.error("[Upload] Failed to enqueue enabled S3 OSS migration task:", enqueueError); + }); + return uploadedFromEnabledConfig.uploaded; + } + if (lastKnownGoodDbS3Config) { try { logger.debug("[Upload] Retrying with last known good database S3 config"); @@ -572,6 +626,22 @@ export async function uploadFile( }); return uploadedFromBackup.uploaded; } + + const uploadedFromEnabledConfig = await uploadWithAnyEnabledStorageConfig( + filename, + buffer, + [primaryStorageConfigId] + ); + if (uploadedFromEnabledConfig) { + await maybeEnqueueAliyunMigrationTask({ + uploaded: uploadedFromEnabledConfig.uploaded, + storageConfig: uploadedFromEnabledConfig.storageConfig, + migrationConfig, + }).catch((enqueueError) => { + logger.error("[Upload] Failed to enqueue enabled S3 OSS migration task:", enqueueError); + }); + return uploadedFromEnabledConfig.uploaded; + } } const envS3Config = buildEnvS3Config(); @@ -651,6 +721,22 @@ export async function createStorageUploadSession(): Promise { + logger.error("[Upload] Failed to enqueue enabled S3 OSS migration task:", migrationError); + }); + return uploadedFromEnabledConfig.uploaded; + } + throw error; } };