首页 / 资讯中心 / 文章详情

在线C语言教学系统开发:Flask+Vue前后端分离与代码判题全解析

在线C语言教学系统开发:Flask+Vue前后端分离与代码判题全解析 ★ FEATURED ARTICLE
1. 这个“flaskdjango”混搭标题背后你先搞清楚自己到底要用哪个看到这个标题我第一反应是很多同学做课设的时候直接拿网上某个现成题目改一改就提交了导致技术栈写得乱七八糟。在线C语言教学系统这个方向本身没问题是个很经典的、适合练手也适合答辩的项目但“python基于flask在线C语言教学系统的设计与实现-vue pycharm django”这个标题前后其实是打架的——Flask和Django是两个完全不同的Web框架你不可能在同一个项目里既用Flask又用Django作为主后端硬要混用只会把自己搞疯。所以我先把这事掰开说清楚你再决定标题怎么改、架构怎么搭。先说结论如果你已经选了Flask那就老老实实把标题里“django”去掉写成“Python基于Flask和Vue的在线C语言教学系统的设计与实现”然后开发工具用PyCharm前后端分离Flask提供RESTful APIVue写管理后台和前端页面。反过来如果你对Django更熟那就把Flask去掉用Django的admin和ORM能省不少事。我见过不少类似的项目有人把Flask当纯接口层、把Django当一个“大壳子”结果两边都起服务、两个框架都要维护数据库连接还经常冲突纯属给自己加戏。这里我帮你理一下选型逻辑。对比项FlaskDjango上手难度低适合快速写接口中等偏高自带admin和ORM自由度高组件自由拼装相对固定但开发效率高自带后台无需要自己写自带admin管理题库很方便适合场景轻量API、前后端分离单体应用、后台管理密集型项目代码量相对少但需要自己组装相对多但样板代码齐全对于在线C语言教学系统我的建议是想快速出效果、答辩时能讲清楚每个模块就用Flask。因为这套系统的核心难度其实不在Web框架上而在“代码判题”和“在线编译”这两个功能上框架选越简单的越好你能把更多精力放在核心逻辑上。也有人会问那PyCharm和Vue是怎么回事其实就是开发工具的搭配PyCharm是IDE写Python后端很方便跑Flask直接点运行即可调试断点比VSCode舒服Vue是前端框架用来做页面交互。你只要分清楚谁是后端、谁是前端这个项目的整体脉络就出来了。我的建议很简单技术栈固定为后端Python 3.8FlaskSQLAlchemyORMJWT做登录认证前端Vue 3 Element Plus打包后由Flask托管静态文件或者单独部署数据库MySQL或SQLite我推荐开发阶段用SQLite答辩前换成MySQL省的到时候因为MySQL环境起不来慌神代码判题调用本机GCC编译执行或者用Docker沙盒隔离开发工具PyCharm Professional社区版也能用但Professional对Flask和SQLAlchemy的支持更友好这个架构足够你完成一个像模像样的系统而且不会有任何框架冲突的问题。2. 在线C语言教学系统的核心模块拆解从用户端到管理端很多同学拿到这种题目第一反应就是“不就是个网站吗”结果做着做着发现功能越做越乱题库、作业、实验、考试全堆在一起代码提交后还不能自动判分最后只能靠手动改卷子那就失去“在线教学”的意义了。所以动手之前功能边界一定要先划清楚。2.1 用户角色划分学生、教师、管理员到底谁能干什么在线C语言教学系统用户角色我建议分三类你别多也别少学生注册登录、浏览课程内容、在线写代码并提交、查看判题结果、查看实验成绩、查看错题记录。教师维护题库、发布作业/实验、查看学生提交记录、手动评阅主观题如果有、统计学生成绩。管理员用户管理、课程管理、系统参数配置、全局数据统计一般就是教师账号加个管理权限不用单独搞一个庞大后台。这三种角色在数据库里的实现其实很简单users表加一个role字段值为student、teacher、admin。有些人非要搞三张用户表完全不必要徒增关联复杂度。2.2 题库管理类型要分清楚数据字段要提前设计好C语言题库是这个系统的核心资源我在设计题库表的时候一开始只做了简单的题目表后来发现不够用又加了好几个字段建议你一次性把结构想好。题目的类型我分了几种选择题/判断题主要用于课后练习前端直接渲染选项后端判答案。填空题涉及字符串匹配可以精确匹配也可以模糊匹配需要设计一个规则字段。编程题这是最核心的部分学生提交C语言源码后端调用编译器编译运行拿预设的测试用例比对输出结果。创建一个编程题至少要包含以下字段class Problem(db.Model): __tablename__ problems id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(200), nullableFalse) description db.Column(db.Text, nullableFalse) # 题目描述 input_desc db.Column(db.Text, nullableFalse) # 输入说明 output_desc db.Column(db.Text, nullableFalse) # 输出说明 sample_input db.Column(db.Text, nullableFalse) # 样例输入 sample_output db.Column(db.Text, nullableFalse) # 样例输出 test_cases db.Column(db.Text, nullableFalse) # 测试用例JSON字符串 level db.Column(db.String(20), defaulteasy) # 难度 creator_id db.Column(db.Integer, db.ForeignKey(users.id)) created_at db.Column(db.DateTime, defaultdatetime.utcnow)这里的test_cases字段是核心我用JSON字符串存储一组测试用例格式像这样[ {input: 1 2, output: 3}, {input: 5 6, output: 11}, {input: 100 200, output: 300} ]为什么要存JSON而不是单独建一张表因为对于课程设计级别的系统多一张测试用例表意味着要多写一套增删改查接口而实际上判题时反正是一次性把数据全读出来的JSON够用了。但如果你的题目数量特别大、想在线管理测试用例那就再拆表也不迟。2.3 实验与作业的绑定题目不能孤立存在题目建好之后还需要把它们组织成“实验”或“作业”。比如“第三次实验循环结构”下面挂10道题学生做完、判题通过就算完成了本次实验。这个关系我用一张assignment表来维护class Assignment(db.Model): __tablename__ assignments id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(200), nullableFalse) description db.Column(db.Text) problem_ids db.Column(db.Text) # 题目ID列表JSON字符串 course_id db.Column(db.Integer, db.ForeignKey(courses.id)) start_time db.Column(db.DateTime) end_time db.Column(db.DateTime)problem_ids存的是JSON字符串比如[1,3,5]这样做的好处是修改作业时不用动多对多表一个接口就能更新整个作业的题目列表。当然这是课程设计级别的粗放做法生产项目不建议但对你来说效率和可维护性足够了。3. 代码判题这个功能才是灵魂为什么很多毕设挂在这里在线C语言教学系统最容易翻车的功能就是代码判题。很多同学习惯用“调cmd执行gcc”这种最简单的方案但实际测试下来会发现各种奇葩问题编译超时、内存溢出、路径带空格、沙盒权限、并发提交把服务器吃垮。这一节我把我实践的完整方案写出来你照着做理论上能避免80%的坑。3.1 判题的核心流程提交、编译、运行、比对判题服务其实是独立于Flask主进程的一套逻辑。我的思路是学生提交代码后Flask接口把代码保存成文件然后交给一个判题类去处理最后把结果写回数据库。为了保证主服务不卡死我当时用的方案是线程池异步处理但为了演示方便同步判题也不是不行本地单用户测试完全没区别。判题流程我拆成了五步接收学生提交的源码和题目ID。从题目记录中取出测试用例列表。把源码写到临时目录。遍历每个测试用例用gcc编译源码只编译一次然后运行编译产物把输入喂给进程捕获标准输出。比对实际输出与期望输出全部通过则该题Accepted否则返回第一个报错用例的信息。核心代码我简化之后大概是这个样子import subprocess import tempfile import os def judge_c_code(source_code, test_cases): with tempfile.TemporaryDirectory() as tmpdir: src_file os.path.join(tmpdir, main.c) exe_file os.path.join(tmpdir, main) with open(src_file, w, encodingutf-8) as f: f.write(source_code) # 编译 compile_result subprocess.run( [gcc, src_file, -o, exe_file], capture_outputTrue, textTrue, timeout10 ) if compile_result.returncode ! 0: return {status: compile_error, detail: compile_result.stderr} # 逐个测试用例运行 for i, case in enumerate(test_cases): case_input case[input].replace(\\n, \n) expected case[output].replace(\\n, \n).strip() try: run_result subprocess.run( [exe_file], inputcase_input, capture_outputTrue, textTrue, timeout5 ) actual run_result.stdout.strip() if run_result.returncode ! 0: return {status: runtime_error, case_index: i, detail: run_result.stderr} if actual ! expected: return { status: wrong_answer, case_index: i, expected: expected, actual: actual } except subprocess.TimeoutExpired: return {status: timeout, case_index: i} return {status: accepted}这里的运行逻辑看起来简单但有几个细节你必须注意编译超时和运行超时要分开设置。编译超时给10秒运行超时给5秒你如果只用一个超时时长遇到递归死循环的题目会把整个判题进程拖死。每一行输入不要用字符串拼测试用例存的时候用\n表示换行取出来之后再转换否则你会在输入格式上踩坑。程序的stdin输入要用inputcase_input传入而不是通过管道写入因为textTrue模式下的input参数更稳定。3.2 怎么应对“Accepted之后乱杀进程”和安全问题代码判题最大隐患不是判错而是恶意代码或死循环。网上很多教程只教到“运行可执行文件”就不讲了那你把服务部署到公网之后学生提交一个while(1);就能让你的服务器CPU打满提交一个system(rm -rf /)噩梦就直接上演。我当时的缓解方案是这样进程运行超时限制这个必须有而且时间要给足但也不能太长我建议3到5秒够C语言跑完任何正常教学题。运行子进程的用户权限要降级。在Linux服务器上专门创建一个低权限用户用subprocess执行时通过preexec_fn切换用户运行。这一步在真实部署时很重要但你在Windows上的PyCharm里没法直接模拟所以你得知道这个思路答辩时能讲出来。用Docker容器做隔离是最稳妥的方案每个提交启一个容器运行完销毁代价是部署复杂度上升。课程设计不强制但如果你写到“提升与展望”里答辩会很加分。我在某个模拟项目X里实测过不设超时限制时一个死循环就能把整个Flask进程卡住所有学生都无法访问系统加上超时限制后单次提交即使死循环也只会占用5秒的CPU时间然后被强制杀掉系统其余功能不受影响。光这一条就能让你的系统从“能跑”变成“真正可用”。3.3 判题结果的状态设计别只有对和错很多同学做判题系统状态就存两个值通过、不通过。但实际使用中学生需要知道自己的代码到底错在哪一步。我当时设计了完整的判题状态机Pending等待判题- Judging判题中 ├── Accepted ├── Compile Error ├── Wrong Answer ├── Runtime Error └── Time Limit Exceeded每一种状态前端都有对应的UI提示比如编译错误要展示编译器的报错信息Wrong Answer要展示样例输入、期望输出和你的输出Runtime Error要展示错误原因和导致异常的那个测试用例序号。这些信息越细学生越能自己debug也越少来问老师为什么不对。数据库里我把判题结果设计成一张submissions表核心字段包括class Submission(db.Model): id db.Column(db.Integer, primary_keyTrue) user_id db.Column(db.Integer, db.ForeignKey(users.id)) problem_id db.Column(db.Integer, db.ForeignKey(problems.id)) assignment_id db.Column(db.Integer, db.ForeignKey(assignments.id)) code db.Column(db.Text) status db.Column(db.String(30)) # accepted / wrong_answer ... detail db.Column(db.Text) # 用于存储错误信息或输出 submit_time db.Column(db.DateTime, defaultdatetime.utcnow) exec_memory db.Column(db.Float) # 内存占用 exec_time db.Column(db.Float) # 运行时间 judge_server db.Column(db.String(50)) # 记录哪台机器判的扩展用顺便提一句exec_memory要拿到的话在Linux平台上可以用resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss来取但跨平台兼容性差。做课设的话我建议你直接把内存统计做成可选功能核心还是判题正确性。4. 前端接入Vue怎么跟Flask安全地配合Vue写前端本身不难难的是前后端对接时你可能会遇到的问题跨域、Token传递、上传代码时格式怎么约定、答题状态怎么联动。这一节我把我在实际项目里验证过的方案写出来。4.1 后端API设计先定接口文档再写代码我自己有个习惯写Flask后端之前先把所有API接口列表用表格列出来这样写前端的时候就知道该调什么接口。这个系统我建议你至少要预留下面这些接口给你自己用方法路径功能权限POST/api/register学生注册公开POST/api/login登录返回JWT公开GET/api/problems题目列表分页、筛选登录GET/api/problems/题目详情登录POST/api/submissions提交代码登录GET/api/submissions/me我的提交记录登录GET/api/assignments作业/实验列表登录POST/api/assignments发布作业教师DELETE/api/assignments/删除作业教师/管理员GET/api/stats/teacher教学统计教师/管理员接口文档先定好前后端并行开发时才不会出现“前端说字段名是status后端返回的是code”这种推诿问题。这个项目我建议你直接在Flask的api/蓝图下统一管理不要散在主app.py里。4.2 跨域问题开发时用代理部署时同源前后端分离最讨厌的就是跨域。我开发的时候前端在localhost:5173后端在localhost:5000直接fetch必然会被CORS拦住。我当时的第一版处理方式是在Flask里装Flask-CORS前端代码里写上完整后端地址调试确实没问题但部署时很麻烦——前端改地址要重新打包而且第三方请求一多CORS配置容易乱。更好的方案是开发阶段在Vue的vite.config.js里配置proxy代理前端请求/api时自动转发到Flask的5000端口浏览器感知不到跨域。部署阶段把Vue打包后的dist目录放到Flask的static目录下然后让Flask把所有非/api路由都渲染index.html这样前端和后端完全同源根本不需要CORS。Vite的代理配置如下你可以直接参考// vite.config.js export default { server: { port: 5173, proxy: { /api: { target: http://localhost:5000, changeOrigin: true } } } }部署时Flask这边这样写app.route(/) def index(): return send_from_directory(dist, index.html) app.errorhandler(404) def not_found(e): # 前后端分离项目如果前端路由刷新时404回归到首页 return send_from_directory(dist, index.html)4.3 登录鉴权JWT怎么在Vue端保存和使用我在这里踩过一次坑一开始图省事把JWT存在localStorage里刷新页面后Token还在但一旦被XSS攻击就可能泄露。后来我改用了合理一些的做法——把Token放在localStorage用来做API请求但Token过期时间设置尽量短比如2小时同时在后端做好Token刷新接口。前端请求拦截器我是这样写的// main.js 或 request.js axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) axios.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { // Token过期跳登录页 router.push(/login) } return Promise.reject(error) } )后端的Flask这边用装饰器验证JWT很简单import jwt from functools import wraps def token_required(f): wraps(f) def decorated(*args, **kwargs): token None if Authorization in request.headers: token request.headers[Authorization].split( )[1] if not token: return jsonify({message: Token is missing.}), 401 try: data jwt.decode(token, app.config[SECRET_KEY], algorithms[HS256]) current_user User.query.get(data[user_id]) except Exception as e: return jsonify({message: Token is invalid.}), 401 return f(current_user, *args, **kwargs) return decorated注意JWT的secret_key一定要单独放到config文件里别写死在代码中项目里我一般用环境变量加载。5. 开发环境与技术选型的实战建议PyCharm配置和数据库初始化5.1 PyCharm里如何把Flask项目结构搭得清爽PyCharm新建项目不要选“Flask”模板自带的app.py单文件结构看起来像个玩具项目。我推荐你一开始就按模块化去建后面扩展功能会舒服很多。一个可用的目录结构长这样online_c_teaching/ ├── app.py # Flask应用入口 ├── config.py # 全局配置 ├── models.py # 所有ORM模型 ├── extensions.py # db、ma 等扩展对象 ├── blueprints/ │ ├── __init__.py │ ├── auth.py # 登录、注册 │ ├── problems.py # 题库接口 │ ├── submissions.py # 提交与判题 │ └── assignments.py # 作业管理 ├── judge/ │ ├── __init__.py │ └── compiler.py # 判题核心逻辑 ├── scripts/ │ └── init_db.py # 数据库初始化脚本 ├── dist/ # 前端打包产物 ├── requirements.txt └── README.md这个结构的好处在于Flask蓝图Blueprint能把路由拆到不同文件models.py集中管理数据库模型judge目录单独放置判题逻辑不跟Web层纠缠。你如果答辩时把项目结构图一放老师说“这个结构可以”很难挑出大毛病。5.2 数据库初始化SQLite切换MySQL要注意的三个坑开发阶段用SQLite确实省事但这里有几个坑你迟早会遇到。我建议你先用SQLite跑通所有功能然后准备答辩前一周换MySQL下面是我踩过的三个典型问题JSON字段兼容性SQLite里没有原生的JSON字段类型我存test_cases用的是Text但MySQL如果用JSON类型查询时返回的数据会被自动解析为字典导致前后端处理的格式不一致。解决办法是统一用Text/字符串类型存JSON读取时手动json.loads。中文编码连接MySQL时一定要加上charsetutf8mb4否则你存题目描述时一旦出现emoji或者生僻字就直接报错。SQLite没有这个问题。时间字段SQLite的DateTime存取比较宽松MySQL要求更严格最好统一用datetime.utcnow赋默认值不要尝试让数据库自己处理。如果你不想折腾MySQL直接全程用SQLite也能答辩前提是题目里别写“支持高并发”否则老师一问你数据量大怎么扛你不好圆。5.3 前后端联调为什么你先别急着写页面我一个血的教训是功能还没跑通就先调前端样式结果前后端联调时发现字段对不上前端代码翻来覆去改浪费了两天。正确做法是先把所有后端API用Postman测一遍确认返回格式、状态码、错误信息都符合预期再开始写Vue页面。比如登录接口你先在Postman里发个POST返回的JSON长这样你心里就有底了{ code: 200, message: success, data: { token: eyJhbGciOiJIUzI1Ni..., username: student01, role: student } }前端页面拿到这份数据就知道该把token存到哪里、用户名显示在哪、角色控制哪些按钮。如果等到页面写完再调试错误信息乱七八糟根本不知道是前端bug还是后端bug。6. 部署到服务器时的真实踩坑记录端口、虚拟环境、进程守护6.1 虚拟环境与依赖锁定别用全局环境裸跑我见过不少同学把项目直接放到服务器上没有用虚拟环境结果服务器原本的Python版本跟本地不一样一装依赖就冲突最后甚至把服务器的系统Python给搞坏了。我自己在本地开发时用conda或venv部署时在服务器上也用venv并要求固定版本写进requirements.txt。python -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt里最关键的几个依赖版本我列出来供参考Flask2.3.2 Flask-SQLAlchemy3.0.5 Flask-Cors4.0.0 PyJWT2.8.0 gunicorn21.2.0生产环境我不用Flask自带的开发服务器werkzeug而是用gunicorn来跑并发能力和稳定性都更好。开发时在PyCharm里直接点运行没问题部署到Linux服务器上你一定得配gunicorngunicorn -w 4 -b 0.0.0.0:5000 app:app这里-w 4表示开4个worker进程能扛住一个班几十个人同时提交代码。如果你还开了线程池做异步判题可以适当调低worker数避免内存被吃满。6.2 同一个端口被占用、GCC版本不一致这类问题在线判题系统对编译环境的依赖很重服务器上的GCC版本如果跟本地不一致本地编译通过、服务器上可能报语法错误比如本地是GCC 12支持了一些新特性服务器上GCC 9不支持。所以部署前一定要在服务器上执行一下gcc --version我当时就遇到过一个学生提交for (int i 0; i n; i)本地GCC 12没问题服务器GCC 7报错说C89模式不支持for循环内声明变量。解决方案是编译时加上-stdc11参数这样不管GCC版本多老都能正确处理C11标准语法。这个细节特别值得注意你的判题参数里最好固定compile_cmd [gcc, src_file, -o, exe_file, -stdc11, -lm]另一个常见的坑是端口被占用。假设你之前启动了Flask开发服务器没有关掉再次启动就会报Address already in use。处理方法很简单lsof -i:5000 kill -9 pid但如果在Windows的PyCharm里遇到直接在进程管理器里结束Python进程即可。6.3 数据库备份别等项目没了才后悔所有课设项目都会面临一个问题答辩前突然数据损坏或误删。我当时在数据库备份上偷了个懒后来一次清理临时文件时不小心把SQLite数据库文件给清掉了好几天的测试数据全没了只能重新初始化。后来我加了个定时备份脚本每天把数据库文件复制一份加上时间戳放到backup目录里。MySQL备份用mysqldumpSQLite直接用脚本复制文件#!/bin/bash cp /path/to/online_c_teaching.db /path/to/backup/online_c_teaching_$(date %Y%m%d).db这个脚本配合crontab每天凌晨跑一次你自己本地开发时也可以手动执行一下几分钟的事关键时刻能救你一命。7. 提高完成度的小细节导出实验成绩、批量导入题库、判题并发控制系统做到这一步已经能跑通所有核心功能了。但如果你想让它在答辩时显得更完整我建议你再加几个“小而美”的功能它们实现成本不高但老师一眼就能看出你花了心思。7.1 成绩Excel导出教师最想要的功能我实测下来教师用户最常用的功能除了布置作业就是导出成绩。在线C语言教学系统如果没有这个功能教师每节课都要手动从网页里抄成绩那系统就失去意义了。实现起来用Flask加一个openpyxl依赖前端点击“导出成绩”按钮后端把该实验所有学生的最高分、提交次数、得分汇总成一个Excel文件返回from flask import send_file from openpyxl import Workbook import io app.route(/api/assignments/int:aid/export) def export_scores(aid): wb Workbook() ws wb.active ws.append([学号, 姓名, 最高分, 提交次数, 最近提交时间]) for student in get_submissions_by_assignment(aid): ws.append([student.username, student.real_name, student.max_score, student.submit_count, student.last_submit_time]) output io.BytesIO() wb.save(output) output.seek(0) return send_file(output, as_attachmentTrue, download_namef实验{aid}_成绩.xlsx, mimetypeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet)7.2 批量导入题库用JSON或者CSV减少手动录入手动一道题一道题地录入很痛苦尤其C语言题库动不动几十上百题。我提供了一个简单的批量导入接口后端接收JSON数组前端做一个“上传JSON文件”的表单。评委会觉得你的系统有工程化思维而不仅仅是玩具。[ { title: 两数之和, description: 输入两个整数输出它们的和。, input_desc: 两个整数以空格分隔。, output_desc: 一个整数。, sample_input: 1 2, sample_output: 3, test_cases: [ {input: 1 2, output: 3}, {input: 5 6, output: 11} ], level: easy } ]7.3 并发控制同一道题防止学生疯狂提交在没有并发控制的情况下学生如果写了个死循环代码提交上去判题进程会一直卡住。我在提交接口里加了一个简单的限制同一个学生针对同一道题30秒内只能提交一次。这个限制能大大降低判题服务器的负载也能防止学生用脚本疯狂刷题。实现就是在submissions表里查一下最近一条记录last Submission.query.filter_by( user_idcurrent_user.id, problem_idproblem_id ).order_by(Submission.id.desc()).first() if last and (datetime.utcnow() - last.submit_time).seconds 30: return jsonify({message: 提交过于频繁请稍后再试}), 429这个功能说出来可能显得很简单但在实际教学使用中非常实用一份试卷几十个学生同时提交的时候这个限制起的作用比想象中大得多。7.4 判题队列让提交体验更顺滑如果你的系统扛不住高并发可以把判题改成“先入队再异步判”。原理很简单当学生提交代码后系统立刻返回“已进入判题队列”然后后台线程去执行编译器判题完成后更新submissions表的状态前端通过轮询接口获取最新结果。这听起来很高级实际上只用到Python的threading和queue模块from queue import Queue from threading import Thread judge_queue Queue() def worker(): while True: task judge_queue.get() if task is None: break # 执行判题逻辑并更新数据库 judge_and_save(task) judge_queue.task_done() Thread(targetworker, daemonTrue).start()开发阶段直接用这个方案比集成Celery轻量得多。我建议你哪怕只有一个用户也把判题流程设计成队列方式答辩的时候你就能说“系统采用任务队列异步判题避免阻塞Web服务”光这个说法就能让老师觉得你的系统不是“单线程玩具”。8. 从课程设计到真正上线资源限制、代码规范与维护边界最后聊几个大实话。很多人做这种系统只把它当成一个“交差”的任务我觉得如果时间允许完全可以把成品做得接近一个真实可用的教学工具。但也别过度设计把你的边界控制清楚。8.1 判题环境对资源的使用为什么我建议你限制进程数和内存真实场景下每个学生提交的代码都会fork一个子进程。如果一个班的50个人同时提交服务器的负载会瞬间拉满。我当时在Linux服务器上做了一次压测发现最有效的限制方式是使用resource模块限制子进程CPU时间和内存上限用操作系统级命令pthread_attr_setstacksize去限制线程栈大小。或者用cgroup限制单个用户的总CPU时间。如果你不想搞这么底层还有一个简单方案把判题进程数量限制为同时最多2个其他请求排队。用一个threading.Semaphore(2)就能实现judge_slot threading.Semaphore(2) def submit_judge(code, test_cases): with judge_slot: return judge_c_code(code, test_cases)这样即使突然有20个人同时交代码最多也就是排队等待系统不会崩。我当时在一台2核4G的小机器上测试排队等待的学生最多等十几秒体验完全可以接受。我再提醒一下Windows上resource模块不可用所以如果你是想在本地Windows上跑判题并统计内存就不要用这个方案了。直接无视内存统计只做超时限制Windows上也能正常运行。8.2 代码风格与命名规范别让老师一看源码就想叹气在线C语言教学系统本身是教学系统如果源码写得乱七八糟会大大削弱项目说服力。我给自己的项目定了几条硬性规范数据库模型类名全部用大写开头的驼峰命名例如SubmissionModel为了跟Flask-SQLAlchemy的db.Model区分清楚所有API路由统一用名词复数不要出现“/get_all_problem_list”这种中式命名改为“/api/problems”就好判题相关的模块尽量用动词放前面比如get_compile_errors、run_test_cases一目了然。这些细节老师不一定会全看但万一他随手打开你的models.py发现命名混乱、逻辑堆叠对你的印象分肯定有影响。8.3 维护边界哪些功能该做、哪些不该做很多同学做课程设计时会想着“我要加积分系统”“我要加论坛”“我要加在线聊天”结果一个都没做完核心功能反而成了半吊子。我建议你按照优先级来第一优先级必须有用户登录注册、题库浏览、代码在线编辑、提交判题、查看判题结果、教师发布作业、实验成绩管理。 第二优先级加分项批量导入题库、Excel导出成绩、提交频率限制、判题队列异步化。 第三优先级有余力再做学生错题本、教学数据可视化、班级/分组管理、在线讨论区、多语言判题支持。一个功能完善的“在线C语言教学系统”能够漂亮地完成前两个优先级就非常优秀了。第三个优先级做一两个亮点出来可以但不要把摊子铺太大。说回部署这一层。我当时把系统装在Linux服务器上之后发现还有一个很隐蔽的问题GCC编译产生的临时文件、学生提交的源码文件会越堆越多如果不定期清理磁盘很快就满了。解决办法是写个清理脚本删除7天前的临时文件再配一个crontab定时任务。find /tmp/judge_sandbox -type f -mtime 7 -delete这些运维细节可能不会在答辩时被问到但你做完之后会明显感觉系统“能用”而不是“勉强能跑”。我自己的体会是课程设计做到最后拼的不是某个花哨功能而是整套系统的稳定性和维护成本控制住了没有。最后再分享一个我个人的小技巧做这种系统一定要保留一份“开发日志”哪怕每天只写三行——今天做了什么、踩了什么坑、明天打算做什么。尤其当你花了三天搞定某个bug回头看看当时是怎么定位的答辩时你能把排查过程讲得头头是道老师一看就知道代码是你自己写的不是网上复制拼凑的。这种“过程沉淀”比任何炫技都有说服力。在线C语言教学系统不只是个Web项目它是你把你对C语言教育的理解、对Web开发的理解、对工程实践的取舍集中在一起的作品。把地基打好把判题做稳把细节做足这个项目就是拿得出手的。
阅读完成 · 觉得有帮助?
咨询建站