ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

hustoj最新源码部署指南:从编译判题核心到撑起教学比赛

hustoj最新源码部署指南:从编译判题核心到撑起教学比赛

简介:《hustoj 最新源码》是华中科技大学在线评测系统的r2133版完整源代码包,面向ACM/ICPC竞赛训练与在线判题场景,适合需要自建OJ平台的院校教练、竞赛选手及系统运维人员。资源内含可直接部署的源码树,覆盖PHP前端页面、JavaScript交互逻辑、C语言判题核心、MySQL数据库脚本及安全配置等模块,还打包了交互式安装脚本,便于快速完成环境初始化与参数调优。包体共2000个文件,以1653个svn-base版本记录、281个PHP脚本、225个JS文件、51个C源文件为主体,另含HTML模板、CSS样式、SQL脚本及PDF文档等,整体仅5.52MB,结构紧凑清晰。目前已有559人学习下载,适合希望深度定制评分逻辑、扩展前端功能或研究OJ判题原理的开发者获取一套干净的源码基线。

1. hustoj 最新源码:从本地跑通到能撑起一场教学比赛

比赛前夜,提交页面卡在“等待”不动,数据库里积压了几十份代码,后台点什么都转圈——这是很多自己维护在线评测系统的老师、竞赛教练都经历过的场景。hustoj 这套老牌开源评测系统能解决这个问题,但前提是你拿到的不是几年前打包的旧发行包,而是最新源码。所谓最新源码,不是某一个 release 压缩包,而是持续更新的主干分支,它直接影响判题核心与新版 PHP、MySQL 的兼容性,也决定你能不能把系统从“能装”推进到“能打”。本文面向两类人:想在校内快速搭一套可用 OJ 的新手,以及已经部署过旧版、想升级并搞清编译与判题细节的熟手。

2. 先确定你要哪种“最新”:主干源码与发行版的差异

2.1 用 git 拉主干:一条命令确认更新频率

很多人在搜索引擎里找“hustoj 源码下载”,下到一个 zip 压缩包就开装。这个包多半是几个月甚至一两年前的快照,判题核心、PHP 接口和数据库结构可能已经和当前的 Web 环境脱节。装完之后,最常见的后果是提交页面白屏,或者判题进程一直在等待队列里不出来。

常见做法是用 git 直接拉取主干仓库。这也是我先做的第一步:

# 克隆主干仓库到本地 git clone https://github.com/.../hustoj.git # 进入仓库目录 cd hustoj # 查看近期提交记录,确认更新活跃度 git log --oneline -10 # 更新到最新代码(后续维护时用) git pull

clone 之后先看提交记录,而不是急着编译。提交信息能反映这个项目近期在改什么:是修了判题机的崩溃,还是调整了数据库字段,或者适配了新版 PHP。如果最近一个月内有提交,说明主干是活的,值得跟;如果是几个月没动,你就要评估这份代码是否还需要自己补丁。后续维护时,git pull前最好先git stash本地改动,避免冲突把源码搞乱。

2.2 目录结构里藏着部署顺序:先把仓库地图看清

拿到源码后,不要直接找 INSTALL 脚本跑。先用十几分钟把目录结构过一遍,我一般按这个顺序看:

目录/文件作用我的判断标准
judge/判题核心,C++ 源码,负责编译运行提交的代码并判定结果这里的 Makefile 存在,说明编译是手工/半自动的
web/PHP 前端,用户提交、后台管理、题目展示都在这看配置文件是db_info.inc.php还是config.inc.php
install/安装脚本与辅助工具,有些分发里会放数据库初始化文件里面如果有 .sql 文件,就是建库的钥匙
db/ 或 install/db/数据库初始化脚本导入它之前别动 web 配置

这个布局不是所有分支都完全一样,但大方向一致。我见过有人反过来,先改 PHP 配置再编译判题机,结果判题进程连不上数据库,回头才发现数据库还没建。按“先编译判题核心 → 再建库导数据 → 最后配 Web”的顺序走,每一步的故障边界是清晰的。

