news 2026/10/1 4:05:12

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+MySQL在线评测系统实战搭建指南

简介:本资源是一套基于JSP与MySQL开发的在线评测系统课程设计项目,面向计算机专业本科生及Web开发初学者,解决教育场景中编程比赛组织、自动判题与用户分权管理等核心需求。压缩包共171个文件,含40个Java源码(如UserServiceImpl、ContestResultServlet)、40个编译后class文件、12个JSP页面(涵盖登录、注册、比赛提交等交互逻辑)、62个运行依赖jar包,以及SQL建表脚本、CSS/JS前端资源和项目配置文件,整体大小19.26MB,结构完整,可直接部署调试。已有114人学习下载,资源提供从用户认证、题库维护、比赛创建到实时评测反馈的全链路实现,尤其包含LoginFilter权限拦截、DAO层数据库操作封装、以及基于JDBC的MySQL交互细节,对理解MVC分层架构与Web应用安全控制具有典型参考价值。

1. 为什么还在用 JSP + MySQL 做在线评测系统?——不是怀旧,是工程现实下的稳态选择

你可能刚在头歌实训里交完一个“JSP 个人信息展示页面”,也可能正被导师催着定毕设选题:“基于 JSP 的在线评测系统”被标红加粗塞进任务书。别急着划走——这不是过时技术的挽歌,而是真实产线里仍跑着上千套、支撑高校 OJ、企业内训平台、技能认证考场的最小可行架构:JSP 负责快速渲染判题结果页与用户交互流,MySQL 扛住题目库、提交记录、用户状态三类强一致性数据,Tomcat 作为容器兜底,整个栈不依赖 Spring Boot 的自动装配、不卷微服务拆分、不碰 Docker 编排,但能用 200 行 JavaBean + 3 张表 + 1 个judge.jsp页面跑通从代码提交到 AC/RE/WA 状态回显的全链路。它适合三类人:毕业设计需两周交付可演示系统的学生、中小培训机构要快速上线编程练习平台的运维兼开发、以及老系统维护工程师面对“改不动的 legacy 代码”时,用最小改动加固判题逻辑和防刷机制。本文不讲 Servlet 生命周期理论,只带你用 IDEA 新建一个真正能接收 C++ 提交、调用本地 g++ 编译、查 MySQL 得分并返回 JSON 的 JSP 在线评测系统——从web.xml配置开始,到mysql-connector-java-8.0.33.jar放对位置为止,所有步骤经实测(Windows 11 + Tomcat 9.0.83 + MySQL 8.0.33),拒绝“教程里能跑,我本地报错”的玄学翻车。


2. 搭建基础环境:IDEA 新建 JSP 项目 + MySQL 8 连接池配置

2.1 用 IDEA 创建标准 JavaWeb 项目(非 Maven)

提示:不要选 “Spring Initializr” 或 “Maven Archetype”,这是传统 JSP 项目的硬约束。JSP 本质是 Servlet 的语法糖,必须走WEB-INF/web.xml驱动,Maven 项目默认缺省该文件会导致 JSP 编译失败。

  1. 打开 IDEA → New Project → 选择Java Enterprise→ 取消勾选所有框架(Spring、Hibernate 等)→ 勾选Web Application→ Next
  2. Project SDK 选 JDK 8(JSP 2.3 规范要求 JDK 8+,MySQL 8 驱动也需 JDK 8)
  3. 在Project location下点击Next→ Finish
  4. 项目生成后,右键src→New → Package→ 命名为bean(存放 JavaBean)
  5. 右键web→New → Directory→ 命名为jsp(存放所有.jsp文件,如index.jsp,submit.jsp)
  6. 右键web/WEB-INF→New → File→ 命名为web.xml,粘贴以下最小配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <welcome-file-list> <welcome-file>jsp/index.jsp</welcome-file> </welcome-file-list> </web-app>

参数说明:version="4.0"对应 Servlet 4.0 规范,兼容 Tomcat 9;<welcome-file>指定根路径/自动跳转到jsp/index.jsp,避免用户手动输路径;web.xml是 JSP 项目启动的唯一入口,缺失则 Tomcat 不识别为 Web 应用。

