chenpei cd6e40d9d2 优化:网络传输性能和稳定性 - TCP缓冲区+并发控制优化
主要改动:
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>
2026-01-27 09:33:09 +08:00

139 lines
4.3 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 {
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 }
);
}
}