从零实现在线OJ平台

从零实现在线OJ平台

什么是OJ?

OJ(Online Judge)简介

OJ(在线判题系统) 是一种自动化编程评测平台,用户可通过网页提交程序源代码(如C/C++、Java、Python等),系统自动编译、执行代码,并基于预设测试用例验证程序的正确性、效率及资源消耗。其核心功能是为编程训练、竞赛和教学提供实时、公正的自动化评测服务


核心功能与特点

  1. 自动化评测
    • 多维度评判:系统对提交的代码进行编译、运行,检测输出是否匹配预期结果,并统计运行时间、内存占用等指标。
    • 即时反馈:返回结果包括 Accepted(通过)Wrong Answer(答案错误)Time Limit Exceed(超时)Memory Limit Exceed(超内存) 等状态,帮助用户快速定位问题
  2. 多语言支持
    • 支持主流编程语言(如C/C++、Java、Python),部分平台还涵盖Shell、SQL等专项语言
  3. 资源与社区功能
    • 题库分类:题目按难度(入门→竞赛级)、知识点(算法、数据结构)分类,便于针对性训练。
    • 竞赛与排名:支持在线举办编程比赛,实时更新用户排名,激发竞争动力
    • 讨论区:用户可交流解题思路,分享代码优化方案

发展起源与应用场景

  • 起源:诞生于 ACM-ICPC 国际大学生程序设计竞赛信息学奥林匹克竞赛(OI),用于自动化评审计分

  • 应用扩展:

    • 教育领域:高校(如浙大ZOJ、北大POJ)将OJ融入程序设计课程,辅助学生练习与作业评测
    • 技术招聘:企业(如LeetCode、HackerRank)通过OJ筛选候选人,考察算法与编码能力
    • 竞赛训练:Codeforces、TopCoder等平台定期举办全球性编程赛事,培养顶尖选手

知名OJ平台推荐

类型 代表平台 特点
国际综合 UVA(西班牙)、Codeforces(俄) 题量庞大,覆盖算法难题,高手云集
国内高校 浙大ZOJ、北大POJ、杭电HDU 题目丰富,贴近竞赛需求,适合新手进阶@ref)
面试向 LeetCode、HackerRank 聚焦企业笔试真题,提供面试模拟环境
竞赛专项 USACO(美国奥赛)、洛谷(NOIP) 分阶段训练,支持查看测试数据

本文所实现的OJ的宏观结构

image-20250701160336831

  1. 公共模块:负责存放工具类
  2. 编译运行模块:负责处理服务模块传来的编译请求并返回结果
  3. OJ服务模块:负责将用户提交的请求,负载均衡式地发送给编译服务模块

项目设计

  1. 编写编译运行模块
  2. 编写服务模块
  3. 编写前端页面

