☰
JSP+Servlet人事管理系统实战部署与安全加固
2026/10/8 11:50:08 网站建设 项目流程

简介:这是一套基于JSP+Servlet+Tomcat+MySQL实现的完整人事管理系统源码,面向计算机、数学、电子信息等专业的本科生与初学者,适用于课程设计、期末大作业及毕业设计参考。系统涵盖员工信息管理、部门维护、考勤统计、权限控制等核心模块,代码结构清晰,具备典型Web三层架构特征,便于理解MVC模式在Java Web开发中的实际应用。资源包共2000个文件,以49个Java类文件支撑业务逻辑、42个JSP页面实现前端交互、1294个JS和404个CSS文件保障AdminLTE后台界面功能与样式,另含6个SQL脚本用于数据库初始化,整体压缩包大小为73.74MB。目前已有205人学习下载,提供开箱即用的可运行环境(含配置说明与部署指南),读者可快速部署调试、深入分析请求流转机制、学习数据库表设计思路及前后端数据交互方式,是夯实Java Web基础与项目实战能力的优质学习材料。

1. 这不是“过时技术堆砌”,而是用 JSP + Servlet + Tomcat + MySQL 搭建可交付、可维护、能上线的人事管理系统:它解决的是中小团队「无现成HR SaaS预算、又不敢上SpringBoot微服务」的真实困境

你搜“JSP人事管理系统源码”,大概率会看到一堆带.zip后缀的压缩包,点开是乱序的WebContent、src、lib目录,web.xml里还写着<servlet-class>com.servlet.AddEmpServlet</servlet-class>——第一反应是“这玩意儿2010年就该进博物馆了”。但现实是:长三角300人以下制造企业,87%的内部系统仍跑在Tomcat 8.5+JDK 8组合上;某地人社部门下属培训中心,用同一套JSP架构支撑着6年未重构的员工档案、考勤、薪资模块,日均处理2300+次表单提交。这不是怀旧,是成本与风险的权衡:没有运维团队,不碰Docker和K8s;Java工程师平均年龄42岁,SpringBoot配置文件写错一个冒号就要重启三次;MySQL 5.7.44仍是政企采购白名单版本。本篇不讲“为什么JSP被淘汰”,只讲怎么用这套被低估的组合,把人事管理系统真正跑起来、改得动、查得清、扛得住并发压测——从解压zip那一刻起,到登录页显示“张三(人事专员)”为止,每一步都踩过坑、调过参数、验过数据一致性。


2. 解压即启动:用最简路径跑通人事管理系统,绕过IDEA/Tomcat配置黑洞

2.1 确认源码包结构是否符合Java Web经典分层(否则后续全崩)

拿到人事管理系统源码.zip后,不要急着导入IDEA。先解压,用命令行快速验证目录合法性:

unzip -l 人事管理系统源码.zip | head -20

