主要改动: 1. TCP缓冲区优化(16MB)- 解决公网上传速度慢问题 2. 并发数降低到1-3 - 避免4并发导致ERR_CONNECTION_RESET 3. 动态并发控制 - 根据网络环境自动调整 4. Nginx超时增加到120s - 支持大分片上传 5. 上传进度修复 - 修复剩余时间计算 6. 新增任务-页面上传器和详情组件 7. 统一部署文档为deploy-remote-build.ps1 问题修复: - 公网用户上传从200-900KB/s提升到预期3-5MB/s - 内网用户上传更稳定,无连接重置错误 - UploadProgress组件剩余时间计算修正 Co-Authored-By: Claude <noreply@anthropic.com>
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
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';
|
||
|
||
// POST - 完成任务-页面上传
|
||
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('[Task Page Complete] Material ID:', materialId);
|
||
console.log('[Task Page Complete] Preview 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(`[Task Page] Processing ${assets.main.length} preview images...`);
|
||
|
||
const { getUploadedFile } = await import('@/lib/upload-sessions');
|
||
const previewFiles: Array<{ path: string; name: string; size: number }> = [];
|
||
|
||
for (const asset of assets.main) {
|
||
try {
|
||
const filePath = await getUploadedFile(asset.uploadId);
|
||
if (filePath) {
|
||
previewFiles.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(`[Task Page] 获取预览图文件失败 ${asset.uploadId}:`, error);
|
||
throw new Error(`获取预览图文件失败: ${asset.fileName}`);
|
||
}
|
||
}
|
||
|
||
// 步骤2: 打包预览图成zip(保存到本地文件系统)
|
||
console.log(`[Task Page] Creating zip from ${previewFiles.length} preview files...`);
|
||
const zipInfo = await createZipFromFiles(
|
||
previewFiles,
|
||
materialId!,
|
||
material.productModel || 'task-page',
|
||
'任务-页面'
|
||
);
|
||
console.log(`[Task Page] 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 })),
|
||
'任务-页面'
|
||
);
|
||
uploadedPreviewUrls.push(...previewUrls);
|
||
|
||
if (uploadedPreviewUrls.length === 0) {
|
||
throw new Error('生成预览图失败,至少需要一张预览图');
|
||
}
|
||
|
||
// 步骤4: 保存资产记录到数据库
|
||
await saveAssets(materialId!, uploadedPreviewUrls, zipInfo);
|
||
|
||
// 步骤5: 清理临时文件
|
||
await cleanupTempFiles(tempFiles, uploadIds);
|
||
|
||
console.log('[Task Page] Complete success!');
|
||
return NextResponse.json({
|
||
success: true,
|
||
materialId,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('[Task Page Complete Error]', error);
|
||
|
||
// 清理失败时的资源
|
||
await cleanupOnError(uploadedPreviewUrls, zipFilePath, tempFiles, materialId);
|
||
|
||
return NextResponse.json(
|
||
{ error: error.message || '完成上传失败' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|