OJ平台架构/用户权限验证/JAVA后端开发

article/2025/10/22 22:55:30

转载自: 我的个人博客

OJ实验平台,采用沙箱后端运行代码,实现安全的实时评测(OJ)。此平台搭建的初衷为学校的数据结构实验提供课程代码的编写和测评,以此方便学生实验时得到实时的反馈,此后陆续被多个实验课程采用。本人在项目开发过程中,主要承担了OJ部分和题目部分的后端开发。

需求分析

将实验平台的功能分为核心业务和底层服务两大模块。

核心业务模块:

  • OJ评测模块
  • 用户管理模块(涉及验证)
  • 作业提交模块

底层服务模块:

  • 日志记录
  • 限流保护
  • 用户反馈
  • 评论/题解
  • 数据库请求(题目、题解、评论等)

技术栈

  • Springboot 后端开发框架,简化应用的搭建和开发过程
  • Redis 非关系型数据库,可用于缓存,保存非持久化数据
  • MySQL 关系型数据库管理系统
  • MyBatis 持久层框架,简化数据库访问
  • Java 后端开发语言
  • 七牛云 用户头像(图片)存储
  • Vue 构建界面的JavaScript框架,提供了一套声明式的、组件化的编程模型
  • ElementUI Vue组件集合
  • JavaScript web脚本语言,动态更新内容
  • Node.js javascript运行时环境

开发工具

  • IDEA 后端开发
  • WebStorm 前端开发
  • Xftp 服务器间文件传输
  • Postman 后端接口测试工具,模拟发送http请求

相关技术原理

沙箱

github仓库: https://github.com/criyle/go-judge

系统架构

+------------------------------------------------------------------------+
| 传输层 (HTTP / WebSocket / FFI / ...)                                  |
+------------------------------------------------------------------------+
| 工作协程 (环境池 和 环境生产者)                                        |
+-----------------------------------------------------------+------------+
| 运行环境                                                  | 文件存储   |
+--------------------+----------------+---------------------+------+-----+
| Linux (go-sandbox) | Windows (winc) | macOS (app sandbox) | 内存 | 磁盘|
+--------------------+----------------+---------------------+------+-----+

运行的代码以文件形式存储在沙箱服务器的内存/磁盘,在linux上,创建功能受限的容器,使代码在容器内运行,避免破坏环境。通过发送HTTP请求使沙箱执行相应操作,在受限的环境中运行程序,具体API接口如下一部分所示

REST API 接口

沙箱服务提供 REST API 接口来在受限制的环境中运行程序。本质是 envexec 的简单封装。

  • /run POST 在受限制的环境中运行程序(下面有例子)
  • /file GET 得到所有在文件存储中的文件 ID 到原始命名映射
  • /file POST 上传一个文件到文件存储,返回一个文件 ID 用于提供给 /run 接口
  • /file/:fileId GET 下载文件 ID 指定的文件
  • /file/:fileId DELETE 删除文件 ID 指定的文件
  • /ws /run 接口的 WebSocket 版
  • /metrics 提供 prometheus 版监控 (使用 ES_ENABLE_METRICS=1 环境变量开启)
  • /debug 提供 go 语言调试接口 (使用 ES_ENABLE_DEBUG=1 环境变量开启)
  • /version 得到本程序编译版本和 go 语言运行时版本