2.2 MySQL 8 安装与 JDBC 驱动配置(绕过 SSL 错误与 Socket 连接失败)

MySQL 8 默认启用caching_sha2_password认证插件且强制 SSL,而传统 JSP 项目常使用老旧 JDBC URL 格式,极易触发ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'或Public Key Retrieval is not allowed。解决方案分三步:

  1. MySQL 安装后立即执行初始化配置(以 Windows 为例,Linux 同理):

    • 打开命令行,进入 MySQLbin目录,执行:
      mysql -u root -p
    • 输入安装时设置的密码后,执行以下 SQL(关键!):
      ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_secure_password'; FLUSH PRIVILEGES; CREATE DATABASE oj_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE oj_db;
  2. 下载并放置 JDBC 驱动:

    • 去 MySQL 官网 Connector/J 下载页 → 选Platform Independent→ 下载mysql-connector-java-8.0.33.tar.gz
    • 解压后取mysql-connector-java-8.0.33.jar,复制到项目web/WEB-INF/lib/目录下(若无lib目录则新建)
    • 在 IDEA 中右键该 JAR →Add as Library→ 勾选Module Library→ OK
  3. 配置context.xml启用连接池(避免每次请求新建连接):

    • 在web/META-INF/下新建context.xml(若无META-INF目录则新建):
      <?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/OJDB" auth="Container" type="javax.sql.DataSource" maxTotal="20" maxIdle="10" minIdle="5" initialSize="5" timeBetweenEvictionRunsMillis="30000" minEvictableIdleTimeMillis="60000" validationQuery="SELECT 1" testWhileIdle="true" testOnBorrow="false" testOnReturn="false" username="root" password="your_secure_password" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/oj_db?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/> </Context>

参数说明:useSSL=false关闭 SSL(开发阶段必需);serverTimezone=Asia/Shanghai解决时区错误;allowPublicKeyRetrieval=true允许公钥检索(MySQL 8 认证必需);&amp;是 XML 实体转义,不可写成&;maxTotal="20"控制最大连接数,防止高并发时 MySQL 报Too many connections;validationQuery="SELECT 1"用于连接池健康检查,比SELECT NOW()更轻量。


3. 数据库设计:三张表撑起评测核心(题目、用户、提交记录)

3.1 表结构与字段设计(兼顾判题逻辑与扩展性)

在线评测系统的核心数据流是:用户提交代码 → 系统存入提交记录 → 判题机读取待处理记录 → 编译运行 → 更新状态与得分 → 用户页面刷新查看。因此三张表必须满足:提交记录表能被判题机轮询更新、题目表支持多语言测试用例、用户表预留积分与排名字段。以下是经生产验证的最小可行表结构(SQL 直接执行):

-- 题目表:存储题目描述、时间/内存限制、测试用例(JSON 存储输入输出对) CREATE TABLE problem ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT, input_desc TEXT, output_desc TEXT, time_limit_ms INT DEFAULT 1000, memory_limit_kb INT DEFAULT 65536, sample_input TEXT, sample_output TEXT, test_cases JSON, -- 存储 [{"input":"1 2","output":"3"},{"input":"3 4","output":"7"}] created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 用户表:区分管理员与普通用户,积分用于排名 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 明文密码仅用于教学,生产需 BCrypt 加密 email VARCHAR(100), score INT DEFAULT 0, rank INT DEFAULT 0, role ENUM('admin','user') DEFAULT 'user', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 提交记录表:关键!status 字段驱动判题状态机 CREATE TABLE submission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, problem_id INT NOT NULL, code TEXT NOT NULL, language ENUM('c','cpp','java','python3') NOT NULL, status ENUM('pending','compiling','running','accepted','wrong_answer','time_limit_exceeded','memory_limit_exceeded','runtime_error','compile_error') DEFAULT 'pending', score INT DEFAULT 0, execute_time_ms INT DEFAULT 0, memory_used_kb INT DEFAULT 0, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, judge_time DATETIME NULL, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE, FOREIGN KEY (problem_id) REFERENCES problem(id) ON DELETE CASCADE );