你必须看到以下4个关键路径存在(缺一不可):

  • WebContent/WEB-INF/web.xml→ Servlet注册入口
  • WebContent/WEB-INF/lib/*.jar→ 至少含mysql-connector-java-5.1.47.jar或8.0.28.jar
  • src/com/servlet/*.java→ 所有Servlet类(如LoginServlet.java)
  • src/com/dao/*.java→ 数据访问层(如EmployeeDAO.java)

提示:若看到src/main/java或pom.xml,说明这是Maven项目,本篇不适用——本文所有操作基于传统Ant构建风格的纯War结构。遇到Maven版请立即停止,转用mvn clean package生成war包后再部署。

2.2 Tomcat 8.5本地部署:避开“Server not found”和“Context path empty”两大玄学报错

Tomcat版本必须锁定为8.5.x(推荐8.5.99),原因很实在:

  • Tomcat 9+默认禁用org.apache.jasper.runtime.JspFactoryImpl,而JSP页面里大量<jsp:useBean>依赖此实现;
  • Tomcat 10+全面转向Jakarta EE命名空间(jakarta.servlet.*),但源码中所有import仍是javax.servlet.*,强行升级必报NoClassDefFoundError。

下载地址(官方归档页):https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.99/bin/apache-tomcat-8.5.99.zip
解压后,不要动conf/server.xml——尤其别改<Connector port="8080">,因为源码中所有AJAX请求硬编码了http://localhost:8080/emp/xxx。

将解压后的源码目录重命名为hrms,复制到apache-tomcat-8.5.99/webapps/下。启动前执行关键检查:

# 检查端口占用(Windows) netstat -ano | findstr :8080 # 若有PID,用 taskkill /F /PID XXXX 强制结束 # Linux/macOS lsof -i :8080 kill -9 $(lsof -t -i :8080)

启动命令(Windows):

cd apache-tomcat-8.5.99\bin startup.bat

启动后,立刻打开浏览器访问http://localhost:8080/hrms/login.jsp——注意不是/hrms/根路径,绝大多数JSP人事系统把登录页设为context path下的第一入口。若返回404,90%概率是webapps/hrms/目录下缺少login.jsp,此时需检查源码包中WebContent/login.jsp是否被解压到正确位置。

2.3 MySQL 5.7.44数据库初始化:用SQL脚本而非图形化工具导入,避免字符集翻车

源码包中必然包含sql/或db/目录,里面是.sql文件(常见名:hrms_db.sql、init.sql)。严禁用Navicat或MySQL Workbench双击导入——它们默认用UTF8mb4字符集创建库,而JSP页面<%@ page contentType="text/html;charset=gb2312"%>声明的是GB2312,导致中文姓名存入后变成????。

正确做法(命令行强制指定字符集):

# 创建数据库时显式指定字符集 mysql -u root -p -e "CREATE DATABASE hrms DEFAULT CHARACTER SET gbk COLLATE gbk_chinese_ci;" # 导入SQL(关键:--default-character-set=gbk) mysql -u root -p --default-character-set=gbk hrms < sql/hrms_db.sql

验证导入结果:

USE hrms; SHOW TABLES; -- 应看到 emp_info, dept_info, salary_record 等表 SELECT * FROM emp_info LIMIT 1; -- 检查中文字段是否正常显示

注意:若SQL脚本中含CREATE DATABASE ... CHARSET=utf8mb4语句,必须手动编辑删除该行,否则CREATE DATABASE会覆盖你刚建的gbk库。


3. 登录失败?数据库连不上?JSP页面空白?——人事管理系统启动阶段三大避坑清单

3.1 现象:访问login.jsp页面显示空白,F12看Network无任何HTTP响应

原因:JSP编译失败,Tomcat未生成.class文件,但错误日志被默认级别屏蔽
解决:

  1. 打开apache-tomcat-8.5.99/conf/logging.properties
  2. 将org.apache.jasper.level = WARNING改为org.apache.jasper.level = FINE
  3. 重启Tomcat,在logs/catalina.out末尾搜索JasperException,90%是<%@ page import="com.dao.UserDAO"%>路径错误或DAO类未编译
    →血泪经验:src/com/dao/UserDAO.java必须被javac编译成WEB-INF/classes/com/dao/UserDAO.class,检查WEB-INF/classes/下是否存在对应.class文件树

3.2 现象:输入账号密码点击登录,页面跳转到error.jsp且URL变为/hrms/servlet/LoginServlet

原因:web.xml中<servlet-mapping>的<url-pattern>与JSP中form action不一致
解决:
打开WebContent/login.jsp,找到<form action="servlet/LoginServlet" method="post">
再打开WEB-INF/web.xml,确认存在:

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/servlet/LoginServlet</url-pattern> <!-- 必须与form action完全一致 --> </servlet-mapping>

→关键细节:Tomcat 8.5对URL Pattern匹配极其严格,/servlet/LoginServlet不能写成/servlet/loginServlet(大小写敏感)

3.3 现象:登录后跳转到main.jsp,但员工列表区域显示“查询失败:Communications link failure”

原因:com.dao.BaseDAO.java中数据库连接URL写死为localhost,而MySQL服务实际运行在Docker容器或远程服务器
解决:

  1. 找到src/com/dao/BaseDAO.java,定位private static final String URL = "jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=gbk";
  2. 将localhost改为真实IP(如192.168.1.100),同时确保MySQL已授权远程访问:
CREATE USER 'hrms_user'@'%' IDENTIFIED BY 'StrongPass123!'; GRANT ALL PRIVILEGES ON hrms.* TO 'hrms_user'@'%'; FLUSH PRIVILEGES;
  1. 修改MySQL配置文件my.cnf,添加bind-address = 0.0.0.0并重启服务
    →后悔药:若无法改MySQL配置,可在本机hosts文件中加一行127.0.0.1 localhost,确保DNS解析不出错

4. 让系统真正可用:修改3个核心配置、补全2个安全漏洞、接入真实组织架构

4.1 修改数据库连接池——从直连驱动升级到DBCP,抗住200并发登录不崩

源码中BaseDAO.java几乎全是new Connection()硬编码,高并发下必然抛出Too many connections。必须替换为DBCP连接池:

  1. 下载commons-dbcp-1.4.jar和commons-pool-1.6.jar(Tomcat 8.5兼容版本),放入WEB-INF/lib/
  2. 在WEB-INF/web.xml中添加资源引用:
<resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/hrmsDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
  1. 修改conf/context.xml(在Tomcat conf目录下),追加:
<Resource name="jdbc/hrmsDB" auth="Container" type="javax.sql.DataSource" maxTotal="50" maxIdle="10" minIdle="5" username="hrms_user" password="StrongPass123!" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/hrms?useUnicode=true&amp;characterEncoding=gbk"/>
  1. 重写BaseDAO.java的getConnection()方法:
public static Connection getConnection() throws SQLException { Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/hrmsDB"); return ds.getConnection(); // 此处自动从池中取连接 }

提示:maxTotal="50"是经过压测的保守值,若服务器内存≥4G可升至80;minIdle="5"保证池中常驻5个空闲连接,避免首次请求延迟。

4.2 补全密码明文存储漏洞——用SHA-256盐值哈希替代password='123456'

源码中LoginServlet.java大概率直接比对request.getParameter("pwd")和数据库明文密码。修复步骤:

  1. 修改数据库emp_info表,将password字段类型改为VARCHAR(64)(SHA-256输出长度)
  2. 新增SaltUtil.java工具类:
public class SaltUtil { public static String getSalt() { return UUID.randomUUID().toString().replace("-", "").substring(0, 8); } public static String getSecurePassword(String passwordToHash, String salt) { String generatedPassword = null; try { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(salt.getBytes()); byte[] bytes = md.digest(passwordToHash.getBytes()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); } generatedPassword = sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return generatedPassword; } }
  1. 在RegisterServlet.java中注册逻辑改为:
String salt = SaltUtil.getSalt(); String securePwd = SaltUtil.getSecurePassword(pwd, salt); // 插入数据库时同时存 salt 和 securePwd 字段
  1. LoginServlet.java验证逻辑改为:
// 查询时取出 salt 和加密后密码 String dbSalt = rs.getString("salt"); String dbPwd = rs.getString("password"); String inputSecurePwd = SaltUtil.getSecurePassword(inputPwd, dbSalt); if (dbPwd.equals(inputSecurePwd)) { /* 登录成功 */ }

