// 弹幕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 } ); } }