3. 先把判题内核编出来:编译 core 的完整命令与参数设定

3.1 编译环境与依赖:Ubuntu 下最容易漏的三个包

判题核心是 C++ 写的,它直接和操作系统打交道:创建沙箱目录、切换用户、限制 CPU 与内存、执行用户程序。编译它需要基础工具链,最容易漏的是 MySQL 客户端开发库——判题机要直连数据库取提交任务,没有这个库,编译到一半会报找不到头文件。

以 Ubuntu/Debian 系为例,依赖安装命令如下:

sudo apt update sudo apt install -y g++ make libmysqlclient-dev

g++提供编译器,make负责读取 Makefile 执行构建,libmysqlclient-dev是判题机连接数据库所需的开发库。如果你用的是较新的 Ubuntu 版本,libmysqlclient-dev 可能被拆成默认包名,装不上时用apt search libmysqlclient查一下当前名字。这里不要贪图省事用 root 账号直接编译,普通用户编译、再 sudo 安装,能避免很多文件属主问题。

3.2 从 make 到 setuid:让判题进程拿到 root 权限的关键两步

依赖装好后,进入 judge 目录编译。常见做法是直接make,有些版本提供make.sh脚本,它会把编译、安装、设置权限串起来:

cd judge # 执行编译 make # 将编译出的判题可执行文件放到系统目录 sudo cp judge_client /usr/bin/judge_client # 设置 setuid 位,这是判题能否运行的关键 sudo chmod u+s /usr/bin/judge_client

chmod u+s这一步是一道分水岭。判题进程必须以 root 身份启动,才能完成几件普通用户做不到的事:把用户程序降权到 nobody 运行、设置资源限制(rlimit)、创建隔离目录。如果不加 setuid 位,进程以 PHP-FPM 或 www-data 身份起来,它没有权限切换用户,提交的代码根本无法运行,页面上的状态就会一直停在“等待”。很多部署翻车都发生在这里,而且因为它不报错,是典型的“玄学故障”。判断方法很简单:ls -l /usr/bin/judge_client,权限位里必须有s而不是x。

3.3 用配置文件控制语言与资源限制:参数表中看边界

编译安装后,判题机需要一个配置文件告诉它数据库在哪、并发跑几个任务、是否开启查重。各分支的路径不完全一致,但常见位置是/home/judge/etc/judge.conf。核心参数如下:

参数作用建议值
OJ_HOST_NAME数据库主机地址本机装填 localhost,分离部署填内网 IP
OJ_USER_NAME数据库用户名单独建一个只读权限的账号更安全
OJ_PASSWORD数据库密码不要用 root 密码裸奔
OJ_DB_NAME数据库名hustoj 系列默认通常是 jol
OJ_RUNNING同时判题的并发数CPU 核心数减一,避免拖垮整机
OJ_SIM_ENABLE是否开启代码相似度查重比赛期间建议开,日常训练可关

我一般会把并发数先设 2,跑通后再按机器配置调。设得过高,多个判题任务同时编译,CPU 飙到 100%,反而拖慢每个任务。改完配置文件后重启判题进程,用ps aux | grep judge确认它在运行。

4. 对接 Web 与数据库:最小可用部署的配置顺序

4.1 建库与导入:两条 SQL 让数据库跟上主干节奏

判题机就绪后,开始处理数据库。先建库再导数据,顺序不能反。老版本里数据库名固定叫 jol,字符集建议用 utf8mb4,避免题目里出现特殊符号时乱码:

mysql -uroot -p

进入 MySQL 命令行后执行:

CREATE DATABASE IF NOT EXISTS jol DEFAULT CHARACTER SET utf8mb4; CREATE USER 'oj'@'localhost' IDENTIFIED BY '这里填一个强密码'; GRANT ALL PRIVILEGES ON jol.* TO 'oj'@'localhost'; FLUSH PRIVILEGES;