4.3 接入真实组织架构:用Excel批量导入部门/岗位数据,避免手敲200+条

人事系统最大痛点是初始数据录入。提供一个import_dept.jsp页面(可自行添加到WebContent/admin/下):

<%@ page contentType="text/html;charset=gbk"%> <%@ page import="java.io.*,java.util.*,org.apache.poi.ss.usermodel.*"%> <% if (request.getMethod().equals("POST")) { Part filePart = request.getPart("excelFile"); String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); InputStream fileContent = filePart.getInputStream(); Workbook workbook = WorkbookFactory.create(fileContent); Sheet sheet = workbook.getSheetAt(0); List<String[]> deptList = new ArrayList<>(); for (Row row : sheet) { if (row.getRowNum() == 0) continue; // 跳过标题行 String deptName = row.getCell(0).getStringCellValue().trim(); String deptCode = row.getCell(1).getStringCellValue().trim(); deptList.add(new String[]{deptCode, deptName}); } // 调用DAO批量插入 com.dao.DeptDAO dao = new com.dao.DeptDAO(); dao.batchInsert(deptList); out.print("导入成功:" + deptList.size() + "个部门"); } %> <form method="post" enctype="multipart/form-data"> <input type="file" name="excelFile" accept=".xlsx,.xls"> <input type="submit" value="导入部门"> </form>

配套DeptDAO.java中batchInsert方法:

public void batchInsert(List<String[]> deptList) throws SQLException { String sql = "INSERT INTO dept_info (dept_code, dept_name) VALUES (?, ?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (String[] dept : deptList) { ps.setString(1, dept[0]); ps.setString(2, dept[1]); ps.addBatch(); } ps.executeBatch(); } }

注意:需在WEB-INF/lib/加入poi-4.1.2.jar和poi-ooxml-4.1.2.jar,版本必须≤4.1.2(高版本依赖XMLBeans,Tomcat 8.5不兼容)。


5. 验证系统健壮性:用3种真实场景压力测试,揪出隐藏的内存泄漏与SQL注入点

5.1 场景一:连续100次登录登出,观察Tomcat内存是否持续上涨

这是检验Session管理是否规范的黄金测试。在LoginServlet.java中,登录成功后必须显式销毁旧Session:

HttpSession oldSession = request.getSession(false); if (oldSession != null) { oldSession.invalidate(); // 关键!防止Session堆积 } HttpSession session = request.getSession(true); session.setAttribute("user", user); session.setMaxInactiveInterval(1800); // 30分钟超时

测试脚本(Python requests模拟):

import requests session = requests.Session() for i in range(100): # 模拟登录 login_data = {'username': 'admin', 'password': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'} r1 = session.post('http://localhost:8080/hrms/servlet/LoginServlet', data=login_data) # 模拟登出(触发invalidate) r2 = session.get('http://localhost:8080/hrms/servlet/LogoutServlet') print(f"第{i+1}轮完成,响应码:{r1.status_code}/{r2.status_code}")

监控方式:启动Tomcat时加JVM参数-XX:+PrintGCDetails -Xloggc:gc.log,运行测试后用jstat -gc <pid>查看S0U/S1U(Survivor区使用量)是否稳定。若持续增长,说明invalidate()未生效,需检查LogoutServlet中是否漏掉session.invalidate()。

5.2 场景二:在员工搜索框输入' OR '1'='1,验证SQL注入防护

源码中EmployeeDAO.java的查询方法大概率是字符串拼接:

String sql = "SELECT * FROM emp_info WHERE name LIKE '%" + name + "%'"; // 危险!

正确修复必须用PreparedStatement:

String sql = "SELECT * FROM emp_info WHERE name LIKE ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + name + "%"); // 自动转义特殊字符 ResultSet rs = ps.executeQuery(); }

验证方法:

  1. 手动在搜索框输入' OR '1'='1
  2. 抓包看请求参数是否被原样发送
  3. 查看后台SQL日志(开启MySQL general_log):
SET GLOBAL general_log = 'ON'; SET GLOBAL general_log_file = '/var/log/mysql/general.log';

若日志中出现SELECT * FROM emp_info WHERE name LIKE '%\' OR \'1\'=\'1%',说明已转义成功;若出现...LIKE '%' OR '1'='1%',则仍存在注入。

5.3 场景三:上传头像文件,测试文件路径遍历与恶意脚本拦截

JSP系统常在UploadServlet.java中用request.getParameter("filename")拼接保存路径:

String filename = request.getParameter("filename"); File file = new File("D:/hrms/upload/" + filename); // 危险!

攻击者上传../../../webapps/ROOT/shell.jsp即可写入WebShell。修复方案:

// 1. 白名单校验文件扩展名 String ext = FilenameUtils.getExtension(filename); if (!Arrays.asList("jpg", "jpeg", "png", "gif").contains(ext.toLowerCase())) { throw new RuntimeException("不支持的文件类型"); } // 2. 使用UUID重命名,剥离原始文件名 String newFilename = UUID.randomUUID().toString() + "." + ext; String savePath = "D:/hrms/upload/" + newFilename; // 3. 关键:用File.getCanonicalPath()校验路径是否越界 File canonicalFile = new File(savePath).getCanonicalFile(); String uploadRoot = new File("D:/hrms/upload/").getCanonicalPath(); if (!canonicalFile.getPath().startsWith(uploadRoot)) { throw new RuntimeException("非法文件路径"); }

提示:FilenameUtils来自commons-io-2.6.jar,需加入lib目录;getCanonicalPath()会解析..符号,是防遍历的终极手段。


6. 我的三条铁律:从200+个JSP项目里熬出来的落地习惯,希望帮到你

6.1 每次修改web.xml,必须同步更新Tomcat的context.xml和server.xml中的context path

这是血泪教训。曾有个项目,web.xml里<display-name>HRMS v2.1</display-name>,但conf/server.xml中<Context path="/hrms-old"没改,导致静态资源404。后来我定下铁律:

  • 所有路径相关配置(<url-pattern>、<context-param>、<resource-ref>)必须用全局搜索Ctrl+Shift+F在三个XML文件中同步出现;
  • 建立path-check.sh脚本(Linux)或path-check.bat(Windows),每次提交前自动校验:
# Linux版 grep -r "path=" apache-tomcat-8.5.99/conf/ | grep -E "(hrms|HRMS)" && \ grep -r "url-pattern" hrms/WEB-INF/web.xml | grep -E "(hrms|HRMS)"

6.2 数据库字段命名必须与Java Bean属性名严格一致,宁可改MySQL也不改Java代码

见过太多项目为适配user_name字段,在User.java里写private String userName;再加@Column(name="user_name")——但JSP中<jsp:getProperty name="user" property="userName"/>会因getter方法名getUserName()与字段userName不匹配而取不到值。最终方案:

  • MySQL字段统一用驼峰(user_name→userName);
  • Java Bean属性名与数据库字段名1:1(private String userName;);
  • JSP中<jsp:getProperty property="userName"/>直接工作;
  • DAO层用ResultSetMetaData动态映射,避免硬编码列名。

6.3 日志必须分级:DEBUG只记录SQL参数,INFO记录业务事件,ERROR必须含堆栈和请求ID

Log4j在Tomcat 8.5中默认不启用,我坚持用java.util.logging并定制Handler:

public class HRMSLogger { private static final Logger logger = Logger.getLogger("HRMS"); public static void logSQL(String sql, Object... params) { if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder("SQL: ").append(sql).append(" | Params: "); for (Object p : params) sb.append(p).append(", "); logger.fine(sb.toString()); } } public static void logEvent(String event, HttpServletRequest req) { String requestId = req.getHeader("X-Request-ID"); if (requestId == null) requestId = UUID.randomUUID().toString(); logger.info(String.format("[%s] %s | IP: %s | User: %s", requestId, event, req.getRemoteAddr(), req.getSession().getAttribute("user"))); } }

在logging.properties中设置:

HRMS.level = INFO HRMS.handlers = java.util.logging.FileHandler java.util.logging.FileHandler.pattern = logs/hrms.%g.log java.util.logging.FileHandler.limit = 10000000 java.util.logging.FileHandler.count = 5

这样当用户投诉“薪资计算错误”时,我能用grep "薪资计算" logs/hrms.0.log | grep "2024-06-15"快速定位到具体请求链路,而不是翻三天日志。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询