前言

QingdaoU OnlineJudge是一款开源的在线判题系统,我当时也是第一次接触基于该系统开发在线编程教育系统,也是踩了不少坑,这里分享下我的个人经验。

课程详情界面


在线课程界面

目前还在开发中

编程题库界面

评测题界面在原来页面基础上将各个题目筛选项统一起来,同时搜索框更加醒目。

笔试题库界面

在原来的只有评测题的基础上增加选择题和填空题两种类型,后续可以再拆分出选择题为单选题和多选题来丰富题库。

评测题答题界面

在原来的答题界面上调整了页面布局,主要的就是答题界面分为左右两栏布局,这样右边在写代码时更加方便的去查看题目的描述、输入、输出以及输入输出样例等信息,其他就是题目一些标签属性信息放到更醒目的位置。

答题结果界面

主要的是调整了测试点由原来的table展示变成下图展示的,也是为了让答题者更能直观的看出评测结果

用户主页界面

将原来的个人信息设置从之前的menu点击切换改成了一个menu下的三个选项卡来分别设置不同的信息。后续其他的menu都会在该页面可以自由切换,这样方便用户操作和自己相关的界面

接口文档界面

QingDaoU OnlineJudge相关模块

后端(Django): https://github.com/QingdaoU/OnlineJudge
前端(Vue): https://github.com/QingdaoU/OnlineJudgeFE
部署(docker+docker-compose):https://github.com/QingdaoU/OnlineJudgeDeploy
判题沙箱(Seccomp): https://github.com/QingdaoU/Judger
判题服务器(对Judger的封装): https://github.com/QingdaoU/JudgeServer

安装

linux安装

linux安装就很简单了,只需要在服务器安装好docker和docker-compose,不会安装可参考我的博客文章 docker以及docker-compose笔记。安装完成后只需要上传上面的部署模块源代码到服务器目录下,
需要保证关于docker-compose里面的宿主机器映射端口都可用,如postgresqll的5432、redis的6379、oj-backend的80和443端口都可用,然后切换到部署目录下执行

docker-compose up -d

windows安装

如果只是想部署一下看看不打算而开的话,windos下安装同linux下安装一样,只不过docker换成docker desktop,当然docker-compose也需要安装的,github有提供可执行文件安装的,很方便。不过也有坑,貌似windows家庭版的安装docker desktop有坑,我踩过了,忘记记录,后面有再次安装,到时候再来记录。

环境搭建

先说下我当时二开的步骤,因为当时公司配的电脑是windows系统,所以在搭建环境的时候也是走了不少路。

前端

先说下前端项目,前端项目安装的时候切记node版本一定要是8.12.0, 一开始我就是官网下载的最新版那边node,直接依赖都安装不上。按照前端的源码的readme的代码如下:

npm install
# we use webpack DllReference to decrease the build time,
# this command only needs execute once unless you upgrade the package in build/webpack.dll.conf.js
set NODE_ENV=development
npm run build:dll# the dev-server will set proxy table to your backend
set TARGET=http://Your-backend# serve with hot reload at localhost:8080
npm run dev

首先上面的命令都是可行的,但是这个环境变量只会作用于当前终端窗口,也就说你下次再打开终端还是不重新设置的话就没了。如果想永久,windows直接设置个环境变量就行。

后端

再说回后端,后端肯定是重中之中了,要想二开那肯定得好好看后端得代码了,要看代码肯定免不了要debug,所以后端是必须要在windows下跑起来。这个倒不难,后端源码下载下来正常启动django项目得步骤即可。数据库得的配置可以改为自己本地的postgresql库。

然后的话就是需要评测功能,而评测功能是依赖另一个项目也就是judge-server,可以下载OnlineJudgeDeploy下载下来然后修改如下部分即可

judge-server:image: registry.cn-hangzhou.aliyuncs.com/onlinejudge/judge_servercontainer_name: judge-serverrestart: alwaysread_only: falsecap_drop:- SETPCAP- MKNOD- NET_BIND_SERVICE- SYS_CHROOT- SETFCAP- FSETIDtmpfs:- /tmpvolumes:- ./data/backend/test_case:/test_case:ro- ./data/judge_server/log:/log- ./data/judge_server/run:/judgerenvironment:- SERVICE_URL=http://127.0.0.1:8080- BACKEND_URL=http://host.docker.internal:8000/api/judge_server_heartbeat/- TOKEN=3c8e8bd79d66259c46d8a3d057278dc8- judger_debug=1ports:- "0.0.0.0:8080:8080"

