feat(API): 添加代码执行 API 路由
- 创建 /api/code/execute 接口 - 集成 Piston API 支持多语言远程执行 - 添加执行超时和错误处理
This commit is contained in:
parent
192cd175da
commit
d6f2c47ddc
143
src/app/api/code/execute/route.ts
Normal file
143
src/app/api/code/execute/route.ts
Normal file
@ -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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user