简介基于 JSP Servlet MySQL 的教务管理系统毕业设计项目面向计算机相关专业学生用于毕业设计、课程设计或 Java Web 综合实训。项目实现了学生信息管理、教师资料维护、课程设置、成绩记录等核心教务功能前端通过 JSP 动态展示页面后台由 Servlet 处理请求与业务逻辑数据库采用 MySQL 存储数据层次清晰。资源共包含 535 个文件压缩包大小约 9.87MB其中源码部分包括 24 个 Java 文件和 10 个 JSP 页面另有编译后的 class 文件、依赖 jar 包、数据库初始化 SQL 脚本、XML/properties 配置文件以及用于界面展示的 GIF/JPG 图片和 CSS/JS 素材项目结构完整导入开发工具后可直接运行。通过源码可清晰理解前端展示、后端控制与数据库交互的完整链路也能参考其中的数据库表设计和分层写法。目前已有 1130 人下载学习对于需要快速起步的毕业生或基础开发人员是一份可直接上手改造成完整系统的参考资料。1. 教务管理系统jspservletmysql从压缩包反推出这套老架构的真实面貌拿到这个 105 号毕业设计压缩包时我第一个动作不是解压而是先看了一眼文件名里的技术组合jspservletmysql。这三个词放在一起基本就能断定这是一套标准的 Java Web 老三层架构而且是很多高校 Java Web 课程和毕业设计还在沿用的主流模板。解压之后里面的 .class 文件名进一步印证了判断——UserDao4MySqlImpl、LoginAction、UserActionForm、FileUploadAction这些命名方式是典型的 Struts 1.x 风格也就是说这个项目的控制层不是裸 Servlet而是套了一层 Action 壳。这套系统解决的是教务场景里最核心的几件事登录认证、用户管理、文件上传下载数据落在 MySQL 里适合正在做类似选题的毕业生、想快速搭一个 Java Web 完整案例的初学者以及需要用现成项目改造成自己毕设的在校生。本文会按「架构原理 → 环境搭建 → 模块拆解 → 踩坑排查 → 答辩改造」的顺序把这个项目从头到尾拆干净。2. 三层协作JSP 展示、Servlet 控制、MySQL 存储的完整请求链路2.1 从 .class 文件名反推系统架构Struts 1.x 风格怎么识别拆开压缩包后我注意到一个有意思的细节源码目录里躺着一批 .class 文件而不是清一色的 .java。这不是项目没给源码而是打包时把编译产物一起打进去了。对于二次开发的人来说这些 class 文件反而是理解架构的捷径——因为 Java 的类名命名习惯会直接把分层逻辑暴露出来。我整理了一下这批 class 文件大致能分成四类类名模式代表类职责推断XxxActionLoginAction、ModifyUserAction、AddUserAction、FindUserAction控制层接收请求、调业务、定跳转XxxActionFormUserActionForm表单数据封装对应 Struts 1.x 的 ActionForm 机制XxxManagerUserManager、FileManager业务逻辑层处理具体业务规则XxxDao4XxxImplUserDao4MySqlImpl、FileDao3MySqlImpl数据访问层封装对 MySQL 的增删改查看到 UserDao4MySqlImpl 这种命名我基本可以确定项目的持久层没有用 MyBatis 或 Hibernate而是原生 JDBC 封装 DAO。「4MySql」这个后缀说明作者当时可能还考虑过其他数据库只是最后锁定了 MySQL。再看 UserActionForm这是 Struts 1.x 的标志性组件——在 Struts 2 里这类类通常叫 UserAction 或直接用模型驱动不会单独拆一个 Form 出来。所以这套系统的控制层基底是 Struts 1.x而 Struts 1.x 本质上就是 Servlet 的封装请求还是要经过 web.xml 里的 ActionServlet 来分发。提示如果你的压缩包里也全是 .class 而没有 .java 源文件别慌。用 JD-GUI 或者 IDEA 自带的 FernFlower 反编译器把 class 拖进去就能还原出可读的 Java 源码。毕业设计场景下反编译出来的代码足够用来理解和改造了。2.2 一个请求从浏览器到数据库再返回数据流转的完整链路理解了类结构之后我建议你亲手走一遍请求链路这对答辩解说是最有用的。以「管理员登录」为例整个流程是这样跑的浏览器提交表单请求打到login.jsp表单的 action 指向/login.doStruts 1.x 的请求后缀一般是 .do。web.xml 里配置的 ActionServlet 拦截到/login.do根据 struts-config.xml 里的映射找到 LoginAction。Struts 框架自动把表单字段填充进 UserActionForm并调用validate()做基础校验。LoginAction 拿到 UserActionForm调用 UserManager 的登录方法UserManager 再调用 UserDao4MySqlImpl 执行 SQL。DAO 通过 JDBC 查询 MySQL把结果一层层返回。LoginAction 根据结果选择 forward 到success.jsp还是error.jsp。这里的核心设计是分层解耦。JSP 只负责展示和收集参数不写业务代码Servlet也就是 Action只做流程控制和页面跳转真正的 SQL 操作全在 DAO 层。这样做的好处是改数据库从 MySQL 换成 Oracle 时只需要新写一个 Dao4OracleImpl业务层不用动。我在实际项目中见过不少把 SQL 直接写在 JSP 里的反面案例维护起来相当痛苦这个项目至少没犯这个毛病。2.3 web.xml 与 Struts 配置环境搭建前必须看懂的两个文件跑通这个项目前有两个配置文件必须看明白。第一个是 web.xml它定义了整个 Web 应用的入口web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee version3.1 display-name教务管理系统/display-name !-- Struts ActionServlet所有 .do 请求的入口 -- servlet servlet-nameaction/servlet-name servlet-classorg.apache.struts.action.ActionServlet/servlet-class init-param param-nameconfig/param-name param-value/WEB-INF/struts-config.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameaction/servlet-name url-pattern*.do/url-pattern /servlet-mapping welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list /web-app逻辑说明url-pattern配置成*.do意味着所有以 .do 结尾的请求都会交给 ActionServlet 处理。load-on-startup设为 1表示 Tomcat 启动时就初始化这个 Servlet而不是等第一个请求来了才加载。welcome-file指定了访问根路径时默认打开 login.jsp。第二个关键文件是 struts-config.xml它建立了「请求路径 → Action → ActionForm → 跳转页面」的映射关系struts-config form-beans form-bean nameuserActionForm typecom.edu.forms.UserActionForm/ /form-beans action-mappings action path/login typecom.edu.actions.LoginAction nameuserActionForm scoperequest forward namesuccess path/main.jsp/ forward nameerror path/login.jsp/ /action /action-mappings /struts-config参数说明nameuserActionForm把这个 ActionForm 绑定到 /login 请求上框架会自动把请求参数填进去scoperequest表示 ActionForm 只在一次请求内有效不会在 session 里留存数据——这是 Struts 1.x 里值得注意的点如果把 scope 改成 session表单数据会跨请求残留多用户并发时容易串数据。我建议你改项目时保持 request 作用域除非有明确的多步向导需求。注意MySQL 8 默认的认证插件是 caching_sha2_password而很多早年的 JDBC 驱动只支持 mysql_native_password。如果启动后报「Unable to load authentication plugin」要么换 8.0.x 的驱动包要么在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;二选一就能解决。3. 环境搭建与部署JDK、Tomcat、MySQL 选型对照和跑通全流程3.1 版本选型这套老代码吃什么版本拿到这种有一定年份的 Java Web 项目第一步不是急着导入 IDEA而是先把运行环境定下来。版本选错了后续全是坑。以我对这套 Struts 1.x JDBC 项目的经验推荐用下面这套组合组件推荐版本选型理由JDK1.8Java 8Struts 1.x 在 JDK 9 上会有模块化访问报错1.8 最稳Tomcat8.5.x支持 Servlet 3.1兼容老项目不像 Tomcat 10 那样把 javax 换成 jakartaMySQL5.7 或 8.05.7 兼容性最好8.0 需要处理认证插件问题IDEIntelliJ IDEA 2021~2023内置 Tomcat 集成反编译能力也强JDBC 驱动mysql-connector-java 5.1.495.7/ 8.0.338.0驱动和数据库版本对应别混用这里有一个容易翻车的点Tomcat 10 把 javax.servlet 包名换成了 jakarta.servletStruts 1.x 这种老框架直接报 ClassNotFoundException。所以千万别图新用 Tomcat 10老老实实装 8.5 系列。JDK 同理用 8 就好不要用 11 或 17——不是跑不了而是 Struts 1.x 这种 2006 年前后流行起来的框架在模块化 JDK 上的反射访问经常被拦排查起来很费时间。3.2 导入 IDEA 并初始化数据库照着做就能跑我一般习惯按下面这几步走每一步都验证过基本不会出幺蛾子第一步导入项目。打开 IDEA选择File → New → Project from Existing Sources选中解压后的项目根目录。如果项目里有.idea文件夹就直接用没有的话选择 Eclipse 导入方式也行。重点是确认项目结构里有 src源码目录、web 或 WebRootJSP 和 WEB-INF 所在目录。第二步检查项目结构。正常的 Java Web 项目长这样guanli/ ├── src/ # Java 源码 │ ├── com/edu/actions/ # Action 控制层 │ ├── com/edu/forms/ # ActionForm │ ├── com/edu/dao/ # DAO 数据访问层 │ ├── com/edu/manager/ # 业务逻辑层 │ └── com/edu/util/ # 工具类数据库连接等 ├── web/ │ ├── login.jsp # 登录页 │ ├── main.jsp # 主框架页 │ ├── WEB-INF/ │ │ ├── web.xml │ │ ├── struts-config.xml │ │ └── lib/ # 依赖 jar 包 │ └── css/ js/ # 静态资源 └── sql/ └── edu_system.sql # 数据库初始化脚本如果你的 src 目录只看到 .class 没看到 .java用 IDEA 选中 class 文件右键Decompile就能还原源码还原后放在对应包路径下。这个操作我每拆一个老项目都要做一遍已经成了肌肉记忆。第三步创建数据库并导入脚本。打开 MySQL 命令行或 Navicat执行-- 创建数据库要注意字符集 CREATE DATABASE IF NOT EXISTS edu_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE edu_system; -- 导入项目自带的 SQL 脚本 SOURCE /你的路径/sql/edu_system.sql; -- 验证表是否创建成功 SHOW TABLES;逻辑说明utf8mb4比utf8多支持了 Emoji 和部分生僻字关键是排序规则utf8mb4_general_ci对中文字符的匹配比较友好。SOURCE命令是 MySQL 命令行特有的导入方式比在 Navicat 里手动执行整个脚本更可靠。执行完SHOW TABLES如果能看到 user、course、score 之类的表数据库这步就过了。第四步修改数据库连接配置。在项目的 com.edu.util 包里找一个名字类似 DBUtil 或 JdbcUtil 的类把连接信息改成你自己的public class DBUtil { // 这三个参数必须和你本地环境一致 private static final String URL jdbc:mysql://localhost:3306/edu_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明characterEncodingutf8保证了中文字符在 JDBC 传输层不乱码serverTimezoneAsia/Shanghai是 MySQL 8 必须加的不加会报 CST 时区错误useSSLfalse是在本地开发时省去 SSL 握手开销。Class.forName在 JDBC 4.0 之后其实可以省略但老项目里保留着也没坏处不用特意删。注意驱动类名 5.x 版本是com.mysql.jdbc.Driver8.x 版本改成了com.mysql.cj.jdbc.Driver。如果导入的是 8.0 驱动但代码里还写着老类名会直接报 ClassNotFoundException。改的时候两处都要看DBUtil 里的 forName 字符串和 WEB-INF/lib 下的 jar 包版本。3.3 配置 Tomcat 并启动从部署到看到登录页数据库准备好之后配置 Tomcat 这一步在 IDEA 里是这样操作的Run → Edit Configurations点左上角加号选 Tomcat Server → Local。Application server 选你本地装好的 Tomcat 8.5 路径。Deployment选项卡里点加号选 Artifact如果是灰色不可选先执行File → Project Structure → Artifacts把项目打成 war exploded 格式。Application context 填/guanli这样访问路径就是http://localhost:8080/guanli。点启动Tomcat 窗口里看到Server startup in [xxx] milliseconds就说明部署成功。启动后浏览器打开http://localhost:8080/guanli正常情况下会跳到 login.jsp 登录页。如果看到 404先去 Tomcat 的webapps/guanli/WEB-INF下确认 classes 目录里有编译好的 .class 文件没有的话说明 IDEA 的构建没把源码编译进去。这一步我踩过好几次基本都是 Artifact 配置漏了「将构建输出加入到 WEB-INF/classes」这个选项。部署成功只是第一步真正的业务能不能跑通要看下一章的核心模块拆解。4. 核心模块拆解登录、用户管理、文件上传的实现与参数细节4.1 LoginAction 与登录认证从表单校验到 session 回写登录是这个系统里最关键的模块也是一般用户访问系统的第一道门。从 class 文件能看到 LoginAction 和 UserActionForm 是分开的说明作者严格遵循了 Struts 1.x 的「Form 收集数据、Action 处理逻辑」模式。反编译还原后的 LoginAction 核心逻辑大致是这样的public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { // 1. 从 ActionForm 里取出表单提交的数据 UserActionForm userForm (UserActionForm) form; String username userForm.getUsername(); String password userForm.getPassword(); // 2. 调用业务层验证用户 UserManager manager new UserManager(); User user manager.login(username, password); // 3. 登录成功则写入 session跳转主页面 if (user ! null) { HttpSession session request.getSession(); session.setAttribute(currentUser, user); return mapping.findForward(success); } else { // 4. 登录失败把错误信息放回 request跳回登录页 request.setAttribute(errorMsg, 用户名或密码错误); return mapping.findForward(error); } } }逻辑说明这段代码的流程非常典型——取数据、调业务、判结果、定跳转。manager.login()内部会调用 UserDao4MySqlImpl 去查询SELECT * FROM user WHERE username? AND password?查到就返回 User 对象查不到返回 null。判断用的是user ! null而不是异常机制这是老项目常见的做法简单但有效。这里有一个容易被忽视的细节验证逻辑用的是明文密码比对。实际生产环境绝不可能这样干密码至少要 MD5 加盐再存。如果你打算拿这个项目去答辩建议在 UserManager 里加一个MD5Util.md5(password)的步骤把密码加密后再传给 DAO这种改动工作量不大但答辩时能撑起一个不小的亮点。4.2 UserManager 与用户管理增删改查背后的 JDBC 细节用户管理模块是系统里 CRUD 最全的部分对应的类文件有 AddUserAction、ModifyUserAction、FindUserAction 和 UserManager。这些 Action 控制转发真正干活的是 UserManager 调 UserDao 执行 SQL。反编译后 UserDao4MySqlImpl 的增删改查方法写法上比较有年代感public class UserDao4MySqlImpl implements UserDao { // 新增用户 public int addUser(User user) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); String sql INSERT INTO user (username, password, realname, role) VALUES (?, ?, ?, ?); ps conn.prepareStatement(sql); ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); ps.setString(3, user.getRealname()); ps.setString(4, user.getRole()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } finally { // 关闭资源先关 ps 再关 conn try { if (ps ! null) ps.close(); } catch (SQLException e) { } try { if (conn ! null) conn.close(); } catch (SQLException e) { } } } }逻辑说明这个写法是原生 JDBC 的标准模板用PreparedStatement而不是Statement拼 SQL能有效避免 SQL 注入——这是当年很多人容易忽略、但这个项目做得对的地方。executeUpdate()返回的是受影响行数大于 0 说明插入成功。finally 块里的资源关闭顺序有讲究先关 PreparedStatement 再关 Connection反了会有连接未释放的风险。参数说明ps.setString(4, user.getRole())里的 role 字段控制权限一般教务系统至少分 admin管理员和 teacher教师两种角色。如果你改造这个项目建议给 DAO 层加一个findUserByPage(int pageNum, int pageSize)分页方法SQL 用LIMIT ?, ?这样用户列表页就不会一次加载几千条数据把页面拖垮。4.3 FileUploadAction 与文件上传老项目里最容易被问倒的模块FileUploadAction 是这批 class 文件里比较特别的一个类。教务系统里通常涉及学生材料、课程资料的提交所以文件上传功能几乎是标配。Struts 1.x 里处理文件上传需要在 ActionForm 里声明一个FormFile类型的属性// 在 UserActionForm 中添加文件字段 private FormFile file; public FormFile getFile() { return file; } public void setFile(FormFile file) { this.file file; }Action 里接收上传文件的逻辑public class FileUploadAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserActionForm userForm (UserActionForm) form; FormFile file userForm.getFile(); // 1. 从 FormFile 里读出文件名和内容 String fileName file.getFileName(); String savePath request.getServletContext().getRealPath(/upload); // 2. 目标目录不存在则创建 File dir new File(savePath); if (!dir.exists()) dir.mkdirs(); // 3. 用文件流写入磁盘 FileOutputStream fos new FileOutputStream(savePath File.separator fileName); fos.write(file.getFileData()); fos.flush(); fos.close(); request.setAttribute(uploadMsg, 上传成功 fileName); return mapping.findForward(success); } }逻辑说明getServletContext().getRealPath(/upload)拿到的是 Web 应用部署目录下的物理路径文件最终会写到 Tomcat 的 webapps/guanli/upload 文件夹里。file.getFileData()一次性把整个文件读进内存小文件没问题大文件会撑爆内存——正常做法应该是用InputStream流式读取再写入。注意FormFile 的getFileName()拿到的只是文件名部分不包含路径。但如果文件名里带着用户的本地路径字符比如 Windows 的C:\Users\...一定要做路径处理否则可能有路径穿越风险。一个简单的处理是file.getFileName().substring(file.getFileName().lastIndexOf(\\) 1)。关于权限控制这个项目的主要模块基本都围绕管理员操作展开。我建议你在改项目的时候花点时间把「登录后才能访问」这个逻辑统一处理——最省事的方案是在 web.xml 里配一个 Filter拦截所有 .jsp 和 .do 请求检查 session 里有没有 currentUser没有就重定向到 login.jsp。这比你每个 JSP 页面里都写一遍 session 判断要干净得多答辩时被问到「你怎么控制未登录用户访问」直接甩这个方案。5. 避坑指南乱码、端口、驱动和版本不对导致的六个常见故障这个项目我在多种环境下跑过以下六条是出现频率最高的坑每条都按「现象 → 原因 → 解决」写清楚你照着排查能省下大量时间。5.1 URL 里的中文参数变成一堆 %E4%B8%AD%E6%96%87现象在查询用户或课程时URL 里带的中文参数传过去后程序中取出来是乱码或者一串百分号编码。原因Tomcat 8.5 默认对GET请求的 URI 编码是 UTF-8但程序里如果没有显式设置 request 编码POST请求的 body 会按 ISO-8859-1 解析而GET请求的参数是放在 URI 里的乱码形态又不一样。解决在 web.xml 里加一个编码过滤器filter filter-nameencodingFilter/filter-name filter-classcom.edu.util.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping同时在server.xml的 Connector 节点上加上URIEncodingUTF-8双管齐下。我最初只加了过滤器GET 请求还是乱码后来发现是 Connector 的 URIEncoding 没配这个细节很容易漏。5.2 启动 Tomcat 报 8080 端口被占用现象启动日志里出现Port 8080 was already in use或者页面一直打不开。原因本地已经跑着一个 Tomcat或某个进程占了 8080。解决macOS/Linux 下执行lsof -i :8080Windows 下执行netstat -ano | findstr 8080拿到 PID 后kill -9或taskkill /F /PID。如果这个端口经常被占建议直接把 Tomcat 的 Connector 端口改成 8090在 server.xml 里搜port8080改掉即可代价是访问 URL 里的端口号要同步换成 8090。5.3 JDBC 连接 MySQL 报Access denied for user rootlocalhost现象程序启动后第一次访问数据库就抛 SQLException提示用户拒绝访问。原因不是驱动问题是密码或者授权问题——要么 DBUtil 里的密码和 MySQL 实际密码不一致要么 root 用户只允许从localhost连接。解决先在 MySQL 命令行里手动验证密码mysql -uroot -p你的密码能进去就说明密码没问题。然后把 DBUtil 里的密码改成和命令行一致。如果项目配置的是远程数据库还要在 MySQL 里执行授权GRANT ALL PRIVILEGES ON edu_system.* TO root% IDENTIFIED BY 密码; FLUSH PRIVILEGES;5.4 反编译出来的源码编译报一大堆红叉现象把 .class 还原成 .java 放进 IDEA 后编译期报错 Java 文件里全是cannot find symbol。原因Struts 1.x 的核心 jarstruts-core-1.3.10.jar、commons-logging 等没有加到依赖里。解决确认 WEB-INF/lib 下有没有这些 jar 包如果没有去老项目的 lib 目录找一份或者从 Maven 中央仓库下载。加进File → Project Structure → Libraries后刷新编译错误应该能消掉大半。另一种常见情况是 class 文件和 .java 共存导致重复类冲突解决方法是把 .class 文件从源码目录里物理删掉只保留 .java。5.5 上传文件超过 10MB 就报Request entity too large现象用 FileUploadAction 上传稍大一点的文件Tomcat 直接返回 413 错误。原因Tomcat 的 maxPostSize 默认是 2MB超过就拒绝请求不进入业务代码。解决在 server.xml 的 Connector 里加两个参数Connector port8080 protocolHTTP/1.1 maxPostSize10485760 maxSwallowSize10485760 /参数说明maxPostSize单位是字节10485760 就是 10MBmaxSwallowSize是 Tomcat 在请求被拒绝后仍然读取了多少 body 数据如果不加大日志里会反复报连接重置。改完重启 Tomcat 生效。如果还要更大把这两个值同步调大就行。5.6 访问页面全是源码直接展示而不是渲染后的效果现象浏览器访问 login.jsp 时地址栏显示的是 JSP 源码或者满屏 HTML 没经过 Tomcat 执行。原因把项目直接丢进了 Tomcat 的 webapps 目录但 JSP 文件放在了 web 目录外的位置Tomcat 没把它当成 Web 应用。Java Web 应用必须有 WEB-INF/web.xml 才能被 Tomcat 识别。解决确保 webapps/guanli 目录下直接就是 login.jsp 和 WEB-INF 目录而不是嵌套了一层。最常见的问题是把整个项目压缩包解压后里面的 guanli 文件夹套了一层同名文件夹Tomcat 找不到正确结构。提示遇到任何 Tomcat 层面的怪问题先去看tomcat/logs/localhost.yyyy-MM-dd.log这个日志文件会把项目部署时的详细报错打出来比 IDEA 控制台的信息完整得多。我排查老项目的第一动作永远是翻这个日志它能省掉大量猜谜时间。6. 从能跑到能答辩功能验证清单与三个低成本改造方向6.1 先用一张自测清单把项目跑「熟」项目能启动不代表万事大吉。每拆一个毕设项目我都会强制自己按下面这张清单过一遍确认每个环节都能对着答辩老师讲清楚功能点自测动作预期结果登录成功输入管理员账号密码跳转 main.jsp页面上显示用户名登录失败输入错误密码返回 login.jsp显示错误提示注销退出点击退出按钮session 清除回登录页添加用户填写新用户信息提交列表页出现新记录修改用户编辑已有用户资料数据库数据同步更新删除用户删除一条测试记录列表记录消失文件上传选择一个文档上传upload 目录出现该文件重复登录开两个浏览器登录同一账号两个会话互不干扰每一行你都要能说清楚「点击之后发生了什么」。答辩最怕的不是项目功能少而是功能躺在那里但说不清实现逻辑。这张表和前面的请求链路结合着看足够你应付大多数提问。6.2 三个低成本改造方向让项目看起来不「太老」这套系统底子不差但 Struts 1.x 原生 JDBC 确实带着明显年代感。如果你想让答辩评分高一点我建议做下面三个改动每个的工作量都在半天以内第一个改动是密码加密。在 UserManager 的 login 和 addUser 方法里把密码先做一次 MD5 再入库和比对对应新增一个 MD5Util 工具类public class MD5Util { public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.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(MD5 加密失败, e); } } }逻辑说明这个工具类对输入的密码做一次不可逆哈希数据库里存的是哈希值而不是明文。String.format(%02x, b)把字节转成两位十六进制字符串保证输出是固定 32 位。改造时注意数据库中已有的用户密码也要同步更新成加密后的值否则老用户全登不进去。第二个改动是给用户列表加分页。现在 FindUserAction 如果是全量查询数据一多页面就卡。加一个LIMIT ?, ?的分页 SQL再在 JSP 页面上放上一页/下一页按钮改动集中在 DAO 和一个 JSP 文件里。第三个改动是把 DAO 层切换成数据库连接池。常见做法是用 Druid 或 HikariCP 替代 DBUtil 里的DriverManager.getConnection// 用 HikariCP 替代 DriverManager 获取连接 private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/edu_system?characterEncodingutf8useSSLfalse); config.setUsername(root); config.setPassword(你的密码); config.setMaximumPoolSize(10); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }参数说明setMaximumPoolSize(10)限制了最大连接数避免高并发时把数据库拖垮HikariCP 的连接获取是池化的比每次都新建连接的效率高一个量级。改动后 DBUtil 对外接口不变所有调getConnection()的地方都不用动。这个改造能在答辩时拿出来说「我优化了数据库连接效率」比空口谈性能有说服力得多。从那以后我每次拿到这类毕业设计压缩包都会先翻 class 文件列表做架构预判再按「版本选型 → 数据库初始化 → 启动验证 → 模块走查 → 改造加固」这个顺序走一遍而不是上来就点运行。项目里最值钱的往往不是代码本身而是你对那套技术栈的理解深度。希望这篇拆解能帮你把这个教务管理系统真正跑熟、吃透在答辩台上稳稳站住。本文还有配套的精品资源点击获取