其中SERVICE_URL一定要和下面的宿主机端口:容器端口对应,不然到时候后端提交的数据时候会找不到地址。

BACKEND_URL是 judge-server会访问后端接口维持心跳(前提是一定要保证后端配置项里面的token一致

然后的话下载 docker desktop 安装docker-compose,然后切换到OnlineJudgeDeploy的目录下执行

docker-compose up judge-server -d

启动 judge-server即可。

因为QDU OJ用到了dramatiq任务队列,如果不想测试队列功能而只是开发可以修改根目录下submission/views/oj.py的第81行代码为如下,本地开发时就不需要启动dramatiq了

from django.conf import settings
from judge.dispatcher import JudgeDispatcher
if settings.DEBUG:JudgeDispatcher(submission.id, problem.id).judge()
else:judge_task.send(submission.id, problem.id)

如果需要启动dramatiq异步任务队列,终端切换到根目录下执行一下命令 python manage.py rundramatiq --processes 2 --threads 4 --log-file ./data/log/dramatiq.log

一些你需要的手段

OnlineJudge全局修改中文

前端修改
我们编辑前端源代码路径src/il8n/index.js如下

import Vue from 'vue'
import VueI18n from 'vue-i18n'
// ivew UI
import ivenUS from 'iview/dist/locale/en-US'
import ivzhCN from 'iview/dist/locale/zh-CN'
import ivzhTW from 'iview/dist/locale/zh-TW'
// element UI
import elenUS from 'element-ui/lib/locale/lang/en'
import elzhCN from 'element-ui/lib/locale/lang/zh-CN'
import elzhTW from 'element-ui/lib/locale/lang/zh-TW'Vue.use(VueI18n)const languages = [{value: 'en-US', label: 'English', iv: ivenUS, el: elenUS},{value: 'zh-CN', label: '简体中文', iv: ivzhCN, el: elzhCN},{value: 'zh-TW', label: '繁體中文', iv: ivzhTW, el: elzhTW}
]
const messages = {}// combine admin and oj
for (let lang of languages) {let locale = lang.valuelet m = require(`./oj/${locale}`).mObject.assign(m, require(`./admin/${locale}`).m)let ui = Object.assign(lang.iv, lang.el)messages[locale] = Object.assign({m: m}, ui)
}
// load language packages
export default new VueI18n({locale: languages[1]['value'],messages: messages
})export {languages}

这样改的话其实也就是修改了默认的页面语言,其实通过看src/pages/oj/views/setting/children/ProfileSetting.vue的代码如下部分:

          <FormItem label="Language"><Select v-model="formProfile.language"><Option v-for="lang in languages" :key="lang.value" :value="lang.value">{{lang.label}}</Option></Select></FormItem>

可以看到QindDaoU OJ是支持用户自定义设置自己的页面国际化的,如果不想让用户自主设置国家化的话那可以注释这块代码,不让用户选择同时修改后端源码的account/models.py里面的UserProfile模型给language 一个默认值,这样以后所有的用户都是默认中文

 language = models.TextField(default="zh-CN")

但是还需要执行sql语句改变以前已经创建过的用户了

update user_profile set language='zh-CN'

另外个人觉得如果你怕麻烦,执行sql语句是最方便的哈哈

用户密码忘记了?

因为用户的密码是通过django提供的密码加密过了,所以数据库修改不现实

上服务器修改

# 先切换到你的oj部署目录下
docker exec -it oj-backend /bin/sh
python3 manage.py inituser --username USERNAME --password NEW_PASSWORD --action=reset

本地连接线上数据库

这种修改方式需要保证SECRET_KEY一样哦

python3 manage.py inituser --username USERNAME --password NEW_PASSWORD --action=reset

或者

# 切换到项目根目录下
python manage.py shell
from django.contrib.auth.hashers import make_password
from accout.models import User
User.objects.filter(username="xxxx").update(password=make_password("123456"))

定义返回数据格式

用过QDU OJ都知道,有用到djangorestframework,但是仅仅使用到了drf提供的序列化器serializer,然后基于django.views.generic.Viewn封装了一个APIView,该APIView提供参数解析、异常信息抽取、数据分页以及统一统一返回格式如下

{"error": null,"data": null
}

没有完全发挥drf提供的功能,如分页器PageNumberPagination和LimitOffsetPagination、viewsets(视图集)和generic(通用试图)以及权限验证

封装response

{"code": "ok","msg“: "请求成功'"data": null
}
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK,HTTP_400_BAD_REQUEST
class APIResponse:@staticmethoddef response(code="", msg="", data=None, status=None, headers=None, **kwargs):result = {"code": code, "msg": msg, "data": data}return Response(data=result, status=status, headers=headers, **kwargs)@staticmethoddef success(msg="请求成功", data=None, status=HTTP_200_OK, headers=None, **kwargs):result = {"code": "ok", "msg": msg, "data": data}return Response(data=result, status=status, headers=headers, **kwargs)@staticmethoddef error(msg="请求失败", data=None, status=HTTP_200_OK, headers=None, **kwargs):result = {"code": "error", "msg": msg, "data": data}return Response(data=result, status=status, headers=headers, **kwargs)

异常拦截

需要添加统一的异常原因是因为在使用drf的序列化器时会直接抛出异常,不加处理的话,返回格式不会和自定义response返回格式统一

from rest_framework import status
from system.response import APIResponse
from django.http import Http404, JsonResponse
from rest_framework import exceptions
from rest_framework.exceptions import PermissionDenied
from rest_framework.views import set_rollback
from rest_framework_simplejwt.exceptions import InvalidToken
import logginglogger = logging.getLogger(__name__)exception_map = {"not_found": "请求数据不存在","permission_denied": "权限未通过","parse_error": "请求参数异常","authentication_failed": "认证失败","not_authenticated": "尚未认证","method_not_allowed": "请求方式不支持","throttled": "超过限流次数","invalid": "请求参数异常","not_acceptable": "要获取的数据格式不支持","unsupported_media_type": "不支持的媒体类型",}def exception_handler(exc, context):if isinstance(exc, Http404):exc = exceptions.NotFound()elif isinstance(exc, PermissionDenied):exc = exceptions.PermissionDenied()if isinstance(exc, exceptions.APIException):headers = {}if getattr(exc, 'auth_header', None):headers['WWW-Authenticate'] = exc.auth_headerif getattr(exc, 'wait', None):headers['Retry-After'] = '%d' % exc.waitcode = exc.default_codeif isinstance(exc, InvalidToken):msg = "token无效或已过期"else:msg = extract_validate_errors(exc.detail)set_rollback()return APIResponse.response(code=code, msg=msg, status=status.HTTP_200_OK, headers=headers)else:logger.error(exc)return APIResponse.error(msg="服务器出错了", status=status.HTTP_500_INTERNAL_SERVER_ERROR)"""提取序列化验证的错误"""def extract_validate_errors(errors):_list = []if isinstance(errors, dict):for k, v in errors.items():if isinstance(v, list):if k=="non_field_errors":_list.append("".join(v))else:_list.append("{0}:{1}".format(k, ";".join(v)))else:print(v)# _list.append(v)msg = ",".join(_list)else:msg = errorsreturn msgclass MyJSONRenderer(JSONRenderer):def render(self, data, accepted_media_type=None, renderer_context=None):if "code" not in data and "msg" not in data:data = {"code": "ok", "msg": "请求成功", "data": data}return super(MyJSONRenderer, self).render(data, accepted_media_type, renderer_context)

在settings.py的REST_FRAMEWORK里面添加自定义的异常处理器和渲染类

REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'system.exceptions.exception_handler','DEFAULT_RENDERER_CLASSES': ['system.renderers.MyJSONRenderer',],
}