建完库后找到源码里带 .sql 后缀的初始化文件,用命令行导入:

mysql -uoj -p jol < /path/to/hustoj/install/db/init.sql

导入完成后检查一下表数量:mysql -uoj -p -e "USE jol; SHOW TABLES;"。如果你拉的主干分支刚更新过数据库结构,导入时若报字段重复或表已存在,说明旧库没清干净,需要先DROP DATABASE jol再重新导入。用独立账号而不是 root 连库,这是我从第一次部署起就坚持的习惯——Web 端被注入时,至少数据库账号权限是受限的,还有后悔药吃。

4.2 让 PHP 连上判题机:配置文件里的四个必改项

hustoj 的 Web 端是 PHP 写的,它和判题机共享同一个数据库。前端把提交写进数据库,判题机轮询取走。后端配置文件常见路径是web/include/db_info.inc.php,里面改四项:

// 数据库配置:本机部署用 localhost,分离部署填 MySQL 所在机器 IP static $DB_HOST = "localhost"; static $DB_USER = "oj"; static $DB_PASS = "这里填上面设的密码"; static $DB_NAME = "jol";

这四个值必须和 4.1 里建库时保持一致。改完先不急着刷新页面,在命令行用 PHP CLI 测一下连接:

php -r "mysqli_connect('localhost','oj','密码','jol') or die(mysqli_connect_error()); echo 'OK';"

输出 OK 再开浏览器。这一步能把 PHP 环境和数据库配置的问题拦截在页面之外,不用对着白屏猜原因。新版源码大多已经用 mysqli 或 PDO 接口,如果你拉到的分支还在用mysql_*函数,在 PHP 7.0 以上环境会直接致命错误,这种情况建议换主干分支。

4.3 nginx + PHP-FPM 的落地写法:本地评测入口就绪

Web 服务器用 nginx 是比较省心的选择,配置里最关键的是把 root 指向 web 目录,并把 PHP 请求交给 PHP-FPM 处理:

server { listen 80; server_name localhost; root /var/www/hustoj/web; index index.php; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; } }

snippets/fastcgi-php.conf是 nginx 自带的 PHP 配置片段,fastcgi_pass指向的 socket 路径要以你系统里实际的 PHP-FPM 版本为准,Ubuntu 20.04 常见路径是/run/php/php7.4-fpm.sock。配好后重启 nginx 和 PHP-FPM:

sudo nginx -t sudo systemctl reload nginx sudo systemctl restart php-fpm

打开浏览器访问http://localhost,这时应该能看到 OJ 首页。接下来进入后台添加一道题,填好题目描述、时间限制、内存限制和测试数据路径。测试数据是放在判题机对应目录下的,常见布局是/home/judge/data/题目ID/下放.in和.out文件,一组测试点对应一对输入输出。第一次部署时,强烈建议先用一道只有一组数据的题试跑全流程,确认能提交、能判题、能出分,再批量加题。没有这一步,直接导入几百道题再排查问题,定位成本会翻好几倍。

5. 部署常见坑:五个翻车现场与排查顺序

5.1 白屏不报错:PHP 版本与 mysqli 扩展那个老梗

现象:首页能开,但提交或查看题目时直接白屏,浏览器控制台没有明显报错。

原因:PHP 7.0 移除了老的mysql_*函数,源码里如果还在用老接口就会直接致命错误;或者 PHP 没装 mysqli 扩展。

解决:先确认装上扩展:php -m | grep mysqli,没有就sudo apt install php-mysql。再看源码里连接数据库的语句,如果用的是mysql_connect,换新分支或全局替换为mysqli_connect。用 PHP CLI 跑一遍连接测试脚本,比看浏览器白屏高效得多。

5.2 提交永远是“等待”:setuid 位与判题机权限玄学

现象:前端提交成功,但状态一直停在 Waiting,队列里积压越来越多,后台没有任何错误提示。