/run 接口返回状态

  • Accepted: 程序在资源限制内正常退出
  • Memory Limit Exceeded: 超出内存限制
  • Time Limit Exceeded:
    • 超出 timeLimit 时间限制
    • 或者超过 clockLimit 等待时间限制
  • Output Limit Exceeded:
    • 超出 pipeCollector 限制
    • 或者超出 -output-limit 最大输出限制
  • File Error:
    • copyIn 指定文件不存在
    • 或者 copyIn 指定文件大小超出沙箱文件系统限制
    • 或者 copyOut 指定文件不存在
  • Non Zero Exit Status: 程序用非 0 返回值退出
  • Signalled: 程序收到结束信号而退出(例如 SIGSEGV
  • Dangerous Syscall: 程序被 seccomp 过滤器结束
  • Internal Error:
    • 指定程序路径不存在
    • 或者容器创建失败
      • 比如使用非特权 docker
      • 或者在个人目录下以 root 权限运行
    • 或者其他错误

接口定义

interface Cmd {args: string[]; // 程序命令行参数env?: string[]; // 程序环境变量// 指定 标准输入、标准输出和标准错误的文件files?: (LocalFile | MemoryFile | PreparedFile | Pipe | null)[];tty?: boolean; // 开启 TTY (需要保证标准输出和标准错误为同一文件)同时需要指定 TERM 环境变量 (例如 TERM=xterm)// 资源限制cpuLimit?: number;     // CPU时间限制,单位纳秒clockLimit?: number;   // 等待时间限制,单位纳秒 (通常为 cpuLimit 两倍)memoryLimit?: number;  // 内存限制,单位 bytestackLimit?: number;   // 栈内存限制,单位 byteprocLimit?: number;    // 线程数量限制strictMemoryLimit?: boolean; // 开启严格内存限制 (仅 Linux,设置 rlimit 内存限制)// 在执行程序之前复制进容器的文件列表copyIn?: {[dst:string]:LocalFile | MemoryFile | PreparedFile};// 在执行程序后从容器文件系统中复制出来的文件列表// 在文件名之后加入 '?' 来使文件变为可选,可选文件不存在的情况不会触发 FileErrorcopyOut?: string[];// 和 copyOut 相同,不过文件不返回内容,而是返回一个对应文件 ID ,内容可以通过 /file/:fileId 接口下载copyOutCached?: string[];// 指定 copyOut 复制文件大小限制,单位 bytecopyOutMax: number;
}enum Status {Accepted,            // normalMemoryLimitExceeded, // mleTimeLimitExceeded,   // tleOutputLimitExceeded, // oleFileError,           // feRuntimeError,        // reDangerousSyscall,    // dgsInternalError,       // system error
}interface Request {requestId?: string; // 给 WebSocket 使用cmd: Cmd[];pipeMapping: PipeMap[];
}interface Result {status: Status;error?: string; // 详细错误信息exitStatus: number; // 程序返回值time: number;   // 程序运行 CPU 时间,单位纳秒memory: number; // 程序运行内存,单位 byterunTime: number; // 程序运行现实时间,单位纳秒// copyOut 和 pipeCollector 指定的文件内容files?: {[name:string]:string};// copyFileCached 指定的文件 idfileIds?: {[name:string]:string};
}

RSA加密原理

RSA是经典的非对称加密算法,拥有两个密钥:公钥和私钥。

公钥和私钥的生成

  1. 准备两个非常大的素数 p p p q q q
  2. 利用字符串模拟计算大素数 p p p q q q 的乘积。
  3. 同样方法计算 m = ( p − 1 ) ( q − 1 ) m = (p-1)(q-1) m=(p1)(q1),这里的 m m m n n n的欧拉函数。
  4. 找到一个数 e ( 1 < e < m ) e(1<e<m) e(1<e<m),满足 g c d ( m , e ) = 1 gcd(m,e)=1 gcd(m,e)=1( m , e m, e m,e互素)。
  5. 计算 e e e在模 m m m上的逆元 d d d,即满足$ ed \ mod \ m = 1$。
  6. 公钥和私钥生成完毕, ( n , e ) (n,e) (n,e)为公钥, ( n , d ) (n,d) (n,d)为私钥。

加密

对于明文 x x x,用公钥 ( n , e ) (n,e) (n,e) x x x加密后得到的密文y为: y = x e m o d n y = x^e \ mod \ n y=xe mod n

解密

对于密文 y y y,用私钥 ( n , d ) (n,d) (n,d) y y y解密后得到的明文x为: x = y d m o d n x = y^d \ mod \ n x=yd mod n

主要功能实现思路

OJ在线评测

OJ评测部分的流程大致如下图所示:
在这里插入图片描述

  1. 根据时间创建文件,将提交的代码以文件形式存入沙箱。
  2. 在Cmd类对象中填入运行代码所需的工具链,如c语言对应gcc
  3. 将代码按沙箱规定的输入格式进行转化。
  4. 向沙箱请求/run接口,发送HTTP请求,并对沙箱返回的结果进行解码。
  5. 若返回状态不是Accepted,则代码执行失败,将错误输出返回给用户,提示用户修改代码。
  6. 若返回状态为Accepted,则代码执行成功,返回代码执行的时间,所占用的内存,输出等信息。
  7. 删除存放在沙箱中的代码文件。
  8. 将用户提交的程序的输出和数据库中对应题目的正确答案进行对比,若一致,则创建提交记录,其状态为成功;否则创建提交记录,其状态为对应的失败原因。

用户管理–邮箱验证

  1. 随机产生6位验证码(包括字母/数字),并将其按用户对应的键存储到Redis。
  2. 只用JavaMailSender将验证码和提示信息发送给用户。
  3. 用户在页面输入验证码,提交,验证码由前端通过接口传到后端。
  4. 后端验证用户提交的验证码是否与Redis中存储的一致,若一致,则验证通过;若不一致,或Redis中不存在对应验证码(过期),则验证失败。
为什么使用Redis作为验证码的存储方式?
  • Redis可设置过期时长,与验证码的时效性相吻合。
  • 验证码是临时的数据,不需要持久化。
  • 数据运行在内存中,唯有持久化时才写到磁盘中,读写性能高。

用户管理–登录

  1. 首先验证账户是否存在,前端发送用户账户名,后端生成验证码a,并以一定的名称+用户id作为键存入Redis(有效期5min),将验证码a和查询数据库所得的用户id返回给前端。若用户id不存在,则登录失败。
  2. 前端将用户输入的密码+第1步返回的验证码a拼接,用SHA256加密后发送给后端。后端将正确密码+验证码a拼接,用SHA256加密,和前端传来的密文进行比较。若一致,则登录成功;否则密码不正确,登录失败。
  3. 删除Redis中存储的验证码a
  4. 根据用户名、用户id、权限级别,创建验证码b,以online_{id}作为键存在Redis中,7天有效。(即意味着用户登陆后7天有效,无需每次使用时重新登录)。

用户管理–修改密码

1 旧密码已知,直接修改

  1. 验证账户是否存在。前端发送用户账户名,后端生成验证码a(有效期5min),并以一定的名称+用户id作为键存入Redis,将验证码a和查询数据库所得的用户id返回给前端。若用户id不存在,则验证失败。
  2. 验证旧密码是否正确。前端将用户输入的旧密码+第1步返回的验证码a拼接,用SHA256加密后发送给后端。后端将正确旧密码+验证码a拼接,用SHA256加密,和前端传来的密文进行比较。若一致,则旧密码验证成功;否则密码不正确,旧密码验证失败,修改密码失败,操作终止。
  3. 删除Redis中存储的验证码a
  4. 若旧密码验证成功,生成验证码b(有效期5min),并以一定的名称+用户id作为键存入Redis(一段时间后失效),标志旧密码验证成功。
  5. 若旧密码验证成功,返回用于加密的RSA公钥。
  6. 修改新密码。若验证码b在redis中不存在,则修改失败,退出。
  7. 验证码b在redis中存在,接受前端发送的用公钥加密后的密码,用RSA的私钥对加密后的密码解密,存入数据库。

2 旧密码遗忘,通过邮箱验证,修改

  1. 验证账户是否存在。前端发送用户账户名,后端生成验证码a,并以一定的名称+用户id作为键存入Redis。
  2. 将第1步生成的验证码a发送到用户的邮箱。
  3. 用户查看邮件后在页面输入收到的验证码。若用户输入的验证码与redis中存储的一致,则将验证码a删除并生成验证码b,一定的名称+用户id作为键存入Redis,并返回RSA公钥。若用户输入的验证码与redis中存储的不一致,提示用户验证码不正确。
  4. 若用户邮箱验证成功(验证码b在redis中存在),则用RSA私钥解码前端传来的加密后的新密码,更新数据库,删除验证码b

http://chatgpt.dhexx.cn/article/PkUa61xq.shtml

相关文章

sql中检查时间是否重叠

先画一个时间轴&#xff0c;方便理解。 设新的时间块&#xff0c;开始时间为start&#xff0c;结束时间为end。数据库中的数据为item 这样可以直观的看出来&#xff0c;新的时间块插入进来&#xff0c;只需要判断 start<item.end and end > item.start 即可 当然上…

JS判断多个时间段是否有重叠(时分)

多个时间段比较 let dateAr [{ s: 01:00, e: 14:08 },{ s: 01:04, e: 02:05 },{ s: 05:21, e: 08:00 },{ s: 10:20, e: 12:08 }, ] function Fn() {for (let k in dateAr) {if (!judege(k)) {return false}}return true } function judege(idx) {for (let k in dateAr) {if (…

合并所有重叠的区间

Python-合并区间 题目 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 示例 1: 输入&#xff1a;interva…

关于sql语句中,统计时间重叠和时间不重叠

关于sql统计时间重叠和时间不重叠的问题 我们在开发过程中总会遇到这样的情况&#xff0c;一行数据中&#xff0c;有id、组、开始时间、结束时间。但是开始时间和结束时间肯定会出现时间重叠问题&#xff0c;比如说下面这种情况。 我们先找出时间重叠的公式&#xff1a; 可以发…

开始时间与结束时间重叠

let endNum ; //整个二维数组for(let itemName of showDate) {//for (let i 0; i < itemName.children.length; i) {if(itemName.screenId itemName.children[i].screenId) {// console.log(itemName.children[i])//把开始时间和结束时间转化成时间戳if(parseInt(itemNam…

在SQL Server中合并重叠日期

1.合并SQL中的时间重叠的记录方法:A --创建临时表&#xff0c;里面有重叠的日期declare t table (Name varchar(100), starttime datetime, endtime datetime); insert into t values(A , 2017-01-02 00:00 , 2017-03-28 00:10),(A , 2017-05-14 23:50 , 2017-05-29 23…

Java判断多个时间段是否重叠

因为项目需求用到比较给定时间段是否与已知时间段有重叠&#xff0c;所以写了一个比较简单易懂的方法&#xff0c;经测试可以满足本人目前的需求&#xff0c;大家如果发现该方法有什么问题可以提出来&#xff0c;如果有更好的方法也可以分享。 package com.demo;import java.u…

SQL中如何求解时间重叠的问题?

点击上方SQL数据库开发&#xff0c;关注获取SQL视频教程 SQL专栏 SQL数据库基础知识汇总 SQL数据库高级知识汇总 问题描述 时间重叠指上下两行数据的时间段有重叠部分&#xff0c;现在要找出这些在时间上有重叠的记录。 具体问题 有7个会议室&#xff0c;每个会议室每天都有人开…

多个日期时间段进行合并计算时长,剔除重叠时间段

多个日期时间段进行合并&#xff0c;算出经历的时长_itrytellyou的博客-CSDN博客_多个时间段合并多个日期时间段进行合并&#xff0c;算出经历的时长背景描述思路代码背景描述在公司进行人员描图模块开发时&#xff0c;涉及到了人员参会时长和参会次数的数据&#xff0c;比如一…

【js】判断时间段之间是否有重叠

一、需求 可以有多个时间段&#xff08;hh:mm&#xff09;&#xff0c;添加下一个时间段以及提交时需要判断各个时间段不能交叉&#xff0c;如下图&#xff1a; 一、方法 let dateAr [{ s: 02:00, e: 03:00 },{ s: 03:00, e: 04:01 },{ s: 05:00, e: 06:00 },{ s: 07:00, e:…

SQL中的时间重叠问题

点击关注上方“SQL数据库开发”&#xff0c; 设为“置顶或星标”&#xff0c;第一时间送达干货 SQL专栏 SQL基础知识第二版SQL高级知识第二版 问题描述 时间重叠指上下两行数据的时间段有重叠部分&#xff0c;现在要找出这些在时间上有重叠的记录。 具体问题 有7个会议室&#…

判断两个时间段交集、时间重叠问题

判断两个时间段范围是否有交集(时间重叠)问题经常遇到&#xff0c;比如预约会议室开会&#xff0c;活动的开始结束等&#xff0c;本文做此分析。 前提&#xff1a;本文时间传参已做限制&#xff0c;结束时间必须大于开始时间 1、正向思维 代码如下 (oneStartTime < other…

判断时间范围是否重叠(原理)

首先问题来源&#xff1a;因为业务需求需要在前端设置一个时间范围&#xff0c;并且可以多次增加&#xff0c;但要保证时间范围不重叠。某一个时间范围内做特定的事情。 让我们来分析一下时间重叠有几种情况 第一种情况&#xff1a;R1的开始时间在R2时间范围内 图① 第二种情…

企业物联网平台如何选择?

物联网平台的考虑因素都有哪些&#xff1f; 通常&#xff0c;物联网平台在安全功能、协议、数据格式、数据分析和可视化能力&#xff0c;以及所提供的应用环境方面进行比较。有很多物联网平台&#xff0c;它们可能拥有全套功能&#xff0c;但可能仍然无法胜任客户任务需求。一…

物联网技术,主要包括哪些核心技术

物联网的关键技术有识别和感知技术&#xff0c;网络与通信技术&#xff0c;数据挖掘与融合技术。 1.识别和感知技术 最常见的就是生活的的二维码了。 通过二维码&#xff0c;我们可以和图片&#xff0c;网址&#xff0c;软件&#xff0c;整个世界联系起来。 2.网络与通信技…

什么是物联网平台

1、什么是物联网平台 阿里云物联网平台为设备提供安全可靠的连接通信能力&#xff0c;向下连接海量设备&#xff0c;支撑设备数据采集上云&#xff1b;向上提供云端API&#xff0c;服务端通过调用云端API将指令下发至设备端&#xff0c;实现远程控制。 物联网平台消息通信流程…

物联网是什么,和互联网之间主要有什么区别

物联网概念最早可追溯到1990年&#xff0c;但当时并未引起重视。而随着互联网的普及、科技的发展&#xff0c;物联网在近年来备受关注&#xff0c;并成为一个新经济增长点的战略新兴产业。 物联网(InternetofThings)&#xff0c;顾名思义就是"物物相连的互联网";更为…

物联网的应用领域有哪些?

智能仓储 目前&#xff0c;国内最成熟的智能仓储解决方案智能仓储解决方案&#xff0c;智能仓储是物流过程的一个环节&#xff0c;智能仓储的应用&#xff0c;保证了货物仓库管理各个环节数据输入的速度和准确性&#xff0c;确保企业及时准确地掌握库存的真实数据&#xff0c;…

物联网的组成包含哪些

物联网采集到的信息常常需要直接报警或者是经过计算机处理后报警&#xff0c;常见的报警形式有声、光、电&#xff08;电话、短信&#xff09;。当所选参数偏离预先设定的限度值时能进行报警的系统。 物联网的组成包含哪些 物联网大致可以分为以下四个层面&#xff0c;即&#…

物联网技术主要有哪些,目前都应用在哪些领域?

现在看这篇文章的你&#xff0c;是在用什么方式阅读这篇文章&#xff1f;是通过手机&#xff1f;还是通过电脑&#xff1f;还是平板电脑呢&#xff1f;无论您是用什么设备&#xff0c;它都是联网的。 互联网的出现是一件非常美好的事情&#xff0c;它为我们提供了以前不可能实…