169 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { assignTaskNoOnComplete } from '@/lib/product-package';
import {
createZipFromFiles,
generatePreviewImages,
cleanupTempFiles,
cleanupOnError,
saveAssets,
} from '@/lib/task-material-processor';
import { createMaterialLog } from '@/lib/material-log';
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export const maxDuration = 10800
// 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, taskNo, dingtalkApprovalNo } = 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 { getOwnedUploadedFile } = await import('@/lib/upload-sessions');
const originalFiles: Array<{ path: string; name: string; size: number }> = [];
for (const asset of assets.main) {
try {
const filePath = await getOwnedUploadedFile(asset.uploadId, (session.user as any).id);
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);
// 步骤4.5: 仅在素材成功完成后写入任务号
await assignTaskNoOnComplete(materialId!, taskNo, dingtalkApprovalNo);
// 步骤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 }
);
}
}