ARTICLE DETAIL

资讯详情

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

JSP+MySQL在线评测系统实战指南:从环境搭建到OJ核心评测逻辑

JSP+MySQL在线评测系统实战指南:从环境搭建到OJ核心评测逻辑 简介本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目面向计算机专业本科生及Web开发初学者解决教育场景中编程比赛组织、自动判题与成绩管理等核心需求。压缩包共171个文件含40个Java源码如UserServiceImpl、ContestResultServlet、40个编译后class文件、12个JSP页面涵盖登录、注册、比赛提交等交互逻辑、62个依赖jar包及1个SQL建表脚本完整覆盖前后端交互、数据库操作与权限控制模块整体大小为19.26MB。已有114人学习下载资源结构清晰包含可直接部署运行的工程骨架、典型DAO层实现UserDaoImpl、ProblemDaoImpl等、带过滤器的登录校验机制LoginFilter及管理员后台基础功能便于理解MVC分层架构、JDBC连接池配置与实时评测流程设计是掌握Java Web全栈开发实践的典型教学案例。1. 这不是个“老古董”Demo基于JSPMySQL的在线评测系统编号100012170能跑通、能改题、能当毕设答辩主力你可能刚在头歌实训里敲完第一个% new Date() %也可能正被导师催着交毕设选题——别急着点开“基于SpringBoot的XX系统”模板。这个编号为100012170的JSPMySQL在线评测系统是少有的、真实跑过完整评测流程的轻量级实战项目它不依赖Tomcat 9或Java 11玄学配置用JDK 8 Tomcat 7/8就能拉起来它没把“评测”二字当摆设ContestResultServlet真会调用Runtime.getRuntime().exec()去编译C/Java代码、比对输出、限时杀进程它连管理员后台都做了权限隔离——LoginFilter拦住非admin访问/admin/路径不是靠注释写着“此处应加权限”。适合三类人大三做课程设计要交源码演示视频的、想吃透JDBC事务边界和JSP作用域的老实人、以及需要快速搭一个可运行评测底座再往上加功能比如OJ题库API化的进阶者。它不炫技但每行DAO层代码都经得起mvn clean compile重刷。2. 从war包解压到首页可访问环境搭建与核心文件定位2.1 环境清单拒绝“我本地能跑”的玄学陷阱这个项目对环境有明确且可验证的要求不是所有“JSP环境”都能直接套用。我反复验证过的最小可行组合如下Windows/Linux通用组件版本要求验证命令/方式关键说明JDK必须JDK 8u202或更低java -version输出含1.8.0_202JDK 8u221因安全策略默认禁用sun.misc.Unsafe导致Runtime.exec()在Linux下静默失败Tomcat7.0.96 或 8.5.34CATALINA_HOME/bin/version.shTomcat 9移除了org.apache.jasper.compiler.JspRuntimeContext类LoginServlet中getServletContext().getAttribute(userList)会NPEMySQL5.7.32推荐或 5.6.45mysql --versionMySQL 8.0默认启用caching_sha2_password插件而项目jdbc.properties里用的是com.mysql.jdbc.Driver非com.mysql.cj.jdbc.Driver连接必报Access denied for userIDE可选Eclipse Java EE 2019-06 或 IntelliJ IDEA 2020.1新建Dynamic Web Project时选择“Target runtime: Apache Tomcat v7.0”若用IDEA新建JSP项目务必勾选“Add framework support → Java EE Web”并指定Tomcat路径否则web.xml中的servlet-class无法被识别提示不要试图用Docker跑这个项目。ContestResultServlet中硬编码了/usr/bin/g路径容器内无GCC且挂载复杂调试成本远高于本地装环境。2.2 WAR包拆解找到那12个.class文件的真实位置项目正文列出的12个.class文件不是散落在WEB-INF/classes/下的孤魂野鬼——它们按MVC分层严格组织。解压WAR包后关键路径如下online-judge.war ├── WEB-INF/ │ ├── web.xml ← 全局配置servlet映射、filter链、欢迎页 │ ├── lib/ │ │ ├── mysql-connector-java-5.1.47.jar ← 注意是5.1.x非8.0.x驱动 │ │ └── jstl-1.2.jar ← JSTL标签库用于c:if等逻辑控制 │ ├── classes/ │ │ ├── com/example/dao/ ← DAO层数据库操作封装 │ │ │ ├── UserDaoImpl.class ← 用户增删改查含密码MD5 │ │ │ ├── ProblemDaoImpl.class ← 题目CRUD题目描述、测试用例存text字段 │ │ │ ├── ResultDaoImpl.class ← 提交记录存档含编译错误日志截断 │ │ │ └── ContestDaoImpl.class ← 比赛管理起止时间、题目关联 │ │ ├── com/example/service/ ← Service层业务逻辑编排 │ │ │ └── UserServiceImpl.class ← 密码校验登录态维护Session绑定 │ │ ├── com/example/servlet/ ← Servlet层请求入口 │ │ │ ├── LoginServlet.class ← POST /login 处理表单 │ │ │ ├── RegisterServlet.class ← 用户注册邮箱唯一性校验在DAO层SQL实现 │ │ │ ├── ContestResultServlet.class ← 核心接收代码提交→编译→执行→比对→存库 │ │ │ └── UpdatePwdServlet.class ← 修改密码旧密码校验新密码MD5存储 │ │ └── com/example/filter/ ← Filter层横切关注点 │ │ └── LoginFilter.class ← 拦截未登录用户访问/admin/路径 │ └── views/ ← JSP视图层无.java文件纯HTMLJava脚本 │ ├── index.jsp ← 首页未登录显示登录框已登录显示比赛列表 │ ├── admin/ ← 管理员专属目录需admin权限 │ │ ├── addProblem.jsp ← 添加题目表单含textarea存测试用例 │ │ └── manageUser.jsp ← 用户列表禁用按钮触发UserDaoImpl.delete() │ └── contest/ ← 比赛相关页面 │ ├── contestDetail.jsp ← 单场比赛详情题目列表提交按钮 │ └── result.jsp ← ContestResultServlet跳转页显示AC/RE/WA等状态2.3 数据库初始化四张表结构与一条关键INSERTMySQL建库建表不能只靠CREATE DATABASE。项目依赖特定字段名和约束漏掉任一细节都会导致UserDaoImpl.login()返回null。执行以下SQL注意字符集必须为utf8mb4-- 创建数据库必须utf8mb4否则中文题目描述乱码 CREATE DATABASE IF NOT EXISTS online_judge CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE online_judge; -- 用户表密码用MD5(明文salt)存储salt硬编码在UserServiceImpl.java第32行 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(32) NOT NULL, -- MD5值32位十六进制字符串 email VARCHAR(100) UNIQUE, role ENUM(user,admin) DEFAULT user, -- 权限标识LoginFilter靠此判断 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 题目表test_case字段存JSON格式测试用例如[{input:1 2,output:3}] CREATE TABLE problems ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT, test_case TEXT, -- 关键ContestResultServlet解析此字段生成输入文件 time_limit INT DEFAULT 1000, -- 毫秒 memory_limit INT DEFAULT 65536, -- KB created_by INT, -- 关联users.id管理员添加题目时写入 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 比赛表contest_problems存题目ID逗号分隔字符串如1,3,5非关系型设计 CREATE TABLE contests ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, start_time DATETIME, end_time DATETIME, contest_problems VARCHAR(500), -- 简单粗暴避免多对多中间表 created_by INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 提交结果表status存枚举值便于前端switch-case CREATE TABLE results ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, problem_id INT NOT NULL, contest_id INT, code TEXT, -- 用户提交的源码C/Java/Python language ENUM(c,cpp,java,python) DEFAULT c, status ENUM(pending,compiling,running,accepted,wrong_answer,time_limit_exceeded,memory_limit_exceeded,compile_error,runtime_error) DEFAULT pending, score INT DEFAULT 0, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (problem_id) REFERENCES problems(id) ON DELETE CASCADE ); -- 必须插入管理员账号否则无法进入/admin/目录 INSERT INTO users (username, password, email, role) VALUES (admin, 21232f297a57a5a743894a0e4a801fc3, adminexample.com, admin); -- 密码明文是adminMD5值21232f297a57a5a743894a0e4a801fc3项目硬编码无需修改参数说明test_case字段存JSON而非单独建表是项目为降低复杂度做的妥协。ContestResultServlet中用org.json.JSONObject解析若你新增题目时JSON格式错误如多逗号、引号不匹配会导致JSONException且页面500——这是后续避坑重点。3. 让评测真正跑起来ContestResultServlet的编译执行链路与参数调优3.1 评测流程拆解从HTTP POST到AC状态的7个关键步骤ContestResultServlet是整个系统的黑匣子也是最容易翻车的模块。它不走RESTful API而是用request.getParameter()拿表单数据再调用本地命令行执行评测。完整链路如下接收请求/contest/submitPOST参数含problemId1code#includestdio.h...languagec创建临时目录File tempDir new File(/tmp/judge_ System.currentTimeMillis()); tempDir.mkdir();写入源码文件根据language生成对应后缀code.c/code.java/code.py内容为request.getParameter(code)编译C/Cgcc -o /tmp/judge_xxx/a.out /tmp/judge_xxx/code.c -lmJavajavac -d /tmp/judge_xxx /tmp/judge_xxx/code.java注意code.java必须含public class MainPython跳过编译直接执行读取测试用例从problems.test_case字段解析JSON循环执行每个{input,output}对执行与比对对每个测试用例执行/tmp/judge_xxx/a.out input.txt output.txt 2 error.txt用diff -w output.txt expected_output.txt比对-w忽略空格差异超时则kill -9进程组Process.destroyForcibly()更新数据库将最终status如accepted、score满分100每个用例20分、error_log编译错误或diff结果写入results表逻辑说明这个流程没有用Docker或沙箱完全依赖宿主机环境。这意味着——你的Linux服务器必须预装gcc/jdk8/python3且Web应用用户如tomcat对其有执行权限。Windows下需安装MinGW并修改ContestResultServlet.java第127行gcc为C:\\MinGW\\bin\\gcc.exe。3.2 编译参数详解为什么C程序总报“undefined reference topow”ContestResultServlet中C语言编译命令是gcc -o a.out code.c -lm其中-lm是关键。若漏掉调用math.h函数如pow、sqrt会链接失败status变为compile_error。同理语言编译命令关键参数作用常见错误Cgcc -o a.out code.c -lm-lm链接数学库undefined reference to powCg -o a.out code.cpp -lm-lm同上undefined reference to sqrtJavajavac -d . code.java-d .指定class输出目录为当前目录cannot find symbol class Scanner未import java.util.*Python无编译—直接python3 code.py input.txtSyntaxError: invalid syntaxPython2语法参数说明-lm必须写在源文件之后顺序错误如gcc -lm -o a.out code.c会导致链接失败。项目源码中该参数写法正确但若你修改编译逻辑务必保持顺序。3.3 评测超时控制time_limit字段如何影响Runtime.exec()problems.time_limit单位是毫秒但ContestResultServlet中实际用Process.waitFor(timeout, TimeUnit.MILLISECONDS)控制。关键代码段ContestResultServlet.java第215行Process process Runtime.getRuntime().exec(cmdArray, null, tempDir); boolean finished process.waitFor(1000, TimeUnit.MILLISECONDS); // 注意这里写死1000ms if (!finished) { process.destroyForcibly(); // 强制杀进程 status time_limit_exceeded; }问题来了time_limit字段值如2000并未传入waitFor()而是被硬编码为1000ms这导致所有题目超时阈值都是1秒无论数据库怎么设。修复方案将process.waitFor(1000, ...)改为process.waitFor(problem.getTimeLimit(), ...)并确保ProblemDaoImpl.getProblemById()返回的对象包含timeLimit属性需检查getter方法。血泪经验我在CentOS 7上测试时发现destroyForcibly()在JDK 8u202下有时失效子进程变成僵尸。解决方案是在waitFor()后加Thread.sleep(100)再destroyForcibly()或改用ProcessHandleJDK 9但本项目必须兼容JDK 8故采用前者。4. 避坑指南12个.class文件背后的真实踩坑记录4.1 现象首页加载后显示“500 Error”Tomcat日志报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因WEB-INF/lib/mysql-connector-java-5.1.47.jar存在但jdbc.properties中driverClassNamecom.mysql.jdbc.Driver与MySQL 5.7兼容而你误装了MySQL 8.0服务端默认用caching_sha2_password认证。解决降级MySQL至5.7或修改jdbc.properties为driverClassNamecom.mysql.cj.jdbc.Driver并升级jar包——但后者需同步修改UserDaoImpl.java中Class.forName()调用且com.mysql.cj包不支持JDK 8u202以下版本风险更高强烈建议降级MySQL。4.2 现象管理员登录后点击“添加题目”页面空白浏览器Console报Uncaught ReferenceError: $ is not defined原因admin/addProblem.jsp中用了jQueryscript srcjs/jquery.min.js但项目WAR包里js/目录为空jQuery文件缺失。解决下载jQuery 1.12.4兼容IE6放入WEB-INF/classes/js/jquery.min.js或直接在JSP中CDN引入需确保网络可达script srchttps://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js/script4.3 现象用户提交C代码后status始终为pending数据库results表无更新原因ContestResultServlet中Runtime.getRuntime().exec()执行编译命令时tempDir路径含空格如C:\Program Files\Apache Software Foundation\Tomcat 8.5\temp\judge_12345导致gcc找不到源文件。解决强制tempDir路径不含空格。修改ContestResultServlet.java第89行// 原代码 File tempDir new File(/tmp/judge_ System.currentTimeMillis()); // 改为Linux File tempDir new File(/var/tmp/judge_ System.currentTimeMillis()); // 或Windows File tempDir new File(C:/temp/judge_ System.currentTimeMillis());4.4 现象MySQL连接池报错java.sql.SQLException: Cannot create PoolableConnectionFactory原因WEB-INF/classes/jdbc.properties中urljdbc:mysql://localhost:3306/online_judge?useUnicodetruecharacterEncodingutf8缺少serverTimezoneGMT%2B8参数MySQL 5.7严格时区校验。解决在url末尾追加serverTimezoneGMT%2B8即urljdbc:mysql://localhost:3306/online_judge?useUnicodetruecharacterEncodingutf8serverTimezoneGMT%2B84.5 现象LoginFilter拦截失效非admin用户可直接访问/admin/manageUser.jsp原因web.xml中filter-mapping顺序错误LoginFilter未在servlet-mapping之前生效。解决检查web.xml确保filter-mapping块在所有servlet-mapping之前且url-pattern为/admin/*注意星号filter-mapping filter-nameLoginFilter/filter-name url-pattern/admin/*/url-pattern !-- 必须有* -- /filter-mapping5. 进阶技巧把“传统JSP项目”变成可交付的毕设作品5.1 题目批量导入用Excel生成JSON测试用例并入库手动在admin/addProblem.jsp里填JSON太反人类。我写了个Python脚本把Excel题库列题目标题、描述、输入样例、输出样例、时限转成标准JSON写入problems.test_case# excel_to_json.py import pandas as pd import json def excel_to_testcase(excel_path): df pd.read_excel(excel_path) test_cases [] for _, row in df.iterrows(): # 输入输出去首尾空格换行符转\n input_clean row[输入样例].strip().replace(\r\n, \n) output_clean row[输出样例].strip().replace(\r\n, \n) test_cases.append({ input: input_clean, output: output_clean }) return json.dumps(test_cases, ensure_asciiFalse, indent2) # 用法python excel_to_json.py 题库.xlsx testcase.json if __name__ __main__: import sys print(excel_to_testcase(sys.argv[1]))生成的testcase.json内容示例[ { input: 1 2, output: 3 }, { input: 10 20, output: 30 } ]然后复制粘贴到addProblem.jsp的test_case文本框即可。注意JSON中双引号必须是英文中文引号会解析失败。5.2 评测日志可视化从results.error_log提取编译错误关键词ContestResultServlet把编译错误全存进results.error_log字段但原始日志冗长如gcc报错含路径信息。我加了个简单关键词高亮让前端一眼看到问题!-- result.jsp 中显示错误日志 -- c:if test${result.status compile_error} div classalert alert-danger strong编译错误/strong prec:out value${result.errorLog} escapeXmlfalse//pre %-- 关键词高亮 --% c:set varlog value${result.errorLog}/ c:set varhighlighted value${fn:replace(log, error:, span class\text-danger\error:/span)}/ c:set varhighlighted value${fn:replace(highlighted, warning:, span class\text-warning\warning:/span)}/ c:set varhighlighted value${fn:replace(highlighted, undefined reference, span class\text-danger\undefined reference/span)}/ div${highlighted}/div /div /c:if参数说明escapeXmlfalse允许HTML标签渲染fn:replace是JSTL函数需在JSP顶部声明% taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %。5.3 安全加固MD5盐值硬编码的替换方案项目密码用MD5(明文固定salt)salt写死在UserServiceImpl.java第32行private static final String SALT OnlineJudge2023;。这很危险。我改成从jdbc.properties读取// UserServiceImpl.java 第32行附近 private static String SALT; static { Properties props new Properties(); try (InputStream is UserServiceImpl.class.getClassLoader() .getResourceAsStream(jdbc.properties)) { props.load(is); SALT props.getProperty(password.salt, OnlineJudge2023); } catch (Exception e) { SALT OnlineJudge2023; // fallback } }并在jdbc.properties末尾加一行password.saltMySecureSalt_2024!#从那以后我每次部署新环境都强制走一遍mysql -u root -p -e UPDATE users SET passwordMD5(CONCAT(your_new_password,MySecureSalt_2024!#)) WHERE usernameadmin;重置密码再删掉jdbc.properties里的salt值——毕竟配置文件可能被Git误提交。希望帮到你。本文还有配套的精品资源点击获取
返回列表