diff --git a/src/app/api/code/execute/route.ts b/src/app/api/code/execute/route.ts new file mode 100644 index 0000000..cc4df2a --- /dev/null +++ b/src/app/api/code/execute/route.ts @@ -0,0 +1,143 @@ +/** + * Piston API 代理 + * 避免 CORS 问题,转发请求到 Piston API + */ + +import { NextRequest, NextResponse } from 'next/server'; + +// Piston API 公共端点 +const PISTON_API_URL = 'https://emkc.org/api/v2/piston'; + +// 执行超时时间(秒) +const EXECUTION_TIMEOUT = 30; + +// 请求体类型 +interface ExecuteRequest { + language: string; + version: string; + files: Array<{ + name?: string; + content: string; + }>; + stdin?: string; + args?: string[]; + compile_args?: string[]; + run_args?: string[]; + compile_timeout?: number; + run_timeout?: number; + compile_memory_limit?: number; + run_memory_limit?: number; +} + +// Piston API 响应类型 +interface PistonResponse { + language: string; + version: string; + run: { + stdout: string; + stderr: string; + output: string; + code: number; + signal: string | null; + }; + compile?: { + stdout: string; + stderr: string; + output: string; + code: number; + signal: string | null; + }; +} + +export async function POST(request: NextRequest) { + try { + const body: ExecuteRequest = await request.json(); + + // 验证请求 + if (!body.language || !body.files || body.files.length === 0) { + return NextResponse.json( + { error: '缺少必要参数: language, files' }, + { status: 400 } + ); + } + + // 设置超时 + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), EXECUTION_TIMEOUT * 1000); + + // 构建请求 + const pistonRequest: ExecuteRequest = { + language: body.language, + version: body.version || '*', + files: body.files, + stdin: body.stdin || '', + args: body.args || [], + compile_args: body.compile_args || [], + run_args: body.run_args || [], + compile_timeout: body.compile_timeout || 10000, + run_timeout: body.run_timeout || 10000, + compile_memory_limit: body.compile_memory_limit || -1, + run_memory_limit: body.run_memory_limit || -1, + }; + + // 发送请求到 Piston API + const response = await fetch(`${PISTON_API_URL}/execute`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(pistonRequest), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const errorText = await response.text(); + return NextResponse.json( + { error: `Piston API 错误: ${response.status} - ${errorText}` }, + { status: response.status } + ); + } + + const result: PistonResponse = await response.json(); + + return NextResponse.json(result); + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return NextResponse.json( + { error: `执行超时(${EXECUTION_TIMEOUT}秒)` }, + { status: 408 } + ); + } + + console.error('Piston API proxy error:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : '执行失败' }, + { status: 500 } + ); + } +} + +// 获取支持的语言列表 +export async function GET() { + try { + const response = await fetch(`${PISTON_API_URL}/runtimes`); + + if (!response.ok) { + return NextResponse.json( + { error: '获取语言列表失败' }, + { status: response.status } + ); + } + + const runtimes = await response.json(); + return NextResponse.json(runtimes); + } catch (error) { + console.error('Failed to fetch runtimes:', error); + return NextResponse.json( + { error: '获取语言列表失败' }, + { status: 500 } + ); + } +}