模块扩展

用户管理

用户管理这块QDU OJ缺乏细颗粒度的权限控制,而且QDU OJ没有保留django-admin自带的user-group-permission权限,只是添加了一个AdminType来区分权限。这块我也还没做,后期考虑做成基于RBAC设计权限模型。

题库管理

QDU OJ只是有评测题,题目类型不丰富,在此基础上新增了选择题、填空题两种题型。

选择题界面

填空题界面

选择题和填空题的内容字段考虑到内容的丰富性继续沿用原本的富文本编辑编辑器。
将题目模型里面的难度、标签、来源单独抽出来放到字典模型里面

class SysDictType(object):PROBLEM_DIFFICULTY = "PROBLEM_DIFFICULTY"PROBLEM_TAGS = "PROBLEM_TAGS"PROBLEM_SOURCE = "PROBLEM_SOURCE"PROBLEM_YEAR = "PROBLEM_YEAR"class StatusType(object):INVALID = 0VALID = 1class SysDict(models.Model):type = models.CharField(verbose_name="类型", max_length=50)name = models.CharField(verbose_name="名称", max_length=50)status = models.SmallIntegerField(verbose_name="状态", help_text="状态(0无效,1有效)", default=StatusType.VALID)create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True)update_time = models.DateTimeField(verbose_name="创建时间", auto_now=True, null=True)order = models.IntegerField(verbose_name="排序", help_text="数字越大,越靠后", default=1)class Meta:db_table = "sys_dict"unique_together = (("name", "dict_type"),)verbose_name = "系统字典表"verbose_name_plural = verbose_name

