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

177 lines
5.4 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.

// 弹幕API - 支持SSE实时推送和普通查询
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
// GET - 获取弹幕列表(支持SSE实时推送)
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const isSSE = searchParams.get('sse') === 'true';
// SSE模式 - 实时推送弹幕
if (isSSE) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
let interval: NodeJS.Timeout | null = null;
let lastActivity = Date.now();
const maxIdleTime = 5 * 60 * 1000; // 5分钟无活动自动关闭
const maxConnectionTime = 30 * 60 * 1000; // 最长连接30分钟
const connectionStartTime = Date.now();
// 清理函数
const cleanup = () => {
if (interval) {
clearInterval(interval);
interval = null;
}
try {
controller.close();
} catch (err) {
// 忽略关闭错误(可能已经关闭)
}
console.log('[Danmaku SSE] Connection closed and cleaned up');
};
try {
// 首次发送最近50条弹幕
const recentDanmaku = await prisma.danmaku.findMany({
orderBy: { createdAt: 'desc' },
take: 50,
});
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(recentDanmaku)}\n\n`)
);
// 每秒检查新弹幕
interval = setInterval(async () => {
try {
// 检查是否超时(无活动或连接时间过长)
const now = Date.now();
if (now - lastActivity > maxIdleTime) {
console.log('[Danmaku SSE] Connection idle timeout, closing');
cleanup();
return;
}
if (now - connectionStartTime > maxConnectionTime) {
console.log('[Danmaku SSE] Connection time limit reached, closing');
cleanup();
return;
}
const newDanmaku = await prisma.danmaku.findMany({
orderBy: { createdAt: 'desc' },
take: 1,
});
if (newDanmaku.length > 0) {
lastActivity = now;
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(newDanmaku[0])}\n\n`)
);
}
} catch (error) {
console.error('[Danmaku SSE] Error in interval:', error);
cleanup();
}
}, 1000);
// 客户端断开连接时清理
request.signal.addEventListener('abort', () => {
console.log('[Danmaku SSE] Client aborted connection');
cleanup();
});
} catch (error) {
console.error('[Danmaku SSE] Error in start:', error);
cleanup();
}
},
});
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
// 普通模式 - 返回弹幕列表
const limit = parseInt(searchParams.get('limit') || '50');
const danmaku = await prisma.danmaku.findMany({
orderBy: { createdAt: 'desc' },
take: limit,
});
return NextResponse.json(danmaku);
} catch (error) {
console.error('Get danmaku error:', error);
return NextResponse.json(
{ error: '获取弹幕失败' },
{ status: 500 }
);
}
}
// POST - 发送弹幕
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json(
{ error: '请先登录' },
{ status: 401 }
);
}
const { content } = await request.json();
// 验证弹幕内容
if (!content || content.trim().length === 0) {
return NextResponse.json(
{ error: '弹幕内容不能为空' },
{ status: 400 }
);
}
if (content.length > 100) {
return NextResponse.json(
{ error: '弹幕内容不能超过100字' },
{ status: 400 }
);
}
// 随机匿名昵称
const anonymousNames = [
'神秘访客', '路过的设计师', '热心网友', '匿名用户',
'悄悄路过', '神秘嘉宾', '隐身大佬', '低调看客',
'默默围观', '潜水员', '匿名网友', '神秘人士'
];
const randomName = anonymousNames[Math.floor(Math.random() * anonymousNames.length)];
// 创建弹幕
const userId = (session.user as any).id;
const danmaku = await prisma.danmaku.create({
data: {
content: content.trim(),
username: randomName,
userId: userId,
},
});
return NextResponse.json(danmaku);
} catch (error) {
console.error('Send danmaku error:', error);
return NextResponse.json(
{ error: '发送弹幕失败' },
{ status: 500 }
);
}
}