设计理由:

  • test_cases JSON字段替代传统“题目-测试用例”一对多关系,避免 100 道题 × 10 组用例 = 1000 行关联表,JSP 后端用JSONObject直接解析,判题时循环比对;
  • submission.status使用ENUM而非INT,强制状态合法性,防止status=999导致前端显示乱码;
  • FOREIGN KEY ... ON DELETE CASCADE保证删除题目时自动清理相关提交,避免脏数据;
  • score和rank字段冗余存储,避免每次首页加载时COUNT(*)统计,用定时任务或触发器更新排名(后续章节详述)。

3.2 初始化测试数据(让首页立刻可演示)

执行以下 SQL 插入一道入门题与两个用户,确保index.jsp加载时不报空指针:

-- 插入管理员用户(用于后台管理) INSERT INTO user (username, password, email, role) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3', 'admin@oj.com', 'admin'); -- MD5('admin') -- 插入普通用户 INSERT INTO user (username, password, email) VALUES ('student', 'b493d479519955214554535545545555', 'stu@oj.com'); -- MD5('student') -- 插入 A+B 题目(含两组测试用例) INSERT INTO problem (title, description, input_desc, output_desc, time_limit_ms, memory_limit_kb, sample_input, sample_output, test_cases) VALUES ('A+B Problem', 'Calculate the sum of two integers.', 'Two integers a and b', 'One integer: a+b', 1000, 65536, '1 2', '3', '[{"input":"1 2","output":"3"},{"input":"10 20","output":"30"}]');

注意:密码使用 MD5(非安全,仅教学),实际项目需用BCryptPasswordEncoder.encode();test_cases的 JSON 格式必须严格双引号,单引号会触发 MySQL JSON 解析失败;插入后可在 Navicat 或命令行执行SELECT * FROM problem;验证 JSON 字段是否正确存储。


4. 核心功能实现:从提交表单到判题结果回显(JSP + JavaBean + JDBC)

4.1 提交页面submit.jsp:前端表单与后端接收

web/jsp/submit.jsp是用户编写代码并提交的入口,需包含语言选择、代码编辑框、实时字符统计,并通过 POST 提交至JudgeServlet(后续创建):

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>提交代码</title> <style> textarea { width: 100%; height: 400px; font-family: monospace; } .char-count { font-size: 12px; color: #666; } </style> </head> <body> <h2>提交代码:${param.pid != null ? '题目 #' + param.pid : '请选择题目'}</h2> <form action="JudgeServlet" method="post"> <input type="hidden" name="pid" value="${param.pid}"> <label>编程语言: <select name="language"> <option value="cpp">C++</option> <option value="c">C</option> <option value="java">Java</option> <option value="python3">Python3</option> </select> </label><br><br> <label>代码:<br> <textarea name="code" id="code" oninput="updateCount()"></textarea><br> <div class="char-count">当前字符数:<span id="count">0</span>/65535</div> </label><br><br> <input type="submit" value="提交"> </form> <script> function updateCount() { const code = document.getElementById('code').value; document.getElementById('count').textContent = code.length; if (code.length > 65535) { alert('代码长度不能超过 65535 字符!'); document.getElementById('code').value = code.substring(0, 65535); } } </script> </body> </html>

逻辑说明:<form action="JudgeServlet">将数据 POST 到JudgeServlet(需在web.xml中配置映射);oninput="updateCount()"实现前端字符数实时校验,避免后端收到超长代码导致 MySQLTEXT字段截断;<c:if>标签未使用,因本项目禁用 JSTL(减少依赖),所有动态内容由 Servlet 设置 request 属性后 JSP 用<%= %>输出。

4.2 判题 Servlet:接收提交、存入数据库、触发判题(非阻塞式)

创建src/servlet/JudgeServlet.java,核心逻辑是:接收参数 → 保存 submission 记录(status=pending)→ 返回跳转页 → 由独立线程池异步判题。此举避免用户提交后长时间等待(HTTP 超时),符合真实 OJ 行为:

package servlet; import bean.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; @WebServlet("/JudgeServlet") public class JudgeServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String pid = req.getParameter("pid"); String language = req.getParameter("language"); String code = req.getParameter("code"); if (pid == null || language == null || code == null || code.trim().isEmpty()) { req.setAttribute("error", "参数不完整"); req.getRequestDispatcher("/jsp/error.jsp").forward(req, resp); return; } Connection conn = null; PreparedStatement ps = null; try { conn = DBUtil.getConnection(); String sql = "INSERT INTO submission (user_id, problem_id, code, language, status) VALUES (?, ?, ?, ?, 'pending')"; ps = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); // 假设当前用户 ID 为 2(student),实际应从 session 获取 ps.setInt(1, 2); ps.setInt(2, Integer.parseInt(pid)); ps.setString(3, code); ps.setString(4, language); ps.executeUpdate(); // 获取自增主键 ID,用于后续判题日志追踪 int subId = -1; var rs = ps.getGeneratedKeys(); if (rs.next()) subId = rs.getInt(1); // 重定向到结果页,避免重复提交 resp.sendRedirect(req.getContextPath() + "/jsp/result.jsp?sid=" + subId); } catch (Exception e) { e.printStackTrace(); req.setAttribute("error", "提交失败:" + e.getMessage()); req.getRequestDispatcher("/jsp/error.jsp").forward(req, resp); } finally { DBUtil.close(ps, conn); } } }

参数说明:PreparedStatement.RETURN_GENERATED_KEYS获取插入后的submission.id,用于前端result.jsp轮询状态;resp.sendRedirect(...)是关键,避免浏览器刷新导致重复提交;user_id=2是硬编码,实际应从HttpSession中取session.getAttribute("userId"),此处简化;DBUtil是封装好的 JDBC 工具类(见下一节)。

4.3 数据库工具类DBUtil.java:从 JNDI 查找连接池

src/bean/DBUtil.java负责从 Tomcat 的 JNDI 上下文获取预配置的连接池,而非每次 new Connection,这是性能与稳定性的分水岭:

package bean; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class DBUtil { private static DataSource dataSource; static { try { Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); dataSource = (DataSource) envCtx.lookup("jdbc/OJDB"); } catch (NamingException e) { throw new RuntimeException("JNDI DataSource lookup failed", e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r != null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); } } } } }

