从零实现在线OJ平台
什么是OJ?
OJ(Online Judge)简介
OJ(在线判题系统) 是一种自动化编程评测平台,用户可通过网页提交程序源代码(如C/C++、Java、Python等),系统自动编译、执行代码,并基于预设测试用例验证程序的正确性、效率及资源消耗。其核心功能是为编程训练、竞赛和教学提供实时、公正的自动化评测服务
核心功能与特点
- 自动化评测
- 多维度评判:系统对提交的代码进行编译、运行,检测输出是否匹配预期结果,并统计运行时间、内存占用等指标。
- 即时反馈:返回结果包括
Accepted(通过)、Wrong Answer(答案错误)、Time Limit Exceed(超时)、Memory Limit Exceed(超内存)等状态,帮助用户快速定位问题
- 多语言支持
- 支持主流编程语言(如C/C++、Java、Python),部分平台还涵盖Shell、SQL等专项语言
- 资源与社区功能
- 题库分类:题目按难度(入门→竞赛级)、知识点(算法、数据结构)分类,便于针对性训练。
- 竞赛与排名:支持在线举办编程比赛,实时更新用户排名,激发竞争动力
- 讨论区:用户可交流解题思路,分享代码优化方案
发展起源与应用场景
-
起源:诞生于 ACM-ICPC 国际大学生程序设计竞赛 和 信息学奥林匹克竞赛(OI),用于自动化评审计分
-
应用扩展:
- 教育领域:高校(如浙大ZOJ、北大POJ)将OJ融入程序设计课程,辅助学生练习与作业评测
- 技术招聘:企业(如LeetCode、HackerRank)通过OJ筛选候选人,考察算法与编码能力
- 竞赛训练:Codeforces、TopCoder等平台定期举办全球性编程赛事,培养顶尖选手
知名OJ平台推荐
| 类型 | 代表平台 | 特点 |
|---|---|---|
| 国际综合 | UVA(西班牙)、Codeforces(俄) | 题量庞大,覆盖算法难题,高手云集 |
| 国内高校 | 浙大ZOJ、北大POJ、杭电HDU | 题目丰富,贴近竞赛需求,适合新手进阶@ref) |
| 面试向 | LeetCode、HackerRank | 聚焦企业笔试真题,提供面试模拟环境 |
| 竞赛专项 | USACO(美国奥赛)、洛谷(NOIP) | 分阶段训练,支持查看测试数据 |
本文所实现的OJ的宏观结构

