ARTICLE DETAIL

资讯详情

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

JSP+MySQL在线评测系统搭建实战指南

JSP+MySQL在线评测系统搭建实战指南 简介本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目面向计算机专业本科生及Web开发初学者解决教育场景中编程比赛组织、自动判题与成绩管理等核心需求。压缩包共171个文件含40个Java源码如UserServiceImpl、ContestResultServlet、40个编译后class文件、12个JSP页面涵盖登录、注册、比赛提交等交互逻辑、62个运行依赖jar包以及SQL建表脚本、CSS/JS前端资源和项目配置文件整体大小19.26MB结构完整可直接部署运行。已有114人学习下载资源提供从用户认证、题库管理、实时评测到管理员后台的全链路实现包含DAO层数据操作封装、Filter权限控制、JDBC连接池配置及典型判题流程逻辑代码规范、模块职责清晰适合作为JSPMySQL综合实践范例深入理解Web应用分层架构与数据库协同设计。1. 为什么还在用 JSP MySQL 做在线评测系统——不是怀旧是稳得住、改得动、跑得久你可能在头歌实训里写过“JSP 用户登录页”在毕设选题列表里扫过“基于 JSP 的 XX 系统”甚至刚被导师拍着桌子说“别整 Spring Boot就用 JSP MySQL部署到学校老服务器上”——这不是技术倒退而是一类真实存在的交付场景校内教学平台、课程实验环境、低运维预算的教务子系统、JavaWeb 教学闭环项目。这类系统不追求高并发、不卷微服务、不碰云原生但必须满足四个硬指标能本地一键启动、数据库结构清晰可查、代码逻辑直白可调试、教师能自己改题目和判题规则。JSP MySQL 正是这种“教学级生产环境”的黄金组合JSP 天然贴合 HTML 渲染逻辑学生改个%score%就能看见分数MySQL 不用 Docker 编排、不配 SSL、不设主从装好服务、建库授权、导入 SQL 就能跑判题核心用 Java 写成独立类和前端解耦测试时直接java JudgeMain input.txt output.txt就能验证逻辑。本文不讲“为什么不用 Spring Boot”而是带你从零搭起一个可运行、可扩展、可交差的在线评测系统骨架包含用户管理、题目发布、代码提交、本地沙箱判题非 Docker、实时状态反馈。所有代码在 Windows/Linux 下均可复现MySQL 版本锁定 5.7–8.0兼容性已实测JSP 运行容器用 Tomcat 9.0不依赖 IDEA 内置插件纯 war 包部署。适合大三课程设计、毕业设计中期交付、教师自建练习平台。2. 搭建最小可运行环境Tomcat MySQL 项目结构三件套2.1 Tomcat 9.0 部署与 JSP 支持验证JSP 不是独立语言它依赖 Servlet 容器解析。Tomcat 9.0 是当前最稳定、对 JSP 2.3 规范支持最完整的版本且无需额外配置即可运行 JSP 页面。不要用 Tomcat 10—— 其包名从javax.*升级为jakarta.*会导致大量老式 JSP 标签库如 JSTL报错而教学环境中 JSTLc:if、c:forEach几乎是标配。下载地址 https://tomcat.apache.org/download.cgi → 选择Apache Tomcat 9→tar.gzLinux或zipWindows解压后进入bin/目录# Linux/macOS chmod x startup.sh shutdown.sh ./startup.sh:: Windows startup.bat访问http://localhost:8080看到 Apache Tomcat 默认首页即成功。关键验证点在webapps/ROOT/下新建test.jsp% page contentTypetext/html;charsetUTF-8 % html body h2Tomcat JSP 连通成功/h2 % new java.util.Date() % /body /html刷新页面若显示当前时间说明 JSP 引擎工作正常。注意此步骤必须完成否则后续所有 JSP 页面将 500 报错且错误日志藏在logs/catalina.out中新手极易卡在此处。2.2 MySQL 5.7–8.0 安装与基础配置教学环境首选 MySQL 5.7兼容性最强或 8.0需关闭严格模式。避坑重点MySQL 8.0 默认启用caching_sha2_password插件而老版 JDBC 驱动如mysql-connector-java-5.1.47.jar不支持导致Connection refused。解决方案分两步安装时跳过密码强度校验仅限教学环境在my.cnfLinux或my.iniWindows的[mysqld]段下添加validate_passwordOFF default_authentication_pluginmysql_native_password创建评测专用数据库与用户避免用 root 操作-- 登录 MySQL 后执行 CREATE DATABASE oj_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER oj_userlocalhost IDENTIFIED BY oj_pass123; GRANT ALL PRIVILEGES ON oj_db.* TO oj_userlocalhost; FLUSH PRIVILEGES;提示utf8mb4是必须项否则用户昵称含 emoji如 ‍会存为?FLUSH PRIVILEGES不可省略否则权限不生效。2.3 项目目录结构拒绝“一个 index.jsp 打天下”传统 JSP 项目易陷入“所有逻辑堆在 JSP 里”的泥潭。我们采用轻量 MVC 分层oj-web/ ├── WEB-INF/ │ ├── web.xml ← Servlet 配置中枢 │ ├── lib/ ← JDBC 驱动、JSTL 等 jar │ └── classes/ ← 编译后的 Java 类含 DAO、Service、Judge ├── static/ ← CSS/JS/图片不经过 Servlet ├── jsp/ │ ├── login.jsp ← 前端页面 │ ├── problem_list.jsp │ └── submit_result.jsp └── src/ ← Java 源码开发时放此处编译后进 classes关键文件WEB-INF/web.xml必须存在且正确Tomcat 9 要求 Servlet 3.1?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-nameOnline Judge System/display-name welcome-file-list welcome-filejsp/login.jsp/welcome-file /welcome-file-list /web-app注意version4.0对应 Servlet 4.0Tomcat 9 完全支持welcome-file指定入口避免访问根路径 404。3. 数据库设计聚焦评测核心实体拒绝过度工程在线评测系统OJ本质是“题目-提交-判题”三元关系。教学项目不必模仿 LeetCode 的复杂标签体系6 张表足矣且全部字段可控、索引明确、无冗余表名用途关键字段带注释user用户信息id(PK),username(UNIQUE),password(SHA256 加密),role(ENUM: student,teacher)problem题目库id(PK),title,description(TEXT),time_limit(ms),memory_limit(KB),input_sample,output_samplesubmission提交记录id(PK),user_id(FK),problem_id(FK),code(LONGTEXT),language(ENUM: java,cpp),status(ENUM: pending,accepted,wrong_answer,compile_error),submit_time(DATETIME)judge_log判题日志id(PK),submission_id(FK),judge_time(DATETIME),exit_code,runtime_ms,memory_kb,error_output(TEXT)testcase测试用例id(PK),problem_id(FK),input_data(TEXT),output_data(TEXT),is_sample(TINYINT)problem_tag题目标签可选id(PK),problem_id(FK),tag_name建表 SQL 示例problem 表CREATE TABLE problem ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 题目标题, description TEXT NOT NULL COMMENT 题目描述支持 Markdown 解析, time_limit INT NOT NULL DEFAULT 1000 COMMENT 时间限制毫秒, memory_limit INT NOT NULL DEFAULT 65536 COMMENT 内存限制KB, input_sample TEXT COMMENT 输入样例, output_sample TEXT COMMENT 输出样例, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意TEXT类型用于存储长文本题目描述、代码不要用VARCHAR(10000)—— MySQL 对VARCHAR有行大小限制65535 字节超长内容会截断AUTO_INCREMENT保证 ID 递增方便前端分页DEFAULT CURRENT_TIMESTAMP自动记录创建时间比 Java 代码写入更可靠。初始化数据脚本init_data.sql放入项目根目录供教师一键导入INSERT INTO problem (title, description, time_limit, memory_limit, input_sample, output_sample) VALUES (AB Problem, 计算两个整数的和, 1000, 65536, 1 2, 3), (Hello World, 输出固定字符串, 500, 1024, , Hello, OJ!);执行方式mysql -u oj_user -p oj_db init_data.sql4. 核心判题模块本地沙箱执行不依赖 Docker在线评测最怕“判题服务崩了学生交不了作业”。教学环境必须规避 Docker、K8s 等重依赖采用Java ProcessBuilder 临时目录隔离 资源限制的轻量沙箱方案。原理为每次提交创建独立进程在限定时间内执行编译/运行捕获 stdout/stderr比对输出。4.1 判题主流程 Java 类JudgeService.java// src/com/oj/service/JudgeService.java package com.oj.service; import java.io.*; import java.nio.file.*; import java.util.concurrent.*; public class JudgeService { // 编译 运行超时毫秒 private static final long COMPILE_TIMEOUT 3000; private static final long RUN_TIMEOUT 5000; public JudgeResult judgeSubmission(String code, String language, String input, String expectedOutput) { JudgeResult result new JudgeResult(); Path tempDir null; try { // 1. 创建临时目录保证隔离 tempDir Files.createTempDirectory(oj_judge_); // 2. 根据语言生成文件并编译 if (java.equals(language)) { Path javaFile tempDir.resolve(Main.java); Files.write(javaFile, code.getBytes()); // 编译 Process compileProc new ProcessBuilder(javac, Main.java) .directory(tempDir.toFile()) .start(); if (compileProc.waitFor(COMPILE_TIMEOUT, TimeUnit.MILLISECONDS)) { int exitCode compileProc.exitValue(); if (exitCode ! 0) { result.setStatus(compile_error); result.setErrorOutput(getProcessOutput(compileProc.getErrorStream())); return result; } } else { result.setStatus(compile_error); result.setErrorOutput(编译超时); return result; } } // 3. 运行程序统一用 Main.class Process runProc new ProcessBuilder(java, Main) .directory(tempDir.toFile()) .redirectInput(new ByteArrayInputStream(input.getBytes())) .start(); // 4. 设置运行超时并捕获输出 if (runProc.waitFor(RUN_TIMEOUT, TimeUnit.MILLISECONDS)) { int exitCode runProc.exitValue(); String actualOutput getProcessOutput(runProc.getInputStream()); String errorOutput getProcessOutput(runProc.getErrorStream()); if (exitCode 0 actualOutput.trim().equals(expectedOutput.trim())) { result.setStatus(accepted); } else if (exitCode ! 0) { result.setStatus(runtime_error); result.setErrorOutput(errorOutput); } else { result.setStatus(wrong_answer); result.setActualOutput(actualOutput); } } else { result.setStatus(time_limit_exceeded); runProc.destroyForcibly(); // 强制终止 } } catch (Exception e) { result.setStatus(system_error); result.setErrorOutput(判题异常: e.getMessage()); } finally { // 5. 清理临时目录重要否则磁盘爆满 if (tempDir ! null) { try { FileUtils.deleteDirectory(tempDir.toFile()); } catch (IOException ignored) {} } } return result; } private String getProcessOutput(InputStream is) throws IOException { StringBuilder sb new StringBuilder(); try (BufferedReader reader new BufferedReader(new InputStreamReader(is))) { String line; while ((line reader.readLine()) ! null) { sb.append(line).append(\n); } } return sb.toString().trim(); } }逻辑说明Files.createTempDirectory()保证每次判题目录唯一避免多提交冲突ProcessBuilder.directory()指定工作目录使javac/java命令在临时目录内执行redirectInput()将测试用例输入注入进程 stdinwaitFor(timeout)是核心防卡死机制超时立即destroyForcibly()FileUtils.deleteDirectory()来自 Apache Commons IO需引入commons-io-2.11.0.jar不能用tempDir.toFile().delete()—— 递归删除失败率高。4.2 判题结果封装类JudgeResult.java// src/com/oj/model/JudgeResult.java package com.oj.model; public class JudgeResult { private String status; // accepted / wrong_answer / compile_error ... private String actualOutput; // 实际输出用于 debug private String errorOutput; // 错误信息编译错误/运行时异常 private long runtimeMs; // 实际运行时间毫秒 private long memoryKb; // 内存占用需 Linux ps 命令配合此处简化为 0 // getter/setter 省略 }关键参数说明COMPILE_TIMEOUT 3000Java 编译通常 1s设 3s 防止网络挂载 NFS 目录等慢盘场景RUN_TIMEOUT 5000教学题目简单5s 足够若需支持 C可增至 10sexpectedOutput.trim()比对前去除首尾空格换行避免学生多打空格被判错actualOutput.trim().equals(...)必须 trim否则3\n和3判为 WA。5. 避坑指南那些让答辩前夜崩溃的 5 个真实问题5.1 现象JSP 页面中文乱码显示??或某些文字原因JSP 文件本身编码、Tomcat 请求编码、MySQL 连接编码三者未统一。常见于 Windows 记事本保存的.jsp文件默认 ANSI 编码。解决用 VS Code 或 IDEA 重存所有.jsp文件为UTF-8 without BOM在每个 JSP 顶部加% page contentTypetext/html;charsetUTF-8 %web.xml中添加过滤器WEB-INF/web.xmlfilter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.2 现象MySQL 连接报错Access denied for user oj_userlocalhost原因MySQL 8.0 默认root用户使用caching_sha2_password而oj_user创建时未指定插件或localhost解析为127.0.0.1导致 host 不匹配。解决登录 MySQL 后执行ALTER USER oj_userlocalhost IDENTIFIED WITH mysql_native_password BY oj_pass123; FLUSH PRIVILEGES;JDBC URL 显式指定serverTimezoneGMT%2B8防止时区错乱String url jdbc:mysql://localhost:3306/oj_db?useSSLfalseserverTimezoneGMT%2B8characterEncodingutf8;5.3 现象提交 Java 代码后判题一直pending日志无报错原因ProcessBuilder启动javac/java时未指定完整路径Linux 下PATH不含 JDK bin 目录Windows 下JAVA_HOME未设或指向 JRE无 javac。解决Linux在 Tomcat 启动脚本setenv.sh中添加export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATHWindows在setenv.bat中添加set JAVA_HOMEC:\Program Files\Java\jdk-11.0.20 set PATH%JAVA_HOME%\bin;%PATH%终极保险在JudgeService.java中硬编码路径开发阶段// 替换 ProcessBuilder(javac, ...) 为 ProcessBuilder pb new ProcessBuilder(/usr/lib/jvm/java-11-openjdk-amd64/bin/javac, Main.java);5.4 现象c:forEach标签不解析页面原样输出c:forEach items${list} varitem原因JSTL 标签库未正确引入或版本不匹配。Tomcat 9 需要jstl-1.2.jar非jstl-api-1.2.jar。解决下载jstl-1.2.jar Maven Repository 放入WEB-INF/lib/JSP 页面顶部声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %5.5 现象判题时java.lang.OutOfMemoryError: Java heap space原因学生提交无限循环代码如while(true){}ProcessBuilder未限制 JVM 堆内存导致 Tomcat 进程 OOM。解决不推荐在java命令中加-Xmx128m因ProcessBuilder启动的是新 JVM不影响 Tomcat正确做法用ulimit限制子进程资源Linux# 在 Tomcat 启动前执行或写入 startup.sh ulimit -v 131072 # 虚拟内存 128MB ulimit -t 5 # CPU 时间 5 秒Windows 无ulimit改用psexec需提前安装psexec -d -e -i cmd /c java -Xmx128m Main6. 进阶技巧让系统真正可用的 3 个实战补丁6.1 题目描述支持 Markdown 渲染不引入 JS 库学生写题目描述时习惯用**加粗**、*斜体*、- 列表但 JSP 直接out.print(description)会原样输出。不用引入 marked.js 或 showdown—— 用 Java 正则做轻量转换兼顾安全与性能// src/com/oj/util/MarkdownRenderer.java public class MarkdownRenderer { public static String render(String markdown) { if (markdown null) return ; // 1. 换行转 br markdown markdown.replaceAll(\n, br); // 2. **粗体** → strong粗体/strong markdown markdown.replaceAll(\\*\\*(.*?)\\*\\*, strong$1/strong); // 3. *斜体* → em斜体/em markdown markdown.replaceAll(\\*(.*?)\\*, em$1/em); // 4. - 列表 → ulli.../li/ul markdown markdown.replaceAll(- (.*?)(br|$), li$1/li); markdown ul markdown /ul; // 5. 过滤 XSS移除 script/style 标签 markdown markdown.replaceAll((?i)(script|style)[^]*.*?/\\1, ); return markdown; } }在 JSP 中调用% page importcom.oj.util.MarkdownRenderer % ${MarkdownRenderer.render(problem.description)}优势无前端依赖、渲染快、防 XSS 基础防护缺点不支持表格/代码块教学场景够用。若需增强再引入commonmark-java库dependencygroupIdorg.commonmark/groupIdartifactIdcommonmark/artifactIdversion0.21.0/version/dependency。6.2 提交记录分页优化避免LIMIT 10000,20性能雪崩当提交量 10 万条SELECT * FROM submission ORDER BY id DESC LIMIT 10000,20会扫描前 10020 行极慢。用游标分页Cursor-based Pagination替代偏移分页-- 第一页取最新 20 条 SELECT * FROM submission ORDER BY id DESC LIMIT 20; -- 第二页假设上一页最后 id10020 SELECT * FROM submission WHERE id 10020 ORDER BY id DESC LIMIT 20;Java DAO 层实现public ListSubmission getSubmissionsAfterId(int lastId, int limit) { String sql SELECT * FROM submission WHERE id ? ORDER BY id DESC LIMIT ?; return jdbcTemplate.query(sql, new SubmissionRowMapper(), lastId, limit); }原理利用主键id有序性用WHERE id ?替代OFFSETMySQL 可走主键索引速度提升百倍。必须确保排序字段有索引ALTER TABLE submission ADD INDEX idx_id_desc (id DESC);。6.3 教师后台一键导出学生提交统计 Excel教师常需导出“某题 AC 率”、“某学生提交明细”给教务处。用 Apache POI 生成.xlsx避免 CSV 编码问题// src/com/oj/servlet/ExportReportServlet.java protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String type req.getParameter(type); // problem_ac_rate or student_submissions Workbook workbook new XSSFWorkbook(); Sheet sheet workbook.createSheet(Report); if (problem_ac_rate.equals(type)) { ListObject[] data problemDao.getAcRateByProblem(); Row header sheet.createRow(0); header.createCell(0).setCellValue(题目ID); header.createCell(1).setCellValue(题目标题); header.createCell(2).setCellValue(AC 数); header.createCell(3).setCellValue(总提交数); header.createCell(4).setCellValue(AC 率); int rowNum 1; for (Object[] row : data) { Row r sheet.createRow(rowNum); r.createCell(0).setCellValue((Integer) row[0]); r.createCell(1).setCellValue((String) row[1]); r.createCell(2).setCellValue((Long) row[2]); r.createCell(3).setCellValue((Long) row[3]); r.createCell(4).setCellValue((Double) row[4]); } } resp.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); resp.setHeader(Content-Disposition, attachment; filenameoj_report.xlsx); workbook.write(resp.getOutputStream()); }关键点Workbook用XSSFWorkbook.xlsx非HSSFWorkbook.xls不支持 65536 行Content-Disposition设为attachment强制下载务必关闭流workbook.write()已处理。我带过三届课程设计最深的教训是别在答辩前三天重构判题模块。曾有个组把ProcessBuilder换成DockerClient结果 Docker 在学校机房虚拟机里根本装不上最后连夜回滚到本地沙箱靠ulimit和try-catch硬扛。现在我的习惯是先跑通javacjava沙箱再加功能数据库字段宁少勿多problem表删掉difficulty字段等学生真需要分级再加所有 JSP 页面必加% page contentTypetext/html;charsetUTF-8 %哪怕只是复制粘贴。这些不是最佳实践是血泪经验换来的最小可行路径。希望帮到你。本文还有配套的精品资源点击获取
返回列表