原因:最常见是/usr/bin/judge_client没有 setuid 位,判题进程以普通用户身份运行,无法执行沙箱和降权操作;另一种可能是判题配置里数据库参数写错,进程自己根本没连上库,每轮查询都是空。

解决:先ls -l /usr/bin/judge_client看权限位,没有 s 就sudo chmod u+s。再手动跑一次判题程序,看输出日志里有没有数据库连接错误。如果 OJ_RUNNING 设得很大而机器核数少,也会表现为堆积,把它降到合理值再观察。

5.3 全对却 0 分:测试数据文件名大小写与编码的黑匣子

现象:本地拿测试数据手动对拍,程序输出完全正确,但提交到 OJ 上报 0 分,点开详情全是 Wrong Answer。

原因:判题机是 Linux,文件名大小写敏感。测试数据里1.In和题目配置里写的1.in不一致;或者.in、.out文件带 UTF-8 BOM 头,判题程序读取时把 BOM 当成了输入的一部分。

解决:进到/home/judge/data/题目ID/目录跑ls -la,核对文件名和后台配置完全一致。用file命令检查编码,有 BOM 就用sed -i 's/^\xEF\xBB\xBF//' 文件名去掉。题目数据上传前先在本地统一做一次命名规范检查,能省掉大批量废题。

5.4 系统日志里什么都没有但页面 500:nginx 与 PHP 版本错配

现象:页面返回 500,nginx error log 里只有一条 FastCGI 发送 0 字节的泛化记录,没有 PHP 具体报错。

原因:PHP-FPM 没启动,或者 socket 路径和 nginx 配置里写的不一致;还可能不同 PHP 版本间配置文件路径差异,导致 PHP-FPM 启动失败。

解决:systemctl status php-fpm看运行状态,ss -lx | grep php查实际 socket 路径,然后回看 nginx 配置。把 PHP-FPM 的错误日志打开,路径通常写在/etc/php/版本/fpm/php.ini的 error_log 配置里,日志里的报错远比 500 页面诚实。

5.5 折腾一晚上第二天全丢:备份习惯要在部署时养好

现象:判题机异常后重装系统或重装服务,结果所有题目、提交记录、用户数据全部清空,才发现库里什么都没备份。

原因:部署时全程关注“能不能跑”,没人关心“数据丢了怎么办”。数据库文件在 MySQL 数据目录下,误删服务目录时不会自动带走数据,但重装 MySQL 会。

解决:部署完成的第一时间就配置备份,最简单的是每天凌晨用 mysqldump 导一次库,再把/home/judge/data用 tar 打包。两者缺一不可,数据库丢了题目还在,数据目录丢了判题无法进行。备份脚本写好后先手动跑一遍,不要等到用到那天才发现备份文件是空的。

6. 再走一步:把源码变成能用一年的稳定服务

6.1 用 systemd 守住判题进程:重启自愈的写法

手动启动的判题进程,SSH 会话一关就没了,服务器一重启还得手工敲命令。用 systemd 管起来,是让它长期稳定运行的关键:

[Unit] Description=hustoj Judge Client After=network.target mysql.service [Service] Type=simple ExecStart=/usr/bin/judge_client Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

保存到/etc/systemd/system/judge.service,然后执行:

sudo systemctl daemon-reload sudo systemctl enable --now judge

这样进程意外退出后 5 秒自动拉起,服务器重启后判题服务也会跟着起来。

6.2 多判题机扩展与数据备份的两种姿势

判题机并不依赖 Web 服务器,它只连数据库取提交。所以性能不够时,加一台机器装好 judge_client,把 judge.conf 里的数据库 IP 指向主库,就能分担判题压力。备份则是两条命令的功夫:

mysqldump -uoj -p jol > /backup/jol_$(date +%F).sql tar -czf /backup/data_$(date +%F).tar.gz /home/judge/data

我的习惯是每次改完源码,先在本地用往年比赛的一批题目跑一遍回归,确认判题结果和旧版本一致,再更新到生产环境。这套流程多花十几分钟,但能让比赛前夜安心睡个整觉。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进