136 lines
3.8 KiB
TypeScript
136 lines
3.8 KiB
TypeScript
// 弹幕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) {
|
||
// 首次发送最近50条弹幕
|
||
const recentDanmaku = await prisma.danmaku.findMany({
|
||
orderBy: { createdAt: 'desc' },
|
||
take: 50,
|
||
});
|
||
|
||
controller.enqueue(
|
||
encoder.encode(`data: ${JSON.stringify(recentDanmaku)}\n\n`)
|
||
);
|
||
|
||
// 每秒检查新弹幕
|
||
const interval = setInterval(async () => {
|
||
try {
|
||
const newDanmaku = await prisma.danmaku.findMany({
|
||
orderBy: { createdAt: 'desc' },
|
||
take: 1,
|
||
});
|
||
|
||
if (newDanmaku.length > 0) {
|
||
controller.enqueue(
|
||
encoder.encode(`data: ${JSON.stringify(newDanmaku[0])}\n\n`)
|
||
);
|
||
}
|
||
} catch (error) {
|
||
console.error('SSE error:', error);
|
||
}
|
||
}, 1000);
|
||
|
||
// 客户端断开连接时清理
|
||
request.signal.addEventListener('abort', () => {
|
||
clearInterval(interval);
|
||
controller.close();
|
||
});
|
||
},
|
||
});
|
||
|
||
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 }
|
||
);
|
||
}
|
||
}
|