JOJ——基于爬虫的在线测评系统(Online Judge)

    技术2026-01-18  8

    这是一个基于爬虫的在线测评系统(OJ)。 相信喜欢刷题的各位大佬应该对OJ并不陌生。本系统旨在使用一个账号,就可以刷遍各个OJ的题目。系统基于后端SpringBoot、Mybatis-Plus、Thymeleaf、Shiro,前端Semantic UI、Echarts 等框架进行开发。源码:GitHub ,如果对您有帮助,麻烦点赞关注一下。项目体验地址:JOJ

    文章目录

    一、 需求分析二、功能介绍2.1 用户2.2 题库2.3 比赛2.3 博客2.5 留言板2.6 其他2.7 后台管理 三、 数据库设计(所涉及的表、ER图)3.1 完整的E-R图3.2 物理模型3.3 各基表描述 四、系统使用说明4.1 系统基本安装环境4.2 项目部署 五、待完善部分。

    一、 需求分析

    Online Judge系统(简称OJ)是一个在线的判题系统。用户可以在线提交程序多种程序(如C、C++)源代码,系统对源代码进行编译和执行,并通过预先设计的测试数据来检验程序源代码的正确性。

    一个用户提交的程序在Online Judge系统下执行时将受到比较严格的限制,包括运行时间限制,内存使用限制和安全限制等。用户程序执行的结果将被Online Judge系统捕捉并保存,然后再转交给一个裁判程序。该裁判程序或者比较用户程序的输出数据和标准输出样例的差别,或者检验用户程序的输出数据是否满足一定的逻辑条件。最后系统返回给用户一个状态:通过(Accepted)、答案错误(Wrong Answer)、超时(Time Limit Exceed)、超内存(Memory Limit Exceed)、运行时错误(Runtime Error)或是无法编译(Compile Error),并返回程序使用的内存、运行时间等信息。

    Online Judge系统最初使用于ACM-ICPC国际大学生程序设计竞赛和OI信息学奥林匹克竞赛中的自动判题和排名。现广泛应用于世界各地高校学生程序设计的训练、参赛队员的训练和选拔、各种程序设计竞赛以及数据结构和算法的学习和作业的自动提交判断中。

    Online Judge一般还提供比赛的功能,但只能由OJ的管理员引用本OJ题库中的题进行创建比赛。而普通用户想创建比赛或者引用多个OJ的题目则无法实现。本系统就是基于这个目的来实现的。

    本系统可以让用户自行创建比赛,而且引用不同OJ的题源。

    本系统还提供了博客模块,可以用户可以在本OJ系统上发布博客。可以用于赛后的题解编写,交流讨论。

    用例模型

    二、功能介绍

    本系统主要可以分为几个模块

    用户题库比赛博客留言板后台其他

    2.1 用户

    用户注册时需要绑定邮箱。用户名必须唯一。若忘记密码,可以通过注册时的邮箱找回。

    2.2 题库

    可以对题库根据标签,源OJ名, 题号等进行查询。在题面界面,可以查看题面描述,样例信息等,

    还有显示该题的提交结果情况等。

    只有用户登陆后才能进行提交。否则只能查看题面信息。

    在题目界面如果用户处于登录状态,回显示最近几次的提交记录。右上角有该题提交的统计信息。

    2.3 比赛

    游客、用户可以对根据比赛名进行查找。

    用户可以从题库中选择题面创建比赛(题面不超过26道)。用户可以对题面的标题进行修改。

    比赛权限分三种,public任何人都可以参加查看,private 需要用户输入相应密码才可以查看。protect 比赛进行时需要输入相应密码才可以进行参加,比赛结束则不需要。

    比赛详情界面有题面的信息。比赛公告,比赛信息,比赛中的提交情况,榜单,留言。

    排名规则:经典ACM规则

    AC(通过题目)越多,排名越靠前。

    AC相同,总用时越少,排名越靠前。

    总用时=∑(每一个题目的用时)

    每一个题目的用时=比赛开始到提交被通过的时间+罚时

    罚时=(通过前)错误代码提交次数*每次罚时(默认为20分钟)

    2.3 博客

    博客模块的开发可以参考博主之前的博客【SpringBoot】JavaWeb博客系统

    用户可以对博客进行增删改,游客可以查询访问博客。

    2.5 留言板

    游客,用户都可以添加留言,回复留言。用户可以删除自己的留言而游客不行。

    2.6 其他

    在首页显示本系统最近一个月,每天的提交数和AC数。

    全网各OJ比赛的信息。

    提交记录

    可以查看提交后的代码。代码是公开的,游客用户均可查看。但是如果是在正在进行中的比赛提交的代码。除了本人和比赛创建者,其他人是无法查看的。

    2.7 后台管理

    具有admin身份的用户,可以管理系统的用户列表、博客列表、博客评论列表、题目列表、提交列表、比赛列表、留言列表。

    三、 数据库设计(所涉及的表、ER图)

    3.1 完整的E-R图

    3.2 物理模型

    3.3 各基表描述

    基表英文名称: joj_blog字段编号英文字段名中文字段名字段类型备注1bid博客编号int主键2uid用户编号int外键3typeId博客类型编号int外键4title博客标题nvarchar(200)5content博客内容text6summary博客摘要text7firstPicture首图地址nvarchar(300)8views浏览量int9createTime创建时间datetime10updateTime更新时间datetime11published是否发布tinyint12comment是否可以评论tinyint 基表英文名称: joj_blog_comment字段编号英文字段名中文字段名字段类型备注1bcid博客评论编号int主键2bid博客编号int3replyCommentId目标评论编号int外键,默认为null4parentCommentId父评论节点编号int外键,默认为null5uid用户编号int外键6content评论内容text7createTime评论时间datetime8email邮箱varchar9remind回复是否提醒tinyint 基表英文名称: joj_blog_type字段编号英文字段名中文字段名字段类型备注1btid博客类型编号int主键2typeName博客类型名称varchar(100) 基表英文名称: joj_calendar_contest字段编号英文字段名中文字段名字段类型备注1oid编号int主键2OJNameOJ名称varchar3title比赛标题varchar(100)4beginTime开始时间datetime5endTime结束时间datetime6url链接varchar 基表英文名称: joj_contest字段编号英文字段名中文字段名字段类型备注1cid比赛编号int主键2uid用户编号int外键3title比赛名称varchar4description介绍varchar5announcement公共varchar6password密码varchar默认为null7beginTime开始时间datetime8endTime结束实现datetime9auth权限类型int1 public 2 protect 3 private10type比赛类型int1 传统的ACM赛制11status比赛状态int0 比赛未开始 1 比赛进行中 2 比赛结束12createTime创建时间datetime 基表英文名称: joj_contest_comment字段编号英文字段名中文字段名字段类型备注1ccid比赛评论编号int主键2replyCommentId回复评判编号int外键,默认为null3uid用户编号int外键4parentCommentId父评论编号int外键,默认为null5cid比赛编号int外键6content评论内容text7createTime创建时间datetime 基表英文名称: joj_contest_problem字段编号英文字段名中文字段名字段类型备注1cpid比赛题目编号int主键2pid题目编号int外键3cid比赛编号int外键4alias题目别名varchar5num题目号varchar 基表英文名称: joj_description字段编号英文字段名中文字段名字段类型备注1did题目描述编号int主键2uid用户编号int外键3pid题目编号int外键4description题目描述内容text5input输入样例介绍text6output输出样例介绍text7samples题目样例text用json数组格式保存。例如:[{‘input’:‘input content’,‘output’:‘output content’}]8hint提示信息text9updateTime更新时间datetime10author作者varchar11remarks备注text 基表英文名称: joj_message字段编号英文字段名中文字段名字段类型备注1mid留言编号int主键2replyMessageId回复留言编号int外键,默认为null3parentMessageId父留言节点编号int外键,默认为null4uid用户编号int5createTime留言创建时间datetime6content留言内容text7email邮箱varchar8nickName昵称varchar9remind回复是否提醒tinyint10avatar头像地址varchar 基表英文名称:joj_oj_daily_board字段编号英文字段名中文字段名字段类型备注1odbid编号int主键2submitCount提交数int3acceptedAC通过数int4collectTime日期date 基表英文名称: joj_participate字段编号英文字段名中文字段名字段类型备注1uid用户编号int主键,外键2cid比赛编号int主键,外键 基表英文名称: joj_problem字段编号英文字段名中文字段名字段类型备注1pid题目编号int主键2uid用户编号int外键3title题目标题varchar4source题目来源varchar5url题目链接varchar6originOJ源OJ名varchar7originProb源OJ题目编号varchar8memoryLimit内存限制int9timeLimit时间限制int10updateTime更新时间datetime 基表英文名称: joj_problem_tag字段编号英文字段名中文字段名字段类型备注1id题目标签编号int主键2tagName标签名varchar 基表英文名称: joj_problem_tags字段编号英文字段名中文字段名字段类型备注1id题目标签编号int主键、外键2pid题目编号int主键、外键 基表英文名称: joj_resource字段编号英文字段名中文字段名字段类型备注1resourceId资源编号int主键2resourceName资源名varchar 基表英文名称: joj_resource字段编号英文字段名中文字段名字段类型备注1roleId角色编号int主键2roleName角色名varchar 基表英文名称:joj_role_resource字段编号英文字段名中文字段名字段类型备注1roleId角色编号int主键2resourceId资源编号int 基表英文名称:joj_statusType字段编号英文字段名中文字段名字段类型备注1id状态编号int主键2type状态类型的值int该表需要插入这几种值,1. AC 2. PE 3. WA 4. TLE 5. MLE 6. OLE 7. RE 8. CE 9. UE 10. Queuing & Judging 11. Pending // 提交后,保存到数据库中的状态 基表英文名称:joj_submission字段编号英文字段名中文字段名字段类型备注1sid提交编号int主键2uid用户编号int外键3pid题目编号int外键4status状态名varchar5statusType状态类型int1. AC 2. PE 3. WA 4. TLE 5. MLE 6. OLE 7. RE 8. CE 9. UE 10. Queuing & Judging 11. Pending // 提交后,保存到数据库中的状态6additionalInfo备注信息text存放编译错误等信息7realRunId源OJ运行IDvarchar8time运行时间int9memory内容消耗int10subTime本地OJ提交的时间datetime11language语言类型varchar填源OJ提交语言时对应的编号12displayLanguage显示的语言名varchar13length代码长度int 基表英文名称:joj_user字段编号英文字段名中文字段名字段类型备注1uid用户编号int主键2userName用户名varchar3nickName用户昵称varchar4password密码varchar5email邮箱varchar6qqQQvarchar7avatar头像地址varchar8createTime创建时间datetime9updateTime更新时间datetime 基表英文名称:joj_user_role字段编号英文字段名中文字段名字段类型备注1uid用户编号int2roleId角色编号int

    四、系统使用说明

    4.1 系统基本安装环境

    开发环境:jdk1.8+tomcat9.0.33+IDEA2019.3+RabbitMQ 2.2.6 +MySQL8.0

    4.2 项目部署

    整个项目分为四大模块。

    JOJ(主服务):用于前端展示,与用户交互等fileserver(文件服务器):存放上传的文件信息。judgeserver(判题机):对用户提交的代码进行测评。extraserver(拓展服务):一些爬虫(爬取题目等)、定时任务(定时更新统计数据信息)、发送邮件

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BB9JNUAN-1593867571846)(joj/25.png)]

    创建一个总工程joj2020。四个模块都继承自joj2020便于jar包版本管理。

    四大模块打包成jar包后,通过下面命令运行

    java -jar jarname

    在mysql数据库中创建好数据库。

    往数据库表joj_user插入一个Spider用户。往joj_problem_tag表中插入支持OJ对应的标签名

    然后在extraserver和joj的配置文件中修改如下对应的配置文件,uid为Spider用户在数据库中的编号。tag.hdu和tag.codeforces为hdu和codeforces标签在数据库中的id

    joj: spider: uid: 1 userName: "System Spider" default: tag: hdu: 1

    安装好Rabbit MQ并创建交换机和消息队列

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tGhzYH89-1593867571847)(joj/26.png)]

    运行前需要求改一些相应的配置信息:

    rabbitmq和mysql的配置信息。(四大部分均需要)

    spring: rabbitmq: host: ip port: 5672 username: guest password: guest datasource: username: root password: root url: yourURL driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource

    发送邮件的相关配置 (extraserver部分)

    #邮件发送的配置 spring.mail.username=your email adresss spring.mail.password= password spring.mail.host=smtp.qq.com #配置465端口,保证在服务器上正常使用 spring.mail.properties.mail.smtp.socketFactory.port=465 spring.mail.default-encoding=UTF-8 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true spring.mail.port=465 spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory spring.mail.properties.mail.smtp.socketFactory.fallback = false

    五、待完善部分。

    由于开发时间优先,目前OJ暂时只支持HDU一个平台。如果您想拓展支持的OJ,可以根据定义号的爬虫的接口接着完善。

    Spider接口

    位于extraserver模块中

    爬取题面信息

    package cn.jxj4869.joj.spider; import cn.jxj4869.joj.entity.Description; import cn.jxj4869.joj.entity.Problem; import java.io.IOException; import java.net.URISyntaxException; public interface Spider { public Description crawl(Problem problem) throws Exception; }

    Submitter接口

    提交代码的接口。具体实现可以参考HDUSubmitter或者有问题可以评论留言。

    package cn.jxj4869.joj.submitter; import cn.jxj4869.joj.entity.Submission; import cn.jxj4869.joj.mapper.ProblemMapper; import cn.jxj4869.joj.mapper.SubmissionMapper; import cn.jxj4869.joj.pojo.Result; import cn.jxj4869.joj.utils.Tools; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; /** * @author JiangXiaoju * @date 2020-06-15 18:03 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public abstract class Submitter { protected Submission submission; protected String userName; protected String password; @Autowired protected SubmissionMapper submissionMapper; @Autowired protected ProblemMapper problemMapper; protected CloseableHttpClient httpClient = HttpClients.createDefault(); protected RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) .setConnectionRequestTimeout(500) .setSocketTimeout(10 * 1000) .build(); public Submitter(String userName, String password) throws Exception { this.userName = userName; this.password = password; login(); } // 登录 protected abstract void login() throws Exception; // 代码提交 protected abstract void submit() throws Exception; // 获取提交结果 protected abstract void getAns() throws Exception; // 两次提交的冷却时间,视OJ而定 public abstract void wait2SubmitTimeLimit(); // 更新提交状态 @Transactional protected void updateSubmission() { submissionMapper.updateById(submission); } // 获取编译错误等信息 protected abstract void getAdditionalInfo() throws Exception; public void work() { try { try { submit(); } catch (Exception e) { e.printStackTrace(); Thread.sleep(2000); // 长时间未提交会自动注销,所以第一次提交失败后,先尝试登录一下。 login(); Thread.sleep(2000); submit(); } submission.setStatus("Running & Judging"); submission.setStatusType(Tools.findStatusType("Running & Judging")); updateSubmission(); getAns(); updateSubmission(); } catch (Exception e) { submission.setStatus("Submit Fail"); submission.setStatusType(Tools.findStatusType("Submit Fail")); updateSubmission(); e.printStackTrace(); }finally { this.submission = null; } } }
    Processed: 0.017, SQL: 9