这样抽出来之后在后台界面就可以方便管理这些字典属性了。

由于QDU OJ的赛事逻辑是在题目表里面增加了contest字段来保证题目属于赛事,这里我增加了新的模型试卷模型,通过试卷和题目一对多,赛事和试卷一对一来实现赛事出试卷的功能。

数据模型

应用 表模型
系统应用 用户表 学生表 教师表
课程应用 课程表 课次表 班级表 赛事表
题目应用 评测题表 选择题表 填空题表 试卷表 试卷题目关联表
操作应用 学生课程关联表 学生班级关联表 老师课次关联表 学生课次关联表 试卷成绩表 题目成绩表 学生赛事关联表

因为评测题目是支持多种语言的判题,所以我觉得可以增对多种语言开设课程,如python 、java、C++等
学生在首页选择对应的课程体系,然后查看课程详情
这里面关于学生选班的问题有两种思路,一种就是课程详情下面有班级列表(学员是否满班),学生自己在选择班级,当然这种的话就需要后台管理员要及时新增新的班级。另一种就是学员只需要自己报名课程,系统会自动分配班级,因为同一个课程下不同班级上的课程内容都是一致的,只不过是班级进度存在不同。这里面也考虑不引入班级概念,学生只需要报名课程,就可以学习课程下的若干课次,这样的话说不定更好。

接口文档

如果是基于QDU OJ单人开发自己用的话,接口文档确实没必要。但是如果是团队开发,接口文档有必要的。而且drf有提供自动生成接口文档的功能。但是如果想实现这种效果,则需要改掉很多代码,目前尝试重构QDU OJ代码来实现自动生成接口文档。

