简介:面向JavaWeb课程设计和毕业设计的学生,这份实战项目提供了基于JSP+SQL的实验教学管理系统完整源码,包含前后端代码、论文、数据库脚本和说明文档。系统围绕实验教学管理核心业务展开,涵盖实验课程安排、学生选课信息、实验成绩录入与查询等典型模块,能够展示JSP页面、Servlet控制层与MySQL数据库之间的完整交互流程,有助于学习者快速掌握JavaWeb分层开发思路,并可直接作为课程设计或毕业设计的项目骨架。压缩包整体约3.66MB,主要文件类型包括JSP页面、Java后端源码、SQL数据库初始化脚本、论文正文及运行说明文档,目录结构按功能划分,便于逐部分对照学习。目前已有88人学习下载,适合具备一定Java基础、希望以完整项目为参照进行二次开发或答辩展示的读者,资源内附运行环境配置说明,便于快速部署运行。
1. 拿到 Java 实战项目包却跑不起来?JSP+sql 实验教学管理系统就得这么拆
当你的下载目录里躺着“Java实战项目——基于javaweb实现的JSP+sql实验教学管理系统(前后端源码+论文+数据库+说明文档).zip”这个压缩包时,你其实已经站在了 Java Web 课程设计最典型的入口前。系统解决的是高校实验课的管理问题:学生选实验、交报告,教师发布任务、打分,管理员维护课程数据。没有高并发也没有微服务,但它把 Java Web 面试最常问的登录、会话、分页、上传、权限拦截全串了起来。适合正在做课设,或者想找一个完整 javaweb 项目练手并写进简历的人。
这种项目包的麻烦点在于:它不像 Spring Boot 项目那样一键启动,目录结构老、依赖靠 lib 文件夹、Tomcat 配置稍不留神就 404。接下来的内容按我做这类项目的固定顺序展开:先拆业务和数据库,再配 IDEA 跑登录,接着做分页和报告上传,最后是避坑和交付前验证。你照着走一遍,能把黑匣子变成能讲清楚的数据流。
2. 先拆库表再碰代码:实验教学管理系统里的核心表结构和数据流
2.1 三种角色与一条主链路:从“建实验课”到“出成绩单”
实验教学管理系统的核心业务可以用三个问题讲完:管理员管什么、教师教什么、学生交什么。管理员维护课程和实验项目的基础属性,相当于数据源;教师把实验任务挂到课程下,设置开放时间和允许提交次数;学生看到实验后下载任务书,上传实验报告,等待教师评分。整个系统最终交付的是一张带分数的成绩报表,这张表是教务管理最刚性的诉求。
我在拿到项目包后不会先解压代码,而是先在纸上画一遍主链路:课程t_course关联实验t_experiment,实验关联提交t_submission,提交关联用户t_user。四张表按创建时间依次展开,恰好复现“管理员建课 -> 教师发实验 -> 学生交报告 -> 教师打分”的完整时序。任何一张表缺字段,比如实验表没有end_time,学生就能在截止日期后继续提交,这就是数据流断裂的典型表现。
这个理解直接决定后续写 SQL 和 JSP 的精力分配。主链路上的五张表是必做项,通知公告、班级分组、实验设备预约这类附表属于加分项。检查项目包里的数据库脚本时,我一般先确认主链路是否完整,再确认外键到底是真的物理外键还是只在查询里写的逻辑外键。很多课程设计为了省事不建物理外键,删除实验后残留一堆无人认领的提交记录,答辩时被追问一下数据一致性就露馅了。
2.2 核心表 DDL:用户、课程、实验和提交记录,字段类型与索引选择
打开数据库脚本,最核心的六张表一般是用户表、课程表、实验表、选课表、提交表和成绩视图。成绩视图可以不物理建表,用view聚合就行。用户表和提交表的 DDL 前面已经给出,这里补充课程表和选课表的设计,这两张表决定了“谁能看到哪个实验”:
CREATE TABLE t_course ( id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(50) NOT NULL, teacher_id INT NOT NULL, semester VARCHAR(20) NOT NULL COMMENT '如 2025-2026-1', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_teacher (teacher_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_elective ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;t_elective上的联合唯一索引uk_student_course保证一个学生只能选一门课一次,这是数据防重的第一道防线。很多旧脚本里压根没有这张选课表,学生选课记录被直接塞进t_submission,导致学生第一次交报告才算是选课成功,教师发布实验后无法提前统计参与人数。semester用 VARCHAR 而非 ENUM,因为学期数据要扩展,用 ENUM 之后加一条新值就得ALTER TABLE,维护成本高。
字段类型方面,凡是参与 WHERE 和 JOIN 的字段,比如student_id、course_id、experiment_id,都必须是整型且建索引。文本字段里exp_desc用 TEXT,不要用 VARCHAR(2000),否则 utf8mb4 字符集下的索引长度会直接超限,又绕进另一个坑。时间字段统一用 DATETIME,Java 侧用java.sql.Timestamp接收,比较时写exp_end_time > NOW(),不要在前端做字符串比较,服务器时区参数没配好时会平白差出 8 小时。
2.3 三角色的菜单权限:session 里放 role,Filter 里做两段式拦截
登录成功后的User对象放进 session,JSP 页面里按role值渲染不同菜单。我的习惯是把菜单写进独立的menu.jsp,通过<c:if>判断角色输出入口:role=0 显示班级管理、课程管理、教师管理;role=1 显示我的实验、学生提交、评分管理;role=2 显示可选实验、我的报告、我的成绩。这个文件被所有业务页面 include,改一处就全站生效。
权限判断的第二段要放在 Servlet 层。只隐藏菜单是不够的,用户可以直接在地址栏输入/admin/courseManage.jsp绕过入口。正确做法是在 Filter 里做 URL 前缀拦截:以/admin/开头的资源只允许 role=0,以/teacher/开头的只允许 role=1,其余业务页面要求 session 非空。遇到不满足的请求不返回 403,而是重定向到登录页,这种处理对用户更友好,也避免暴露后台地址。
还有一个容易忽略的 session 过期问题。很多项目包的web.xml里没有显式配置session-timeout,默认值 30 分钟。实验报告上传如果中途卡顿超过 30 分钟,提交时会跳回登录页,用户写了半天的内容全丢。我一般会在web.xml里写 60 分钟,并在上传页面加一句“长时间编辑请定期保存”的提示。这是很小的改进,但体验差别很明显。
3. 在 IDEA 里运行 javaweb 项目:JSP+Servlet 骨架搭建和登录模块参数
3.1 为什么这类项目不用 Spring Boot,而是坚持 JSP+Servlet+JDBC
先说结论:实验教学管理系统用 JSP+Servlet+JDBC,是因为它把每个环节都暴露在阳光下。JSP 页面里可以直接看到 request、session 和查询结果,适合演示“前端怎么把表单数据交给 Servlet,Servlet 怎么拼 SQL 查库”;Spring Boot 会把数据访问藏进 Mapper 层,对练手的人来说反而像个黑匣子。
JDBC 裸写虽然啰嗦,但它逼着你理解四件事:加载驱动、建立连接、预编译指令、处理结果集。换成 MyBatis 后这些细节几乎全部消失,而 Java Web 面试里问得极多的PreparedStatement防注入、连接超时关闭、事务提交时机,恰好都在这种 JSP+sql 项目里要自己写。JSP 的四大作用域 page、request、session、application 也是面试常客,亲手用过 session 判断登录状态,比背十遍概念都管用。
所以我的建议是:别嫌这类项目老。把它拆干净再合上,比追一个 Spring Cloud 脚手架有用得多。尤其当目标是短期搞定课程设计时,JSP+Servlet 的调试链路短,一个 Tomcat 就可以跑完,不牵扯 Maven 私服、Nacos 注册中心这些额外负担。
3.2 导入现有源码:IDEA 的 Artifact、Tomcat 和 Application Context 配置
用 IDEA 运行传统 javaweb 项目,最容易卡在 Artifacts。项目包解压后一般是src/放 Java 文件,web/放 JSP 和 WEB-INF,lib/放 mysql-connector-java.jar。导入步骤我按顺序说全:先在新窗口里用 File -> New -> Project from Existing Sources 选择解压目录,导入类型选 Eclipse 或 Web,看原项目用什么组织格式。接着在 File -> Project Structure 里点 Artifacts,加一个 Web Application: Exploded,把左侧的web目录映射进去。这一步漏掉,Tomcat 启动后必定 404。
然后配置 Run/Debug Configurations,添加 Local Tomcat Server。在 Deployment 标签页里加入刚才建好的 exploded artifact,Application context 填/expms。如果不填,IDEA 会自动用_war_exploded当根路径,浏览器访问/expms/login.jsp时就会打到一个不存在的目录上。
# 检查 Tomcat 端口占用(macOS / Linux) lsof -i:8080 # Windows 下用 netstat 检查端口 netstat -ano | findstr :8080这三条命令的作用是排查端口被占用。表现是 Tomcat“启动成功”但浏览器一直转圈或连接重置。找到占用进程后,直接把 Tomcat 端口改到 8081,再重新启动。另外确认JAVA_HOME指向 JDK 而不是 JRE,很多机器装了多个 JDK,IDEA 默认选错了运行时。这个环节能一次跑通,后面所有调试都有基础。
3.3 登录模块前后端代码:Servlet 接收表单,PreparedStatement 查用户表
现在动手写登录模块。先看后端LoginServlet,它接收login.jsp的表单,完成编码设置、JDBC 查询和 session 写入:
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement( "SELECT id, real_name, role FROM t_user WHERE username=? AND password=?")) { ps.setString(1, username); ps.setString(2, MD5Util.md5(password)); ResultSet rs = ps.executeQuery(); if (rs.next()) { User u = new User(rs.getInt("id"), rs.getString("real_name"), rs.getInt("role")); req.getSession().setAttribute("user", u); resp.sendRedirect("index.jsp"); } else { req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } catch (Exception e) { e.printStackTrace(); resp.sendError(500, "数据库异常"); } } }这段代码的顺序是:先设置请求编码,从表单取username和password,再用 try-with-resources 拿到连接和预编译语句。注意密码先用 MD5 转一遍再比较,这是课程设计项目包里的常态;生产环境应该换 BCrypt 加盐。参数说明:ps.setString(1, ...)的索引从 1 开始,和 SQL 中?出现的顺序一一对应。如果改回用Statement拼接字符串,遇到用户名里的单引号就直接崩,这也是 JDBC 裸写最常见的翻车点。
对应的login.jsp页面要注意表单路径不能写死:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <body> <form action="${pageContext.request.contextPath}/login" method="post"> <input type="text" name="username" placeholder="学号/工号" required> <input type="password" name="password" placeholder="密码" required> <button type="submit">登录</button> </form> <c:if test="${not empty error}"> <p style="color:red">${error}</p> </c:if> </body> </html>${pageContext.request.contextPath}会动态输出/expms,即使你后来改了 Tomcat 的 Application context,前端代码也不用动。<c:if>是 JSTL 的标签,老项目包里一般已经带jstl-1.2.jar;如果没有,把 error 用<%=request.getAttribute("error")%>也能显示。这里有个隐藏参数:Tomcat 8.5 之后连接器的maxPostSize默认只有 2MB,如果登录表单里夹带了太大的验证码图片,表单会被静默拒绝,请求看起来像是没有提交。我一般在server.xml里把它调到 10MB,或者干脆不在登录页放体积大的图片字段。
4. 跑通实验报告上传与分页列表:SQL 边界参数、@MultipartConfig 与 PreparedStatement
4.1 列表分页的 SQL 写法:LIMIT 与 OFFSET 的参数换算
实验教学管理系统里最容易被放大的功能是列表页。学生列表、实验列表、成绩列表都要分页。最常见的错误是先把全表查出来再在内存里做 subList,数据量过千浏览器就开始卡。正确做法是用 SQL 的 LIMIT 配合偏移量,让数据库只回传一页数据。下面这条 SQL 同时统计每个实验的提交人数,是教师端“我的实验”页面的标准写法:
SELECT e.id, e.exp_name, e.start_time, e.end_time, IFNULL(COUNT(s.id), 0) AS submit_count FROM t_experiment e LEFT JOIN t_submission s ON s.experiment_id = e.id GROUP BY e.id ORDER BY e.start_time DESC LIMIT 10 OFFSET 20;LIMIT 10 OFFSET 20表示跳过前 20 条再取 10 条,也就是第 3 页。前端页码从 1 开始,后端换算公式是offset = (currentPage - 1) * pageSize。这两个数字就是分页必调参数:pageSize 决定接口返回多少条,OFFSET 决定从哪条开始。这里也提醒一下,SQL Server 和 MySQL 方言不同:SQL Server 用OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY且必须配套ORDER BY。标题里的 JSP+sql 并不限定数据库,但面试时说清这种差异很加分。
对应的Page<T>工具类一般持有list、currentPage、pageSize、totalCount、totalPages五个字段。总页数计算公式是totalPages = (totalCount + pageSize - 1) / pageSize,这个向上取整的写法比Math.ceil更直观,也避免总页数为 0。JSP 渲染上一页下一页时,首尾页码要禁用,判断条件分别是currentPage <= 1和currentPage >= totalPages。
4.2 报告上传参数:savePath 目录、文件大小上限和版本号字段
报告上传是整个系统的功能核心,也是最容易埋雷的部分。老项目普遍用 commons-fileupload 依赖,需要commons-fileupload和commons-io两个 jar;如果项目包用的是 Servlet 3.0 的PartAPI,则零依赖,直接在 Servlet 上打@MultipartConfig注解就行。先把保存路径和文件名的处理讲清楚:
@MultipartConfig( maxFileSize = 1024 * 1024 * 50, // 单文件最大 50MB maxRequestSize = 1024 * 1024 * 200, // 单次请求总大小 200MB fileSizeThreshold = 1024 * 1024 * 1 // 超过 1MB 落盘临时文件 ) @WebServlet("/uploadReport") public class UploadReportServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); User user = (User) req.getSession().getAttribute("user"); int experimentId = Integer.parseInt(req.getParameter("expId")); Part filePart = req.getPart("reportFile"); String rawName = filePart.getSubmittedFileName(); String ext = rawName.substring(rawName.lastIndexOf(".")); String serverName = UUID.randomUUID().toString().replace("-", "") + ext; String basePath = getServletContext().getRealPath("/uploads/experiment"); File destDir = new File(basePath); if (!destDir.exists()) { destDir.mkdirs(); } String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date()); File destPath = new File(destDir, dateDir); if (!destPath.exists()) destPath.mkdirs(); filePart.write(destPath.getAbsolutePath() + File.separator + serverName); // 插入提交记录并自动累加 version,SQL 略 resp.sendRedirect("myReport.jsp"); } }这段代码里有三个参数值得调:maxFileSize决定单份报告能不能传上来,很多同学交几十 MB 的工程压缩包被静默拒绝却找不到原因;fileSizeThreshold低于实际文件大小时,文件先写临时文件再流转,避免 JVM 堆溢出。保存路径里再用日期分一层子目录,是批量报告下文件系统不卡顿的常规做法。文件名用 UUID 重命名而不是保留原名,是为了避免两个学生交同名的report.docx互相覆盖。这是血泪经验换来的:文件名一定不能信任浏览器传参。
版本号字段是另一个容易忽略的边界参数。允许一个学生多次提交时,t_submission表里依赖联合唯一索引(student_id, experiment_id, version),每次插入前先查出当前最大 version 再加一,而不是直接 UPDATE 旧记录。这样保留历史版本,教师和审计都能看到改动痕迹。如果项目包里没有这个设计,建议补上,因为“重复提交算哪一次成绩”是实验教学系统最容易被问到的业务问题。
4.3 SQL 注入防线:从万能密码绕过看 PreparedStatement 为什么是唯一正解
很多 JSP 项目包的登录代码为了图省事,会写字符串拼接 SQL,比如"SELECT * FROM t_user WHERE username='" + name + "' AND password='" + pwd + "'"。这时输入admin' or '1'='1就能把条件变成恒真,这就是安全测试里常说的“sql注入万能密码绕过”。实验教学管理系统的登录接口同样逃不掉,扫一遍就能让你的后台裸奔。
正确做法是前面登录代码里已经展示的PreparedStatement。它能防注入的原因在于:预编译语句在数据库端先固定了解析结构,?只代表字面值,不会被重新解释成 SQL 关键字。哪怕你传' or 1=1 --,它也被当作完整的字符串去做值比较,不产生逻辑绕过。除了注入防护,预编译还天然解决了单引号转义问题,这是裸写 SQL 时最烦人的细节。
long start = System.currentTimeMillis(); PreparedStatement ps = conn.prepareStatement( "SELECT id FROM t_user WHERE username=? AND password=?"); ps.setString(1, username); ps.setString(2, password); long cost = System.currentTimeMillis() - start; if (cost > 200) { System.err.println("slow sql [login]: " + cost + "ms"); }这段代码补充一个我习惯加在 DAO 里的细节:打印 SQL 执行耗时。实验教学系统的数据量通常不大,但如果某条查询超过 200 毫秒,优先检查t_submission表上的联合索引是否被LEFT JOIN破坏了。JDBC 连接串也要注意参数,jdbc:mysql://localhost:3306/expms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai少配一个characterEncoding,中文写入就可能直接乱码。这算是 javaweb 项目里最常见的“配置半对”状态。
5. javaweb 实验教学系统避坑记录:驱动、乱码、事务和 404 的五个现场
5.1 启动 Tomcat 报 ClassNotFoundException:mysql-connector 没进 WEB-INF/lib
现象:Tomcat 一启动,日志立刻出现java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,所有页面 500。你不知道该去改哪里,因为这个传统项目没有 pom.xml,所有 jar 都躺在普通lib文件夹里。
原因:Tomcat 只加载WEB-INF/classes与WEB-INF/lib下的类。放在项目根目录lib下的 jar 虽然能被 IDE 在编译期找到,但运行期根本不会进入 Tomcat 的类路径,于是驱动类找不到,Class.forName直接抛异常。
解决:把mysql-connector-java.jar复制到web/WEB-INF/lib后右键 Add as Library,然后再去 Project Structure 的 Artifacts 里查看输出项,确认 jar 出现在部署包中。如果还不行,删掉本地的.idea目录重新导入。这个坑在“idea运行javaweb项目配置”的搜索记录里常年居前,核心就是类加载目录对不对。
5.2 页面和数据库里的中文全变问号:三层编码没对齐
现象:登录后 JSP 显示学生姓名是???,写入t_experiment的实验名称也是???。用 Navicat 查看表数据,中文已经变成问号,但 JSP 文件确实保存成了 UTF-8。
原因:乱码链路断了三个点。第一,JSP 顶部只写了pageEncoding="UTF-8",这只解决文件本身读取,HTTP 响应头里的 ContentType 没设置,浏览器按默认编码解析;第二,请求侧没有request.setCharacterEncoding("UTF-8"),POST 表单中文进入内存就是错的;第三,JDBC URL 没带characterEncoding=UTF-8,MySQL 按数据库默认字符集接收字节,存进去自然乱码。
解决:三层全部补齐。请求侧加 Filter 统一setCharacterEncoding("UTF-8"),响应侧每个 JSP 写<%@ page contentType="text/html; charset=UTF-8" %>,JDBC URL 追加useUnicode=true&characterEncoding=UTF-8。另外数据库连接和表统一用utf8mb4而不是utf8,因为utf8mb4覆盖生僻字和 Emoji。这里有个快速判断标准:如果你导入 SQL 文件时中文正常,但项目运行时乱码,问题几乎都在 JDBC URL。
5.3 数据插进去了却在列表里消失:事务没提交被隐式回滚
现象:上传实验报告成功后,跳转到列表 JSP 却看不到新记录;去数据库查,记录也不存在。多次重新上传后,数据库里只有零星几条,像是随机丢数据。
原因:某些项目包里的DBUtil.getConnection()会执行conn.setAutoCommit(false),但业务代码里只调用了executeUpdate(),没有在 finally 中commit()。连接在关闭时,JDBC 默认回滚未提交事务,于是一条看似成功的 INSERT 实际上被悄悄撤销。
解决:写操作必须显式提交。最小修法是执行完立即conn.commit(),但更稳的是把所有写操作放进同一个事务方法里:
try (Connection conn = DBUtil.getConnection()) { conn.setAutoCommit(false); PreparedStatement ps1 = conn.prepareStatement( "INSERT INTO t_submission(student_id, experiment_id, version) VALUES (?,?,?)"); ps1.executeUpdate(); PreparedStatement ps2 = conn.prepareStatement( "UPDATE t_experiment SET submit_count = submit_count + 1 WHERE id = ?"); ps2.executeUpdate(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; }参数说明:多条写操作必须包在同一个事务里,任何一条失败都要整体回滚,否则会出现“提交记录存在但实验统计数没变”的脏数据。类似的坑还有连接池maxActive设成 5,学生并发交报告时全部卡在等连接,日志里全是Connection is not available。实验教学系统的并发不高,连接池参数按 10 到 20 配就够。
5.4 IDEA 部署后永远 404:Application context 与 artifact 名字不一致
现象:Tomcat 显示startup in xxx ms,浏览器访问/login.jsp却 404,直接访问静态资源也一样。控制台没有任何异常堆栈。
原因:IDEA 里 Local Tomcat 部署的 artifact 名和 Application context 不一致。例如 artifact 是expms_war_exploded,IDEA 自动生成的根路径就成了/expms_war_exploded/,你访问/expms/login.jsp自然找不到资源。
解决:在 Run/Debug Configurations 的 Deployment 标签页里,把 Application context 手动填成/expms,然后重新启动。如果还是 404,翻 Tomcat 日志找The absolute uri: http://java.sun.com/jsp/jstl/core这一行,这表示缺少 JSTL jar,把jstl-1.2.jar放进WEB-INF/lib再重启。另外改完代码不要反复重启 Tomcat,用 IDEA 的 Update resources 热部署效率高得多。
5.5 下载报告时文件名乱码或变成 UUID:HTTP 响应头没做转码
现象:教师点击下载学生报告,浏览器保存的文件名变成一长串 UUID,或中文部分全部变成%E6%9D%8E这类编码串。文件内容本身完好,但文件名对不上学生信息。
原因:文件存到服务器时已经用 UUID 重命名,数据库中只保留了原始文件名。下载响应通过resp.setHeader("Content-Disposition", "attachment;filename=" + rawName)下发时,编码不匹配,中文文件名在跨浏览器场景下就乱码。
解决:下载时把文件名重新编码为UTF-8的 URL 编码,或者直接转成ISO-8859-1返回给浏览器。我一般用第一套方案中的URLEncoder.encode(rawName, "UTF-8"),这样 Chrome 和 Edge 都能正确还原。与此同时,上传时在t_submission里完整保留file_name字段,不要在save_path里去解析文件名,这两列分工要清楚。
6. 交付前用一条 SQL 验证数据闭环,再把乱码 Filter 收口成项目习惯
6.1 验证系统数据闭环:一条多表 JOIN 检查所有链路
项目跑通不等于数据对。我接手这类项目后,交付前一定会执行这条 SQL,它把用户、实验、提交记录三张表串起来,看能否还原学生视角的完整视图:
SELECT u.real_name, e.exp_name, s.version, s.score FROM t_user u JOIN t_submission s ON s.student_id = u.id JOIN t_experiment e ON s.experiment_id = e.id ORDER BY e.exp_name, u.real_name;这条 SQL 如果按预期返回每个人的提交版本和分数,说明从登录到成绩查询的链路没有断裂。如果只返回部分数据,去查t_submission是否有脏数据,或者关联条件漏了版本过滤。再追加一条聚合 SQL:SELECT experiment_id, COUNT(*), AVG(score) FROM t_submission WHERE score IS NOT NULL GROUP BY experiment_id,就能给论文的测试部分提供真实数字,而不是拿网上的假截图。
6.2 用 Filter 统一收口编码和登录态,再考虑迁移 Maven
我每次拿到 JSP 老项目,第一件事不是加功能,而是先写一个EncodingAndAuthFilter,把散落在各处页面的编码设置和权限校验集中起来:
@WebFilter("/*") public class EncodingAndAuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); HttpServletRequest request = (HttpServletRequest) req; String uri = request.getRequestURI(); if (uri.endsWith("login.jsp") || uri.endsWith("/login") || uri.contains("/static/")) { chain.doFilter(req, resp); return; } HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { ((HttpServletResponse) resp).sendRedirect("login.jsp"); } else { chain.doFilter(req, resp); } } }这段代码的意义在于:所有页面不再重复处理中文编码和登录跳转,Filter 统一挡在前面。放行规则把登录入口、登录 Servlet 和静态资源排除,其余请求一律要 session。改完之后用浏览器无痕窗口直接访问/admin/courseManage.jsp,你会发现已经进不去了,这才是完整的权限闭环。再进一步,可以把项目从传统 lib 结构迁移到 Maven,在pom.xml里声明javax.servlet-api和mysql-connector-java,用maven-war-plugin接管打包。这种“javaweb项目完整案例mysql”的升级路线,既保留原项目业务代码,又把依赖管理纳入了现代工程体系,值得在论文最后一章提一笔。我现在接手任何 JSP 项目,都会先跑通登录、再查乱码、再看事务,这三个习惯能解决九成问题。希望帮到你。
本文还有配套的精品资源,点击获取