ARTICLE DETAIL

资讯详情

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

JavaWeb在线报名系统课设源码解析:JSP+Servlet+JDBC三层架构实战

JavaWeb在线报名系统课设源码解析:JSP+Servlet+JDBC三层架构实战 简介这是一份基于Java Web的在线报名系统课程设计完整项目主要面向Java初学者及正在准备课程设计的高校学生。系统实现了考生注册登录、个人资料维护、按身份证号或准考证号查询成绩、在线问答等考生端功能也提供管理员对考生信息和成绩的查询、修改、删除、录入、报表生成以及网上缴费模拟等后台模块覆盖在线报名类系统常见业务流程有助于理解JSPServlet模式下的分层架构、会话管理与数据库操作。压缩包共130个文件约5.24MB主要包含31个JSP页面、26个Java源文件及对应class编译文件还附有Word版设计报告、数据库配置文件和界面图片素材结构清晰可导入IDE对照学习。已有617人学习下载。对于正在构思在线报名或考试管理系统的读者这套源码提供了完整的前后端交互示例、数据库表设计思路和报告撰写参考能够快速迁移到类似课程设计项目中。1. 为什么选这个在线报名系统三件套齐全的 JavaWeb 闭环项目每年课设季Java web在线报名系统这类题目总能排进题库热门前三。原因很直白它把考生注册、登录验证、信息修改、成绩录入、组合查询、在线问答、网上缴费串成了一条完整业务链一个系统同时覆盖 JSP、Servlet、JDBC、数据库设计、会话管理这些 JavaWeb 核心考点。这份资源是完整打包的课程设计源码、数据库脚本、课程设计报告三件套都在压缩包里适合两类人——准备交课设但不想从零搭建的学生以及想拿一套带报告的完整项目反推 JavaWeb 代码组织方式的初学者。要理解它的价值先得看清这套代码是怎么把业务流程落到表结构、Servlet 和 JSP 页面里的。2. 先拆结构再动手JSP Servlet JDBC 的经典三层架构怎么对上号拿到压缩包别急着导入 IDE 跑先花五分钟把包里有什么看清楚。这门课设的代码组织方式非常典型表现层用 JSP 收集和展示数据控制层用 Servlet 接收请求、调度业务数据层用 JDBC 直连 MySQL 完成增删改查。没有 Spring、没有 MyBatis就是最原始的 JavaWeb 三件套。这种结构放在今天看反而有它的好处——每一条请求从浏览器到数据库再返回页面中间发生了什么一目了然做课设答辩时老师问到底层原理你也能答得上来。2.1 从 .class 类名反推六块业务这个包里的类名就是功能地图压缩包内层能看到一串编译后的 .class 文件和 .bmp 截图类名本身已经把功能边界划清楚了。我把它们按模块归了一下类名对应模块对应摘要里的功能点EnterOnline.class考生注册与登录入口考生注册、登录接口凭数据库信息验证身份UserMessage.class个人信息管理考生添加、查看、修改详细个人信息Score.class考生成绩查询通过身份证号和准考证号查询成绩AdminEnterScore.class管理员成绩录入网上成绩录入修改成绩AdminCheckScore.class管理员成绩审核与查询按组合方式查询成绩产生成绩报表UserFeeOnline.class网上缴费模块考生通过网上银行缴费便于管理和查收Check.class登录状态校验未登录用户访问受保护页面时拦截并跳转登录页这种“类名即功能地图”的命名风格是这类课设源码包的通用规律。你拿到手先做一件事把类名和题目要求的功能点逐个连线连完线你就知道这套代码的完整性如何哪些功能是核心、哪些只是凑数的。我之前拿到过一个报名系统类名写得天花乱坠一打开里面全是空方法这种坑在下载源码时最容易踩。2.2 标准目录结构对照src、WebRoot、classes 与编译产物的正确关系这个项目是 Eclipse 老式工程结构和现在 Maven 项目的 src/main/java 布局不同。标准目录结构大致是OnlineSignup/ ├── src/ # Java 源码根目录 │ ├── com/xxx/... # 包结构Servlet、DAO、Bean 都在这里 │ └── ... # 各个 .java 源文件 ├── WebRoot/ # Web 根目录新版 IDE 里常叫 webapp │ ├── WEB-INF/ │ │ ├── web.xml # Web 部署描述符Servlet 映射、过滤器配置 │ │ └── lib/ # 依赖 jar 包比如 mysql-connector-java │ ├── login.jsp # 登录页 │ ├── register.jsp # 注册页 │ ├── ... # 其他 JSP 页面 │ └── images/ # 静态资源 └── ...数据库脚本和报告文档注意包里出现 .class 文件说明这是编译过之后才打包的。.class 是字节码文件不是源码真正能修改的是 src 目录下的 .java 文件。导入 IDE 后先检查两个地方第一JDK 版本和编译级别对不对第二Tomcat 运行环境有没有加到 Build Path。这两个配置错了项目在 IDE 里能打开但一运行就报 ClassNotFoundException 或 404。我一般拿到这类老工程第一步就是 Project → Clean 全量重新编译一次把旧的 classes 产物清掉避免 IDE 用缓存编译结果启动导致改代码不生效。2.3 为什么不建议随手改成 Spring Boot课设代码的学习边界有些读者一看到 JSP 和 JDBC 直连就觉得“技术陈旧”恨不得立刻改造成 Spring Boot MyBatis Plus。我建议先按住这个冲动。这门课设的评分标准通常明确要求体现 JSP、Servlet、JavaBean、JDBC 这些基础技术点你改成 Spring Boot 后反而没法展示“原始链路”的能力。更重要的是这份代码的价值正是它的“不抽象”——你可以在 EnterOnline 和 Score 里清楚看到一次表单提交如何经过 Servlet 的 doPost、调用 DAO、拼 SQL、执行查询、把结果 setAttribute 回 JSP 的完整过程。这个链路理解透了后面看 Spring MVC 的 DispatcherServlet、MyBatis 的 Mapper 代理本质上都是在这个链路上包了一层壳。当然它的边界也很明显没有 Maven 依赖管理jar 包靠 WEB-INF/lib 手动维护没有连接池每个 DAO 操作都现开连接SQL 有注入风险密码是明文存储。这些都是课设项目的正常水平也正好是你在报告“不足与改进”章节可以写的内容。先把这套代码跑通、读懂、能答辩再谈改造。3. 数据库设计与初始化五张表串起报名、缴费到成绩查询这套系统的数据库设计是整个项目的地基。摘要里的功能点看起来很多但落到表结构上其实只有五张核心表考生表、管理员表、成绩表、问答表、缴费表。理解这五张表的关系你就理解了整个报名系统的数据流转。需要说明的是原始压缩包里的数据库脚本我没有执行过修改下面这版表结构设计是基于摘要功能点反推的、在课设场景下最合理的方案。如果你拿到的 sql 文件字段名略有出入按“职责对齐”的思路核对即可不要死磕字段名。3.1 表结构拆解主键策略、冗余字段与各表职责先看整体职责划分。考生表负责存储注册信息和报名资料成绩表记录每个考生的各科分数问答表存放考生提问和管理员回复缴费表标记每位考生的缴费状态管理员表单独存放后台账号。要点在成绩表和缴费表如何与考生表关联以及为什么主键选择自增 id 而非身份证号。我习惯把这种关联关系用一个表格说明课设报告里也建议画同样的表表名核心字段作用关联方式t_studentid、username、password、id_card、exam_no、phone考生身份与报名信息主表t_adminid、admin_name、password管理员登录凭证独立t_scoreid、exam_no、subject、score、record_time考生各科成绩exam_no 关联 t_studentt_questionid、student_id、question、answer、ask_time、reply_time在线问答双方内容student_id 关联 t_studentt_paymentid、exam_no、amount、status、pay_time缴费状态记录exam_no 关联 t_student这里有两个设计决策值得在答辩时主动讲。第一个是成绩表和缴费表都用 exam_no 准考证号而不是 student_id 主键做关联原因是准考证号在业务上对考生和管理员都是可见的成绩查询接口只需要准考证号和身份证号两个条件就能完成不需要回查考生表主键少一次 join 查询。这是典型的“用空间换时间”的冗余设计。第二个是问答表和缴费表都带时间字段ask_time 和 pay_time 在生成报表时会成为排序依据这也是摘要里“产生成绩报表”“有助于管理与查收”这两个功能点的数据基础。3.2 建表脚本落地编码、默认值与查询索引一次配齐下面是这五张表的建表脚本。字符集统一用 utf8mb4因为 MySQL 的 utf8 实际是 utf8mb3存不了生僻字和 emoji。身份证号、准考证号、科目名这三个字段是高频查询条件建议建索引虽然课设数据量小感觉不到差别但答辩时老师问“查询慢怎么办”你能接上话。CREATE DATABASE IF NOT EXISTS online_signup DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE online_signup; -- 考生表注册、登录、个人信息维护 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键自增, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录用户名唯一, password VARCHAR(64) NOT NULL COMMENT 登录密码课设可用明文生产必须哈希, student_name VARCHAR(50) NOT NULL COMMENT 考生姓名, id_card CHAR(18) NOT NULL UNIQUE COMMENT 身份证号18位定长, exam_no VARCHAR(20) NOT NULL UNIQUE COMMENT 准考证号业务查询键, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话允许为空, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱非必填, address VARCHAR(200) DEFAULT NULL COMMENT 通讯地址, register_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间默认当前时间 ) ENGINEInnoDB COMMENT考生信息表; -- 管理员表后台账号独立存储 CREATE TABLE t_admin ( id INT PRIMARY KEY AUTO_INCREMENT, admin_name VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB COMMENT管理员表; -- 成绩表一个考生多科成绩按准考证号关联 CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, exam_no VARCHAR(20) NOT NULL COMMENT 准考证号关联 t_student, subject VARCHAR(50) NOT NULL COMMENT 考试科目, score DECIMAL(5,2) NOT NULL COMMENT 成绩保留两位小数, record_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 录入时间, UNIQUE KEY uk_exam_subject (exam_no, subject) COMMENT 同一考生同一科目只能有一条成绩, KEY idx_exam_no (exam_no) ) ENGINEInnoDB COMMENT考生成绩表; -- 问答表考生提问、管理员解答 CREATE TABLE t_question ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 提问考生主键, question VARCHAR(500) NOT NULL COMMENT 问题内容, answer VARCHAR(500) DEFAULT NULL COMMENT 管理员回答null表示未回复, ask_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 提问时间, reply_time DATETIME DEFAULT NULL COMMENT 回复时间 ) ENGINEInnoDB COMMENT在线问答表; -- 缴费表模拟网银缴费记录 CREATE TABLE t_payment ( id INT PRIMARY KEY AUTO_INCREMENT, exam_no VARCHAR(20) NOT NULL COMMENT 准考证号, amount DECIMAL(10,2) NOT NULL COMMENT 缴费金额, status TINYINT DEFAULT 0 COMMENT 0未缴费 1已缴费, pay_time DATETIME DEFAULT NULL COMMENT 实际缴费时间, KEY idx_exam_no (exam_no) ) ENGINEInnoDB COMMENT缴费记录表;脚本里有几个参数值得单独说明。id_card 用 CHAR(18) 而不是 VARCHAR(18)因为身份证号定长 18 位CHAR 按定长存储检索时不用计算长度性能略优。score 用 DECIMAL(5,2) 而不用 FLOAT因为浮点类型在比较相等时存在精度误差成绩这种需要精确判定的数据必须用定点数。成绩表加了一个联合唯一键 uk_exam_subject保证同一考生同一科目只有一条成绩这是让“修改成绩”功能逻辑成立的关键约束——如果允许重复修改时就不知道改的是哪一条了。3.3 JDBC 连接配置驱动类、URL 参数与数据库版本对应关系数据库建好后Java 代码要连上它。这个项目的连接配置通常写在一个 DBUtil 工具类里用静态代码块加载驱动用 DriverManager.getConnection 获取连接。这是 JDBC 直连的标准写法也是课设里最容易出问题的位置——问题不在代码而在你本机的 MySQL 版本。package com.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/online_signup ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, java.sql.Statement stmt, java.sql.ResultSet rs) { // 关闭顺序rs - stmt - conn反向关闭 if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码有两个版本差异大的参数项。DRIVER 这行如果你连的是 MySQL 5.x可以用 com.mysql.jdbc.Driver如果是 MySQL 8.x这个类已经被移除必须用 com.mysql.cj.jdbc.Driver否则会报 ClassNotFoundException。连接串里的 serverTimezoneAsia/Shanghai 是 MySQL 8.x 强制要求的时区参数不加会报 CST 时区识别不了的异常5.x 不强制但建议加上省得数据库和 JVM 默认时区不一致导致时间字段差 8 小时。useSSLfalse 是关掉 SSL 握手本地开发环境没必要开能省一次加密握手的时间。一个容易被忽略的细节PASSWORD 要和本地 MySQL 实际一致别直接抄 123456。我之前帮人排查过一次“明明盯着代码看不出错”的启动失败最后发现是数据库密码改了代码里还留着旧值。这个类所有模块复用改密码只改这一处改完务必重新编译整个项目别让 IDE 用旧 class 跑。4. 把三条核心链路跑通登录拦截、报名维护与成绩录入的代码走读数据库就位之后接下来是代码走读。这个项目最值得读的不是某个复杂算法而是三条业务链路的组织方式登录校验怎么拦住未授权访问、考生报名和成绩查询怎么操作数据库、成绩录入和组合查询怎么处理动态条件。读懂了这三条链路这个系统你就掌握了八成。4.1 登录校验怎么拦Filter 拦截器搭配 Session 的最小实现先看登录链路的拦截逻辑。这个系统的受保护资源包括考生个人信息页、成绩查询页、缴费页以及全部管理员页面。最小可行的做法是写一个 Filter拦截所有 .jsp 和 Servlet 路径检查 Session 里有没有登录标记没有就跳回 login.jsp。package com.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; // 登录页和注册页不需要校验直接放行 String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(register.jsp) || uri.endsWith(LoginServlet)) { chain.doFilter(req, resp); return; } // 检查 Session 中是否存在登录标记 HttpSession session req.getSession(false); Object loginUser (session ! null) ? session.getAttribute(loginUser) : null; if (loginUser null) { // 未登录重定向到登录页并携带来源路径便于登录后跳回 resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } Override public void init(FilterConfig filterConfig) throws ServletException { // 过滤器初始化时可读取 web.xml 中的 init-param } Override public void destroy() { // 释放资源 } }这段过滤器代码的逻辑是先放行登录页、注册页和登录 Servlet 本身然后从请求里拿 Session注意 getSession(false) 的参数——false 表示如果当前没有 Session 就返回 null 而不是新建一个。这一点是课设里经常写错的地方很多人用 getSession()导致未登录用户每次访问都会被创建一个空 Session白占内存不说还会干扰判断。最后如果 Session 里没有 loginUser 属性就重定向到登录页有就放行继续走后续链路。对应的 web.xml 里要注册这个过滤器映射范围写成 /*同时给登录 Servlet 设置 url-pattern。有个小细节重定向时要加 req.getContextPath() 前缀。ContextPath 是应用部署的根路径比如你在 Tomcat 里把项目部署成 /OnlineSignup那重定向地址就得是 /OnlineSignup/login.jsp。漏掉这个前缀页面会跳到 localhost:8080/login.jsp 而不是应用下的登录页直接 404。4.2 考生报名与信息修改后端校验兜底前端校验只做体验考生报名链路的核心是注册 Servlet 和考生信息 Servlet。注册页收集用户名、密码、身份证号、准考证号等字段提交给 ServletServlet 先查重再插入。下面这段是注册逻辑的关键片段。package com.servlet; import com.dao.StudentDao; import com.entity.Student; public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(utf-8); String username request.getParameter(username); String password request.getParameter(password); String idCard request.getParameter(idCard); String examNo request.getParameter(examNo); // 后端必须重新校验不信任前端传过来的任何数据 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty() || idCard null || idCard.length() ! 18 || examNo null || examNo.trim().isEmpty()) { request.setAttribute(msg, 必填项缺失或格式不正确); request.getRequestDispatcher(register.jsp).forward(request, response); return; } StudentDao dao new StudentDao(); // 查重用户名和身份证号都要求唯一 if (dao.isUsernameExists(username)) { request.setAttribute(msg, 用户名已被占用); request.getRequestDispatcher(register.jsp).forward(request, response); return; } Student stu new Student(); stu.setUsername(username); stu.setPassword(password); // 课设阶段明文入库生产环境必须 BCrypt 或 MD5盐 stu.setIdCard(idCard); stu.setExamNo(examNo); boolean ok dao.insertStudent(stu); if (ok) { response.sendRedirect(login.jsp); } else { request.setAttribute(msg, 注册失败请检查资料后重试); request.getRequestDispatcher(register.jsp).forward(request, response); } } }注意这段代码的校验顺序。先做空值和格式校验再做业务唯一性校验最后才写库。顺序很重要——如果把查重放在格式校验之前用户随便传一个空字符串SQL 查出来的结果没有意义还会多一次无谓的数据库交互。idCard.length() ! 18 这种硬校验可以挡掉绝大多数手误但注意身份证号最后一位可能是 X所以这个字段在数据库里设计成 CHAR(18) 而非 CHAR(17)校验位就是不为难使用者。还有一点页面上用 JavaScript 做的校验只是体验优化后端 Servlet 必须重新校验。原因很简单——前端校验可以被绕过任何人用 Postman 直接向 RegisterServlet 发请求就能跳过页面限制。课设答辩时老师十有八九会问“前端已经校验了为什么后端还要写一遍”这个答案要提前准备好。4.3 成绩录入与组合查询多条件动态 SQL 的预编译写法成绩模块是管理员端最重的功能包含录入、组合查询、修改、报表四个操作。组合查询是这里的技术难点——查询条件可能是准考证号、身份证号、姓名、科目中的任意几个SQL 必须根据前端传来的条件动态拼接。下面这段代码处理了动态拼接同时用预编译防止 SQL 注入。package com.dao; public class ScoreDao { /** * 组合条件查询成绩 * 条件全部可选只对非空条件追加 SQL */ public ListScore queryByCondition(String examNo, String idCard, String subject) { // 基础 SQL 先固定WHERE 子句动态追加 StringBuilder sql new StringBuilder( SELECT s.exam_no, s.subject, s.score, st.student_name, st.id_card FROM t_score s JOIN t_student st ON s.exam_no st.exam_no WHERE 11 ); ListObject params new ArrayList(); if (examNo ! null !examNo.trim().isEmpty()) { sql.append(AND s.exam_no ? ); params.add(examNo.trim()); } if (idCard ! null !idCard.trim().isEmpty()) { sql.append(AND st.id_card ? ); params.add(idCard.trim()); } if (subject ! null !subject.trim().isEmpty()) { sql.append(AND s.subject ? ); params.add(subject.trim()); } sql.append(ORDER BY s.exam_no, s.subject); // 执行时用 PreparedStatement? 占位符防止注入 Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString()); for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } ResultSet rs ps.executeQuery(); // 封装结果集的代码省略常规 while(rs.next()) 映射到对象 } }这里的 WHERE 11 在不在代码里老开发看法不一。它的实际作用不是判断条件而是让后续的 AND 拼接不需要判断“是不是第一个条件”——每个条件只管 append AND 字段 ?不用写额外的标记变量来区分首条件。代价是 WHERE 11 在 MySQL 里几乎无性能损耗但会在日志里显得业余。更优雅的做法是维护一个 List conditions 收集条件片段最后统一用 AND 拼接。课设里两种都可以关键是 params 列表和 SQL 里的 ? 必须一一对应——顺序错了成绩就查错人了。另一个血泪教训是千万别用字符串拼 SQL 的方式动态拼接比如AND s.exam_no examNo 。如果 examNo 被传成 OR 11整个查询条件被绕过返回全部考生的成绩。这就是教科书级别的 SQL 注入。用 PreparedStatement 占位符写注入这条路就堵死了。答辩时主动提一句“预编译能防注入”这分是稳拿的。4.4 网上缴费模块的本地模拟没有支付接口时怎么把流程闭环摘要里写的是“考生通过网上银行进行交费”但课程设计不可能真的对接网银接口。常见做法是把缴费流程模拟成三步考生在缴费页看到待缴订单点击确认后进入一个模拟支付确认页点击“确认支付”按钮后后端更新缴费状态。真正对接网银需要商户号、回调地址、签名验签这些在课设环境里都不具备报告里如实写“模拟缴费流程真实支付需对接第三方接口”就行。package com.servlet; public class PayServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); Student stu (Student) session.getAttribute(loginUser); String examNo stu.getExamNo(); // 实际项目从表单取金额这里按固定报名费模拟 double amount 120.00; // 更新缴费状态记录缴费时间 PaymentDao dao new PaymentDao(); boolean ok dao.updatePayStatus(examNo, amount); if (ok) { // 跳转到缴费成功页展示缴费凭证 request.setAttribute(payAmount, amount); request.setAttribute(payTime, new java.util.Date()); request.getRequestDispatcher(pay_success.jsp).forward(request, response); } else { request.setAttribute(msg, 缴费失败请稍后重试或联系管理员); request.getRequestDispatcher(pay.jsp).forward(request, response); } } }模拟缴费的关键在 PaymentDao.updatePayStatus 方法里的 SQL大概是UPDATE t_payment SET status1, pay_timeNOW() WHERE exam_no? AND status0。注意后面这个 AND status0 条件——它防止重复提交导致同一笔订单被支付两次这在并发场景下是个简单的乐观锁思路。本地课设几乎碰不到并发但写上去会显得你考虑过边界情况。缴费完成后考生的缴费状态变化会直接反映在管理端的 t_payment 表里管理员查收时按 status 字段筛一遍就能看到谁没交、谁交了、什么时候交的。5. 部署避坑指南从 Tomcat 404 到中文乱码五个高频故障一次排完这一章写的是我拆这类老课设项目时反复踩过的坑。每一类都按现象、原因、解决的顺序写你照着排查即可。这些坑里有一半是环境问题一半是代码本身的历史遗留问题但它们有一个共同点报错信息都不直接靠看日志猜很玄学。5.1 项目在 Tomcat 10 上启动失败javax.servlet 与 jakarta.servlet 的差别现象是 Tomcat 点启动后控制台不报错但浏览器访问项目路径直接 404或者在启动日志里看到大量java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet。原因很明确Apache 在 Tomcat 10.0 之后把 JavaEE 的 javax.servlet 命名空间迁移成了 Jakarta EE 的 jakarta.servlet。这份课设源码是基于旧规范的所有import javax.servlet.*的类在 Tomcat 10 里根本找不到。IDE 不报错的原因是编译时用的 Tomcat 9 运行时库启动时换成了 Tomcat 10编译和运行环境不一致。解决的方法是装一个 Tomcat 8.5 或 9.0推荐 9.0兼容性和稳定性都更好然后重新关联 IDE 的 Server Runtime Environment。再不行就全局替换源码里的import javax.servlet为import jakarta.servlet但那些 jar 包里的拦截器配置也会受影响工作量不小。课设场景换 Tomcat 9 是最省事的后悔药。5.2 数据库连接报错驱动类、时区、端口三连问现象是启动项目后点登录页面报Communications link failure或Unknown database online_signup有的还会出现The server time zone value CST is unrecognized。原因有三个高频来源。第一驱动类写错——MySQL 8 里 com.mysql.jdbc.Driver 已被移除要用 com.mysql.cj.jdbc.Driver。第二连接串缺 serverTimezone 参数。第三数据库端口不是默认的 3306或者 MySQL 服务压根没启动。解决时按顺序排查先services.msc确认 MySQL 服务在运行再用命令行mysql -uroot -p验证密码接着确认端口netstat -ano | findstr 3306最后才改 DBUtil 里的连接串。别一上来就改代码很多时候是服务没起浪费半小时盯着一行代码看都看不出毛病。5.3 中文乱码JSP、Servlet、数据库三处编码必须同频现象是注册时输入中文姓名存进数据库变成???或者页面上查询出来的中文全是乱码。原因是一个老生常谈的编码链路问题。JSP 页面本身的 pageEncoding、浏览器提交表单时用的字符集、Servlet 里读请求参数的编码、MySQL 连接串里的 characterEncoding、数据库表默认字符集这五处只要有一处不是 utf8汉字就可能出乱码。解决的措施是五处统一。第一JSP 页面第一行写% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%并且meta charsetUTF-8。第二Servlet 里读参数前先执行request.setCharacterEncoding(utf-8)这一步必须在第一次 getParameter 之前调用。第三连接串里加characterEncodingutf8。第四建库时用DEFAULT CHARACTER SET utf8mb4。第五如果你用的 MySQL 5.5 以下版本utf8mb4 可能不支持但那个时代的东西现在基本碰不到了统一 UTF-8 即可。5.4 导入项目后包结构错乱Build Path 与部署路径修正现象是源码里能看到 .java 文件但 IDE 里没有把它们识别成源码一堆类名旁边显示红叉或者运行提示找不到主类。原因是用 Eclipse 的 Open Projects from File System 导入老工程时.classpath 和 .project 文件可能没被正确加载导致源码目录、输出目录、依赖库的配置全部丢失。这个项目没有 Maven,所有依赖只能手动配。解决的步骤是先确认 src 目录被标记为源码根目录WebRoot 被标记为 Web 资源目录再把 WEB-INF/lib 下的 mysql-connector-java.jar 加进 Build Path最后把 JDK 和 Tomcat 运行时也加到 Build Path全量重新编译。IDEA 里对应操作是右键目录 → Mark Directory as Sources Root以及 Project Structure → Libraries 里添加 jar。5.5 端口被占用改 Tomcat 端口后别忘了同步项目访问路径现象是 Tomcat 启动时报Port 8080 required by Tomcat v9.0 Server at localhost is already in use或者另一个项目占着 8080你改了 Tomcat 的 HTTP 端口后页面却跳转到一个完全不相关的服务。解决时在 Tomcat 安装目录 conf/server.xml 里改三处端口8080 是 HTTP 主端口8009 是 AJP 端口8005 是关闭指令端口。这三个都要改成未被占用的值比如 8081、8010、8006。但注意改完端口后访问地址变成http://localhost:8081/项目名/凡是代码里硬编码了localhost:8080的跳转链接都要同步改否则会跳到别人的服务。我看过有人只改 server.xml 没改代码里的跳转路径结果缴费成功后跳到了一个完全不相干的本地服务折腾了一个晚上才定位到是端口写死了。6. 验收与加分改造从“能跑”到“耐看”的三个低成本动作系统能跑通、功能都正常这只是课设的及格线。拿高分还得看两个东西能不能按文档快速复现、有没有体现出超出课设平均水平的工程意识。这一章讲三个低成本的改进动作都不用大改代码但对报告和答辩加分的性价比很高。6.1 五分钟验收清单拿到最终部署好的系统建议按下面这个清单过一遍确认无遗漏再写报告。这张表同时可以直接作为课程设计报告里“系统测试”章节的素材。测试项操作步骤预期结果注册与查重用同一用户名注册两次第二次提示用户名已占用登录拦截未登录直接访问个人中心 URL跳转到登录页成绩查询输入正确的准考证号身份证号显示该考生全部科目成绩成绩录入与修改管理员录入一条成绩后再次修改同科目成绩被更新而非新增记录组合查询只输科目、只输姓名、两者都输返回结果集随条件变化缴费闭环考生完成模拟支付管理员查看缴费表状态由 0 变 1pay_time 有值问答回复考生提问管理员后台回复考生端能看到回答内容和时间6.2 连接池替换与密码哈希两个加分项加分项一是把 DBUtil 从 DriverManager 直连改成 Druid 连接池。改造量不大核心就三步把 druid jar 包放进 WEB-INF/lib新建 druid.properties 配置DBUtil 里的 getConnection 改为从池子里取。连接池的好处是反复创建连接的性能开销省掉了而且连接数可控——这个点几乎所有答辩老师都认。# druid.properties driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/online_signup?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20加分项二是密码哈希存储。现在的代码里密码是明文入库的报告里写清楚“后续改进方向是使用加盐哈希”。如果时间允许直接改成 MD5 加盐也就加一个工具类的事注册时存摘要、登录时比对摘要。别直接存明文密码是课设答辩时最容易暴露工程意识短板的地方。// MD5加盐示例盐值可以用用户名保证同密码不同用户摘要不同 public static String md5WithSalt(String password, String salt) { String toHash password { salt }; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(toHash.getBytes(utf-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(密码加密失败, e); } }从那以后我每次拿到这类课设源码包都强制自己先走一遍“建库 → 改连接串 → 指定 Tomcat 9 → 全量编译 → 跑验收清单”的流程五分钟内确认项目能不能跑省下后面几天排雷的时间。这套系统最值钱的地方不是代码本身而是让你能在一次完整的业务闭环里看清 JavaWeb 的底层运转方式看清楚之后再做任何框架都会顺手很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表