简介:这是一套面向JavaWeb初学者的教务管理系统实战项目,基于MVC架构实现高校教务核心业务场景,涵盖学生、教师、教务员及系统管理员四类角色的完整权限与功能模块,适用于课程设计、毕业设计及Web开发入门实践。资源包共223个文件,含82个Java源码(如StudentBasicInformation.java、OptionalCourseServlet.java)、82个编译后class文件、38个JSP页面(支撑登录、成绩查询、报名管理等交互)、6个核心jar依赖及1个MySQL建库脚本(sql),整体压缩包仅2.6MB,结构清晰、轻量易部署。已有11657人学习下载,适合在Windows环境下使用Eclipse+Tomcat 7.0.84+JDK1.8快速运行调试。读者可直接获取完整可运行的分层代码结构、Filter统一拦截处理、DAO数据访问封装、多角色Session管控逻辑,以及包含密码找回、选课/考级报名、成绩录入与学籍维护等真实业务流程的实现细节,为后续功能扩展提供扎实基础。
1. 教务管理系统JavaWeb项目:一个能跑通、能改、能交作业的真实教学级系统
你刚接手一门《Java Web开发实践》课程设计,老师只甩来一句“做个教务系统”,没给需求文档、没给数据库脚本、连登录页长啥样都没说——这时候,一个结构清晰、模块完整、连 Tomcat 都配好了就能直接 run 的 JavaWeb 教务系统,就是你的救命稻草。这不是玩具 Demo,它真实覆盖了学生选课、教师录成绩、管理员维护班级/课程/教师/学生四大核心角色的 CRUD 流程,前后端分离程度适中(JSP + Servlet + JDBC),MySQL 表结构合理、外键约束完整,连web.xml中的 Servlet 映射和过滤器链都按企业级习惯组织好了。它不追求 Spring Boot 的自动装配玄学,也不堆砌 Vue 的响应式黑匣子,而是用最扎实的 request/response 生命周期、session 管理、事务控制,把 JavaWeb 的底子给你夯实在 IDE 里。适合大三刚学完 JDBC 和 Servlet 的同学快速上手调试,也适合带实训课的导师直接拆成 6 个实验任务下发——从建库建表到成绩导出 Excel,每一步都有对应包、类、JSP 页面可定位、可打断点、可改字段。
2. 项目结构与技术栈解析:看清骨架再动刀
2.1 目录结构即开发地图:src 与 WebContent 的分工逻辑
项目采用经典 JavaWeb 标准布局,未使用 Maven(避免初学者被 pom.xml 里的 scope 和 profile 劝退),所有依赖 JAR 包统一放在WebContent/WEB-INF/lib/下。关键目录如下:
src/ ├── dao/ // 数据访问层:纯 JDBC 封装,每个表对应一个 XxxDao.java │ ├── impl/ // Dao 接口实现类,含 Connection 获取、PreparedStatement 参数绑定、ResultSet 映射 │ └── interface/ // Dao 接口定义,如 StudentDao、CourseDao ├── entity/ // 实体类:Student、Teacher、Course、Score、ClassInfo 等,字段与数据库表严格一一对应 ├── service/ // 业务逻辑层:XxxService.java,含事务控制(手动 commit/rollback)、参数校验、跨表关联逻辑 │ └── impl/ // Service 实现类,调用多个 Dao 组合操作(如:选课 = 查询课程余量 + 插入选课记录 + 更新余量) ├── servlet/ // 控制层:XxxServlet.java,处理 HTTP 请求,调用 Service,转发/重定向到 JSP ├── filter/ // 登录过滤器 LoginFilter.java:拦截 /admin/*、/teacher/*、/student/* 路径,校验 session 中用户类型与权限 └── util/ // 工具类:DBUtil(封装 getConnection()、close())、DateUtil(日期格式化)、ExcelExportUtil(Apache POI 导出成绩表) WebContent/ ├── index.jsp // 入口页:根据 session 是否存在跳转至 login.jsp 或首页 ├── login.jsp // 统一登录页:输入账号密码,提交至 LoginServlet ├── admin/ // 管理员视图:manage_student.jsp、manage_course.jsp、manage_teacher.jsp 等 ├── teacher/ // 教师视图:view_class.jsp(查看所授班级)、input_score.jsp(录入成绩) ├── student/ // 学生视图:select_course.jsp(选课列表+选课按钮)、view_score.jsp(查看已修课程及成绩) └── WEB-INF/ ├── web.xml // 核心配置:servlet-mapping、filter-mapping、welcome-file-list └── lib/ // mysql-connector-java-5.1.47.jar、poi-3.17.jar、jstl-1.2.jar 等提示:
web.xml是整个项目的“总开关”。务必确认<servlet>标签中的servlet-class路径是否与src中实际类路径一致(如servlet.LoginServlet),且<url-pattern>与 JSP 表单action属性值完全匹配(如/login对应<url-pattern>/login</url-pattern>)。路径错一个字母,404 就是常态。
2.2 技术选型务实派:为什么不用 Spring?为什么坚持 JSP?
Servlet + JSP:不是守旧,而是教学穿透力强。学生在
LoginServlet里打断点,能清晰看到request.getParameter("username")如何拿到表单值,session.setAttribute("user", user)如何把对象存进会话,request.getRequestDispatcher("/admin/index.jsp").forward(request, response)如何完成页面跳转——这些是框架封装后你永远看不到的“血肉”。JDBC 原生操作:
dao.impl.StudentDaoImpl中的public List<Student> findAll()方法,代码直白:public List<Student> findAll() throws SQLException { List<Student> list = new ArrayList<>(); String sql = "SELECT id, name, gender, birth_date, class_id FROM student"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student s = new Student(); s.setId(rs.getInt("id")); s.setName(rs.getString("name")); s.setGender(rs.getString("gender")); s.setBirthDate(rs.getDate("birth_date")); s.setClassId(rs.getInt("class_id")); list.add(s); } } return list; }关键点说明:
try-with-resources自动关闭资源(避免连接泄漏);rs.getDate()与实体类java.util.Date字段类型匹配;SQL 语句无拼接,杜绝 SQL 注入——这就是安全、可读、可调试的 JDBC 实践。MySQL 5.7+:建表脚本
db_init.sql中明确使用ENGINE=InnoDB支持事务,student表外键class_id REFERENCES class_info(id)确保数据一致性。score表主键为联合主键(student_id, course_id),天然防止重复选课。
2.3 核心流程闭环:以“学生选课”为例走通全链路
选课功能是检验系统健壮性的试金石,它横跨前端交互、权限控制、业务校验、数据库事务四个层面:
- 前端触发:
student/select_course.jsp中每个课程行有「选课」按钮,onclick="location.href='SelectCourseServlet?courseId='+${c.id}" - 权限拦截:
filter.LoginFilter检查 session 中是否存在student类型用户,否则重定向至login.jsp - 业务校验:
servlet.SelectCourseServlet调用service.impl.StudentService.selectCourse(studentId, courseId)- 先查
course表获取max_capacity和当前已选人数(SELECT COUNT(*) FROM score WHERE course_id = ?) - 若
count >= max_capacity,返回错误提示“课程已满” - 否则开启事务,执行两步:
INSERT INTO score(student_id, course_id, score) VALUES (?, ?, NULL); -- 初始成绩为空 UPDATE course SET current_capacity = current_capacity + 1 WHERE id = ?;
- 先查
- 结果反馈:成功则
request.setAttribute("msg", "选课成功"),转发回select_course.jsp显示绿色提示;失败则request.setAttribute("error", "选课失败:课程已满"),显示红色提示。
这个闭环里没有魔法,只有if/else、try/catch、conn.setAutoCommit(false)、conn.commit()—— 你改一行 SQL,加一个日志输出,就能立刻看到效果。
3. 运行环境搭建与首次启动:从零到 localhost:8080/admin/index.jsp
3.1 环境清单:版本对齐是少踩 80% 坑的前提
| 组件 | 推荐版本 | 为什么必须匹配 | 验证方式 |
|---|---|---|---|
| JDK | 1.8u202 | Servlet 3.1 规范要求,高版本(如 17)会导致web.xml解析失败 | java -version输出含1.8.0_ |
| Tomcat | 8.5.99 | 与web.xml中<web-app version="3.1">完全兼容,9.x+ 默认禁用invokerservlet 可能影响调试 | 解压后bin/version.bat(Windows)或bin/version.sh(Linux) |
| MySQL | 5.7.32 | db_init.sql中DATETIME默认值CURRENT_TIMESTAMP在 5.6 下不支持 | mysql --version |
| Eclipse EE | 2021-09 | 内置 Tomcat 插件成熟,对web.xml语法检查友好 | Help → About Eclipse → 版本号 |
注意:不要用 IntelliJ IDEA 直接导入为 Maven 项目!本项目无
pom.xml,IDEA 会误判依赖缺失。正确做法是 File → New → Project from Existing Sources → 选择项目根目录 → 选择 “Import project as general project” → 手动配置 Facets(Dynamic Web Module 3.1 + Java 1.8)。
3.2 数据库初始化:三步到位,拒绝空库报错
- 创建数据库:UTF-8 编码,避免中文乱码
CREATE DATABASE edu_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 执行建表脚本:
db_init.sql文件位于项目根目录,用 MySQL 客户端执行提示:若执行报错
Unknown character set: 'utf8mb4',说明 MySQL 版本过低,降级为utf8(但需同步修改db_init.sql中所有utf8mb4为utf8) - 插入基础数据:
init_data.sql提供管理员账号admin/123456、测试教师t001/123456、学生s001/123456,确保首次登录不卡死
3.3 Eclipse 中部署与启动:关键配置项截图级还原
右键项目 → Properties → Project Facets
- 勾选
Dynamic Web Module→ Version3.1 - 勾选
Java→ Version1.8 - 点击
Further configuration available...→ 设置Content directory为WebContent,Generate web.xml deployment descriptor勾选(确保已有web.xml不被覆盖)
- 勾选
右键项目 → Properties → Deployment Assembly
- 确认
src→/WEB-INF/classes - 确认
WebContent→/ - 确认
WebContent/WEB-INF/lib→/WEB-INF/lib - 删除任何
Maven Dependencies条目(本项目无 Maven)
- 确认
服务器配置:Window → Preferences → Server → Runtime Environments → Add → Apache Tomcat v8.5 → Browse 选择 Tomcat 解压目录
- 关键一步:双击已添加的 Tomcat Server → Open launch configuration → Arguments → VM arguments 添加:
解决 JSP 中文乱码问题-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8
- 关键一步:双击已添加的 Tomcat Server → Open launch configuration → Arguments → VM arguments 添加:
启动验证:右键项目 → Run As → Run on Server → 选择 Tomcat 8.5 → Finish
- 浏览器打开
http://localhost:8080/→ 自动跳转login.jsp - 输入
admin/123456→ 成功进入http://localhost:8080/admin/index.jsp
- 浏览器打开
4. 常见问题排查:那些让我重启三次 Tomcat 的血泪经验
4.1 现象:访问http://localhost:8080/显示 404,但 Tomcat 控制台无报错
原因:Eclipse 未将项目发布到 Tomcat 的webapps目录,或web.xml中welcome-file-list指向的index.jsp路径错误
解决:
- 检查
WebContent/WEB-INF/web.xml中<welcome-file-list>是否为:<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> - 右键项目 → Properties → Deployment Assembly → 确认
WebContent映射到/(不是/WebContent) - 手动进入
Tomcat/webapps/目录,确认存在与项目同名的文件夹(如edu_system),且其下有index.jsp
4.2 现象:登录时用户名密码正确,但反复跳回login.jsp,无任何错误提示
原因:LoginServlet中session.setAttribute("user", user)执行了,但后续filter.LoginFilter里session.getAttribute("user")取不到值,本质是 Session ID 未正确传递
解决:
- 检查
login.jsp表单method是否为post(LoginServlet的doPost方法处理登录) - 检查
web.xml中filter-mapping顺序:LoginFilter必须在CharacterEncodingFilter之后(否则中文参数乱码导致查询失败) - 在
LoginServlet开头加日志:System.out.println("Session ID: " + request.getSession().getId());,对比LoginFilter中打印的 ID 是否一致
4.3 现象:点击「录入成绩」报java.lang.ClassNotFoundException: org.apache.poi.ss.usermodel.Workbook
原因:poi-3.17.jar未正确部署到WEB-INF/lib/,或 Eclipse 的 Build Path 中误删了该 JAR
解决:
- 进入
WebContent/WEB-INF/lib/目录,确认poi-3.17.jar文件存在且大小 > 3MB - 右键项目 → Properties → Java Build Path → Libraries → 确认
poi-3.17.jar在列表中,且Deploy Path为WEB-INF/lib - 终极验证:在
util.ExcelExportUtil中写一行new XSSFWorkbook();,看是否编译报错
4.4 现象:学生选课后,score表新增记录,但course表current_capacity未增加
原因:StudentService.selectCourse()方法中事务未生效,conn.setAutoCommit(false)后忘记conn.commit()
解决:
- 定位
service.impl.StudentService的selectCourse方法,检查finally块中是否有:if (conn != null) { try { if (success) conn.commit(); // 成功才提交 else conn.rollback(); // 失败回滚 } finally { conn.close(); } } - 在
INSERT和UPDATE语句后加System.out.println("Executed SQL: " + sql);,确认两条 SQL 都执行了
4.5 现象:admin/manage_student.jsp中表格数据显示为??(方块乱码)
原因:MySQL 连接 URL 缺少useUnicode=true&characterEncoding=UTF-8参数
解决:
- 修改
util.DBUtil中getConnection()方法的 URL:private static final String URL = "jdbc:mysql://localhost:3306/edu_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"; - 重启 Tomcat,刷新页面
5. 二次开发实战:从「能跑」到「能交」的三个关键改造
5.1 改造一:为成绩录入页增加「按学期筛选」功能(前端+后端联动)
原始teacher/input_score.jsp列出教师所有课程,无法区分学期。教学要求需支持按semester(学期)筛选,如「2023-2024-1」。
步骤分解:
数据库扩展:为
course表添加semester VARCHAR(20)字段,并更新测试数据ALTER TABLE course ADD COLUMN semester VARCHAR(20) DEFAULT '2023-2024-1'; UPDATE course SET semester = '2023-2024-1' WHERE id IN (1,2,3);DAO 层增强:在
dao.interface.CourseDao新增方法// 查询指定教师、指定学期的课程 List<Course> findCoursesByTeacherAndSemester(int teacherId, String semester) throws SQLException;Service 层实现:
service.impl.TeacherService中实现该方法,SQL 使用WHERE teacher_id = ? AND semester = ?Servlet 控制:
servlet.InputScoreServlet中接收semester参数(默认为当前学期),调用 Service 获取课程列表String semester = request.getParameter("semester"); if (semester == null || semester.trim().isEmpty()) { semester = "2023-2024-1"; // 默认学期 } List<Course> courses = teacherService.findCoursesByTeacherAndSemester(teacherId, semester); request.setAttribute("courses", courses); request.setAttribute("semester", semester); request.getRequestDispatcher("/teacher/input_score.jsp").forward(request, response);JSP 前端:在
input_score.jsp顶部添加学期筛选下拉框<form method="get"> <select name="semester" onchange="this.form.submit()"> <option value="2023-2024-1" ${param.semester == '2023-2024-1' ? 'selected' : ''}>2023-2024-第一学期</option> <option value="2023-2024-2" ${param.semester == '2023-2024-2' ? 'selected' : ''}>2023-2024-第二学期</option> </select> </form>
提示:此改造验证了 MVC 分层价值——只改 DAO 接口、Service 实现、Servlet 参数处理、JSP 展示逻辑,四层代码各司其职,无一处需要动其他模块。
5.2 改造二:成绩导出 Excel 增加「班级维度」统计(Apache POI 深度应用)
原始ExcelExportUtil.exportScoreList()导出全校成绩。教学管理需按班级导出,如「计算机2101班」所有学生各科成绩。
核心代码增强(util.ExcelExportUtil.java):
public static void exportClassScore(HttpServletResponse response, List<ScoreWithStudent> scoreList, String className) throws IOException { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("成绩表-" + className); // 表头 String[] headers = {"学号", "姓名", "课程名称", "成绩"}; XSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 int rowNum = 1; for (ScoreWithStudent s : scoreList) { XSSFRow row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(s.getStudentId()); row.createCell(1).setCellValue(s.getStudentName()); row.createCell(2).setCellValue(s.getCourseName()); row.createCell(3).setCellValue(s.getScore() == null ? "未录入" : s.getScore().toString()); } // 自动列宽 for (int i = 0; i < headers.length; i++) { sheet.autoSizeColumn(i); } // 响应输出 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(className + "_成绩.xlsx", "UTF-8")); workbook.write(response.getOutputStream()); }调用入口:在servlet.ExportScoreServlet中,先根据班级 ID 查询ScoreWithStudent(含学生姓名、课程名的关联数据),再调用上述方法。ScoreWithStudent是新定义的 VO 类,非 Entity,专为导出场景优化。
5.3 改造三:登录安全加固——密码加密存储(BCrypt 实战)
原始系统明文存密码,严重不符合教学规范。需升级为 BCrypt 加密(bcrypt-3.0.jar已放入lib)。
改造清单:
- DAO 层:
dao.impl.AdminDaoImpl.login()中,密码比对从rs.getString("password").equals(inputPassword)改为:String hashedPassword = rs.getString("password"); if (BCrypt.checkpw(inputPassword, hashedPassword)) { ... } - Service 层:
AdminService.register()中,密码入库前加密:String hashed = BCrypt.hashpw(rawPassword, BCrypt.gensalt(12)); admin.setPassword(hashed); - 数据库:
admin表password字段长度需扩大至VARCHAR(100)(BCrypt 哈希值约 60 字符)
从那以后我每次做用户认证模块,都强制走一遍「明文密码 → BCrypt 加密 → 数据库存储 → 登录时 BCrypt 校验」全流程,哪怕只是 Demo。因为密码安全不是可选项,是刻进
User实体类构造函数里的铁律。希望帮到你。
本文还有配套的精品资源,点击获取