
简介这份资源是一篇基于JavaWeb的实习管理系统设计与实现的毕业设计论文面向专科与本科计算机相关专业学生可用于毕业论文撰写或课程设计参考。论文围绕实习管理中的信息不透明、效率低下等痛点完整梳理了从绪论、需求分析、系统设计到数据库设计、功能实现与测试部署的全过程并涉及MVC架构、Servlet/JSP、JDBC及Spring等技术的实际应用有助于读者理解JavaWeb项目的整体开发流程。压缩包内包含1个docx文档大小约32KB虽为单文件文本型资料但字数达万字结构较为完整。目前已有133人学习下载适合正在筹备毕业设计或希望快速了解实习管理系统开发思路的学生参考使用。1. 基于 Javaweb 的实习管理系统为什么它是最稳的 Javaweb 毕设选题无论课程设计还是毕业论文实习管理系统都是 Javaweb 热门选题里最稳妥的一类业务不复杂却能干净地把 Servlet、JSP、MySQL 和 Tomcat 这一整套完整链路串起来。但有个反直觉的地方——这类系统最折磨人的根本不是业务代码而是三层架构的边界划分、审核状态的推进方式以及 IDEA 里的项目部署配置。我见过好几个照着 Javaweb 毕业设计案例复现的同学栽的不是业务逻辑而是 Tomcat 版本和数据库连接参数。这篇要拆的是一套基于 Javaweb 的实习管理系统的设计与实现文档覆盖学生实习申请、双导师审核、周报提交、成绩评定和院系统计的完整数据链路。适合要做 Javaweb 毕业设计、或者想拿一套完整案例把 Servlet JSP MySQL 项目跑通的人。跟着复现一遍你拿到的不只是论文内容而是一套能运行、能答辩、还能改造成自己业务的底子。2. 系统设计与数据库建模三层架构、四类角色与审核状态机实习管理系统的难点不在某个算法而在业务状态的组织方式。同一份实习申请学生要提交校内指导老师要审企业导师还要确认中途可能被驳回重提通过后又得持续产生周报和成绩。如果上来就写代码你会发现自己不停改表结构。所以拿到文档后我建议先把第 2 章和第 3 章的架构设计读透再动手。2.1 三层架构与 MVC 边界为什么用 Servlet 而不是直接套 SSM这套系统的技术路线是经典的 Javaweb 三层架构Web 层Servlet JSP负责接收请求、参数校验和页面跳转Service 层负责业务规则和状态流转DAO 层只干一件事——拼 SQL、绑参数、返回结果。如果你是按黑马 Javaweb 笔记那套节奏学过来的对这个分层不会陌生JSP 只负责渲染页面不许在 JSP 里直接写 JDBCServlet 不写业务只在拿到参数后转交给 Service。选 Servlet JSP 而不是 Spring 全家桶不是技术落后而是毕业设计场景下更好用。一方面答辩时老师会追问框架原理经典三层结构一句话就能讲清另一方面Servlet 的请求转发和响应逻辑非常直白出问题时不至于陷入“黑匣子”式的框架报错。用一个简单的例子说明边界学生提交申请时Servlet 只做三件事——设置请求编码、调 Service 方法、重定向到列表页Service 判断这是“存草稿”还是“正式提交”决定状态初值填 0 还是 1DAO 负责把数据写进 tb_application 表。职责不清的典型表现是有人把 SQL 写在 Servlet 里导致页面 Java 代码里到处是 Connection维护时想改一个字段要翻十几个文件。这套文档的价值就在于把边界提前固定住。2.2 四类角色与状态机审核流的骨架全在状态表里系统的参与者分成四类权限边界天然清晰。角色核心职责主要操作学生填写实习申请提交周报查看审核结果新增申请、修改草稿、提交周报校内指导老师审核学生申请评阅周报给定成绩通过/驳回申请批阅周报企业导师确认实习单位真实性参与过程评价确认实习、填写企业评价学院管理员维护师生企业信息查看统计数据用户管理、实习汇总统计状态机是整个系统最容易翻车的地方。我见过不少实现把实习申请状态直接用字符串“已通过”“已驳回”存进表里结果查询和统计只能靠 LIKE 匹配还容易写错。正确做法是给状态定义一个有序的数值枚举让每条记录在任何时刻都落在一个确定节点上。这套文档里的状态设计大概如下状态值含义可流向0草稿11待校内审核2、32校内已通过待企业确认4、33已驳回14实习中55已结束无核心原则只有一个状态只能按箭头方向移动不能跳步驳回后只能回到“待提交审核”不能直接变成“实习中”。这个设计在文档的第三章有详细说明也正是代码里 WHERE status ? 条件的来由。2.3 核心表结构与 DDL 示例申请单是绝对的主表整个库围绕两张主表展开用户表和实习申请表。用户表存学生、教师、企业的登录身份实习申请表存每次实习业务数据周报表和成绩表都以它为外键。下面这截 DDL 是从常见实现里提炼出来的申请单结构-- 实习申请表一次实习申请对应一条记录 CREATE TABLE tb_application ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 关联 tb_user.id, company VARCHAR(100) NOT NULL COMMENT 实习单位名称, position VARCHAR(50) COMMENT 实习岗位, start_date DATE NOT NULL COMMENT 实习开始日期, end_date DATE NOT NULL COMMENT 实习结束日期, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1待校内审核 2待企业确认 3驳回 4实习中 5已结束, submit_time DATETIME COMMENT 正式提交时间, audit_opinion VARCHAR(500) COMMENT 最近的审核意见, audit_time DATETIME COMMENT 最近审核时间, KEY idx_student (student_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;student_id 不直接存学生姓名而是关联 tb_user 表这是为了避免一个学生多条申请时反复存相同个人信息。status 字段加索引是必须的因为列表页和统计页都按状态筛选。遇到“实习单位”这种业务字段给它留够 100 字符并加上 NOT NULL 约束比在 Service 层判断空字符串更可靠。我还见过有人在表里直接用 BLOB 存周报附件结果数据库文件快速膨胀这种方案不建议在毕业设计里用正确做法是文件存磁盘表里只存访问路径。3. 实习申请与审核流程从 Servlet 到 DAO 的完整链路实现系统最核心的用例是“学生提交申请、教师审核”这一节把这条链路从页面请求一直拆到 SQL 语句。代码只是骨架重点是看它如何把状态机和三层边界落到每一行。3.1 学生提交申请服务端校验、状态初值与事务边界学生在前端填完单位、岗位、起止时间后表单 POST 到 StudentApplicationServlet。这个 Servlet 的 doPost 方法负责收参和校验WebServlet(/student/application) public class StudentApplicationServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 不管是普通表单还是 AjaxPOST 中文都必须先设置编码 req.setCharacterEncoding(UTF-8); String studentId (String) req.getSession().getAttribute(studentId); String company req.getParameter(company); String position req.getParameter(position); String startDate req.getParameter(startDate); String endDate req.getParameter(endDate); String submitType req.getParameter(submitType); // draft / submit // 服务端校验不能省前端校验只能当用户体验 if (studentId null || company null || company.trim().isEmpty() || startDate null || endDate null) { resp.sendRedirect(req.getContextPath() /application/edit.jsp?errorparam); return; } InternshipApplicationService service new InternshipApplicationService(); service.submit(studentId, company, position, startDate, endDate, submitType); resp.sendRedirect(req.getContextPath() /student/application/list); } }这段代码的逻辑核心是三个判断首先从 session 拿登录学生 ID而不是信任前端传来的 studentId防止越权其次 submitType 用 draft 和 submit 区分是保存草稿还是正式提交Service 层据此决定状态初值是 0 还是 1最后所有参数校验放在 server 端因为前端校验可以被绕过。需要注意Service 层 submit 方法内部如果包含“插入申请 首次审核记录”两步操作就应该用事务包起来避免主表写入成功但审核表崩溃造成脏数据。DAO 层插入时用的是 PreparedStatement 参数绑定public int insert(InternshipApplication app) throws SQLException { String sql INSERT INTO tb_application(student_id, company, position, start_date, end_date, status, submit_time) VALUES (?,?,?,?,?,?,NOW()); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, app.getStudentId()); ps.setString(2, app.getCompany()); ps.setString(3, app.getPosition()); ps.setDate(4, new java.sql.Date(app.getStartDate().getTime())); ps.setDate(5, new java.sql.Date(app.getEndDate().getTime())); ps.setInt(6, app.getStatus()); ps.executeUpdate(); ResultSet keys ps.getGeneratedKeys(); if (keys.next()) app.setId(keys.getInt(1)); } }这里的核心习惯是日期类型从 Java 端 new java.sql.Date 转一次避免时间戳和日期互相污染状态值直接 setInt配合第 2 章的状态枚举让数据库里存的永远是干净的数字而不是“已通过”这种不经拆的文字RETURN_GENERATED_KEYS 拿回自增主键后续写周报表需要这个 ID。3.2 教师审核状态推进必须带“预期状态”条件教师点击“通过”或“驳回”后请求进入 AuditServlet。这里最容易出的问题不是 SQL 写错而是审核人连点两次按钮请求重复执行把状态和审核意见覆盖成双份。解决的方法是在 Service 层用条件更新public boolean audit(String applicationId, int expectedStatus, int targetStatus, String opinion) { String sql UPDATE tb_application SET status ?, audit_opinion ?, audit_time NOW() WHERE id ? AND status ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, targetStatus); ps.setString(2, opinion); ps.setString(3, applicationId); ps.setInt(4, expectedStatus); // 关键条件 int rows ps.executeUpdate(); return rows 1; // 返回 true 说明只有一条记录被更新 } catch (SQLException e) { return false; } }注意第 4 个参数的写法WHERE id ? AND status ?。expectedStatus 是调用方认为“当前应该处于的状态”。如果教师 A 已经把这个申请从“待校内审核”改成“待企业确认”教师 B 再基于旧状态提交更新影响行数是 0更新失败后台会给出“该申请已被审核请刷新页面”的提示。这个条件更新就是状态机的防线比先查后改再更新的三步操作靠谱得多。调用审核接口时Service 层还要记录一条审核日志就算业务表改崩了也能从日志看出是谁在什么时候把状态推到了哪里。3.3 周报提交与列表查询写库和读库要分两条路周报是实习过程的产物逻辑比申请更简单但也同样是典型的两条路写周报走“插入或更新”看周报走“联表分页”。写周报时很多同学直接 UPDATE 周报表但忘了判断该内容是否已经存在。我习惯按“有则更新、无则插入”处理先在周报表里按 application_id week_no 查一次存在就改正文不存在就新增。列表查询则关注一个现实问题一个学生一学期有十几次周报如果不分页页面会越来越卡。SELECT a.id, a.company, a.status, w.week_no, w.title, w.update_time FROM tb_application a LEFT JOIN tb_weekly_report w ON w.application_id a.id AND w.week_no ? WHERE a.student_id ? ORDER BY w.week_no DESC LIMIT ?, ?上面这条 SQL 的要点在于week_no 是动态占位符可以只查某一周的周报LEFT JOIN 保证申请单不因缺少周报而消失LIMIT 的两个参数分别来自 pageIndex 和 pageSize页码数在 Service 层算好不能直接拿前端传来的字符串拼进 SQL否则就是注入点。如果你希望列表页同时显示“最近一次周报的提交时间”可以把子查询拆出来避免一表多 JOIN 之后数据翻倍导致分页总数对不上。4. IDEA 运行 Javaweb 项目的完整配置导入、Tomcat、MySQL 建库连库很多复制下来的 Javaweb 项目代码本身没问题问题全出在“跑不起来”。这里单独给 IDEA 运行 Javaweb 项目配置一个完整过程按这个顺序操作基本一次能在本地把 Tomcat 和 MySQL 接通。4.1 版本匹配JDK 1.8 Tomcat 9 MySQL 5.7/8.0 最省心先确认一个版本组合。很多项目是基于 JDK 1.8 写的代码里用了 date 处理、字符串拼接等常见写法放到 JDK 17 也能跑但部分老依赖会有兼容问题。我自己一直用 JDK 1.8 Tomcat 9.0.x MySQL 5.7 的组合这套组合是所有经典 Javaweb 项目兼容面最广的。如果你本机是 MySQL 8.0也没关系驱动换成 mysql-connector-java 8.0.x 即可参数上多做两个设置就行。组件推荐版本备注JDK1.88u191老工程兼容性最好Tomcat9.0.x支持 javax.servlet与 Javaweb 代码一致MySQL5.7 或 8.0连接参数有差异见 4.3IDEA2022 及以上配置方式通用需要特别注意的是 Tomcat 10。Tomcat 10 开始把 javax.servlet 包移到了 jakarta.servlet老项目里的 import javax.servlet.* 会直接编译报错或启动后 ClassNotFoundException。除非你想把所有 Servlet 依赖全改成 jakarta 前缀否则直接锁死 Tomcat 9。4.2 导入项目与 Artifacts 配置war exploded 才是开发态IDEA 里跑 Javaweb 项目核心步骤不是点 Run而是把 Artifacts 和 Tomcat 关联起来。不少同学卡在这一步只点了一下 Add Configuration选了一个 Tomcat Server然后 Run结果浏览器 404。第一步Project Structure 打开左侧选 Artifacts点加号选择 Web Application: ExplodedFrom Modules选择项目主模块。这里一定要选 Exploded不要选 warwar 是压缩包每次改 JSP 都要重新打包开发效率极低。第二步到 Tomcat 配置里找到 Deployment 页签点加号选择 Artifact然后选中刚才配置好的 exploded 包。再往下找 Application context这里决定你的访问路径。如果想直接用 http://localhost:8080/ 访问就把它设置为 /。第三步回 Server 页签确认 JRE 下拉框选的是 JDK 1.8 而不是 JRE 目录。很多人混淆 JRE 和 JDK老项目需要 JDK 里的 javac 做编译选错会报“cannot find javac”。配置完成后可以观察到 IDEA 会生成一个 Target 目录存放编译产物这是正常现象。4.3 MySQL 建库与 db.properties一次连接成功避开三个经典报错数据库连接是报错重灾区。先通过命令行或 Navicat 执行建库语句再配置 db.properties 文件。以下是命令行方式mysql -uroot -p登录后执行-- 建立独立库和专用账号避免项目连 root 造成安全隐患 CREATE DATABASE IF NOT EXISTS intern_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER internlocalhost IDENTIFIED BY L3arnDev; GRANT ALL PRIVILEGES ON intern_db.* TO internlocalhost; FLUSH PRIVILEGES;专用账号的好处是即使链接配置泄露也只影响这一个库。接着把 db.properties 里的连接参数改成下面这样这是结合 MySQL 8.0 和 5.7 的通用写法jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/intern_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameintern jdbc.passwordL3arnDev这里四个参数是经典的关键characterEncodingutf8 保证中文不乱码useSSLfalse 关掉 SSL 警告serverTimezoneAsia/Shanghai 解决 MySQL 8.0 的时区报错allowPublicKeyRetrievaltrue 是 MySQL 8.0 用 caching_sha2_password 认证时必须要加的否则会报“Public Key Retrieval is not allowed”。如果本机是 MySQL 5.7驱动可以用 com.mysql.jdbc.Driver但更建议统一用 com.mysql.cj.jdbc.Driver它能兼容 5.7 和 8.0省得以后切换数据库时还要改驱动类名。把完整建表 SQL 脚本导入后再用一个简单的测试类执行 SELECT 1返回结果说明连接成功。4.4 启动验证日志里看到什么才算真正起来配置完成后点击运行关注 Console 窗口的三类输出第一Tomcat 启动日志出现“Server startup in XXXX ms”是启动成功的标志第二项目部署日志会有“Deployment of web application archive ... has finished in XXXX ms”字样第三数据库连接日志如果你在项目里配置了数据源初始化此时会看到连接池输出。如果页面还是 404先别急着怀疑代码检查地址栏端口号和 Application context 是否匹配比如 Tomcat 8080、context 是 /intern那访问路径就是 http://localhost:8080/intern/。如果 Console 完全没有输出 SQL 日志一定是连接没走到 DAO 层用 4.3 的测试类先把连接问题排除掉这是最快定位问题的方式。5. 避坑指南Javaweb 项目部署与运行中的五个典型故障排查这一章记录的是我在部署和复现这类 Javaweb 项目时实际踩过的坑每一条都是“现象 → 原因 → 解决”的结构你也可以把它当作排查手册。5.1 Tomcat 10 导致的 javax.servlet 兼容性故障现象项目在 IDEA 中选 Tomcat 10 后启动瞬间报错 ClassNotFoundException: javax.servlet.Filter或者所有页面 404。原因Tomcat 10 不再提供 javax.servlet 包改成了 jakarta.servlet。解决在 Server 配置里把 Tomcat 切换为 9.0.x如果你只有 Tomcat 10那就把项目里所有 import javax.servlet.* 替换成 jakarta.servlet.*同时 pom.xml 或 lib 里的 servlet-api 依赖也要换。项目规模小的话换包不算太麻烦但毕业后复用这套代码的同学大概率用的是 Tomcat 9所以要么统一 9要么在文档里写清楚。5.2 IDEA 部署后 404Artifacts 和 Application context 未配对现象Tomcat 能启动访问 localhost:8080 也能看到默认页面但访问 /项目名/ 就是 404。原因项目没有出现在 Deployment 列表里或者 Application context 跟实际访问路径不一致。解决打开 Edit Configurations → Deployment → 加号 → Artifact选择 exploded再检查 Application context 是不是 / 或 /intern。注意一个细节context 为 /intern 时访问路径必须是 http://localhost:8080/intern/xxx少了前缀就 404这是最容易看漏的地方。5.3 MySQL 8.0 连接被拒时区、SSL 与 Public Key Retrieval现象驱动加载成功但拿连接时报错“Communications link failure”或“Public Key Retrieval is not allowed”。原因MySQL 8.0 默认启用 caching_sha2_password 认证同时要求明确时区。解决在 jdbc.url 末尾补上 serverTimezoneAsia/Shanghai、useSSLfalse、allowPublicKeyRetrievaltrue 三个参数如果不想改代码也可以在 MySQL 里执行 ALTER USER internlocalhost IDENTIFIED WITH mysql_native_password BY L3arnDev;把认证方式切回 mysql_native_password。从长期看我建议连接参数一次写全因为这种方式对 5.7 和 8.0 都适用改配置比改数据库账号更安全。5.4 中文乱码三个环节统一 UTF-8顺序不能错现象页面上中文正常但插入数据库变成问号或者数据库存的是中文页面上显示乱码。原因JSP 页面编码、Servlet 取参编码、数据库连接编码三个环节没统一。解决第一JSP 文件最顶部加 % page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %第二Servlet 里在所有 getParameter 之前调用 req.setCharacterEncoding(UTF-8)建议用一个 Filter 统一处理不要在每个 Servlet 里重复写第三MySQL 连接参数带 characterEncodingutf8建库时用 utf8mb4。记住一个口诀页面写、请求转、数据库收三个环节全用 UTF-8 就对了。如果还乱码可能是表或字段本身就是 latin1需要 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4。5.5 审核状态被覆盖条件更新写成了无条件覆盖现象教师点击两次“通过”系统生成了两条审核记录或者状态从“待企业确认”被倒退回“待校内审核”。原因UPDATE 语句没有带 status 条件每次都直接 SET status 目标状态。解决把更新语句写成 UPDATE tb_application SET status ?, audit_opinion ? WHERE id ? AND status ?更新前传人 expectedStatus。这样第二次点击时status 已经不匹配影响行数为 0程序可以提示“该申请已被处理”。这条不算疑难杂症但偏偏是实习管理系统里出现频率最高的问题我把它列在这里。6. 进阶用法状态机落到数据库里把审核流做成一个闭环系统能跑通基本流程后可以再做两件提高质量的事情先把整条状态流验证一遍再给审核增加到数据库层面的防并发能力。这两件事都不难但能让答辩或二次开发时加分不少。6.1 先验证闭环再优化页面我每次部署完会准备一组预置数据按“学生新增草稿 → 正式提交 → 校内通过 → 企业确认 → 添加周报 → 评分结业”的顺序逐条操作每一步都查一次 tb_application.status。用下面的 SQL 就能直观看到状态变化是否合理SELECT id, student_id, status, submit_time, audit_time FROM tb_application ORDER BY id;正常的记录应该看到 status 依次从 0 变为 1、2、4、5且 audit_time 不为空。如果某一步状态跳到 3 之后还能直接变 4就是审核 Service 里没做状态合法性判断回到 3.2 的代码检查条件更新。6.2 给审核加乐观锁并发场景下两个老师同时审核同一份申请条件更新只能防“同状态提交”但没法区分先后。更稳的做法是给表加一列 version 整数更新时带版本号并递增UPDATE tb_application SET status ?, audit_opinion ?, version version 1 WHERE id ? AND status ? AND version ?Java 侧把 version 作为参数传入更新后检查影响行数。如果为 0说明有人抢先改了这条数据本次操作直接回滚并提示重新加载。这招是从订单库存系统里迁移过来的思路放在审核场景里也好用。从那以后我每次部署这类 Javaweb 项目都强制自己先完整走一遍“新增 — 提交 — 审核 — 驳回 — 改 — 再提交 — 通过”的闭环再来调页面样式和细节因为状态流才是这类系统的生命线。这套实习管理系统的设计与实现最大的价值就是让你在毕业设计答辩时能清楚回答“状态怎么控制、并发怎么防止、边界在哪三层”这三个高频问题。希望帮到你。本文还有配套的精品资源点击获取