诊断是判题时写进库的,规则改了旧提交不跟着变。这个子命令把库里的诊断按现在的规则重做: Python 只重做已经有诊断的(重跑第一个失败测试点拿回溯,所以要在线上 oj-api 容器里跑), C / C++ 用 info 里存着的信号和退出码直接补上(原来一条都没有)。比赛提交不诊断, 重跑不出来的保留原样,只写 runtime_error 这一个键。默认只读预演,--apply 才写。 本机验过:dev 与编译两种形态都跑通,--apply 之后再跑是 0 条变化。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
3.2 KiB
TypeScript
74 lines
3.2 KiB
TypeScript
/**
|
||
* 唯一入口。所有角色都从这里按子命令分叉。
|
||
*
|
||
* 为什么不保留三个独立入口文件:`bun build --compile` 一次只产出一个二进制,
|
||
* 而部署要跑 HTTP 服务、判题 worker,SQL 判题还要 fork 一个能被 SIGKILL 的子进程
|
||
* (见 judge/sql/index.ts)。三个入口就得编三个二进制、镜像里塞三份运行时。
|
||
* 一个二进制 + 子命令,镜像里只有一份,compose 里改 command 就能换角色。
|
||
*
|
||
* oj2-api # 等同 serve
|
||
* oj2-api serve # HTTP + WebSocket
|
||
* oj2-api worker # BullMQ 判题消费者
|
||
* oj2-api healthcheck # 探活,给 Dockerfile 的 HEALTHCHECK 用
|
||
* oj2-api sql-child # SQL 判题子进程,由服务自己 spawn,不该手动调
|
||
* oj2-api migrate # 执行待办的数据库迁移,部署时由 docker/deploy.sh 调
|
||
* oj2-api recount # 把题目/用户的计数列、成就的已解锁数重算回与明细一致,默认只读预演
|
||
* oj2-api rediagnose # 运行时错误的诊断按现在的规则重做,默认只读预演
|
||
*
|
||
* 用动态 import 而非顶层 import:这几个模块都有导入即执行的副作用
|
||
* (Bun.serve、连 Redis 开消费者),静态导入会让 sql-child 也把整个服务拉起来。
|
||
*/
|
||
|
||
export {} // 只有动态 import 的话 TS 不认这是模块,顶层 await 会报错
|
||
|
||
const command = process.argv[2] ?? "serve"
|
||
|
||
switch (command) {
|
||
case "serve":
|
||
await import("./index")
|
||
break
|
||
case "worker":
|
||
await import("./worker")
|
||
break
|
||
case "migrate": {
|
||
const { runMigrations } = await import("./db/migrate")
|
||
await runMigrations()
|
||
break
|
||
}
|
||
// 数据订正,跟着二进制走而不是留成源码脚本 —— 生产镜像里没有 bun 也没有源码。
|
||
// 反范式计数列被重判等操作带偏之后拿它对账,默认只读预演,--apply 才写。
|
||
case "recount": {
|
||
const { recount } = await import("./scripts/recount")
|
||
process.exit(await recount({ apply: process.argv.slice(3).includes("--apply") }))
|
||
}
|
||
// 同上:诊断规则改了之后,把库里已有的诊断重做一遍(要连判题机和测试点,只能在线上跑)
|
||
case "rediagnose": {
|
||
const { rediagnose } = await import("./scripts/rediagnose")
|
||
process.exit(await rediagnose({ apply: process.argv.slice(3).includes("--apply") }))
|
||
}
|
||
case "sql-child": {
|
||
const { runSqlChild } = await import("./judge/sql/child")
|
||
await runSqlChild()
|
||
break
|
||
}
|
||
// 运行镜像是 debian-slim,没有 curl/wget,探活让二进制自己做。
|
||
// 只打 /health,不碰库 —— 库挂了该由库自己的 healthcheck 报,
|
||
// 不该让 api 容器跟着被判成不健康、进而被重启。
|
||
case "healthcheck": {
|
||
const { config } = await import("./config")
|
||
try {
|
||
const response = await fetch(`http://127.0.0.1:${config.port}/health`, {
|
||
signal: AbortSignal.timeout(3000),
|
||
})
|
||
process.exit(response.ok ? 0 : 1)
|
||
} catch {
|
||
process.exit(1)
|
||
}
|
||
}
|
||
default:
|
||
console.error(
|
||
`未知子命令:${command}\n可用:serve | worker | migrate | recount | rediagnose | healthcheck | sql-child`,
|
||
)
|
||
process.exit(2)
|
||
}
|