基于QingDao Online Judge开发在线编程教育网相关推荐

  1. vb和php一起开发,基于VB和PHP开发在线人事工资管理系统

    计 算机 科 学 消费 电子 年 月 下 基于 和 开发在线人事工资管理系统 孙 丽 娜 (哈 尔滨铁 道 职 业技 术 学 院 ,哈 尔滨 ) 摘 要 :人事 工资管理 系统越 来越 多的应 用于企 ...

  2. go语言打印日期_基于 Go 语言开发在线论坛(八):消息、视图及日期时间本地化...

    我们接着上篇在线论坛的进度,由于之前所有页面和消息文本都是英文的,而我们开发的应用基本都是面向中文用户的,所以需要对项目进行本地化,今天正好借着这个入门项目给大家介绍下如何在 Go Web 应用中进行 ...

  3. 一种基于CUDA标准的异构并行编程模型开发简介

    一种基于CUDA标准的异构并行编程模型开发简介 目录 一.绪论 1.1研究背景及意义 1.2目标平台体系结构简介 二.HPPA基本组成结构 三.编译工具链开发 3.1 拆分工具HPCufe开发 3.2 ...

  4. 基于python物流管理系统毕业设计-Python程序设计实验报告一 :熟悉IDLE和在线编程平台...

    Python程序设计实验报告 班级 物流192 姓名 张羽 学号 3190505221 成绩 日期 3月5日 指导老师 修宇 实验名称 实验一 熟悉IDLE和在线编程平台 实验目的 1.掌握pytho ...

  5. 用手机写代码:基于 Serverless 的在线编程能力探索

    简介:Serverless 架构的按量付费模式,可以在保证在线编程功能性能的前提下,进一步降低成本.本文将会以阿里云函数计算为例,通过 Serverless 架构实现一个 Python 语言的在线编程 ...

  6. 考试君 - 基于.NET 5语言的Furion框架开发在线考试系统

    简介: 考试君 - 基于.NET 5语言的Furion框架开发在线考试系统 网盘下载地址: http://kekewl.net/GiRBtDuKMeP0 图片:

  7. 基于React和fabricjs开发的在线名片、海报设计器,大前端项目在线图片编辑器源码分享

    基于React和fabricjs开发的在线名片.海报设计器,大前端项目在线图片编辑器 大家好我是伟伟权 现在我给大家介绍一个我的前端项目 这是一个名片设计器 它是使用react加fabric js进行 ...

  8. Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户、物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据、人工智能、机器学习项目开发

    Java语言开发在线音乐推荐网 音乐推荐系统 网易云音乐爬虫 基于用户.物品的协同过滤推荐算法 SSM(Spring+SpringMVC+Mybatis)框架 大数据.人工智能.机器学习项目开发Mus ...

  9. matlab 28335,基于DSP28335和MATLAB在线编程VF控制实现毕业设计

    基于DSP28335和MATLAB在线编程VF控制实现毕业设计 毕业设计(论文 题 目 基于 DSP28335 和 MATLAB 在线编程 V/F 控制实现 专 业 电气工程及其自动化 班 级 电气 ...

最新文章

  1. LoadRunner 测试Oracle数据库及Siebel性能
  2. apollo在Linux下读不到参数,Apollo的基本使用及常见问题
  3. mysql1756_MySQL Error_code: 1756
  4. find与grep命令简介及正则表达式(转)
  5. 腾讯 tars java_腾讯 Tars 基础框架手动搭建——填掉官方 Guide 的坑
  6. Axios和Ajax处理后台返回文件流实现文件下载(图片和Excel)
  7. Device Tree Usage(转)https://elinux.org/Device_Tree_Usage
  8. javascript详解函数原型对象prototype与constructor
  9. 11. 瞬时响应:网站的高性能架构
  10. 2021-06-27Date时间
  11. 如何接收谷歌账号的注册短信
  12. 计算机辅助设计和辅助制造简称,计算机辅助设计与制造
  13. 昭阳k20-80拆机过程
  14. python moving average_Python实现滑动平均(Moving Average)的例子
  15. mycat启动报错:but failed to start(个例)
  16. 开启Windows的文件大小写区分功能
  17. 仔细看这几段代码,看看你是Python新鸟还是老鸟
  18. Ramnit感染型蠕虫病毒专杀工具
  19. 计算机毕业设计Node.js+Express校园二手拍卖网(源码+程序+lw+远程调试)
  20. OKR与MBO/KPI的区别

热门文章

  1. test.java:7: error: error while writing test: test.class (Permission denied)
  2. 基于springBoot框架开发短视频去水印源码项目+IntelliJ IDEA运行测试+本地实现功能
  3. 一位姐姐的朋友惨遭前同事诈骗
  4. Fluent中的网格自适应技术
  5. 删除表格一行数据并更新后表格数据无变化的解决方法
  6. ybt1271_潜水员
  7. 串行通信 软件仿真STM32与74LS164通信
  8. Android 低功耗BLE蓝牙适配总结
  9. 李彦宏发全员信:在国家抗击疫情的特殊时刻,每个人都要尽职尽责
  10. 商城项目SKU实现(详细)