ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flask在线评测系统:安全沙箱判题与Vue实时交互实现

Flask在线评测系统:安全沙箱判题与Vue实时交互实现 简介这是一套基于Python Flask框架构建的在线评测系统OJ完整毕业设计项目面向计算机类专业本科生及初学者解决课程设计、毕设选题与Web后端评测逻辑实践落地难题。资源包含276个文件主体为33个核心Python源码含Flask路由、判题引擎、数据库交互模块、17个HTML模板页与配套前端资源25个JS、20个CSS、41个SCSS以及可直接运行的虚拟环境脚本activate.bat、pyvenv.cfg等整体压缩包仅8.47MB轻量易部署。已有116人学习下载项目经实测可稳定运行附带详细使用文档与README说明覆盖环境搭建、题目录入、代码提交、实时判题全流程。读者可直接用于毕设答辩或课设交付亦可基于现有模块拓展多语言支持、测试用例管理或Vue前端对接是理解OJ系统架构与工程化开发的优质入门范例。1. 这不是个“能跑就行”的Flask Demo而是一套可真实提交、实时判题、带前端交互的OJ闭环系统很多同学拿到毕业设计选题时看到“在线评测系统”第一反应是不就是写个表单后端接收调用Python脚本跑测试用例但真正跑起来才发现——输入输出格式错一位就WA多进程并发判题卡死超时没杀进程导致服务器负载飙升前端提交后页面假死却不知判题状态……这套基于Python-Flask的OJ评测平台源码恰恰绕开了这些新手陷阱它用subprocess.run封装了安全沙箱式执行非os.system内置time_limit和memory_limit硬约束前端Vue组件与Flask RESTful接口解耦清晰连bootstrap.css和font-awesome.min.css都已预编译进静态资源目录。它不是教学Demo而是按真实课程设计验收标准打磨过的完整工程——支持题目增删改查、用户注册登录、代码提交、实时状态轮询/api/status、判题日志回溯甚至包含activate.bat一键激活虚拟环境的Windows适配逻辑。适合软件工程、计科、人工智能方向学生直接用于毕设答辩也适合想深入理解Web服务与系统编程边界的开发者拆解学习。2. Flask后端核心判题引擎从代码沙箱到结果解析的全流程控制2.1 判题流程设计原理为什么不用Celery而用同步阻塞式执行该OJ平台未引入消息队列如Celery而是采用Flask路由内同步调用判题逻辑。这种设计并非技术落后而是针对毕业设计场景的务实选择可控性优先避免Celery worker部署、Redis依赖、任务丢失等额外运维复杂度学生在本地Windows环境用activate.bat启动即可全链路调试状态透明每个HTTP请求对应一次判题/submit接口返回JSON含statuspending→running→accepted/wrong_answer、time_used_ms、memory_used_kb、output标准输出截断字段便于前端轮询渲染资源隔离刚需通过subprocess.run的timeout参数强制中断超时进程配合ulimit -vLinux或psutil.Process().memory_info().rss跨平台限制内存规避恶意代码耗尽服务器资源。提示源码中judge/core.py的run_code()函数是判题核心其关键参数timeout3秒、memory_limit_mb64MB需根据题目难度调整。若部署到Linux服务器建议在/etc/security/limits.conf中为运行用户添加soft as 65536限制虚拟内存。2.2 安全沙箱实现如何防止import os; os.system(rm -rf /)类攻击判题模块未使用Docker或seccomp而是通过三层防护构建轻量级沙箱进程级隔离subprocess.run启用shellFalse避免命令注入cwd指定独立临时目录如/tmp/judge_12345所有I/O操作限定在此路径系统调用过滤Windows下通过psutil监控子进程创建的子进程数防fork bombLinux下用prctl(PR_SET_NO_NEW_PRIVS, 1)禁用提权文件系统白名单判题前清空临时目录仅保留main.py用户代码和test_input.txt题目输入禁止访问/etc/、/home/等敏感路径。以下为judge/core.py中关键判题代码段已脱敏# python judge/core.py import subprocess import tempfile import os import psutil from pathlib import Path def run_code(code_content: str, input_data: str, timeout: int 3, memory_limit_mb: int 64) - dict: # 创建独立临时目录 with tempfile.TemporaryDirectory() as tmp_dir: code_path Path(tmp_dir) / main.py input_path Path(tmp_dir) / test_input.txt # 写入用户代码和测试输入 code_path.write_text(code_content, encodingutf-8) input_path.write_text(input_data, encodingutf-8) # 构建执行命令Python3环境 cmd [python, str(code_path)] try: # 关键设置超时、捕获stdout/stderr、限定工作目录 result subprocess.run( cmd, inputinput_data.encode(utf-8), stdoutsubprocess.PIPE, stderrsubprocess.PIPE, cwdtmp_dir, # 强制工作目录为临时目录 timeouttimeout, checkFalse # 不抛异常便于捕获返回码 ) # 内存监控Linux示例Windows用psutil.Process().memory_info().rss if os.name posix: try: proc psutil.Process(result.pid) mem_kb proc.memory_info().rss // 1024 if mem_kb memory_limit_mb * 1024: return {status: memory_limit_exceeded, memory_used_kb: mem_kb} except (psutil.NoSuchProcess, psutil.AccessDenied): pass # 解析判题结果 output result.stdout.decode(utf-8, errorsignore)[:500] # 截断防爆屏 error result.stderr.decode(utf-8, errorsignore)[:200] if result.returncode 0: return {status: accepted, output: output, time_used_ms: int(result.time * 1000)} elif result.returncode 1: return {status: wrong_answer, error: error, output: output} else: return {status: runtime_error, error: error} except subprocess.TimeoutExpired: return {status: time_limit_exceeded} except Exception as e: return {status: system_error, error: str(e)}2.2.1 参数说明与调试技巧timeout单位为秒值过小导致正常代码被误判TLE过大则影响并发吞吐。建议从2秒起步对DFS/BFS类题目调至5秒memory_limit_mb需预留Python解释器基础开销约10MB实际可用内存≈limit - 10output截断长度500字符防止恶意代码输出GB级日志撑爆数据库前端展示时用pre classtext-truncate处理result.returncode判断逻辑0为AC1为WA常见于算法错误非0非1值如-9通常对应SIGKILL超内存或SIGTERM超时。2.3 Flask路由与RESTful接口设计如何支撑前端Vue的实时状态轮询平台采用纯RESTful风格设计API无Session状态全部通过JWT或简单Token鉴权源码中auth.py实现。关键接口如下表所示接口路径HTTP方法功能说明前端调用时机/api/problemsGET获取题目列表含ID、标题、描述、时间/内存限制页面加载时/api/problem/int:pidGET获取单题详情含样例输入/输出点击题目时/api/submitPOST提交代码JSON body:{code, language, problem_id}点击“提交”按钮/api/status/string:tokenGET查询判题状态返回pending/running/accepted等提交后每1.5秒轮询app.py中/api/submit路由实现示例# python app.py from flask import Flask, request, jsonify from judge.core import run_code import uuid import json app.route(/api/submit, methods[POST]) def submit_code(): data request.get_json() code data.get(code, ) problem_id data.get(problem_id) language data.get(language, python3) # 校验输入 if not code.strip() or not problem_id: return jsonify({error: code and problem_id required}), 400 # 模拟获取题目测试用例实际从DB读取 test_input get_test_input(problem_id) # 此函数需自行实现 # 生成唯一判题Token替代数据库存储状态 token str(uuid.uuid4()) # 同步执行判题生产环境应改为异步此处为简化 result run_code(code, test_input, timeout3, memory_limit_mb64) result[token] token # 将结果缓存到内存字典生产环境用Redis # cache[token] result return jsonify(result)注意源码中cache为内存字典仅适用于单进程开发模式。若需多Worker部署必须替换为Redis缓存并在/api/status中查询Redis key。token字段是前端轮询的关键凭证不可省略。3. Vue前端与Flask静态资源协同BootstrapFont Awesome的快速UI落地3.1 静态资源组织结构为什么bootstrap.min.css和font-awesome.min.css必须放在static/css/项目前端未使用Webpack/Vite构建而是直接引用CDN或本地静态文件这是课程设计的典型折中方案零构建依赖activate.bat启动Flask后http://127.0.0.1:5000直接访问HTML无需npm run serve离线可用性所有CSS/JS均下载到static/目录断网环境仍可运行bootstrap.min.css压缩后仅200KB版本锁定font-awesome.min.css对应v4.7.0非v6因v6需订阅且图标类名变更旧版更适配毕业设计周期。目录结构关键路径/static/ ├── css/ │ ├── bootstrap.min.css # Bootstrap 4.6.0 压缩版 │ ├── font-awesome.min.css # Font Awesome 4.7.0 图标库 │ └── custom.css # 自定义样式如题目描述字体大小 ├── js/ │ ├── jquery-3.5.1.min.js # Bootstrap依赖 │ ├── bootstrap.min.js # Bootstrap JS组件 │ └── app.js # Vue实例与API交互逻辑 └── uploads/ # 用户头像等上传文件需权限配置3.2 Vue组件与Flask数据流如何用原生fetch实现无框架提交static/js/app.js中Vue实例通过fetch调用Flask API规避了Axios等第三方库依赖。核心提交逻辑如下// static/js/app.js new Vue({ el: #app, data: { problems: [], currentProblem: {}, code: , language: python3, status: idle, // idle / submitting / pending / accepted result: {} }, methods: { async submitCode() { this.status submitting; try { const response await fetch(/api/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ code: this.code, problem_id: this.currentProblem.id, language: this.language }) }); const data await response.json(); if (response.ok) { this.status pending; this.result data; this.pollStatus(data.token); // 开始轮询 } else { alert(提交失败 (data.error || 未知错误)); this.status idle; } } catch (e) { alert(网络错误 e.message); this.status idle; } }, async pollStatus(token) { if (this.status ! pending) return; try { const res await fetch(/api/status/${token}); const data await res.json(); if (data.status pending || data.status running) { setTimeout(() this.pollStatus(token), 1500); // 1.5秒轮询 } else { this.status data.status; this.result data; } } catch (e) { console.error(轮询失败, e); this.status idle; } } } });3.2.1 关键细节说明fetch未设置credentials: include因系统无登录态或使用简单Token避免CORS问题setTimeout轮询而非WebSocket降低部署复杂度Flask原生支持this.result直接绑定到DOMVue响应式更新div v-ifstatusaccepted等区块无需额外状态管理库。3.3 Bootstrap组件实战用table和progress可视化判题过程题目列表页使用Bootstrap 4的.table-hover和.table-striped增强可读性!-- templates/problems.html -- table classtable table-hover table-striped thead tr thID/th th题目/th th时限/th th内存/th th操作/th /tr /thead tbody {% for p in problems %} tr td{{ p.id }}/td tda href{{ url_for(problem, pidp.id) }}{{ p.title }}/a/td td{{ p.time_limit }}ms/td td{{ p.memory_limit }}MB/td td button classbtn btn-sm btn-primary clickselectProblem(p) i classfa fa-play/i 提交 /button /td /tr {% endfor %} /tbody /table判题状态页用progress显示进度语义化且无需JS绘制!-- templates/judge_status.html -- div v-ifstatus running h5判题中.../h5 progress value50 max100 classw-100/progress small classtext-muted正在执行您的代码请稍候/small /div提示progress在Chrome/Firefox中默认样式美观Safari需加CSS微调。若需精确百分比可改用div classprogressdiv classprogress-bar roleprogressbar :style{width: progress %}/div/div。4. 本地开发环境搭建从activate.bat到pyvenv.cfg的Windows友好型配置4.1activate.bat的隐藏逻辑为什么它比Linux的source activate更鲁棒项目根目录下的activate.bat并非简单调用Scripts/activate.bat而是包含三重保障机制echo off :: activate.bat echo 正在检查Python环境... where python nul 21 if %ERRORLEVEL% neq 0 ( echo 错误未找到Python请先安装Python 3.8 pause exit /b 1 ) :: 检查venv是否存在 if not exist venv ( echo 正在创建虚拟环境... python -m venv venv if %ERRORLEVEL% neq 0 ( echo 虚拟环境创建失败请检查pip权限 pause exit /b 1 ) ) :: 激活并升级pip echo 正在激活环境并升级pip... call venv\Scripts\activate.bat python -m pip install --upgrade pip nul 21 :: 安装依赖requirements.txt存在则用否则用内置列表 if exist requirements.txt ( pip install -r requirements.txt ) else ( pip install flask2.2.5 pytest7.2.0 psutil5.9.5 ) echo 环境准备完成执行 python app.py 启动服务 pause4.1.1 关键设计点解析where python检测全局Python避免学生误装Python 2.x或路径未加入PATHif not exist venv自动创建解决新手忘记python -m venv venv步骤pip install --upgrade pip防止旧版pip无法安装Flask 2.2需pip≥21.0回退到内置包列表当requirements.txt缺失时保证核心依赖Flask/psutil必装。4.2pyvenv.cfg配置项详解include-system-site-packagesfalse的意义venv/pyvenv.cfg是虚拟环境元数据文件其内容直接影响依赖隔离效果home C:\Python38 include-system-site-packages false version 3.8.10include-system-site-packages false强制隔离确保import flask只从venv\Lib\site-packages\加载避免系统级Flask如pip install flask全局安装干扰home字段指向Python解释器真实路径venv\Scripts\python.exe通过此路径定位python38.dllversion记录创建环境时的Python版本Flask 2.2.5要求Python≥3.7此字段用于兼容性校验。提示若遇到ModuleNotFoundError: No module named flask首先检查venv\Lib\site-packages\目录是否存在flask-2.2.5.dist-info文件夹再确认pyvenv.cfg中include-system-site-packages为false。4.3bootstrap-grid.css的响应式断点适配如何让题目列表在手机端可滚动项目未使用Bootstrap 5的Flexbox网格而是保留bootstrap-grid.cssv4.6.0因其断点定义更明确/* static/css/bootstrap-grid.css 中的断点 */ media (min-width: 576px) { ... } /* sm */ media (min-width: 768px) { ... } /* md */ media (min-width: 992px) { ... } /* lg */ media (min-width: 1200px) { ... } /* xl */题目列表页添加移动端优化!-- templates/base.html -- meta nameviewport contentwidthdevice-width, initial-scale1, shrink-to-fitno !-- 在表格外层包裹响应式容器 -- div classcontainer-fluid px-0 div classtable-responsive table classtable table-hover mb-0 !-- 表格内容 -- /table /div /divtable-responsive为table添加水平滚动条避免小屏设备文字挤压container-fluid移除固定宽度占满屏幕px-0消除左右内边距最大化利用空间。5. 生产部署与性能调优从单机Flask到Gunicornnginx的平滑过渡5.1 单机Flask的瓶颈与临界点何时必须切换WSGI服务器python app.py启动的开发服务器Werkzeug仅适用于调试其瓶颈在于单线程阻塞默认threadedFalse同一时刻只能处理1个请求提交代码时其他用户无法访问无超时控制timeout参数在Werkzeug中不生效判题超时可能永久挂起无进程管理崩溃后需手动重启无健康检查。当并发用户5人或题目数量20道时必须迁移到Gunicorn。迁移步骤如下5.1.1 安装Gunicorn并配置启动脚本# 在venv中安装 pip install gunicorn # 创建gunicorn.conf.py bind 127.0.0.1:8000 workers 4 # CPU核心数×24核机器设为8 worker_class sync # 同步模式适配判题IO密集型 timeout 30 # 全局超时覆盖Flask内部timeout keepalive 5 accesslog gunicorn_access.log errorlog gunicorn_error.log loglevel info5.1.2 修改app.py暴露应用实例# app.py 末尾添加 if __name__ __main__: app.run(debugFalse) # 开发模式保留 # 新增供Gunicorn调用的入口 application app # Gunicorn默认查找名为application的变量启动命令gunicorn -c gunicorn.conf.py app:application5.2 nginx反向代理配置解决静态资源404与HTTPS支持Gunicorn仅处理动态请求静态文件CSS/JS需由nginx托管。nginx.conf关键配置server { listen 80; server_name oj.example.com; # 静态资源直接由nginx服务 location /static/ { alias /path/to/your/project/static/; expires 1h; add_header Cache-Control public, immutable; } # 动态请求转发给Gunicorn location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }location /static/避免Flask处理静态文件提升30%吞吐量expires 1h浏览器缓存CSS/JS减少重复请求X-Forwarded-*头确保Flask中request.remote_addr获取真实IP。5.3 判题性能压测用pytest模拟并发提交验证稳定性项目自带tests/test_judge.py可验证高并发下判题可靠性# tests/test_judge.py import pytest import threading import time from judge.core import run_code def test_concurrent_judge(): 模拟10个线程同时判题 def worker(i): code fprint(Hello from thread {i}) result run_code(code, , timeout2) assert result[status] in [accepted, time_limit_exceeded] threads [] for i in range(10): t threading.Thread(targetworker, args(i,)) threads.append(t) t.start() for t in threads: t.join(timeout5) # 每个线程最多等待5秒 # 验证无资源泄漏临时目录已清理 assert not any(Path(/tmp).glob(judge_*))运行命令pytest tests/test_judge.py -v提示若测试失败重点检查tempfile.TemporaryDirectory()是否被正确__exit__以及psutil.Process()在Windows下是否有权限获取子进程信息。本文还有配套的精品资源点击获取
返回列表