diff --git a/lib/__tests__/public-display.test.ts b/lib/__tests__/public-display.test.ts index 9bd955a..4559c59 100644 --- a/lib/__tests__/public-display.test.ts +++ b/lib/__tests__/public-display.test.ts @@ -13,13 +13,14 @@ describe('public display helpers', () => { beforeEach(() => { process.env.S3_PUBLIC_DOMAIN = 'https://s3.jiansun.vip/gongxiangpan' + delete process.env.S3_FILE_PATH }) afterEach(() => { process.env = { ...originalEnv } }) - test('应统一生成 public-display 对象路径', () => { + test('builds normalized public-display object paths', () => { expect(buildPublicDisplayObjectPath('/task-detail/material-1/main/cover.webp')).toBe( 'public-display/task-detail/material-1/main/cover.webp', ) @@ -28,13 +29,21 @@ describe('public display helpers', () => { ) }) - test('应基于当前 S3_PUBLIC_DOMAIN 生成 public-display 公网链接', () => { + test('builds public-display URLs from S3_PUBLIC_DOMAIN', () => { expect(buildPublicDisplayUrl('task-detail/material-1/detail/detail-1.webp')).toBe( 'https://s3.jiansun.vip/gongxiangpan/public-display/task-detail/material-1/detail/detail-1.webp', ) }) - test('应提供展示图常用路径构造函数', () => { + test('includes S3_FILE_PATH in public-display URLs when configured', () => { + process.env.S3_FILE_PATH = '由壹点AI/' + + expect(buildPublicDisplayUrl('white-background/material-1/sku-1/front.webp')).toBe( + 'https://s3.jiansun.vip/gongxiangpan/由壹点AI/public-display/white-background/material-1/sku-1/front.webp', + ) + }) + + test('builds common display relative paths', () => { expect(buildMaterialPublicDisplayPath('preview-1.webp')).toBe('materials/preview-1.webp') expect(buildTaskDetailPublicDisplayPath('material-1', 'detail', 'detail-1.webp')).toBe( 'task-detail/material-1/detail/detail-1.webp', diff --git a/lib/__tests__/white-background-upload-route.test.ts b/lib/__tests__/white-background-upload-route.test.ts index 531e406..19ffc71 100644 --- a/lib/__tests__/white-background-upload-route.test.ts +++ b/lib/__tests__/white-background-upload-route.test.ts @@ -73,6 +73,7 @@ describe('white-background upload route', () => { writeFile: jest.Mock unlink: jest.Mock } + let txMaterialUpdateMany: jest.Mock let consoleErrorSpy: jest.SpyInstance beforeEach(() => { @@ -99,8 +100,12 @@ describe('white-background upload route', () => { skuName: 'XG014799', }) ;(prisma.materialAsset.findMany as jest.Mock).mockResolvedValue([]) + txMaterialUpdateMany = jest.fn().mockResolvedValue({ count: 1 }) ;(prisma.$transaction as jest.Mock).mockImplementation(async (callback: any) => callback({ + material: { + updateMany: txMaterialUpdateMany, + }, materialAsset: { deleteMany: jest.fn().mockResolvedValue({ count: 0 }), createMany: jest.fn().mockResolvedValue({ count: 1 }), @@ -164,6 +169,17 @@ describe('white-background upload route', () => { Buffer.from('original-webp'), ) expect(prisma.$transaction).toHaveBeenCalled() + expect(txMaterialUpdateMany).toHaveBeenCalledWith({ + where: { + id: 'material-1', + previewUrl: '', + }, + data: { + previewUrl: expect.stringMatching( + /^https:\/\/s3\.jiansun\.vip\/gongxiangpan\/public-display\/white-background\/material-1\/sku-1\/XG014799-.*\.webp$/, + ), + }, + }) }) test('sourceType 缺失时应返回 400', async () => { diff --git a/lib/public-display.ts b/lib/public-display.ts index 0cd7423..34e3608 100644 --- a/lib/public-display.ts +++ b/lib/public-display.ts @@ -29,6 +29,17 @@ export function buildPublicDisplayObjectPath(relativePath: string): string { return `${PUBLIC_DISPLAY_PREFIX}/${normalized}` } +function buildStorageObjectPath(objectPath: string): string { + const normalizedPrefix = normalizeRelativePath(process.env.S3_FILE_PATH || '') + const normalizedObjectPath = normalizeRelativePath(objectPath) + + if (!normalizedPrefix) { + return normalizedObjectPath + } + + return normalizeRelativePath(`${normalizedPrefix}/${normalizedObjectPath}`) +} + export function buildPublicDisplayUrl(relativePath: string): string | null { const publicDomain = normalizeS3PublicDomain() const objectPath = buildPublicDisplayObjectPath(relativePath) @@ -37,7 +48,7 @@ export function buildPublicDisplayUrl(relativePath: string): string | null { return null } - return `${publicDomain}/${objectPath}` + return `${publicDomain}/${buildStorageObjectPath(objectPath)}` } export async function uploadPublicDisplayToS3( diff --git a/lib/standard-model-white-background-service.ts b/lib/standard-model-white-background-service.ts index 17be79f..25ac289 100644 --- a/lib/standard-model-white-background-service.ts +++ b/lib/standard-model-white-background-service.ts @@ -94,6 +94,16 @@ function isUsableFile(file: File | null | undefined): file is File { ) } +function resolvePreviewUrlFromWhiteBackgroundAsset(asset: { fileUrl?: string | null } | null | undefined): string | null { + const fileUrl = String(asset?.fileUrl || '').trim() + if (!fileUrl) { + return null + } + + const publicDisplayPath = buildWhiteBackgroundPublicDisplayPathFromAssetPath(fileUrl) + return publicDisplayPath ? buildPublicDisplayUrl(publicDisplayPath) : fileUrl +} + function getUploadedFixedViews(fixedImages: Partial>) { return STANDARD_MODEL_WHITE_BG_VIEWS.filter((view) => isUsableFile(fixedImages[view.key])) } @@ -223,6 +233,7 @@ export async function uploadStandardModelWhiteBackground( fileSize: bigint sortOrder: number }> = [] + let rightFront45PreviewUrl: string | null = null for (let index = 0; index < STANDARD_MODEL_WHITE_BG_VIEWS.length; index++) { const view = STANDARD_MODEL_WHITE_BG_VIEWS[index] @@ -253,6 +264,9 @@ export async function uploadStandardModelWhiteBackground( 'image/webp', ) uploadedPreviewUrls.push(previewUrl) + if (view.key === 'rightFront45') { + rightFront45PreviewUrl = previewUrl + } assetsToCreate.push({ materialId: input.materialId, @@ -313,6 +327,9 @@ export async function uploadStandardModelWhiteBackground( hasCompleteWhiteBackgroundAssetGroup({ fixed: nextFixed }) && nextFixedCount === STANDARD_MODEL_WHITE_BG_VIEWS.length const uploadedAt = new Date() + const materialPreviewUrl = + rightFront45PreviewUrl || + resolvePreviewUrlFromWhiteBackgroundAsset(existingWhiteBackgroundGroup?.fixed.rightFront45) await prisma.$transaction(async (tx) => { if (assetTypeListToReplace.length > 0) { @@ -340,6 +357,18 @@ export async function uploadStandardModelWhiteBackground( whiteBackgroundUploadedAt: uploadedAt, }, }) + + if (materialPreviewUrl) { + await tx.material.updateMany({ + where: { + id: input.materialId, + previewUrl: '', + }, + data: { + previewUrl: materialPreviewUrl, + }, + }) + } }) assetsPersisted = true