简介:这是一套基于JavaWeb的学生实习管理系统项目源码,面向计算机相关专业正在做毕设的学生以及需要项目实战练习的Java学习者,可直接作为毕业设计或课程设计使用。系统采用B/S结构,后台基于JSP、Servlet与JDBC实现,数据库使用MySQL,开发环境为JDK、Eclipse与Tomcat,涵盖学生、老师、管理员三种角色:学生可管理实习日志与实习反馈,老师可对学生实习情况进行评价,管理员则负责班级、学生、老师、通知及实习信息的管理与个人密码修改,功能完善、界面美观、操作简单。资源包共3个文件,包含项目源码压缩包、MySQL数据库脚本以及项目说明文档,整体约4.88MB,结构清晰,便于快速导入与二次开发。目前已有3372人学习下载,适合希望掌握JavaWeb完整开发流程、积累项目经验的读者参考使用。
1. 从一份「能跑起来」的 JavaWeb 学生实习管理系统说起
很多计算机毕设选题卡在同一个地方:题目看着简单,真动手才发现「学生实习管理系统」这七个字背后藏着一整套业务闭环。学生要提交实习单位、上传周报、等导师批阅;导师要分配名额、审核材料、打分;教务要导出成绩、统计去向。这三类角色权限不同、数据互相依赖,用纯前端或者单文件 JSP 硬写,做到一半就会崩。
这个标题里的关键词是 JavaWeb、学生实习管理系统、项目源码、数据库脚本、毕设。它真正解决的不是「写个增删改查」,而是给你一套结构清晰、能本地跑通、能改能答辩的完整工程。适合两类人:一是时间紧、需要一份可复现骨架的毕设同学;二是想借一个真实业务练 Servlet、JSP、JDBC、MVC 分层和 MySQL 建表的初学者。下面我按「先立住结构、再动手复现、最后讲坑」的顺序,把这条路走一遍。
2. 学生实习管理系统的分层结构与技术选型:为什么不用一锅炖
2.1 三层架构到底分的是什么
JavaWeb 项目最容易翻车的地方,是把所有逻辑塞进 JSP。页面里写 JDBC、写业务判断、写 HTML,改一个字段要翻五个文件。学生实习管理系统这种带审批流的业务,必须做分层,否则后期加一个「导师驳回后学生重新提交」的状态流转,你会想砸键盘。
常见做法是经典 MVC 三层:
- 表现层(Web 层):Servlet 或 SpringMVC 控制器,负责接收请求、校验参数、跳转页面。
- 业务层(Service 层):处理实习申请、审批、周报、成绩这些业务规则,事务控制放在这里。
- 数据访问层(DAO 层):只跟数据库打交道,一个实体对应一个 DAO,SQL 集中管理。
实体类(Entity/POJO)贯穿三层,比如Student、Internship、WeeklyReport、Teacher。分层之后,改数据库字段只动 DAO 和实体,改业务规则只动 Service,页面改样式只动 JSP,互不牵连。
提示:毕设答辩时老师最爱问「你的项目分层了吗、事务加在哪一层」。把 Service 层作为事务边界答出来,基本稳。
2.2 技术栈怎么选才不给自己挖坑
标题写的是 JavaWeb,不是 SpringBoot,所以核心是 Servlet + JSP + JDBC 这套原生组合。但热词里 SpringBoot、Vue 出现频率很高,很多人纠结要不要上框架。我的建议是:毕设以「能讲清楚、能跑通、能改」为第一优先级。
| 方案 | 技术组合 | 适合场景 | 风险 |
|---|---|---|---|
| 原生 JavaWeb | Servlet + JSP + JDBC + MySQL | 想讲清底层原理、答辩好解释 | 样板代码多,手写分页和事务累 |
| SSM | Spring + SpringMVC + MyBatis | 想体现框架能力 | 配置多,环境容易起不来 |
| SpringBoot | SpringBoot + MyBatis + 前端 | 追求开发速度 | 偏离「JavaWeb」标题,底层被封装 |
如果标题锁定 JavaWeb,就老老实实用 Servlet + JSP + JDBC。JDBC 虽然原始,但每一步都看得见,答辩时能说清连接怎么拿、事务怎么提交、SQL 注入怎么防。想省事可以用 DBUtils 或手写一个 BaseDao 封装增删改查,但别引入重量级框架把底层盖住。
数据库统一用 MySQL 5.7 或 8.0,字符集utf8mb4,引擎 InnoDB(要事务和外键就必须 InnoDB)。JDK 用 8 或 11,Tomcat 用 8.5 或 9,这几个版本组合最稳,别追新。
2.3 角色与权限模型先定死
动手写代码前,先把角色和权限画清楚,这是整个系统的骨架。学生实习管理系统典型有四类角色:
- 学生:维护个人信息、提交实习申请、上传周报、查看成绩。
- 指导教师:审核自己名下学生的申请、批阅周报、录入成绩。
- 企业导师(可选):确认实习接收、评价学生。
- 管理员:管理用户、院系、实习批次、导出统计。
权限控制用「登录后把角色放进 Session,过滤器统一拦截」的方式。写一个AuthFilter,判断当前请求路径是否属于该角色可访问范围,不满足就跳登录页。别在每个 Servlet 里重复写if(role!=...),那是维护灾难。
// AuthFilter.java 核心逻辑:登录校验 + 角色拦截 public class AuthFilter implements Filter { // 无需登录即可访问的路径白名单 private static final String[] WHITE_LIST = {"/login", "/register", "/css/", "/js/"}; @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String uri = request.getRequestURI(); String path = uri.substring(request.getContextPath().length()); // 1. 白名单直接放行 for (String w : WHITE_LIST) { if (path.startsWith(w)) { chain.doFilter(req, resp); return; } } // 2. 未登录跳转登录页 Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login"); return; } // 3. 角色校验:/admin/ 路径仅管理员可进 if (path.startsWith("/admin/") && !"ADMIN".equals(getRole(user))) { response.sendError(403, "无权限访问"); return; } chain.doFilter(req, resp); } private String getRole(Object user) { /* 从用户对象取角色 */ return "STUDENT"; } }这段过滤器的逻辑说明:白名单放行静态资源和登录注册;Session 里没有loginUser就说明没登录,直接重定向;路径前缀带/admin/的做角色判断,非管理员返回 403。参数上,WHITE_LIST要按你实际目录调整,getContextPath()保证部署到非根路径时跳转正确。注意过滤器要在web.xml里配置或加@WebFilter注解,且顺序要在字符编码过滤器之后。
3. 数据库脚本怎么设计:从建库建表到外键约束
3.1 核心表结构与字段设计
数据库脚本是毕设里最容易被忽视、又最容易被追问的部分。学生实习管理系统的表不用多,但关系要清楚。核心六张表:用户表、学生表、教师表、实习单位表、实习申请表、周报表,再加一张成绩表。
-- 建库:字符集 utf8mb4,引擎 InnoDB CREATE DATABASE IF NOT EXISTS internship_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE internship_db; -- 用户表:登录凭证与角色 CREATE TABLE sys_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT '建议存MD5或BCrypt', real_name VARCHAR(50) NOT NULL, role VARCHAR(20) NOT NULL COMMENT 'STUDENT/TEACHER/ADMIN', status TINYINT DEFAULT 1 COMMENT '1正常 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- 实习申请表:核心业务表 CREATE TABLE internship_apply ( apply_id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, company_name VARCHAR(100) NOT NULL, position VARCHAR(50), start_date DATE, end_date DATE, status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING/APPROVED/REJECTED', teacher_id INT COMMENT '指导教师', apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_apply_student FOREIGN KEY (student_id) REFERENCES student(student_id), CONSTRAINT fk_apply_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ) ENGINE=InnoDB;建表逻辑说明:sys_user统一管理登录,学生和教师表通过user_id关联,避免每个角色一套登录逻辑。internship_apply的status字段是审批流的核心,用字符串枚举比数字可读性强,答辩时一眼看懂。外键约束保证不会出现「申请指向一个不存在的学生」,但要注意:外键会让删除变麻烦,如果后期要物理删除学生,得先删关联申请,或者改用逻辑删除(is_deleted字段)。
参数上,VARCHAR(100)存密码够用(MD5 是 32 位,BCrypt 是 60 位);日期用DATE不用DATETIME,因为实习起止只精确到天;status默认PENDING保证新申请进入待审状态。
3.2 索引与外键:什么时候加、什么时候别加
索引不是越多越好。学生实习管理系统里,高频查询是「按学生查申请」「按教师查待审」「按状态筛选」,所以:
internship_apply(student_id)加普通索引,学生查自己的申请走索引。internship_apply(teacher_id, status)加联合索引,教师查待审列表最常用。sys_user(username)建唯一索引,登录查询和防重名一举两得。
别加索引的情况:status这种只有三四个值的字段单独加索引意义不大,选择性太低,MySQL 可能直接全表扫描。周报表如果数据量大,(student_id, week_no)加联合索引,防止同一学生同一周重复提交。
外键在毕设里建议保留,因为它能体现你懂关系完整性。但生产环境很多团队会去掉外键、改由应用层保证,原因是外键在高并发写入时容易锁等待。毕设数据量小,保留外键利大于弊,答辩还能讲出「为什么用 InnoDB」。
注意:导出数据库脚本时,用
mysqldump -u root -p internship_db > internship_db.sql,加--default-character-set=utf8mb4防止中文乱码。导入时先CREATE DATABASE再source,别直接拖进客户端执行,容易漏掉建库语句。
3.3 初始化数据与测试账号
脚本里最好带几条初始化数据,方便一导入就能登录测试。至少插一个管理员、两个学生、两个教师,再插几条不同状态的实习申请,这样一跑起来页面就有内容,不用手动造数据。
INSERT INTO sys_user(username, password, real_name, role) VALUES ('admin', MD5('123456'), '系统管理员', 'ADMIN'), ('stu001', MD5('123456'), '张三', 'STUDENT'), ('tea001', MD5('123456'), '李老师', 'TEACHER');密码用MD5()函数直接生成,登录时 Java 端也用同样算法比对。这里有个血泪经验:MD5 不加盐不安全,但毕设够用,如果老师追问,就答「生产环境应使用 BCrypt 加盐哈希」,别硬扛说 MD5 安全。
4. 本地跑通的最小步骤:从导入脚本到访问首页
4.1 环境准备与项目导入
先把环境对齐,版本不一致是新手最大的翻车点。JDK 8、Tomcat 8.5、MySQL 5.7/8.0、IDEA 或 Eclipse,这四个装好。IDEA 里导入 JavaWeb 项目的关键配置:
File -> Project Structure -> Modules,确认src标记为 Sources,web或webapp标记为 Web Resource Directory。Artifacts里新增Web Application: Exploded,输出目录指向out/artifacts。Run -> Edit Configurations,新增 Tomcat Local,Deployment 里加上刚才的 Artifact,Application context 设成/internship。
数据库连接配置一般放在src/db.properties或c3p0.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/internship_db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码参数说明:serverTimezone必须写,否则 MySQL 8 会报时区错误;useSSL=false关掉 SSL 警告;characterEncoding=utf8mb4保证中文和 emoji 不乱码。驱动类 MySQL 8 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver,写错直接ClassNotFoundException。
4.2 数据库脚本导入与连接测试
导入脚本用命令行最稳:
# 登录 MySQL mysql -u root -p # 执行脚本(注意路径) source /path/to/internship_db.sql; # 验证表是否建好 USE internship_db; SHOW TABLES; SELECT COUNT(*) FROM sys_user;如果source报错,多半是脚本里没有USE语句或路径含空格。导入后先跑一个 JDBC 连接测试,别急着启动 Tomcat:
public class DBTest { public static void main(String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/internship_db?serverTimezone=Asia/Shanghai&useSSL=false"; try (Connection conn = DriverManager.getConnection(url, "root", "你的密码")) { System.out.println("连接成功:" + conn.getMetaData().getDatabaseProductVersion()); } } }连接成功再往下走。这一步能排除 80% 的「启动就报 500」问题——很多 500 其实是数据库连不上,但错误堆栈被 Tomcat 吞了,看着像代码问题。
4.3 启动访问与登录验证
Tomcat 启动后访问http://localhost:8080/internship/login。如果 404,检查 Application context 和web.xml里的welcome-file;如果 500,看 Tomcat 日志logs/catalina.out或 IDEA 控制台最下面的Caused by。
登录流程走通后,重点验证三件事:学生登录只能看到自己的申请;教师登录能看到待审列表;管理员能进后台。这三条过了,说明过滤器和权限模型是对的。用初始化账号stu001/123456登录,提交一条实习申请,再用tea001登录审批,整个闭环跑一遍,比看十遍代码都管用。
5. 避坑与排查:那些让毕设卡三天的真实问题
5.1 中文乱码:POST 和 GET 要分开治
现象:表单提交后数据库里是???,或者页面显示问号。原因:请求编码和数据库编码不一致。解决:POST 请求在过滤器里统一设request.setCharacterEncoding("UTF-8");GET 请求在 Tomcat 的server.xml里给 Connector 加URIEncoding="UTF-8";数据库连接串加characterEncoding=utf8mb4;建库建表都用utf8mb4。四处都对齐,乱码基本绝迹。
5.2 空指针:Session 过期后直接取属性
现象:登录后放一会儿,点任何操作都报NullPointerException。原因:Session 超时(默认 30 分钟),getAttribute("loginUser")返回 null,后面直接.getRole()就炸了。解决:过滤器里先判空再放行,或者在每个需要登录的 Servlet 开头统一校验。别指望用户「不会放那么久」,答辩演示时你自己就可能超时。
5.3 事务没生效:Connection 不是同一个
现象:Service 里先插申请、再更新状态,中间抛异常,结果申请插进去了但状态没更新。原因:两次 DAO 调用各自getConnection(),是两个连接,事务管不到一起。解决:把 Connection 通过参数传给 DAO,或者用 ThreadLocal 绑定同一个连接,在 Service 层统一setAutoCommit(false)、commit()、rollback()。这是 JDBC 事务最经典的坑,答辩老师很爱问。
5.4 外键导致删除失败
现象:删除一个学生,报Cannot delete or update a parent row。原因:该学生还有实习申请记录,外键约束挡住了。解决:要么先删子表记录再删主表,要么改用逻辑删除(加is_deleted字段,查询时过滤)。毕设推荐逻辑删除,既保留数据又避免外键冲突,还能讲出「软删除」这个概念。
5.5 部署后静态资源 404
现象:本地 IDEA 里 CSS 正常,打成 war 部署到 Tomcat 后样式全丢。原因:JSP 里用了相对路径,或者过滤器把/css/也拦了。解决:路径统一用${pageContext.request.contextPath}/css/xxx.css;过滤器白名单里放行静态资源目录。别用../这种相对路径,部署路径一变就失效。
6. 让系统从「能跑」到「能答辩」:状态机与统计导出技巧
把基本功能跑通只是及格线,想让这份学生实习管理系统在答辩时站得住,得在业务深度上做一点加法。最划算的切入点是把实习申请的状态流转做成显式状态机。很多同学用一堆if-else判断状态,代码乱、说不清。正确做法是定义状态枚举和允许的迁移:
| 当前状态 | 允许操作 | 目标状态 | 操作角色 |
|---|---|---|---|
| PENDING | 通过 | APPROVED | 教师 |
| PENDING | 驳回 | REJECTED | 教师 |
| REJECTED | 重新提交 | PENDING | 学生 |
| APPROVED | 录入成绩 | FINISHED | 教师 |
这张表往答辩 PPT 一放,老师立刻知道你懂业务建模。代码里用一个Map<String, List<String>>维护合法迁移,非法操作直接抛业务异常,比散落的 if 判断清晰得多。
第二个加分项是统计导出。教务角色往往需要「按院系统计实习人数」「按状态统计申请数」。用一条 SQL 搞定:
SELECT d.dept_name, COUNT(*) AS total, SUM(CASE WHEN a.status='APPROVED' THEN 1 ELSE 0 END) AS approved FROM internship_apply a JOIN student s ON a.student_id = s.student_id JOIN department d ON s.dept_id = d.dept_id GROUP BY d.dept_name;导出成 Excel 可以用 POI,但毕设里更省事的做法是导出 CSV——用PrintWriter写逗号分隔,加个 BOM 头防 Excel 乱码,几十行代码搞定,不用引依赖。CSV 的坑是字段里含逗号或换行会串列,简单处理就是给字段加双引号并转义内部引号。
第三个技巧是给关键操作加日志。审批、成绩录入这类动作,往一张operation_log表里记一条:谁、什么时候、对哪条数据、做了什么。答辩时被问「怎么追溯操作」,这就是答案。日志表不用复杂,五个字段足够。
最后说个我自己的习惯:每改完一个功能,就用初始化账号把完整流程重跑一遍,别攒到最后一起测。毕设时间紧,攒着测的结果往往是答辩前一晚发现登录都进不去。数据库脚本也留一份干净的、一份带测试数据的,演示用带数据的,交付用干净的。这套系统本身不复杂,难的是把每个环节都想到、都验证过。希望帮到你。
本文还有配套的精品资源,点击获取