Files
suanfa/server/server.cjs
T

220 lines
6.6 KiB
JavaScript
Raw Normal View History

2026-06-15 10:59:56 +08:00
/**
* C 代码编译运行后端服务
* 接收 C 代码 → 编译 → 运行 → 返回输出
*/
const express = require('express')
const cors = require('cors')
const { execSync } = require('child_process')
const path = require('path')
const fs = require('fs')
const app = express()
const PORT = 3001
app.use(cors())
app.use(express.json({ limit: '1mb' }))
// 查找可用的 C 编译器
// w64devkit GCC 路径
const W64DEVKIT_DIR = path.join(__dirname, 'bin', 'w64devkit', 'w64devkit')
const W64DEVKIT_GCC = path.join(W64DEVKIT_DIR, 'bin', 'gcc.exe')
const W64DEVKIT_PREFIX = path.join(W64DEVKIT_DIR, 'lib', 'gcc')
function findCompiler() {
const candidates = [
// MinGW / MSYS2 (PATH)
'gcc.exe',
'x86_64-w64-mingw32-gcc.exe',
'i686-w64-mingw32-gcc.exe',
// TCC (Tiny C Compiler)
'tcc.exe',
// Clang
'clang.exe',
// MSVC
'cl.exe',
// 本项目的便携编译器
path.join(__dirname, 'bin', 'tcc.exe'),
path.join(__dirname, 'bin', 'gcc.exe'),
path.join(__dirname, '..', 'tools', 'tcc', 'tcc.exe'),
]
// 先在 PATH 中查找
for (const name of ['gcc.exe', 'tcc.exe', 'clang.exe', 'cl.exe']) {
try {
execSync(`where ${name}`, { stdio: 'pipe', timeout: 3000 })
return { path: name, name }
} catch {}
}
// 检查 w64devkit GCC(需设置 GCC_EXEC_PREFIX
if (fs.existsSync(W64DEVKIT_GCC)) {
return { path: W64DEVKIT_GCC, name: 'gcc.exe', execPrefix: W64DEVKIT_PREFIX }
}
// 再检查其他具体路径
for (const candidate of candidates) {
try {
const absPath = path.resolve(candidate)
if (fs.existsSync(absPath)) {
return { path: absPath, name: path.basename(absPath) }
}
} catch {}
}
// 搜索 Visual Studio MSVC 安装目录
try {
const vsPaths = [
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC',
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC',
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC',
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC',
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC',
]
for (const vsPath of vsPaths) {
if (fs.existsSync(vsPath)) {
const versions = fs.readdirSync(vsPath).sort().reverse()
for (const ver of versions) {
const clPath = path.join(vsPath, ver, 'bin', 'Hostx64', 'x64', 'cl.exe')
if (fs.existsSync(clPath)) {
return { path: clPath, name: 'cl.exe' }
}
}
}
}
} catch {}
return null
}
// 编译并运行 C 代码
app.post('/api/run-c', (req, res) => {
const { code } = req.body
if (!code) {
return res.status(400).json({ error: '请提供 C 代码' })
}
const compiler = findCompiler()
if (!compiler) {
return res.status(400).json({
error: '未找到 C 编译器',
hint: '请安装 MinGW-w64 GCC 编译器',
installGuide: [
'--- 安装 MinGW-w64 ---',
'方法一(推荐):下载 MinGW-w64 GCC',
' 1. 访问 https://winlibs.com/ 下载 GCC (MinGW-w64)',
' 2. 解压并将 bin/ 目录添加到系统 PATH 环境变量',
' 3. 重启终端后即可使用',
'',
'方法二(轻量):下载 TCC (Tiny C Compiler)',
' 将 tcc.exe 放入 server/bin/ 目录',
'',
'安装后重新启动: npm run server',
].join('\n')
})
}
const tmpDir = path.join(__dirname, '..', 'tmp')
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, { recursive: true })
}
const srcFile = path.join(tmpDir, `code_${Date.now()}.c`)
const outFile = path.join(tmpDir, `code_${Date.now()}.exe`)
try {
// 写入源码
fs.writeFileSync(srcFile, code, 'utf-8')
let compileCmd
const execOptions = {
timeout: 15000,
cwd: tmpDir,
encoding: 'utf-8',
env: { ...process.env }
}
if (compiler.name.includes('tcc')) {
// TCC: 编译并运行一步到位(支持 -run)
compileCmd = `"${compiler.path}" -run "${srcFile}"`
} else if (compiler.execPrefix) {
// w64devkit GCC: 需要设置 GCC_EXEC_PREFIX 并加入 PATH
const w64binDir = path.dirname(compiler.path)
execOptions.env.GCC_EXEC_PREFIX = compiler.execPrefix + path.sep
execOptions.env.PATH = w64binDir + path.delimiter + (execOptions.env.PATH || '')
compileCmd = `"${compiler.path}" "${srcFile}" -o "${outFile}" -Wall -std=c99`
} else {
// GCC/Clang: 先编译再运行
compileCmd = `"${compiler.path}" "${srcFile}" -o "${outFile}" -Wall -std=c99 2>&1`
}
// 编译
const compileOutput = execSync(compileCmd, execOptions)
let runOutput = compileOutput
// GCC/Clang: 编译后还需运行 exe
if (!compiler.name.includes('tcc') && fs.existsSync(outFile)) {
try {
runOutput = execSync(`"${outFile}"`, {
timeout: 10000,
cwd: tmpDir,
encoding: 'utf-8',
env: execOptions.env
})
} catch (runErr) {
runOutput = runErr.stdout || runErr.message
}
}
// 清理临时文件
try {
if (fs.existsSync(srcFile)) fs.unlinkSync(srcFile)
if (fs.existsSync(outFile)) fs.unlinkSync(outFile)
} catch {}
res.json({ output: runOutput })
} catch (compileErr) {
// 编译错误
res.json({
output: compileErr.stdout || compileErr.message,
isError: true
})
} finally {
// 确保清理
try {
if (fs.existsSync(srcFile)) fs.unlinkSync(srcFile)
if (fs.existsSync(outFile)) fs.unlinkSync(outFile)
} catch {}
}
})
// 健康检查
app.get('/api/status', (req, res) => {
const compiler = findCompiler()
res.json({
status: 'ok',
compiler: compiler ? `${compiler.name} (${compiler.path})` : '未安装'
})
})
// 下载 TCC 脚本
app.get('/api/setup-compiler', (req, res) => {
res.json({
message: '请手动下载 TCC:',
url: 'https://raw.githubusercontent.com/FooBarWidget/tinycc/master/tcc.exe',
instructions: `将 tcc.exe 放入 ${path.join(__dirname, 'bin')} 目录`
})
})
app.listen(PORT, () => {
console.log(`✅ C 代码运行服务已启动: http://localhost:${PORT}`)
const compiler = findCompiler()
if (compiler) {
console.log(` 检测到编译器: ${compiler.name}`)
} else {
console.log(` ⚠️ 未检测到 C 编译器,请安装后使用`)
console.log(` 下载 TCC: https://raw.githubusercontent.com/FooBarWidget/tinycc/master/tcc.exe`)
console.log(` 放置路径: ${path.join(__dirname, 'bin', 'tcc.exe')}`)
}
})