import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { createZipFromFiles, generatePreviewImages, cleanupTempFiles, cleanupOnError, saveAssets, } from '@/lib/task-material-processor'; import { createMaterialLog } from '@/lib/material-log'; // POST - 完成3D原图上传 export async function POST(request: NextRequest) { let materialId: string | null = null; const tempFiles: string[] = []; // 需要清理的临时文件路径 let zipFilePath: string | null = null; const uploadedPreviewUrls: string[] = []; // 新生成的S3预览图URLs const uploadIds: string[] = []; // 需要清理的uploadId列表 try { const session = await getServerSession(authOptions); if (!session) { return NextResponse.json({ error: '请先登录' }, { status: 401 }); } const body = await request.json(); const { materialId: matId, assets } = body; materialId = matId; if (!materialId || !assets) { return NextResponse.json( { error: '缺少必填字段' }, { status: 400 } ); } if (!assets.main || assets.main.length === 0) { return NextResponse.json( { error: '至少需要上传一张原图' }, { status: 400 } ); } console.log('[3D Original Complete] Material ID:', materialId); console.log('[3D Original Complete] Original images count:', assets.main.length); // 验证素材是否存在且属于当前用户 const material: any = await prisma.material.findUnique({ where: { id: materialId }, }); if (!material) { return NextResponse.json( { success: false, error: '素材不存在' }, { status: 404 } ); } if (material.userId !== (session.user as any).id) { return NextResponse.json( { success: false, error: '无权限操作此素材' }, { status: 403 } ); } // 步骤1: 从chunk临时目录读取原图文件 console.log(`[3D Original] Processing ${assets.main.length} original images...`); const { getUploadedFile } = await import('@/lib/upload-sessions'); const originalFiles: Array<{ path: string; name: string; size: number }> = []; for (const asset of assets.main) { try { const filePath = await getUploadedFile(asset.uploadId); if (filePath) { originalFiles.push({ path: filePath, name: asset.fileName, size: asset.fileSize, }); tempFiles.push(filePath); uploadIds.push(asset.uploadId); } else { throw new Error(`找不到uploadId对应的文件: ${asset.uploadId}`); } } catch (error) { console.error(`[3D Original] 获取原图文件失败 ${asset.uploadId}:`, error); throw new Error(`获取原图文件失败: ${asset.fileName}`); } } // 步骤2: 打包原图成zip(保存到本地文件系统) console.log(`[3D Original] Creating zip from ${originalFiles.length} original files...`); const zipInfo = await createZipFromFiles( originalFiles, materialId!, material.productModel || '3d-original', '任务-3D原图' ); console.log(`[3D Original] Zip created: ${zipInfo.fileName} (${zipInfo.fileSize} bytes)`); zipFilePath = zipInfo.filePath; // 步骤3: 生成预览图并上传到S3 const previewUrls = await generatePreviewImages( assets.main.map((a: any) => ({ uploadId: a.uploadId, fileName: a.fileName })), '任务-3D原图' ); uploadedPreviewUrls.push(...previewUrls); if (uploadedPreviewUrls.length === 0) { throw new Error('生成预览图失败,至少需要一张预览图'); } // 步骤4: 保存资产记录到数据库 await saveAssets(materialId!, uploadedPreviewUrls, zipInfo); // 步骤5: 清理临时文件 await cleanupTempFiles(tempFiles, uploadIds); // 步骤6: 记录上传日志 await createMaterialLog({ materialId: materialId!, userId: (session.user as any).id, action: 'UPLOAD', changes: { uploadType: { oldValue: null, newValue: '任务-3D原图', }, previewCount: { oldValue: 0, newValue: uploadedPreviewUrls.length, }, zipFileName: { oldValue: null, newValue: zipInfo.fileName, }, }, }); console.log('[3D Original] Complete success!'); return NextResponse.json({ success: true, materialId, }); } catch (error: any) { console.error('[3D Original Complete Error]', error); // 清理失败时的资源 await cleanupOnError(uploadedPreviewUrls, zipFilePath, tempFiles, materialId); return NextResponse.json( { error: error.message || '完成上传失败' }, { status: 500 } ); } }