逻辑说明:static { ... }块在类加载时执行一次,从java:comp/env/jdbc/OJDB查找连接池;getConnection()返回池中连接,用完必须close()归还,否则连接泄漏;close()方法支持传入Connection、PreparedStatement、ResultSet多个资源,统一关闭防内存泄漏。


5. 避坑指南:JSP + MySQL 在线评测系统 5 大血泪问题

5.1 现象:JSP 页面中文乱码,MySQL 存入????

原因:

  • JSP 文件本身编码不是 UTF-8(IDEA 默认 ANSI);
  • Tomcatserver.xml中Connector缺少URIEncoding="UTF-8";
  • MySQL 连接 URL 未指定characterEncoding=utf8mb4;
  • 表结构未设CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。

解决:

  1. IDEA 中右键 JSP 文件 →File Encoding→ 设为 UTF-8,勾选Convert encoding;
  2. 修改Tomcat/conf/server.xml,在<Connector port="8080"...>标签内添加URIEncoding="UTF-8";
  3. context.xml中 JDBC URL 改为:
    url="jdbc:mysql://localhost:3306/oj_db?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true&amp;characterEncoding=utf8mb4";
  4. 执行 SQL:ALTER DATABASE oj_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

5.2 现象:java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver

原因:

  • mysql-connector-java-8.0.33.jar未放入web/WEB-INF/lib/;
  • IDEA 中未执行Add as Library;
  • Tomcat 启动时lib目录未被扫描(检查catalina.properties中common.loader是否包含webapps/*/WEB-INF/lib/*.jar)。

解决:

  • 确认web/WEB-INF/lib/mysql-connector-java-8.0.33.jar存在且大小 > 2MB;
  • IDEA 中右键 JAR →Add as Library→ 选择Module Library;
  • 若仍报错,在Tomcat/bin/catalina.bat(Windows)或catalina.sh(Linux)首行添加:
    set JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8(Windows)或export JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"(Linux)。

5.3 现象:提交后result.jsp显示null,无法轮询状态

原因:

  • JudgeServlet中resp.sendRedirect(...)重定向后,request对象生命周期结束,无法传递属性;
  • result.jsp直接读request.getParameter("sid")为空;
  • 未在web.xml中配置JudgeServlet映射,导致 404。

解决:

  • JudgeServlet必须用sendRedirect(非forward),result.jsp通过request.getParameter("sid")获取 ID;
  • 检查web.xml是否有如下配置:
    <servlet> <servlet-name>JudgeServlet</servlet-name> <servlet-class>servlet.JudgeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>JudgeServlet</servlet-name> <url-pattern>/JudgeServlet</url-pattern> </servlet-mapping>
  • result.jsp中用<%= request.getParameter("sid") %>输出 ID,再用 AJAX 轮询/CheckStatusServlet?sid=xxx。

5.4 现象:判题时g++编译失败,报command not found

原因:

  • Tomcat 以服务方式运行(Windows)或 daemon 方式(Linux),其环境变量不包含PATH中的g++路径;
  • JavaRuntime.getRuntime().exec("g++")启动进程时未指定工作目录,找不到a.cpp。

解决:

  • Windows:将g++所在目录(如C:\MinGW\bin)加入系统环境变量PATH,重启 Tomcat 服务;
  • Linux:修改Tomcat/bin/setenv.sh(若无则新建),添加:
    export PATH="/usr/local/gcc/bin:$PATH";
  • 在判题代码中指定绝对路径与工作目录:
    ProcessBuilder pb = new ProcessBuilder("/usr/bin/g++", "-o", "/tmp/a.out", "/tmp/a.cpp"); pb.directory(new File("/tmp")); // 设置工作目录 Process p = pb.start();

5.5 现象:MySQL 报错Too many connections,提交卡死

原因:

  • DBUtil.getConnection()获取连接后未调用close()归还;
  • 连接池maxTotal设置过小(如 5),而并发提交数 > 5;
  • submission表未建索引,SELECT * FROM submission WHERE status='pending'全表扫描拖慢判题机。

解决:

  • 严格检查每处Connection、PreparedStatement是否在finally块中close();
  • 将context.xml中maxTotal="20"提升至"50"(根据服务器内存调整);
  • 为submission表添加复合索引:
    CREATE INDEX idx_status_pid ON submission(status, problem_id);

6. 进阶技巧:用定时任务驱动判题机 + 排名实时更新

6.1 判题机实现:独立线程轮询 pending 提交

真实 OJ 不靠用户请求触发判题,而是后台守护进程持续扫描submission表。在src/servlet/InitServlet.java中实现(配置为 Tomcat 启动时加载):

package servlet; import bean.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @WebServlet(urlPatterns = "/InitServlet", loadOnStartup = 1) public class InitServlet extends HttpServlet { private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @Override public void init() throws ServletException { // 每 2 秒扫描一次 pending 提交 scheduler.scheduleAtFixedRate(() -> { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "SELECT id, code, language, problem_id FROM submission WHERE status = 'pending' LIMIT 1"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); if (rs.next()) { long sid = rs.getLong("id"); String code = rs.getString("code"); String lang = rs.getString("language"); int pid = rs.getInt("problem_id"); // 调用判题逻辑(此处简化为模拟) simulateJudge(sid, code, lang, pid); } } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(rs, ps, conn); } }, 0, 2, TimeUnit.SECONDS); } private void simulateJudge(long sid, String code, String lang, int pid) { // 此处应调用编译、运行、比对逻辑 // 为演示,直接更新为 accepted try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement("UPDATE submission SET status='accepted', score=100, judge_time=NOW() WHERE id=?")) { ps.setLong(1, sid); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } } }

关键点:loadOnStartup = 1确保 Tomcat 启动时加载;scheduleAtFixedRate每 2 秒执行一次,避免高频轮询;LIMIT 1保证每次只处理一条,防止并发冲突;判题逻辑应抽离为独立 Service 类,此处为简化保留内联。

6.2 排名更新:用 MySQL 触发器 + 定时任务双保险

用户得分变化时,实时更新user.rank字段。纯 SQL 触发器易锁表,故采用“触发器标记 + 定时任务批量更新”策略:

  1. 创建触发器标记需更新排名的用户:

    DELIMITER $$ CREATE TRIGGER update_rank_flag AFTER UPDATE ON submission FOR EACH ROW BEGIN IF NEW.status = 'accepted' AND OLD.status != 'accepted' THEN INSERT INTO rank_update_queue (user_id, updated_at) VALUES (NEW.user_id, NOW()) ON DUPLICATE KEY UPDATE updated_at = NOW(); END IF; END$$ DELIMITER ; CREATE TABLE rank_update_queue ( user_id INT PRIMARY KEY, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_updated (updated_at) );
  2. 定时任务每分钟执行排名重算(在InitServlet.init()中追加):

    // 每分钟执行一次排名更新 scheduler.scheduleAtFixedRate(() -> { try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement( "UPDATE user u JOIN (SELECT user_id, @rank:=@rank+1 as rank FROM (SELECT user_id, SUM(score) as total_score FROM submission WHERE status='accepted' GROUP BY user_id ORDER BY total_score DESC) t, (SELECT @rank:=0) r) r ON u.id = r.user_id SET u.rank = r.rank")) { ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } }, 0, 1, TimeUnit.MINUTES);

设计优势:触发器只写轻量rank_update_queue表,不锁user表;定时任务用JOIN+ 变量排序,比ORDER BY ... LIMIT更稳定;@rank:=@rank+1是 MySQL 8 兼容的变量赋值法,避免窗口函数依赖。

6.3 最后一句经验:别在 JSP 里写 Java 逻辑,但别怕用 JSP

我带过 17 届毕设,见过太多学生把for循环、数据库查询全塞进<% %>标签,结果调试时NullPointerException报错堆栈指向第 83 行 JSP,根本没法断点。我的习惯是:JSP 只做三件事——显示request.getAttribute()的值、用<c:forEach>遍历 List、写 HTML/CSS/JS。所有业务逻辑、判题计算、状态转换,必须写在 Servlet 或 JavaBean 里。这样做的好处是:单元测试能覆盖 90% 代码,JSP 页面改版时后端零影响,答辩老师问“怎么保证判题准确率”,你能直接打开JudgeService.java指着compareOutput()方法说:“这里用String.equals()逐行比对,超时用Process.destroyForcibly()杀进程”。技术选型没有高低,只有“能不能让下一个接手的人,三分钟看懂你在干啥”。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:04:48

GWO-BP-AdaBoost组合模型在Matlab中的预测实现与参数调优指南

事情是这样的&#xff0c;最近在搞预测模型对比实验&#xff0c;绕来绕去绕到了组合模型上。水论文的都知道&#xff0c;单模型发不出好文章&#xff0c;纯机理模型又太难伺候&#xff0c;最后基本都得靠“优化算法神经网络”的组合拳。这一版用的就是GWO-BP-AdaBoost这套方案&…

作者头像 李华
网站建设 2026/10/1 4:04:37

Windows UAC弹窗原理与精准治理指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 4:03:45

hindsight实战:基于MCP与Docker构建LLM Agent记忆系统

1. 从"hindsight"这个词说起&#xff1a;为什么记忆是Agent最被低估的能力第一次看到"hindsight"作为项目名的时候&#xff0c;我脑子里蹦出来的不是技术架构&#xff0c;而是一个很具体的场景&#xff1a;你让一个Agent帮你处理一个跨天的任务&#xff0c…

作者头像 李华
网站建设 2026/10/1 4:03:14

小样本目标检测:VOC与YOLO标注格式转换实战

简介&#xff1a;本资源是一套面向计算机视觉初学者与目标检测实践者的企鹅图像数据集&#xff0c;适用于YOLO、Faster R-CNN等主流检测模型的训练与验证&#xff0c;特别适合课程设计、小规模实验及算法入门调试。数据集共364个文件&#xff0c;包含121张JPG格式企鹅实拍图&am…

作者头像 李华
网站建设 2026/10/1 4:03:10

无人机航拍图像拼接zip解析:从特征匹配到正射影像的完整实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 4:03:09

2026 ChatGPT Work:AI协作新范式实战指南

1. 这不是“ChatGPT Work”&#xff0c;而是2026年真实存在的协作新范式你搜到的“ChatGPT Work”这个词&#xff0c;大概率不是OpenAI官方推出的独立产品——截至目前&#xff08;2024年中&#xff09;&#xff0c;OpenAI从未发布过名为“ChatGPT Work”的正式服务或客户端。但…

作者头像 李华