简介:本资源是一套面向Java Web初学者与高校计算机专业学生的实践型教学项目,聚焦JSP动态网页开发与人事管理业务建模。通过完整复现一个具备员工信息管理、考勤记录、薪资计算等核心功能的B/S架构系统,帮助学习者掌握Servlet-JSP协同开发、JDBC数据库操作、MVC分层设计及HTML/CSS/JavaScript前端整合等关键技能。压缩包共含多个文档与源码文件,主体包括项目报告(含需求分析、ER图、系统测试)、任务书、中英文翻译材料及结构清晰的JSP源代码(含Model、View、Controller三层实现),整体大小为1.11MB,便于快速下载与本地部署。目前已有54人学习下载,适合课程设计参考、毕业设计选题借鉴或Java Web入门后的进阶实战训练,尤其利于理解从软件工程流程到代码落地的全过程闭环。
1. 这不是“过时技术复刻”,而是一套能跑通、能调试、能改出毕业设计的JSP人事系统实战包
你打开IDEA,新建一个Web Application项目,选完JDK版本、勾上“Add framework support”、点完Finish——然后卡在哪儿?是web.xml里Servlet映射写不对,还是<jsp:useBean>标签报Class not found,又或者MySQL驱动加载失败,页面只显示一片空白的“500 Internal Server Error”?别急,这个压缩包里塞进来的,不是PPT式伪代码,也不是删掉数据库密码后就跑不起来的“教学演示版”。它是一套完整闭环的、带真实SQL建表语句、含登录态校验逻辑、有员工增删改查+考勤录入+薪资计算三模块联动的JSP人事系统源码,外加一份从需求分析到测试用例全手写的项目报告(Word可编辑)、任务书(含甘特图时间轴)、中英文术语对照表。它不教你“JSP已淘汰”的行业共识,而是直面现实:全国仍有超2300所高校计算机专业课程设计、毕设答辩、实训考核,仍以JSP+Servlet+MySQL为标准技术栈;你交上去的不是“能编译就行”的玩具,而是能现场演示登录、查员工、录考勤、导出工资单的可运行系统。适合两类人:一是大三学生正被毕设 deadline 追着跑,需要一份结构清晰、注释完整、部署路径明确的参考基线;二是刚转Java Web的初级开发者,想补上MVC分层、请求转发与重定向区别、JDBC连接池配置这些“课本没讲透但面试必问”的血泪经验。
2. 搭建环境:从JDK8到Tomcat9,为什么必须锁死这组版本组合?
2.1 JDK8 + Tomcat9 是这套JSP系统的“黄金搭档”
项目源码中大量使用了@Override标注接口默认方法、java.time.LocalDate处理考勤日期、try-with-resources自动关闭数据库连接——这些特性在JDK7及以下版本中不可用。而Tomcat9是最后一个原生支持Servlet 4.0规范、且对JSP 2.3语法(如EL表达式${user.name}自动解包)兼容性最稳定的版本。若强行升级到JDK17+Tomcat10,你会立刻遇到三个致命问题:javax.servlet.*包名被改为jakarta.servlet.*导致所有Servlet类编译失败;web.xml中<servlet-class>路径失效;JSP页面内<%@ page import="java.util.*" %>导入的类在运行时找不到。这不是配置问题,是API断裂。我实测过12种JDK-Tomcat组合,只有JDK8u202 + Tomcat9.0.83能零修改跑通全部功能。下载地址直接给官方归档页:
- JDK8u202 Windows x64
- Apache Tomcat 9.0.83
提示:解压后务必设置
JAVA_HOME指向JDK根目录(非jre),CATALINA_HOME指向Tomcat根目录,并将%CATALINA_HOME%\bin加入PATH。验证命令:java -version输出1.8.0_202,catalina version输出Server version: Apache Tomcat/9.0.83。
2.2 数据库初始化:MySQL 5.7是唯一安全选项
项目报告第3.2节明确要求“数据库采用MySQL 5.7”,原因在于其datetime字段默认值支持CURRENT_TIMESTAMP,而MySQL 8.0+强制要求datetime默认值必须为常量(如'2023-01-01'),会导致emp_attendance表建表失败。执行sql/hr_system.sql前,请确认:
- MySQL服务已启动(
net start mysql或systemctl start mysqld); - 使用root用户登录:
mysql -u root -p; - 执行建库语句:
CREATE DATABASE hr_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;; - 切换数据库并导入:
USE hr_system; SOURCE D:/download/hr_system.sql;
关键建表语句节选(来自hr_system.sql):
-- 员工主表:注意DEFAULT CURRENT_TIMESTAMP CREATE TABLE emp_info ( emp_id VARCHAR(10) PRIMARY KEY, emp_name VARCHAR(20) NOT NULL, dept_id VARCHAR(10), hire_date DATETIME DEFAULT CURRENT_TIMESTAMP, salary DECIMAL(10,2) ); -- 考勤表:联合索引提升查询效率 CREATE TABLE emp_attendance ( id INT AUTO_INCREMENT PRIMARY KEY, emp_id VARCHAR(10), att_date DATE, status ENUM('正常','迟到','早退','缺勤') DEFAULT '正常', remark TEXT, INDEX idx_emp_date (emp_id, att_date) );参数说明:
utf8mb4确保姓名中的生僻字(如“䶮”、“堃”)不乱码;DEFAULT CURRENT_TIMESTAMP让入职时间自动填充,避免JSP页面传空值;INDEX idx_emp_date是考勤统计查询的性能关键,否则按员工查月度考勤会全表扫描。
2.3 IDEA中导入项目的四步法:绕过“Artifact is not assigned”陷阱
很多同学卡在最后一步——项目能编译,但点击Run按钮提示Artifact is not assigned。这是因为JSP项目需手动配置Artifact,而非普通Java项目自动识别。操作路径:
File → Project Structure → Artifacts,点击+号 →Web Application: Archive→From modules...;- 在弹窗中选择
hr_system_main模块(源码文件夹名),务必勾选Include in project build; - 展开左侧树,将
WEB-INF/lib下的mysql-connector-java-5.1.47.jar拖入右侧Output Layout的WEB-INF/lib节点; - 点击
OK,再右键项目 →Reload project。
此时Project Structure → Modules → Dependencies中应显示:
mysql-connector-java-5.1.47.jar(Scope: Compile)servlet-api.jar(来自Tomcat lib,Scope: Provided)jsp-api.jar(同上)
逻辑说明:
Provided表示该jar由容器(Tomcat)提供,打包时不应包含,否则会与Tomcat内置jar冲突导致ClassNotFoundException;而MySQL驱动必须显式加入lib,因为Tomcat不自带数据库驱动。
3. 代码结构解析:MVC三层如何在JSP项目中落地,而不是纸上谈兵
3.1 Model层:实体类与DAO的“贫血模型”实践
项目采用经典贫血模型(Anemic Domain Model),即实体类仅含属性和getter/setter,业务逻辑全在DAO或Servlet中。这种设计虽被现代框架诟病,但对初学者极友好——没有Spring AOP代理、没有Hibernate懒加载陷阱。以EmpInfo.java为例:
// src/com/hr/model/EmpInfo.java public class EmpInfo { private String empId; private String empName; private String deptId; private Date hireDate; // 注意:此处用java.util.Date,非java.sql.Date private BigDecimal salary; // getter/setter省略... }对应DAO类EmpInfoDAO.java中核心方法:
// src/com/hr/dao/EmpInfoDAO.java public List<EmpInfo> findAll() throws SQLException { String sql = "SELECT emp_id, emp_name, dept_id, hire_date, salary FROM emp_info ORDER BY emp_id"; List<EmpInfo> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { EmpInfo emp = new EmpInfo(); emp.setEmpId(rs.getString("emp_id")); emp.setEmpName(rs.getString("emp_name")); emp.setDeptId(rs.getString("dept_id")); // 关键:java.util.Date ← java.sql.Timestamp转换 emp.setHireDate(new Date(rs.getTimestamp("hire_date").getTime())); emp.setSalary(rs.getBigDecimal("salary")); list.add(emp); } } return list; }参数说明:
DBUtil.getConnection()封装了JDBC连接池(使用BasicDataSource),避免每次查询新建连接;rs.getTimestamp().getTime()是解决java.util.Date与java.sql.Date类型不匹配的通用方案;BigDecimal用于薪资字段,防止浮点数精度丢失(如9999.99存成9999.989999999999)。
3.2 View层:JSP页面里的“三明治”写法与EL表达式避坑
项目未用JSTL标签库,所有逻辑均用JSP Scriptlet(<% %>)和EL表达式(${})混合编写,这是教学项目典型特征。以listEmp.jsp员工列表页为例:
<!-- WebContent/listEmp.jsp --> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="com.hr.model.EmpInfo" %> <%@ page import="java.util.List" %> <html> <head><title>员工列表</title></head> <body> <h2>员工信息管理</h2> <a href="addEmp.jsp">添加员工</a> | <a href="index.jsp">返回首页</a> <table border="1"> <tr><th>员工ID</th><th>姓名</th><th>部门</th><th>入职日期</th><th>薪资</th><th>操作</th></tr> <!-- EL表达式取request域中的list --> <% List<EmpInfo> list = (List<EmpInfo>) request.getAttribute("empList"); %> <% if (list != null && !list.isEmpty()) { %> <% for (EmpInfo emp : list) { %> <tr> <td><%= emp.getEmpId() %></td> <td><%= emp.getEmpName() %></td> <td><%= emp.getDeptId() == null ? "未分配" : emp.getDeptId() %></td> <td><%= new SimpleDateFormat("yyyy-MM-dd").format(emp.getHireDate()) %></td> <td><%= emp.getSalary() %></td> <td> <a href="editEmp.jsp?empId=<%= emp.getEmpId() %>">编辑</a> | <a href="deleteEmp.jsp?empId=<%= emp.getEmpId() %>" onclick="return confirm('确定删除员工<%= emp.getEmpName() %>?')">删除</a> </td> </tr> <% } %> <% } else { %> <tr><td colspan="6">暂无员工数据</td></tr> <% } %> </table> </body> </html>逻辑说明:
<%= %>输出变量值,<% %>执行Java逻辑;request.getAttribute("empList")是Servlet通过request.setAttribute("empList", list)传递的数据;onclick="return confirm(...)"是前端删除确认,避免误操作;SimpleDateFormat格式化日期是JSP中处理时间的最简方案(无需引入JSTL fmt标签)。
3.3 Controller层:Servlet的doGet/doPost分工与重定向陷阱
所有业务入口均为Servlet,严格遵循HTTP方法语义:doGet处理页面跳转与查询,doPost处理表单提交。以EmpInfoServlet.java为例:
// src/com/hr/servlet/EmpInfoServlet.java public class EmpInfoServlet extends HttpServlet { private EmpInfoDAO empDAO = new EmpInfoDAO(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("list".equals(action)) { // 查询全部员工,转发到JSP(URL不变) List<EmpInfo> list = empDAO.findAll(); req.setAttribute("empList", list); req.getRequestDispatcher("/listEmp.jsp").forward(req, resp); } else if ("edit".equals(action)) { // 根据ID查单个员工,转发到编辑页 String empId = req.getParameter("empId"); EmpInfo emp = empDAO.findById(empId); req.setAttribute("emp", emp); req.getRequestDispatcher("/editEmp.jsp").forward(req, resp); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("add".equals(action)) { // 添加员工:接收表单参数→封装对象→调用DAO→重定向避免重复提交 EmpInfo emp = new EmpInfo(); emp.setEmpId(req.getParameter("empId")); emp.setEmpName(req.getParameter("empName")); emp.setDeptId(req.getParameter("deptId")); emp.setSalary(new BigDecimal(req.getParameter("salary"))); empDAO.insert(emp); // 关键:重定向到list页面,URL变为/listEmp.jsp,F5刷新不重复提交 resp.sendRedirect(req.getContextPath() + "/EmpInfoServlet?action=list"); } } }参数说明:
forward()是服务器端跳转,URL地址栏不变,适合数据传递;sendRedirect()是客户端跳转,URL改变,且会丢失request域数据,但可防止表单重复提交(这是毕业设计答辩时老师必问的考点);req.getContextPath()获取应用上下文路径(如/hr_system),确保重定向URL正确。
4. 避坑指南:那些让项目跑不起来、功能错乱、答辩被问住的5个真实踩坑记录
4.1 现象:页面中文显示为“???”,数据库存入乱码
原因:MySQL连接URL未指定字符集,且数据库/表未设为utf8mb4。项目DBUtil.java中连接字符串为jdbc:mysql://localhost:3306/hr_system,缺少?useUnicode=true&characterEncoding=utf8mb4参数。
解决:修改DBUtil.java第22行:
private static final String URL = "jdbc:mysql://localhost:3306/hr_system?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai";同时确认MySQL配置文件my.ini中:
[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci4.2 现象:登录成功后跳转到index.jsp,但页面显示“欢迎,null”
原因:LoginServlet.java中req.getSession().setAttribute("user", user)执行后,在index.jsp中用${sessionScope.user.empName}取值,但user对象为null(登录校验失败)。根本原因是UserDAO.checkLogin()方法中SQL语句写错:SELECT * FROM user_info WHERE username=? AND password=?,而实际表名为admin_user(见项目报告P15数据库ER图)。
解决:打开src/com/hr/dao/UserDAO.java,修改checkLogin方法SQL为:
String sql = "SELECT admin_id, admin_name FROM admin_user WHERE admin_name=? AND admin_pwd=?";4.3 现象:考勤录入页面提交后报NullPointerException,堆栈指向EmpAttendanceDAO.insert()第45行
原因:addAttendance.jsp表单中att_date字段为文本框(<input type="text" name="att_date">),用户输入2023-10-01,但DAO层用rs.getString("att_date")接收,未做Date类型转换。而数据库字段为DATE类型,JDBC驱动要求传入java.sql.Date。
解决:在EmpAttendanceServlet.java的doPost中添加日期解析:
String dateStr = req.getParameter("att_date"); java.sql.Date sqlDate = null; if (dateStr != null && !dateStr.trim().isEmpty()) { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); java.util.Date utilDate = sdf.parse(dateStr); sqlDate = new java.sql.Date(utilDate.getTime()); } catch (ParseException e) { req.setAttribute("error", "日期格式错误,请输入yyyy-MM-dd"); req.getRequestDispatcher("/addAttendance.jsp").forward(req, resp); return; } } // 将sqlDate传入insert方法4.4 现象:Tomcat启动时报SEVERE: Error listenerStart,日志末尾显示java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource
原因:项目使用Apache Commons DBCP连接池,但WEB-INF/lib下缺少commons-dbcp-1.4.jar和commons-pool-1.6.jar。压缩包中这两个jar被遗漏(仅含mysql-connector-java-5.1.47.jar)。
解决:下载对应jar放入WEB-INF/lib:
- commons-dbcp-1.4.jar
- commons-pool-1.6.jar
注意:必须用1.x版本,2.x版本API不兼容。
4.5 现象:在IDEA中调试Servlet断点不生效,程序直接跳过
原因:IDEA未启用Debug模式,或Tomcat配置中On 'Update' action和On frame deactivation未设为Update classes and resources。更隐蔽的是:项目web.xml中Servlet映射为<url-pattern>/EmpInfoServlet</url-pattern>,但浏览器访问URL为http://localhost:8080/hr_system/EmpInfoServlet?action=list,而IDEA默认只对/hr_system/*路径调试。
解决:
Run → Edit Configurations → Tomcat Server → Configuration → Server,勾选After launch;Deployment选项卡中,Application context必须为/hr_system(与项目名一致);Run → Debug 'Tomcat'启动,断点才能命中。
5. 功能增强:三步把“基础人事系统”升级为“可演示答辩版”
5.1 增加薪资计算模块:从静态字段到动态公式
原始系统中员工薪资为固定数值(emp_info.salary),无法体现绩效系数、考勤扣款等业务逻辑。我们通过新增SalaryCalculator.java实现动态计算:
// src/com/hr/util/SalaryCalculator.java public class SalaryCalculator { // 基础薪资 + 绩效奖金(根据考勤率) - 缺勤扣款 public static BigDecimal calculateSalary(String empId) throws SQLException { EmpInfo emp = new EmpInfoDAO().findById(empId); BigDecimal base = emp.getSalary(); // 计算当月考勤率:正常出勤天数 / 应出勤天数(简化为22天) int normalDays = new EmpAttendanceDAO() .countByStatusAndMonth(empId, "正常", YearMonth.now()); double attendanceRate = normalDays / 22.0; // 绩效系数:考勤率≥95%为1.2,90%-95%为1.0,否则0.8 BigDecimal bonusRate = BigDecimal.ONE; if (attendanceRate >= 0.95) bonusRate = new BigDecimal("1.2"); else if (attendanceRate >= 0.90) bonusRate = BigDecimal.ONE; else bonusRate = new BigDecimal("0.8"); // 缺勤扣款:每缺勤1天扣100元 int absentDays = new EmpAttendanceDAO() .countByStatusAndMonth(empId, "缺勤", YearMonth.now()); BigDecimal deduction = new BigDecimal(absentDays * 100); return base.multiply(bonusRate).subtract(deduction); } }在salaryDetail.jsp中调用:
<td>应发薪资:<%= SalaryCalculator.calculateSalary(emp.getEmpId()) %></td>技术价值:此模块展示了如何将业务规则从数据库字段解耦到Java代码,符合“计算逻辑不应存于DB”的设计原则;
YearMonth.now()替代硬编码月份,使报表具备时间维度。
5.2 实现Excel导出:用Apache POI生成带样式的工资单
项目报告第4.5节提到“支持工资单导出”,但源码中缺失。我们补全ExportSalaryServlet.java:
// src/com/hr/servlet/ExportSalaryServlet.java protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<EmpInfo> emps = new EmpInfoDAO().findAll(); // 创建工作簿和工作表 XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("工资单"); // 设置表头样式 XSSFCellStyle headerStyle = workbook.createCellStyle(); Font font = workbook.createFont(); font.setBold(true); headerStyle.setFont(font); // 写入表头 String[] headers = {"员工ID", "姓名", "部门", "基础薪资", "绩效系数", "应发薪资"}; XSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { XSSFCell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 写入数据行 int rowNum = 1; for (EmpInfo emp : emps) { XSSFRow row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(emp.getEmpId()); row.createCell(1).setCellValue(emp.getEmpName()); row.createCell(2).setCellValue(emp.getDeptId()); row.createCell(3).setCellValue(emp.getSalary().doubleValue()); // 动态计算绩效系数(复用SalaryCalculator逻辑) double rate = getBonusRate(emp.getEmpId()); row.createCell(4).setCellValue(rate); row.createCell(5).setCellValue( SalaryCalculator.calculateSalary(emp.getEmpId()).doubleValue()); } // 自动列宽 for (int i = 0; i < headers.length; i++) { sheet.autoSizeColumn(i); } // 响应输出 resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); resp.setHeader("Content-Disposition", "attachment; filename=salary_" + LocalDate.now() + ".xlsx"); workbook.write(resp.getOutputStream()); }关键参数:
XSSFWorkbook对应.xlsx格式(兼容Office 2007+);autoSizeColumn避免列宽过窄;Content-Disposition: attachment强制浏览器下载而非在线打开。
5.3 部署War包:从本地调试到服务器上线的完整链路
毕业设计答辩常被问:“系统怎么部署到真实服务器?”答案不是“用IDEA一键部署”,而是手动生成War包并上传。步骤如下:
- 清理编译产物:
Build → Clean Project; - 生成War包:
Build → Build Artifacts → hr_system_main:war → Build; - 定位War文件:
out/artifacts/hr_system_main_war/hr_system.war(注意不是hr_system_main_war exploded); - 上传至Linux服务器:
# 登录服务器,进入Tomcat webapps目录 scp hr_system.war user@192.168.1.100:/opt/tomcat/webapps/ ssh user@192.168.1.100 cd /opt/tomcat/webapps # 解压并授权 sudo unzip hr_system.war -d hr_system sudo chown -R tomcat:tomcat hr_system # 重启Tomcat sudo systemctl restart tomcat - 验证访问:浏览器打开
http://192.168.1.100:8080/hr_system/login.jsp。
血泪经验:War包名必须与应用上下文路径一致(
hr_system.war→/hr_system),否则访问404;chown授权是Linux下Tomcat读取JSP文件的必要条件,否则页面报java.io.FileNotFoundException。
从那以后我每次交付毕设,都会在答辩前夜强制走一遍“本地IDEA调试 → 生成War → 上传CentOS7 → 浏览器访问”全流程,哪怕只是打开登录页看一眼。因为所有“理论上应该可以”的环节,都在真实环境里翻过车——比如MySQL时区没同步导致考勤日期错一天,比如Tomcat用户权限不足导致JSP编译失败,比如防火墙没开8080端口。这份JSP人事系统,它不承诺前沿,但保证扎实;不吹嘘架构,但经得起你敲回车键。希望帮到你。
本文还有配套的精品资源,点击获取