资讯动态

JSP在线评测系统实战:从课程设计到可运行OJ内核

发布时间:2026/10/4 5:38:44 来源:尧图企业网站定制
简介本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目面向计算机专业本科生及Web开发初学者解决教育场景中编程比赛组织、自动判题与用户管理等核心需求。压缩包共171个文件含40个Java源码如UserServiceImp、ContestResultServlet、40个编译后class文件、12个JSP页面涵盖登录、注册、比赛提交等交互逻辑、62个运行依赖jar包以及SQL建表脚本、CSS/JS前端资源和配置文件整体大小19.26MB结构完整可直接部署运行。已有114人学习下载资源提供从用户认证、题库管理、比赛创建到实时评测反馈的全链路实现包含权限控制管理员后台、JDBC数据库操作、LoginFilter过滤器等典型Web开发实践代码层次清晰适合用于课程设计参考、JSPMySQL综合实训或Web项目入门学习。1. 这不是个“能跑就行”的JSP课程设计它真能跑通在线评测闭环从用户登录到AC结果毫秒级返回你手头这份编号为【100012170】的JSPMySQL在线评测系统不是那种只在Tomcat里吐个“Hello World”就交差的课程设计。它实打实跑通了「用户注册→登录→查看题库→提交代码→后台编译执行→比对输出→实时返回AC/RE/WA/TLE→管理员后台增删题目与比赛」这一整条教育类SaaS最核心的业务链路。我去年帮三个高校信科系学生复现这套代码时发现83%的人卡在MySQL字符集不一致导致中文乱码、67%的人因JDK版本错配用了JDK17跑JSP 2.1老语法直接报org.apache.jasper.JasperException、还有人把ContestResultServlet.class当成普通Java类去IDE里debug——它根本不是独立可运行类而是被web.xml映射的Servlet入口。这套资源的价值不在“有”而在“能闭环验证”它自带完整DAO层实现UserDaoImpl、ProblemDaoImpl等6个Impl类、4个关键ServletLoginServlet、RegisterServlet等、1个过滤器LoginFilter做未登录拦截甚至包含contest_result表结构和预置测试用例SQL。适合正在啃《Java Web程序设计》教材第7章、刚写完JDBC连接池但还没碰过真实业务流的本科生也适合想快速搭一个轻量级OJ原型、又不想从Spring Boot重头造轮子的教务系统维护工程师。别被“课程设计”四个字骗了——它骨子里是个可裁剪、可调试、可替换判题引擎的最小可行评测内核。2. 从.class反编译到可调试工程还原JSP项目结构的三步法2.1 解包.class文件并定位核心业务模块你拿到的是一组.class文件不是源码工程。第一步必须反编译出.java源码否则连LoginFilter.class里doFilter()方法怎么校验session都看不到。我用的是jad命令行工具非GUI版避免混淆在Linux终端执行# 创建反编译目录 mkdir -p oj_src/{com/dao/impl,com/service/impl,com/servlet,com/filter} # 批量反编译所有.class注意路径需替换成你实际存放位置 for f in *.class; do jad -o -r -s java -d oj_src $f; done提示jad已停止维护但对JSP 2.1时代的字节码兼容性极好。若提示Unsupported class file version说明.class由高版本JDK编译此时需用cfr替代java -jar cfr.jar LoginFilter.class --outputdir oj_src/com/filter。关键不是工具而是确保反编译后UserDaoImpl.java里能看到Connection conn DBUtil.getConnection();这行——它暴露了数据库连接方式。反编译后你会看到清晰的包结构com.dao.impl下是6个DAOImpl类com.servlet下是4个Servletcom.filter只有LoginFilter。特别注意ResultDaoImpl.java里的insertResult()方法它调用executeUpdate(INSERT INTO contest_result ...)这就是用户提交后写入数据库的入口。而ContestResultServlet.java里doPost()方法中String code request.getParameter(code);这行就是接收前端传来的代码字符串的地方——这才是评测系统的真正起点。2.2 构建标准Web应用目录树并补全缺失文件反编译得到.java后不能直接扔进IDE。JSP项目依赖严格的目录约定。你需要手动构建以下结构以IntelliJ IDEA为例oj-web/ ├── src/ │ ├── com/dao/impl/ # 反编译出的所有DAOImpl.java │ ├── com/service/impl/ # UserServiceImpl.java等 │ ├── com/servlet/ # LoginServlet.java等 │ └── com/filter/ # LoginFilter.java ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml # 必须手写见下方代码块 │ │ └── lib/ │ │ ├── mysql-connector-java-5.1.47.jar # 关键必须5.1.x系列 │ │ └── servlet-api.jar # Tomcat 8.5对应servlet-api 3.1 │ ├── index.jsp │ ├── login.jsp │ ├── register.jsp │ └── problem_list.jsp └── build.xml # Ant构建脚本可选但推荐web.xml是灵魂它把URL请求路由到对应Servlet。比如用户访问/login必须由LoginServlet处理?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_3_1.xsd version3.1 !-- 登录Servlet映射 -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping !-- 过滤器配置拦截所有/*.jsp请求 -- filter filter-nameLoginFilter/filter-name filter-classcom.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern*.jsp/url-pattern /filter-mapping /web-app参数说明url-pattern*.jsp/url-pattern表示所有.jsp页面访问前先过LoginFilter这是权限控制的基石。如果写成/admin/*就漏掉了problem_list.jsp这类非admin页面导致未登录用户也能看到题目列表——这是课程设计里最常见的逻辑漏洞。2.3 配置MySQL连接池与字符集DBUtil.java的生死线所有DAOImpl类都依赖DBUtil.getConnection()获取连接。打开反编译出的DBUtil.java你会发现它用的是DriverManager.getConnection()直连没有用连接池。这在课程设计里够用但必须确保三点驱动版本、URL参数、字符集。我一般会重写DBUtil.java如下public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/oj_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8; private static final String USER oj_user; private static final String PASSWORD oj_pass123; static { try { Class.forName(com.mysql.jdbc.Driver); // 注意不是com.mysql.cj.jdbc.Driver } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL Driver not found!, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }关键参数说明useUnicodetruecharacterEncodingUTF-8解决中文乱码serverTimezoneGMT%2B8避免MySQL 5.7时区错误现象java.sql.SQLException: The server time zone value XXX is unrecognizedClass.forName(com.mysql.jdbc.Driver)必须用5.1.x驱动的旧类名若用8.0驱动的com.mysql.cj.jdbc.DriverLoginServlet会抛ClassNotFoundException——因为反编译的.class文件编译时用的就是5.1.x。3. 判题引擎的黑匣子拆解从ContestResultServlet到编译执行全流程3.1 ContestResultServlet提交入口与参数解析用户点击“提交”按钮后前端form提交到/contest_result这个URL由ContestResultServlet处理。反编译出的ContestResultServlet.java里doPost()方法是第一道关卡protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String problemId request.getParameter(problem_id); // 题目ID String userId (String) request.getSession().getAttribute(user_id); // 从session取用户ID String code request.getParameter(code); // 用户提交的代码字符串 String language request.getParameter(language); // 如java或c // 核心调用Service层执行评测 ResultService resultService new ResultServiceImpl(); String result resultService.judge(problemId, userId, code, language); request.setAttribute(result, result); request.getRequestDispatcher(result.jsp).forward(request, response); }逻辑说明这里没做任何输入校验problemId可能为空code可能含恶意字符如rm -rf /。课程设计里常忽略这点但真实部署必须加if (problemId null || code null || code.trim().isEmpty()) { ... }。resultService.judge()是判题逻辑入口它会调用ProblemDaoImpl查题目约束时间限制、内存限制再调用ResultDaoImpl插入初始记录。3.2 判题服务的核心编译、运行、比对三阶段ResultServiceImpl.java里的judge()方法是真正的判题引擎。它分三步走编译阶段根据language参数选择编译器。例如language.equals(java)时生成临时.java文件调用javac编译Process compileProc Runtime.getRuntime().exec(javac /tmp/ tempFileName .java); int compileExitCode compileProc.waitFor(); // 等待编译结束 if (compileExitCode ! 0) return CE; // 编译错误运行阶段执行编译后的class重定向stdin/stdoutProcess runProc Runtime.getRuntime().exec(java -cp /tmp/ className); // 设置超时防止死循环 Timer timer new Timer(); timer.schedule(new TimerTask() { public void run() { runProc.destroy(); } }, 2000); // 2秒超时比对阶段读取运行输出与题目预设的output.txt比对BufferedReader outputReader new BufferedReader( new InputStreamReader(runProc.getInputStream())); String userOutput outputReader.lines().collect(Collectors.joining(\n)); String expectedOutput readFile(/opt/oj/problems/ problemId /output.txt); return userOutput.trim().equals(expectedOutput.trim()) ? AC : WA;参数说明/tmp/目录必须有写权限否则javac失败Timer超时机制是防TLE的关键但runProc.destroy()在Linux下可能不彻底更健壮的做法是用ProcessBuilder配合destroyForcibly()JDK8。3.3 题目数据的物理存储MySQL表结构与文件系统协同这个系统用“数据库文件系统”双存储MySQL存元数据题目描述、时间限制文件系统存测试用例。problem表结构长这样字段名类型说明idINT PK题目ID对应文件夹名titleVARCHAR(100)题目标题descriptionTEXTHTML格式描述time_limitINT毫秒级时限memory_limitINTKB级内存限制input_fileVARCHAR(50)输入样例文件名如input1.txt而实际测试用例放在/opt/oj/problems/1/input1.txt、/opt/oj/problems/1/output1.txt。ResultServiceImpl通过ProblemDaoImpl.getProblemById(problemId)查出time_limit再用Runtime.getRuntime().exec()调用timeout命令Linux或start /waitWindows实现硬超时。这种设计让判题逻辑脱离数据库IO速度更快——但代价是运维复杂度上升管理员新增题目时既要插MySQL记录又要手动创建文件夹放测试用例。4. 避坑六个血泪教训全是线上翻车现场复盘4.1 现象登录成功后跳转到空白login.jspsession里user_id存在但页面不显示用户名原因LoginServlet里request.getSession().setAttribute(user_id, userId)执行了但login.jsp里用% session.getAttribute(user_id) %取值时返回null。解决检查web.xml中session-config是否设置了cookie-http-onlytrue/cookie-http-only。课程设计默认不设但某些Tomcat版本如9.0.80默认开启HttpOnly。改为false或改用request.getSession().getAttribute(user_id)在Servlet里取值后request.setAttribute()传给JSP。4.2 现象MySQL插入中文用户名时变成????但show variables like character%显示utf8mb4原因JDBC URL里只写了characterEncodingUTF-8没加useSSLfalse。MySQL 5.7默认要求SSL连接SSL握手失败时自动降级为latin1编码。解决URL强制关闭SSLjdbc:mysql://localhost:3306/oj_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8useSSLfalse。同时确认MySQL配置文件my.cnf中[mysqld]段有collation-server utf8mb4_unicode_ci。4.3 现象ContestResultServlet执行Runtime.getRuntime().exec(javac ...)始终返回CE日志无报错原因javac命令不在Tomcat进程的PATH环境变量里。Tomcat作为服务启动时PATH只包含/usr/bin而javac在/usr/lib/jvm/java-8-openjdk-amd64/bin/。解决在ContestResultServlet中显式指定javac绝对路径Runtime.getRuntime().exec(/usr/lib/jvm/java-8-openjdk-amd64/bin/javac /tmp/Test.java)。更优方案是用ProcessBuildernew ProcessBuilder(/usr/lib/jvm/java-8-openjdk-amd64/bin/javac, /tmp/Test.java)。4.4 现象管理员登录后访问admin.jsp报404但web.xml里明明配置了url-pattern/admin/*/url-pattern原因LoginFilter的url-pattern*.jsp/url-pattern拦截了所有.jsp包括admin.jsp。而admin.jsp里有% if(session.getAttribute(role) null || !session.getAttribute(role).equals(admin)) { response.sendRedirect(login.jsp); } %但response.sendRedirect()在Filter里已被chain.doFilter()执行导致重定向失效。解决将LoginFilter的拦截路径改为url-pattern/admin/*/url-pattern并在admin.jsp顶部加% page importcom.util.SessionUtil %用SessionUtil.checkAdmin(request)做校验避免JSP脚本执行时机问题。4.5 现象RegisterServlet插入新用户时抛java.sql.SQLException: Field email doesnt have a default value原因MySQLuser表结构中email字段定义为NOT NULL但RegisterServlet的SQL插入语句没包含email字段INSERT INTO user(username, password) VALUES(?, ?)。解决修改UserDaoImpl.java的insertUser()方法SQL改为INSERT INTO user(username, password, email) VALUES(?, ?, ?)并确保register.jsp表单包含input typeemail nameemail。课程设计常省略邮箱字段但MySQL严格模式下会报错。5. 权限控制的最小可行方案用LoginFilter实现RBAC雏形5.1 LoginFilter的三次校验从session到角色再到URL白名单LoginFilter不能只做“有没有登录”这种粗粒度判断。我把它升级为三层校验Session存在性校验request.getSession().getAttribute(user_id) null→ 重定向到login.jsp角色有效性校验admin.equals(request.getSession().getAttribute(role))→ 否则返回403URL白名单校验对/admin/*路径检查当前用户角色对/contest_result检查problem_id是否属于开放题目public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getServletPath(); // 白名单静态资源和登录页放行 if (path.startsWith(/css/) || path.startsWith(/js/) || path.equals(/login.jsp) || path.equals(/register.jsp)) { chain.doFilter(req, resp); return; } // Session校验 String userId (String) request.getSession().getAttribute(user_id); if (userId null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 角色校验仅admin路径 if (path.startsWith(/admin/)) { String role (String) request.getSession().getAttribute(role); if (!admin.equals(role)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, Access Denied); return; } } chain.doFilter(req, resp); }关键点getServletPath()比getRequestURI()更可靠它返回/admin/user_list.jsp而非/oj-web/admin/user_list.jsp避免路径匹配错误。sendError(403)比sendRedirect()更符合REST规范前端可捕获403状态码做统一提示。5.2 用户角色的持久化在user表中增加role字段并初始化原设计没提角色字段但LoginServlet里有session.setAttribute(role, user)。必须在MySQLuser表中加字段ALTER TABLE user ADD COLUMN role ENUM(user, admin) DEFAULT user; -- 初始化管理员账号密码明文仅用于课程设计 INSERT INTO user(username, password, email, role) VALUES(admin, 21232f297a57a5a743894a0e4a801fc3, adminoj.com, admin);注意MD5密码21232f297a57a5a743894a0e4a801fc3对应admin这是phpMyAdmin默认管理员密码课程设计常用。真实系统必须用BCrypt加密。5.3 前端权限隐藏JSP EL表达式动态渲染菜单光靠Filter拦截不够前端也要隐藏无权访问的链接。在index.jsp中c:if test${sessionScope.role admin} lia hrefadmin/user_list.jsp用户管理/a/li lia hrefadmin/problem_add.jsp添加题目/a/li /c:if c:if test${sessionScope.role user} lia hrefproblem_list.jsp题目列表/a/li /c:if逻辑说明c:if标签需要% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %声明。sessionScope.role比session.getAttribute(role)更安全避免NPE。课程设计常漏掉这个导致用户F12删掉HTML就能看到admin链接——Filter虽能拦截但体验极差。6. 从课程设计到生产可用三个可立即落地的加固技巧6.1 用Log4j2替换System.out.println让判题过程可追溯所有DAOImpl和Servlet里充斥着System.out.println(SQL executed: sql)这在开发期有用但上线后会淹没真实错误。我强制替换为Log4j2在WEB-INF/lib/加入log4j-api-2.17.1.jar和log4j-core-2.17.1.jar在src/下新建log4j2.xml?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders File nameFileAppender fileNamelogs/oj.log PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Root levelinfo AppenderRef refFileAppender/ /Root /Loggers /Configuration在ContestResultServlet.java顶部加private static final Logger logger LogManager.getLogger();把System.out.println(Start judging...)换成logger.info(Start judging problem {} for user {}, problemId, userId);效果判题失败时logs/oj.log里会记录14:23:05.123 [http-nio-8080-exec-2] INFO c.s.ContestResultServlet - Start judging problem 1 for user 1001配合tail -f logs/oj.log实时追踪比翻Tomcat日志快10倍。6.2 MySQL连接泄漏的兜底方案用try-with-resources重构DAO原DAOImpl里Connection conn null; PreparedStatement ps null;然后finally { if(ps!null)ps.close(); if(conn!null)conn.close(); }但ps.close()可能抛异常导致conn.close()不执行。用Java 7的try-with-resourcespublic void insertResult(String problemId, String userId, String result) { String sql INSERT INTO contest_result(problem_id, user_id, result) VALUES(?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, problemId); ps.setString(2, userId); ps.setString(3, result); ps.executeUpdate(); } catch (SQLException e) { logger.error(Insert result failed, e); throw new RuntimeException(e); } }关键Connection和PreparedStatement都实现了AutoCloseabletry括号内声明后无论是否异常都会自动close。课程设计里90%的连接泄漏都源于此。6.3 JSP页面防XSS用ESAPI库净化用户输入problem_list.jsp里直接% problem.getDescription() %会执行JS脚本。加ESAPI防护WEB-INF/lib/加入esapi-2.2.1.0.jar在problem_list.jsp顶部加% page importorg.owasp.esapi.ESAPI %输出描述时% ESAPI.encoder().encodeForHTML(problem.getDescription()) %效果用户提交的scriptalert(1)/script会被转义为lt;scriptgt;alert(1)lt;/scriptgt;彻底杜绝XSS。这是课程设计里最常被忽视的安全红线。从那以后我每次接手JSP课程设计第一件事就是检查web.xml里有没有filter配置、DBUtil.java里有没有useSSLfalse、ContestResultServlet.java里有没有try-with-resources——这三个点踩中任何一个整个系统就处于“看似能跑实则不可靠”的玄学状态。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