SpringBoot+Vue在线编程训练系统实战
简介这是一套面向计算机专业本科生的高质量Java毕业设计实战项目基于SpringBoot后端与Vue前端构建的编程训练系统适用于毕业设计选题、课程设计及期末大作业开发参考。系统涵盖用户管理、题目练习、在线评测、成绩统计等核心模块技术栈典型、结构清晰兼顾教学性与工程实践性。资源包共758个文件包含131个Java后端逻辑文件、151个JavaScript与38个Vue组件文件支撑前端交互42个CSS与SCSS样式文件保障界面呈现另有SQL数据库脚本、Bat部署脚本、MP4演示视频及详细使用文档整体47.62MB开箱即用。目前已有153人学习下载所有代码已在Windows 10/11环境实测通过答辩获97分高分附带完整部署教程与调试说明可直接运行、快速二次开发或作为技术方案范本参考。1. 这不是又一个“学生管理系统”SpringBootVue编程训练系统到底在解决什么真问题你见过太多标着“高分项目”的Java毕业设计压缩包——点开一看用户登录、课程列表、成绩录入三板斧打完就收工。但这个标题里的“编程训练系统”核心不在“管理”而在“训练”它要让学习者在浏览器里写代码、实时看到运行结果、被自动判题、获得针对性反馈整个过程不依赖本地IDE、不手动编译、不反复切窗口。它本质是一个轻量级的在线编程实训平台对标的是LeetCode的练习模式、但更贴近高校《Java程序设计》《Web开发实训》这类课程的真实教学节奏。适合计算机专业本科生做毕设也适合某高校实验室快速搭建一门课的配套实践环境。它不追求工业级并发但必须保证单机部署下30人同时在线编码不卡顿、判题逻辑可配置、错误提示能准确定位到行号——这才是“高分”的底层逻辑功能闭环、边界清晰、可演示、可讲清楚技术选型理由。下面我们就从零开始把这套系统真正跑起来、调明白、避掉那些让答辩前夜崩溃的坑。2. 后端筑基用SpringBoot搭出可判题的REST API服务一个编程训练系统后端不是只做CRUD。它得接收代码字符串、安全地执行、捕获输出与异常、限时中断、返回结构化结果。这要求我们绕过传统MVC思维直击执行引擎层。2.1 为什么选SpringBoot而非纯Spring或SSM常见误区是认为“毕设用SSM更显功底”。但本项目中SpringBoot的价值在于三点硬需求内嵌Tomcat Actuator健康检查部署时不用配外部容器java -jar一条命令启动导师现场验收时不会因端口冲突或war包部署失败当场翻车自动装配判题所需组件比如用ConfigurationProperties绑定application.yml里的沙箱超时参数如judge.timeout: 2000比XML里手写bean少写8行配置简化安全隔离逻辑后续接入代码执行沙箱时SpringBoot的ControllerAdvice能统一拦截所有判题请求前置校验用户权限与题目状态避免在每个PostMapping(/submit)里重复写if (!user.canSubmit())。提示不要在pom.xml里盲目加spring-boot-starter-webflux。本系统IO密集型操作文件读写、进程启动远多于高并发请求WebFlux的异步非阻塞反而增加调试复杂度用默认的Servlet容器更稳。2.2 判题核心用ProcessBuilder安全执行Java代码关键不是“怎么运行代码”而是“怎么防止代码跑飞”。直接Runtime.getRuntime().exec()是自杀式写法——恶意代码能调用System.exit(0)杀掉整个JVM或用while(true)耗尽CPU。正确姿势是// JudgeService.java public JudgeResult executeCode(String code, String input) throws IOException, InterruptedException { // 1. 生成唯一临时目录避免多用户提交同名类冲突 Path tempDir Files.createTempDirectory(judge_ System.currentTimeMillis()); // 2. 写入Main.java强制要求类名为Main简化判题逻辑 Path sourceFile tempDir.resolve(Main.java); Files.write(sourceFile, code.getBytes(StandardCharsets.UTF_8)); // 3. 编译javac Main.java捕获编译错误 ProcessBuilder compilePB new ProcessBuilder(javac, Main.java); compilePB.directory(tempDir.toFile()); compilePB.redirectErrorStream(true); Process compileProc compilePB.start(); String compileOutput readProcessOutput(compileProc); if (compileProc.waitFor() ! 0) { return JudgeResult.fail(编译错误 compileOutput); } // 4. 执行java -cp . -Dfile.encodingUTF-8 Main严格限定classpath和编码 ProcessBuilder runPB new ProcessBuilder(java, -cp, ., -Dfile.encodingUTF-8, Main); runPB.directory(tempDir.toFile()); runPB.redirectInput(ProcessBuilder.Redirect.PIPE); // 输入重定向 runPB.redirectErrorStream(true); Process runProc runPB.start(); // 5. 写入输入数据模拟标准输入 try (OutputStream stdin runProc.getOutputStream()) { stdin.write(input.getBytes(StandardCharsets.UTF_8)); stdin.close(); } // 6. 设置超时并等待关键 boolean finished runProc.waitFor(3, TimeUnit.SECONDS); // 严格3秒超时 if (!finished) { runProc.destroyForcibly(); // 强制终止子进程树 return JudgeResult.timeout(); } String runOutput readProcessOutput(runProc); int exitCode runProc.exitValue(); return exitCode 0 ? JudgeResult.success(runOutput.trim()) : JudgeResult.runtimeError(runOutput.trim()); }参数说明tempDir每次判题独立目录避免Main.class被覆盖或并发冲突redirectErrorStream(true)合并stdout/stderr统一捕获输出否则System.err.println()内容会丢失waitFor(3, TimeUnit.SECONDS)超时控制必须在Process层面不能靠线程sleep否则子进程仍在后台运行destroyForcibly()JDK8才支持彻底杀死子进程及其子进程如恶意代码fork的bash旧版本需用ProcessHandle或第三方库。2.3 题目与测试用例的数据库设计别用一张大表存所有题目。按教学逻辑拆解problem表题目描述、难度、标签如“循环”“数组”、是否启用test_case表外键关联problem_id字段含input文本、expected_output文本、is_sample是否为样例、order_num执行顺序submission表记录每次提交的user_id、problem_id、code存储原始字符串方便复现、statusAC/RE/TLE等、execute_time_ms。注意code字段用TEXT类型别用VARCHAR(1000)——学生写个冒泡排序都可能超长expected_output必须存原始换行符\n前端展示时用white-space: pre-wrap渲染否则Hello\nWorld显示成HelloWorld。3. 前端交互Vue实现代码编辑、实时运行与结果可视化Vue不是用来套模板的而是解决“如何让学生不离开页面完成一次完整编程训练闭环”。重点在编辑器集成、状态驱动UI、以及与后端判题API的精准对接。3.1 为什么选Vue 2.x而非Vue 3 Composition API毕设场景下Vue 2.x仍是更稳妥的选择生态成熟vue-codemirrorv5对Vue 2支持零踩坑而Vue 3版需额外处理ref响应式与CodeMirror实例绑定文档友好某高校《前端开发实训》教材仍以Vue 2为范例答辩时解释data()函数比setup()更直观构建简单vue-cli 3.x生成的项目npm run build直接输出静态文件扔进SpringBoot的static目录即可无需配置Nginx反向代理。提示禁用eslint-plugin-vue的vue/multi-word-component-names规则。毕设里写CodeEditor.vue会被报错但Editor.vue语义不清——直接关掉省得答辩前改命名。3.2 CodeMirror集成不只是语法高亮很多教程只教codemirror :valuecode inputupdateCode/但这无法满足训练需求。必须注入以下能力!-- Editor.vue -- template div classeditor-container codemirror refcm v-modelcode :optionscmOptions readyonCmReady changeonCodeChange / !-- 运行按钮状态由判题中/空闲决定 -- button clickrunCode :disabledisJudging classrun-btn {{ isJudging ? 判题中... : 运行 }} /button /div /template script import { codemirror } from vue-codemirror import codemirror/lib/codemirror.css import codemirror/mode/java/java.js // Java语法高亮 import codemirror/addon/edit/closebrackets.js // 自动补全括号 import codemirror/addon/fold/foldgutter.css // 折叠代码 import codemirror/addon/fold/brace-fold.js export default { components: { codemirror }, data() { return { code: public class Main {\n public static void main(String[] args) {\n System.out.println(Hello, World!);\n }\n}, isJudging: false, cmOptions: { mode: text/x-java, theme: material, lineNumbers: true, tabSize: 4, autoCloseBrackets: true, foldGutter: true, gutters: [CodeMirror-linenumbers, CodeMirror-foldgutter], // 关键禁止缩放避免学生误操作导致编辑器失焦 viewportMargin: Infinity, } } }, methods: { onCmReady(cm) { // 初始化后聚焦编辑器提升体验 cm.focus() // 绑定CtrlEnter快捷键运行 cm.addKeyMap({ Ctrl-Enter: () this.runCode() }) }, async runCode() { if (this.isJudging) return this.isJudging true try { const res await this.$http.post(/api/judge, { code: this.code, input: this.inputData || // 样例输入框内容 }) this.judgeResult res.data } catch (e) { this.judgeResult { status: ERROR, message: e.response?.data?.message || 网络错误 } } finally { this.isJudging false } } } } /script关键细节autoCloseBrackets写{自动补}减少语法错误foldGutter支持折叠main方法体长代码不滚动viewportMargin: Infinity禁用虚拟滚动确保光标始终可见CodeMirror默认只渲染可视区滚动时可能丢焦点CtrlEnter快捷键教学场景下学生更习惯IDE操作逻辑降低学习成本。3.3 判题结果可视化用状态机驱动UI反馈别用v-ifstatus AC写一堆分支。定义清晰的状态码让UI自动适配状态码含义UI表现学生感知AC正确✅ 绿色显示“通过所有测试用例”即时正向反馈WA答案错误❌ 红色“第2组输入期望5得到0”精准定位错误点TLE超时⚠️ 橙色“运行超时3000ms”提示算法效率问题CE编译错误 红色“Main.java:3: 错误: 找不到符号”直接跳转到报错行// JudgeResult.vue computed: { statusIcon() { const icons { AC: ✅, WA: ❌, TLE: ⚠️, CE: , RE: , ERROR: } return icons[this.result.status] || ❓ }, statusColor() { const colors { AC: green, WA: red, TLE: orange, CE: red, RE: purple } return colors[this.result.status] || gray } }注意WA状态必须对比expected_output与actual_output的逐行差异而不是简单。用diff算法如jsdiff库生成带/-标记的差异文本学生一眼看出哪一行错了。4. 全链路避坑那些让答辩前夜删库重来的5个致命细节这些坑90%的毕设项目都踩过。不是代码写错而是对“教学系统”特殊性的误判。4.1 现象学生提交代码后页面卡死10秒然后报“Network Error”原因前端未设置API请求超时后端判题进程卡住如死循环HTTP连接一直挂起浏览器最终断连。解决前端Axios全局配置axios.defaults.timeout 5000后端JudgeService中ProcessBuilder的waitFor()必须设超时见2.2节且超时后destroyForcibly()血泪经验在application.yml里加server.tomcat.connection-timeout3000强制Tomcat在3秒无响应时断开连接避免线程池耗尽。4.2 现象同一道题学生A通过学生B提交相同代码却显示“编译错误”原因Main.java文件写入时未指定字符集Windows系统默认GBKLinux默认UTF-8中文注释导致编译失败。解决所有文件IO操作强制指定StandardCharsets.UTF_8见2.2节代码在application.yml中添加spring: http: encoding: charset: UTF-8 force: true web: resources: static-locations: classpath:/static/4.3 现象管理员在后台发布新题目学生刷新页面后看不到需清缓存才显示原因Vue CLI构建的静态资源被浏览器强缓存index.html的Cache-Control: max-age31536000。解决构建时在vue.config.js中关闭HTML缓存module.exports { configureWebpack: { plugins: [ new HtmlWebpackPlugin({ cache: false // 关键 }) ] } }或更彻底在SpringBoot中配置静态资源缓存策略在WebMvcConfigurer中Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/) .setCachePeriod(0); // 禁用缓存 }4.4 现象学生用Mac提交代码判题结果显示“找不到符号”但Windows上正常原因Mac的javac默认源码编码为UTF-8但若学生用TextEdit保存Java文件可能带BOM头javac无法识别。解决后端读取code字符串后先移除BOMpublic static String removeBom(String str) { if (str ! null str.startsWith(\uFEFF)) { return str.substring(1); } return str; }前端CodeMirror配置value时用removeBom(code)预处理。4.5 现象系统运行一周后磁盘空间告警/tmp目录塞满数万judge_*临时文件夹原因Files.createTempDirectory()创建的目录未清理判题完成后应主动删除。解决在JudgeService.executeCode()末尾添加// 判题完成后递归删除临时目录无论成功失败 try { FileUtils.deleteDirectory(tempDir.toFile()); } catch (IOException e) { log.warn(Failed to delete temp dir: {}, tempDir, e); }使用Apache Commons IO的FileUtils比原生NIO更鲁棒处理只读文件等边界。5. 高分验证用三个可演示动作证明这不是Demo而是真实训练系统高分毕设的核心是让评委相信“这系统真能用、真能教、真能学”。不要堆砌技术名词用三个具体动作现场演示闭环价值。5.1 动作一现场发布一道新题并让学生5分钟内完成提交与通过操作路径管理员登录后台 → 题目管理 → 新增题目“计算斐波那契数列第n项”在测试用例中添加样例1输入10期望输出55样例2输入1期望输出1样例3边界输入0期望输出0学生打开题目页看到已预置的Main类框架学生编写递归解法故意不加边界判断点击“运行” → 显示TLE超时学生改为迭代解法再次运行 → 显示AC。为什么高分全程在10分钟内完成证明系统支持“发布-练习-反馈-修正”教学闭环且TLE状态能真实反映算法效率问题不是摆设。5.2 动作二故意提交恶意代码验证沙箱防护有效性操作路径学生在编辑器中输入public class Main { public static void main(String[] args) { Runtime.getRuntime().exec(rm -rf /tmp); // Linux命令 // 或 System.exit(1); // 退出JVM System.out.println(hacked); } }点击运行 → 系统返回RE运行时错误并显示错误信息java.io.IOException: Cannot run program rm: error13, Permission denied查看服务器/tmp目录确认无文件被删除检查SpringBoot进程确认仍在运行未被System.exit终止。为什么高分证明判题模块具备生产级安全意识不是简单try-catch而是通过操作系统级权限隔离ProcessBuilder默认无root权限和超时熔断双重保障。5.3 动作三导出全班编程训练数据生成教学分析报告操作路径管理员进入“数据统计”页选择时间范围如最近一周点击“导出Excel”Excel包含每位学生problem_id、statusAC/CE/WA、submit_time、execute_time_ms汇总表各题目通过率、平均提交次数、最常出错的题目TOP3导师据此发现“70%学生在‘字符串反转’题上首次提交WA错误集中在charAt()索引越界”从而在下次课重点讲解边界条件。为什么高分超越“能用”体现“教学价值”。数据导出功能用POI实现但重点不在技术而在数据维度设计——execute_time_ms字段让教师能分析算法效率掌握度这是普通管理系统没有的深度。我带过的某跨平台系统毕设学生最初只做了基础判题答辩被问“这和在线OJ有什么区别”当场卡壳。后来他加了导出分析功能用真实数据图展示班级薄弱点导师立刻点头“这个设计抓住了教学系统的本质。”希望帮到你。本文还有配套的精品资源点击获取