- 公共模块:负责存放工具类
- 编译运行模块:负责处理服务模块传来的编译请求并返回结果
- OJ服务模块:负责将用户提交的请求,负载均衡式地发送给编译服务模块
项目设计
- 编写编译运行模块
- 编写服务模块
- 编写前端页面
公共模块的设计
-
日志类
-
这个日志系统实现得非常简洁高效,下面我详细解析其设计和实现:
-
整体设计理念
- 轻量化:只包含核心功能,没有冗余依赖
- 即时输出:日志直接输出到控制台
- 流式操作:采用
<<流式操作符输出日志内容 - 信息丰富:包含关键元数据(日志等级、文件名、行号等)
- 零配置:开箱即用,无需初始化
-
核心组件解析
- 日志等级系统
enum { INFO, // 普通信息 DEBUG, // 调试信息 WARNING, // 警告信息 ERROR, // 错误信息 FATAL // 严重错误 };特点:
- 使用简单整数代替枚举类,减少类型转换开销
- 级别从低到高排列 (0-4)
- 支持快速扩展新等级
- 核心日志函数
inline std::ostream &Log(const std::string &level, const std::string &file_name, int line) { // 构造日志头信息 std::string message = "["; message += level; message += "]["; message += file_name; message += "]["; message += std::to_string(line); message += "]["; message += TimeUtil::GetTimeStamp(); // 使用工具类获取时间戳 message += "] "; std::cout << message; // 输出日志头 return std::cout; // 返回输出流 }关键特性:
- 构造日志头:
- 包含四部分元数据:
- 日志等级(如 “[INFO]”)
- 文件名(如 “[main.cpp]”)
- 行号(如 “[42]”)
- 时间戳(如 “[1633023456]”)
- 格式示例:
[INFO][main.cpp][42][1633023456]
- 包含四部分元数据:
- 行内优化:
- 使用
inline关键字消除函数调用开销 - 直接操作字符串避免多次I/O操作
- 使用
- 流式返回:
- 返回
std::cout使得能链式输出内容 - 支持任意类型的数据输出(通过
operator<<)
- 返回
-
核心日志宏
#define LOG(level) Log(#level, __FILE__, __LINE__)宏技巧解析:
- 字符串化操作:
#level将日志等级转为字符串(INFO → “INFO”)- 避免手动输入字符串导致错误
- 预定义宏:
__FILE__:获取当前源文件名__LINE__:获取当前代码行号- 自动捕获代码位置信息
- 用户友好接口:
- 简化调用:
LOG(INFO)替代完整函数调用 - 类型安全:编译器检查日志等级
- 简化调用:
使用示例
// 普通日志 LOG(INFO) << "系统启动成功" << "\n"; // 调试信息 LOG(DEBUG) << "收到请求,ID=" << request_id << "\n"; // 错误日志 if(error_code) { LOG(ERROR) << "操作失败,错误码: " << error_code << "\n"; }输出示例:
[INFO][server.cpp][35][1633023456] 系统启动成功 [DEBUG][request_handler.cpp][78][1633023457] 收到请求,ID=1001 [ERROR][database.cpp][122][1633023458] 操作失败,错误码: 503技术亮点
- 性能优化:
- 内存操作:使用字符串拼接代替多次I/O
- 缓冲控制:不强制刷新(
std::endl),由用户控制 - 内联函数:消除调用开销
- 元数据自动捕获:
- 通过预处理器宏自动获取文件名和行号
- 时间戳通过工具类动态获取
- 日志等级自动转为字符串
- 扩展性:
- 轻松添加新日志等级(只需在枚举添加)
- 支持与其他流输出结合使用
- 跨平台:
- 基于标准C++实现
- 依赖极少的系统功能
-
-
工具类
-
整体结构
代码位于
ns_util命名空间下,包含四个主要工具类:TimeUtil- 时间处理工具PathUtil- 文件路径处理工具FileUtil- 文件操作工具StringUtil- 字符串处理工具
- TimeUtil 类 (时间处理工具)
class TimeUtil { public: // 获取秒级时间戳 static std::string GetTimeStamp() { struct timeval _time; gettimeofday(&_time, nullptr); return std::to_string(_time.tv_sec); } // 获取毫秒级时间戳 static std::string GetTimeMs() { struct timeval _time; gettimeofday(&_time, nullptr); return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000); } };功能说明:
GetTimeStamp():- 使用
gettimeofday系统调用获取当前时间 - 返回从1970年1月1日0时至今的秒数(字符串格式)
- 使用
GetTimeMs():- 同样基于
gettimeofday系统调用 - 返回从1970年1月1日0时至今的毫秒数(字符串格式)
- 计算方式:
(秒数 × 1000) + (微秒数 ÷ 1000)
- 同样基于
使用场景:
- 日志时间戳
- 性能测量
- 唯一ID生成基准
- PathUtil 类 (文件路径处理工具)
const std::string temp_path = "./temp/"; class PathUtil { public: // 基础路径构建方法 static std::string AddSuffix(const std::string &file_name, const std::string &suffix) { return temp_path + file_name + suffix; } // 各种文件类型路径生成器 static std::string Src(const std::string &file_name) { // 源代码文件 return AddSuffix(file_name, ".cpp"); } static std::string Exe(const std::string &file_name) { // 可执行文件 return AddSuffix(file_name, ".exe"); } static std::string CompilerError(const std::string &file_name) { // 编译错误 return AddSuffix(file_name, ".compile_error"); } static std::string Stdin(const std::string &file_name) { // 标准输入 return AddSuffix(file_name, ".stdin"); } static std::string Stdout(const std::string &file_name) { // 标准输出 return AddSuffix(file_name, ".stdout"); } static std::string Stderr(const std::string &file_name) { // 标准错误 return AddSuffix(file_name, ".stderr"); } };核心设计:
- 统一的临时文件目录:
./temp/ - 基于文件基本名 + 后缀的统一路径构建
- 使用点语法简化各类文件的路径获取
文件类型说明:
方法名 后缀 用途 Src.cppC++源代码文件 Exe.exe可执行程序 CompilerError.compile_error编译器错误信息 Stdin.stdin程序输入重定向文件 Stdout.stdout程序输出重定向文件 Stderr.stderr程序错误输出重定向文件 示例:
PathUtil::Src("1234") // -> "./temp/1234.cpp" PathUtil::Exe("1234") // -> "./temp/1234.exe" PathUtil::Stderr("1234") // -> "./temp/1234.stderr"- FileUtil 类 (文件操作工具)
class FileUtil { public: // 检查文件是否存在 static bool IsFileExists(const std::string &path_name) { struct stat st; return stat(path_name.c_str(), &st) == 0; } // 生成唯一文件名 static std::string UniqFileName() { static std::atomic_uint id(0); // 原子计数器 id++; return TimeUtil::GetTimeMs() + "_" + std::to_string(id); } // 写入文件 static bool WriteFile(const std::string &target, const std::string &content) { std::ofstream out(target); if (!out.is_open()) return false; out.write(content.c_str(), content.size()); out.close(); return true; } // 读取文件 static bool ReadFile(const std::string &target, std::string *content, bool keep = false) { content->clear(); std::ifstream in(target); if (!in.is_open()) return false; std::string line; while (std::getline(in, line)) { *content += line; if (keep) *content += "\n"; // 可选保留换行符 } in.close(); return true; } };核心功能详解:
A. 文件存在检查 (
IsFileExists)- 使用
stat系统调用 - 返回
true仅当文件存在且能获取状态信息 - 不区分文件类型(目录也会返回 true)
B. 唯一文件名生成 (
UniqFileName)- 使用原子计数器
std::atomic_uint保证线程安全 - 组合元素:毫秒时间戳 + 递增ID
- 生成示例:
"1633023456789_1","1633023456790_2" - 并发安全:适合多线程/多进程环境
C. 文件写入 (
WriteFile)- 简单覆盖式写入
- 二进制安全:直接写入原始内容
- 返回布尔值表示成功与否
D. 文件读取 (
ReadFile)-
可选参数
keep控制是否保留换行符
keep=false(默认):按行读取并丢弃换行符keep=true:读取后添加\n保持原始格式
-
兼容各种换行符格式(Unix/LF, Windows/CRLF)
- StringUtil 类 (字符串处理工具)
class StringUtil { public: static void SplitString(const std::string &str, std::vector<std::string> *target, const std::string &sep) { // 使用Boost进行分割 boost::split(*target, str, boost::is_any_of(sep), boost::algorithm::token_compress_on); } };功能说明:
- 基于 Boost 库的字符串分割功能
- 参数说明:
str: 待分割的输入字符串target: 输出分割结果(字符串向量)sep: 分隔符集合(可以是多个字符)
token_compress_on: 压缩连续分隔符(避免空元素)
示例:
std::vector<std::string> parts; StringUtil::SplitString("a,b,c,,d", &parts, ","); // 结果: {"a", "b", "c", "d"} (token_compress_on生效)系统依赖说明
- Linux系统调用:
gettimeofday(获取高精度时间)stat(文件状态检查)
- 第三方依赖:
- Boost库 (仅用于字符串分割)
整体设计特点
- 实用主义:每个类聚焦解决特定问题
- 静态方法:所有功能无需实例化即可使用
- 原子操作:唯一文件名生成保证线程安全
- 路径抽象:统一管理临时文件位置
- 可选参数:提供灵活控制(如换行符保留)
典型使用场景
- 编译系统:
// 生成唯一文件名 std::string filename = FileUtil::UniqFileName(); // 写入源码 FileUtil::WriteFile(PathUtil::Src(filename), user_code); // 检查可执行文件是否存在 if (FileUtil::IsFileExists(PathUtil::Exe(filename))) { // 编译成功处理 }- 日志处理:
// 带时间戳的日志 std::string log_entry = "[" + TimeUtil::GetTimeMs() + "] " + message; // 错误信息分割 std::vector<std::string> error_lines; StringUtil::SplitString(compiler_output, &error_lines, "\n");- 运行环境隔离:
// 重定向运行环境 std::string stdin_path = PathUtil::Stdin(filename); std::string stdout_path = PathUtil::Stdout(filename); std::string stderr_path = PathUtil::Stderr(filename); -
前端模块设计
首页
这个HTML文件是一个在线判题系统(OJ)的首页,提供了简洁而功能明确的用户界面。下面我将从结构、样式和功能三个维度详细解析这个首页设计:
页面结构分析
1. 整体布局 (container)
<div class="container">
<div class="navbar">...</div>
<div class="content">...</div>
</div>
- container:顶层容器,包裹所有页面内容
- navbar:导航栏区域
- content:主要内容区域
2. 导航栏结构 (navbar)
<div class="navbar">
<a href="/">首页</a>
<a href="/all_questions">题库</a>
<a href="#">竞赛</a>
<a href="#">讨论</a>
<a href="#">求职</a>
<a class="login" href="#">登录</a>
</div>
- 6个导航链接,包括:
- 首页:当前页面
- 题库:跳转到题目列表
- 竞赛:预留功能
- 讨论:预留功能
- 求职:预留功能
- 登录:用户登录入口
3. 主内容区结构 (content)
<div class="content">
<h1 class="font_">欢迎来到我的OnlineJudge平台</h1>
<p class="font_">这个我个人独立开发的一个在线OJ平台</p>
<a class="font_" href="/all_questions">点击我开始编程啦!</a>
</div>
- 主标题(h1):平台欢迎语
- 副标题(p):平台简介
- 行动按钮(a):核心功能入口
视觉设计特色
1. 整体视觉风格
- 简约现代:没有多余的装饰元素
- 高对比度:黑白主色调+绿色点睛色
- 空间留白:200px的上边距创造舒适空间
- 统一字体:使用系统默认无衬线字体
2. 导航栏设计
.navbar {
background-color: black;
overflow: hidden;
}
- 黑色背景:突出导航区域
- 隐藏溢出:确保浮动元素不破坏布局
- 悬停效果:绿色背景增加交互反馈
导航项样式
.navbar a {
display: inline-block;
width: 80px;
color: white;
line-height: 50px;
text-align: center;
}
- 固定宽度:每个导航项80px
- 垂直居中:行高=容器高度(50px)
- 文字居中:视觉平衡
3. 主内容区设计
.content {
width: 800px;
margin: 0 auto;
margin-top: 200px;
text-align: center;
}
- 居中布局:800px宽 + auto margin
- 垂直呼吸空间:200px顶部间距
- 居中对齐:所有内容居中显示
文本样式
.font_ {
display: block;
margin-top: 20px;
text-decoration: none;
}
- 块级显示:每个元素独占一行
- 统一间距:20px垂直间距
- 无装饰链接:去下划线更简洁
用户体验设计
1. 导航体验
- 当前页面指示:未实现,可添加active类
- 悬停反馈:颜色变化提示可点击
- 功能分组:平台功能在左,用户功能在右
2. 核心功能入口
<a class="font_" href="/all_questions">点击我开始编程啦!</a>
- 行动召唤(CTA):显眼的文字引导
- 直达核心功能:跳转到题库页面
- 无障碍设计:语义化标签+清晰链接文本
3. 响应式考虑
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- 视口配置:移动友好设计基础
- 暂缺媒体查询:未实现完整响应式
全部问题模板
这个题目列表模板是一个动态生成题目列表的HTML页面,使用了CTemplate模板引擎来动态渲染题目数据。以下是该模板的详细分析:
整体结构设计

核心功能区域
1. 导航栏
<div class="navbar">
<a href="/">首页</a>
<a href="/all_questions">题库</a>
<a href="#">竞赛</a>
<a href="#">讨论</a>
<a href="#">求职</a>
<a class="login" href="#">登录</a>
</div>
- 导航项目:首页、题库、竞赛、讨论、求职
- 登录入口:固定在导航栏右侧
- 视觉反馈:鼠标悬停时背景变绿
2. 标题区域
<div class="question_list">
<h1>OnlineJuge题目列表</h1>
<!-- ... -->
</div>
- 使用绿色主题色强调平台名称
- 居中布局增强视觉焦点
3. 题目列表表格
<table>
<tr>
<th class="item">编号</th>
<th class="item">标题</th>
<th class="item">难度</th>
</tr>
{
{#question_list}}
<tr>
<td class="item">{
{number}}</td>
<td class="item"><a href="/question/{
{number}}">{
{title}}</a></td>
<td class="item">{
{star}}</td>
</tr>
{
{/question_list}}
</table>
表格设计特点:
- 简洁表头:
- 编号、标题、难度三列
- 清晰标识信息类型
- 动态行渲染:
- 使用CTemplate模板语法
{ {#question_list}}标记循环开始{ {number}}、{ {title}}、{ {star}}占位符填充数据
- 题目链接:
- 标题可点击跳转到题目详情
- URL格式:
/question/题目编号 - 悬停效果:蓝色+下划线
4. 页脚区域
<div class="footer">
<h4>优快云:ZZJM</h4>
</div>
- 简单版权信息
- 作者署名及来源标识
视觉设计亮点
1. 色彩方案
| 元素 | 背景色 | 文字色 | 悬停色 |
|---|---|---|---|
| 导航栏 | #000000 | #FFFFFF | #008000 |
| 表格标题行 | rgb(243, 248, 246) | #000000 | - |
| 题目行链接 | - | #000000 | #0000FF |
2. 排版细节
.question_list table {
font-size: large;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
margin-top: 50px;
}
- 字体选择:清晰易读的无衬线字体集
- 字号设置:large级别确保可读性
- 间距控制:50px顶部间距创造舒适呼吸空间
3. 表格样式优化
.item {
width: 100px;
height: 40px;
font-size: large;
font-family:'Times New Roman', Times, serif;
}
- 单元格尺寸:固定高度40px确保行高一致
- 字体切换:内容区使用衬线字体提升可读性
- 背景层次:浅蓝绿色背景提高识别度
响应式设计考虑
1. 基础响应式
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2. 自适应布局
.question_list {
width: 800px;
margin: 0px auto;
}
- 固定宽度居中布局
- 适合主流屏幕尺寸
3. 待增强的响应式
/* 可添加媒体查询增强小屏体验 */
@media (max-width: 800px) {
.question_list {
width: 95%;
padding-top: 20px;
}
.item {
width: auto;
padding: 0 10px;
}
.navbar a {
width: auto;
padding: 0 10px;
font-size: medium;
}
}
交互设计细节
1. 导航体验
.navbar a:hover {
background-color: green;
}
- 悬停提示当前选项
- 颜色变化提供视觉反馈
2. 题目链接交互
.item a {
text-decoration: none;
color: black;
}
.item a:hover {
color: blue;
text-decoration:underline;
}
- 默认状态:无下划线、黑色文字
- 悬停状态:蓝色文字+下划线
- 点击预期:跳转到题目详情页
模板引擎应用
CTemplate语法解析
| 语法 | 说明 | 作用 |
|---|---|---|
{
{#question_list}} |
列表区块开始 | 标记题目循环开始 |
{
{/question_list}} |
列表区块结束 | 标记题目循环结束 |
{
{number}} |
变量替换 | 题目编号占位符 |
{
{title}} |
变量替换 | 题目标题占位符 |
{
{star}} |
变量替换 | 题目难度占位符 |
动态渲染流程

问题详情模板
这个模板是OJ系统的题目详情页面,集成了题目展示、代码编辑和提交功能,使用了ACE代码编辑器提供专业的编程体验。下面我将详细解析这个模板的设计和实现:
整体结构设计

核心功能区域
1. 导航栏 (navbar)
<div class="navbar">

最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