公共模块的设计

  1. 日志类
    • 这个日志系统实现得非常简洁高效,下面我详细解析其设计和实现:

    • 整体设计理念

      1. 轻量化:只包含核心功能,没有冗余依赖
      2. 即时输出:日志直接输出到控制台
      3. 流式操作:采用 << 流式操作符输出日志内容
      4. 信息丰富:包含关键元数据(日志等级、文件名、行号等)
      5. 零配置:开箱即用,无需初始化
    • 核心组件解析

      1. 日志等级系统
      enum {
          INFO,     // 普通信息
          DEBUG,    // 调试信息
          WARNING,  // 警告信息
          ERROR,    // 错误信息
          FATAL     // 严重错误
      };
      

      特点:

      • 使用简单整数代替枚举类,减少类型转换开销
      • 级别从低到高排列 (0-4)
      • 支持快速扩展新等级
      1. 核心日志函数
      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;      // 返回输出流
      }
      

      关键特性:

      1. 构造日志头
        • 包含四部分元数据:
          • 日志等级(如 “[INFO]”)
          • 文件名(如 “[main.cpp]”)
          • 行号(如 “[42]”)
          • 时间戳(如 “[1633023456]”)
        • 格式示例:[INFO][main.cpp][42][1633023456]
      2. 行内优化
        • 使用 inline 关键字消除函数调用开销
        • 直接操作字符串避免多次I/O操作
      3. 流式返回
        • 返回 std::cout 使得能链式输出内容
        • 支持任意类型的数据输出(通过 operator<<
    • 核心日志宏

    #define LOG(level) Log(#level, __FILE__, __LINE__)
    
    宏技巧解析:
    1. 字符串化操作
      • #level 将日志等级转为字符串(INFO → “INFO”)
      • 避免手动输入字符串导致错误
    2. 预定义宏
      • __FILE__:获取当前源文件名
      • __LINE__:获取当前代码行号
      • 自动捕获代码位置信息
    3. 用户友好接口
      • 简化调用: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
    

    技术亮点

    1. 性能优化
      • 内存操作:使用字符串拼接代替多次I/O
      • 缓冲控制:不强制刷新(std::endl),由用户控制
      • 内联函数:消除调用开销
    2. 元数据自动捕获
      • 通过预处理器宏自动获取文件名和行号
      • 时间戳通过工具类动态获取
      • 日志等级自动转为字符串
    3. 扩展性
      • 轻松添加新日志等级(只需在枚举添加)
      • 支持与其他流输出结合使用
    4. 跨平台
      • 基于标准C++实现
      • 依赖极少的系统功能
  2. 工具类
    • 整体结构

      代码位于 ns_util 命名空间下,包含四个主要工具类:

      • TimeUtil - 时间处理工具
      • PathUtil - 文件路径处理工具
      • FileUtil - 文件操作工具
      • StringUtil - 字符串处理工具
    1. 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);
        }
    };
    

    功能说明:

    1. GetTimeStamp():
      • 使用 gettimeofday 系统调用获取当前时间
      • 返回从1970年1月1日0时至今的秒数(字符串格式)
    2. GetTimeMs():
      • 同样基于 gettimeofday 系统调用
      • 返回从1970年1月1日0时至今的毫秒数(字符串格式)
      • 计算方式:(秒数 × 1000) + (微秒数 ÷ 1000)

    使用场景:

    • 日志时间戳
    • 性能测量
    • 唯一ID生成基准
    1. 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");
        }
    };
    

    核心设计:

    1. 统一的临时文件目录:./temp/
    2. 基于文件基本名 + 后缀的统一路径构建
    3. 使用点语法简化各类文件的路径获取

    文件类型说明:

    方法名 后缀 用途
    Src .cpp C++源代码文件
    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"
    
    1. 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)

    1. 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生效)
    

    系统依赖说明

    1. Linux系统调用:
      • gettimeofday (获取高精度时间)
      • stat (文件状态检查)
    2. 第三方依赖:
      • Boost库 (仅用于字符串分割)

    整体设计特点

    1. 实用主义:每个类聚焦解决特定问题
    2. 静态方法:所有功能无需实例化即可使用
    3. 原子操作:唯一文件名生成保证线程安全
    4. 路径抽象:统一管理临时文件位置
    5. 可选参数:提供灵活控制(如换行符保留)

    典型使用场景

    1. 编译系统
    // 生成唯一文件名
    std::string filename = FileUtil::UniqFileName();
    
    // 写入源码
    FileUtil::WriteFile(PathUtil::Src(filename), user_code);
    
    // 检查可执行文件是否存在
    if (FileUtil::IsFileExists(PathUtil::Exe(filename))) {
        // 编译成功处理
    }
    
    1. 日志处理
    // 带时间戳的日志
    std::string log_entry = "[" + TimeUtil::GetTimeMs() + "] " + message;
    
    // 错误信息分割
    std::vector<std::string> error_lines;
    StringUtil::SplitString(compiler_output, &error_lines, "\n");
    
    1. 运行环境隔离
    // 重定向运行环境
    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模板引擎来动态渲染题目数据。以下是该模板的详细分析:

整体结构设计

image-20250701183222002

核心功能区域
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>

表格设计特点:

  1. 简洁表头
    • 编号、标题、难度三列
    • 清晰标识信息类型
  2. 动态行渲染
    • 使用CTemplate模板语法
    • { {#question_list}}标记循环开始
    • { {number}}{ {title}}{ {star}}占位符填充数据
  3. 题目链接
    • 标题可点击跳转到题目详情
    • 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}} 变量替换 题目难度占位符
动态渲染流程

image-20250701183443356

问题详情模板

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

整体结构设计

image-20250701183729911

核心功能区域
1. 导航栏 (navbar)
<div class="navbar">
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值