简介本资源是一套面向Java Web初学者与高校计算机专业学生的实践型教学项目聚焦JSP技术栈构建企业级人事管理系统解决Web开发入门者缺乏完整MVC项目实战经验的问题。压缩包共含多个核心文件项目报告详述需求分析、数据库ER设计、Servlet-JSP协同逻辑及系统测试任务书明确开发目标与分工中英文翻译文档辅助理解源代码完整呈现Model-View-Controller三层结构涵盖员工信息管理、考勤、薪资计算等业务模块并集成JDBC数据库操作与基础前端交互。资源总计约1.11MB轻量易部署适合本地Tomcat环境快速运行学习。已有54人下载学习读者可直接复用源码结构、借鉴数据库设计范式、掌握JSP内置对象与Servlet生命周期实践同时通过报告反推软件工程全流程夯实Java Web开发基础能力。1. 这不是又一个“Hello World”JSP demo它真能跑通员工入职、考勤打卡、薪资核算全流程且所有代码可调试、所有SQL可执行、所有页面可本地访问你可能已经点开过十几个标着“JSP人事系统”的压缩包解压后发现index.jsp打不开、login.jsp报500错误、数据库脚本里写着CREATE DATABASE hrms CHARACTER SET gbk COLLATE gbk_chinese_ci——而你的MySQL 8.0默认是utf8mb4或者项目报告里写“采用MVC三层架构”但src目录下只有com.hrms.servlet和com.hrms.dao根本找不到com.hrms.model包又或者任务书说“支持绩效评估打分”可源码里连评分表结构都没建。这不是教学资源这是玄学考古现场。而这份「基于JSP的人事管理系统设计与实现」恰恰相反它用最朴素的Tomcat 7 MySQL 5.7组合把员工信息增删改查、部门/岗位树形管理、月度考勤统计含迟到早退自动标记、基本薪资计算岗位工资绩效系数×基础值、管理员权限分级HR专员仅看本部门超级管理员全局可见全部跑通在单机环境。它不炫技不套Spring Boot不加Redis缓存就靠原生JDBC Servlet JSP Bootstrap 3.3.7 jQuery 1.12.4把Java Web开发的“肌肉记忆”刻进每一行代码里。适合大三刚学完《Java Web程序设计》的学生做课程设计复现也适合转岗Java后端的测试/运维工程师补全Web层链路认知——因为你能看到request.getParameter()怎么漏掉空格校验导致SQL注入风险能看到session.setAttribute(user, user)之后为什么logout时必须调用session.invalidate()能看到web.xml里 1 对初始化连接池的真实影响。它不承诺“企业级高并发”但保证“你照着README部署完打开localhost:8080/hrms/login.jsp输入admin/123456就能进后台”。2. 从零启动Tomcat 7 MySQL 5.7 JDK 1.7 环境搭建与项目部署四步闭环2.1 为什么必须锁定JDK 1.7、Tomcat 7、MySQL 5.7这组“古董组合”这不是怀旧是兼容性刚需。项目源码中大量使用了Override标注接口方法如HttpServlet.doGet()而JDK 1.6不支持对接口方法的Overrideweb.xml头声明为web-app xmlnshttp://java.sun.com/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd version2.5这意味着它只兼容Servlet 2.5规范——Tomcat 8默认启用Servlet 3.0会忽略servletservlet-class中的类加载顺序导致自定义Filter如LoginFilter.java无法拦截未登录请求更关键的是数据库驱动源码DBUtil.java里写死Class.forName(com.mysql.jdbc.Driver)这是MySQL Connector/J 5.x的驱动类名而MySQL 8.0驱动已改为com.mysql.cj.jdbc.Driver且强制要求serverTimezone参数。若强行用新版环境你会在控制台看到java.lang.ClassNotFoundException: com.mysql.jdbc.Driver然后花两小时查Maven依赖——而实际上这个项目根本没用Maven所有jar都在WEB-INF/lib/下包括mysql-connector-java-5.1.18-bin.jar。所以我的建议是在Windows上用Chocolateychoco install openjdk17 jdk8不行必须1.7、macOS用SDKMANsdk install java 1.7.0-openLinux用update-alternatives切版本。Tomcat直接下载archive版apache-tomcat-7.0.109.zipMySQL选mysql-5.7.33-winx64.zipWindows或mysql-5.7.33-macos10.15-x86_64.dmgmacOS。别碰Docker镜像——那些预装环境往往默认UTF8编码而本项目SQL脚本用的是GBK。2.2 数据库初始化执行hrms.sql前必须做的三处手动修改项目提供的hrms.sql文件位于源码根目录非WEB-INF/下但它不能直接source hrms.sql。原因有三第一建库语句CREATE DATABASE hrms DEFAULT CHARACTER SET gbk COLLATE gbk_chinese_ci;在MySQL 5.7中会报错因为gbk_chinese_ci排序规则已被弃用。解决方案删掉COLLATE gbk_chinese_ci保留DEFAULT CHARACTER SET gbk即可第二用户授权语句GRANT ALL PRIVILEGES ON hrms.* TO hrmslocalhost IDENTIFIED BY hrms123;中密码hrms123长度不足8位MySQL 5.7默认开启validate_password插件会拒绝创建。执行前先运行SET GLOBAL validate_password.length 6;临时放宽策略第三employee表中entry_date字段类型为DATE但插入语句里写的是2020-01-01 00:00:00带时间MySQL会截断并警告。需将所有YYYY-MM-DD HH:MM:SS格式改为YYYY-MM-DD。提示我一般会用VS Code打开hrms.sql用正则替换(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}→$1再全局替换COLLATE gbk_chinese_ci为空。执行完再运行FLUSH PRIVILEGES;。2.3 项目部署war包生成与Tomcat context路径的硬编码陷阱源码未提供pom.xml因此不能用Maven打包。正确流程是将整个基于JSP的人事管理系统源代码文件夹重命名为hrms必须小写因web.xml中url-pattern/hrms/*/url-pattern已固定路径复制到$CATALINA_HOME/webapps/下启动Tomcatstartup.bat或./startup.sh访问http://localhost:8080/hrms/login.jsp。但这里有个致命细节项目中所有超链接都写死为hreflogin.jsp或actionservlet/LoginServlet这意味着它必须部署在ROOT上下文或与项目名同名的context下。如果你把文件夹命名为hrms_system然后访问http://localhost:8080/hrms_system/login.jsp登录后跳转会变成http://localhost:8080/servlet/LoginServlet404因为form action是相对路径浏览器会拼接到当前域名根路径。解决方法只有两个要么重命名文件夹为hrms要么全局搜索替换所有action和href中的路径——但login.jsp里有17处index.jsp里有23处emp_list.jsp里还有分页URL……太容易漏。所以我的血泪经验是部署前先执行find . -name *.jsp -exec sed -i s/action/action\/hrms\//g {} \;Linux/macOS或用Notepad批量替换。2.4 验证部署成功三个必测接口与对应日志定位点部署完成后不要急着登录先验证底层链路数据库连通性访问http://localhost:8080/hrms/test_db.jsp项目自带测试页若返回Connection Success!且显示当前时间则JDBC配置正确若报java.sql.SQLException: Access denied for user hrmslocalhost检查MySQL用户密码是否匹配DBUtil.java第18行private static final String PASSWORD hrms123;Servlet映射直接请求http://localhost:8080/hrms/servlet/DeptListServlet应返回JSON格式部门列表如[{id:1,name:技术部},{id:2,name:人事部}]若404则检查web.xml中servlet-mapping的url-pattern是否为/servlet/*且servlet-name与servlet-class一致静态资源访问http://localhost:8080/hrms/css/bootstrap.min.css应返回CSS内容若404检查hrms/css/路径是否存在且文件权限是否可读Linux/macOS执行chmod -R 644 hrms/css/。注意Tomcat日志在$CATALINA_HOME/logs/catalina.out启动失败时重点看Caused by:行登录失败时查localhost.date.log搜索LoginServlet关键字。3. 源码深度拆解从login.jsp到LoginServlet再到UserDAO看透JSPServletJDBC的请求生命周期3.1 login.jspJSP内置对象的典型误用与防御式写法login.jsp表面简单实则埋着三个新手高频翻车点第一% String msg request.getParameter(msg); %后直接% msg %输出若msg为null会打印null字符串。正确写法是% (msg null || msg.trim().isEmpty()) ? : msg %第二表单提交method为post但LoginServlet中只写了doPost()没写doGet()导致用户手动在地址栏输入/hrms/login.jsp?msgerror时触发405 Method Not Allowed。应在LoginServlet.java中补全doGet(HttpServletRequest req, HttpServletResponse resp)并调用doPost(req, resp)第三密码明文传输。虽然这是教学项目但input typepassword只是前端掩码抓包仍可见明文。真实项目必须加HTTPS教学环境至少应加% page contentTypetext/html;charsetGBK%确保中文不乱码并在web.xml中配置security-constraint强制HTTP重定向HTTPS本项目未配属合理简化。!-- login.jsp 关键片段 -- % page languagejava importjava.util.* pageEncodingGBK% % String msg request.getParameter(msg); // 防御式判空避免输出null msg (msg null || msg.trim().isEmpty()) ? : msg; % !DOCTYPE html html head meta charsetGBK title人事管理系统登录/title /head body div classalert alert-danger rolealert% msg %/div form actionservlet/LoginServlet methodpost input typetext nameusername required input typepassword namepassword required button typesubmit登录/button /form /body /html这段代码的关键在于pageEncodingGBK与meta charsetGBK必须严格一致否则中文用户名提交后在Servlet中request.getParameter(username)会乱码。JSP页面的字符编码由pageEncoding决定而浏览器渲染用meta两者错位就会出现“张三”变“å¼ ä¸‰”。3.2 LoginServlet从HTTP请求到Session建立的完整链路LoginServlet.java是整个MVC的控制器入口其doPost()方法执行流程如下调用request.setCharacterEncoding(GBK)——这是处理POST中文参数的唯一有效方式getParameter()前必须设获取username和password注意此处无任何加密密码以明文传入实例化UserDAO调用findUserByUsernameAndPassword(username, password)查询数据库若查到用户将User对象存入sessionsession.setAttribute(user, user)重定向至index.jsp。这里有个易被忽略的边界UserDAO.findUserByUsernameAndPassword()方法中SQL是拼接的String sql SELECT * FROM user WHERE username username AND password password ;这是典型的SQL注入漏洞。教学项目中故意保留此写法目的是让学习者意识到PreparedStatement的必要性。修复方案是将该方法改为String sql SELECT * FROM user WHERE username? AND password?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();提示UserDAO.java第42行开始的findUserByUsernameAndPassword方法就是练习PreparedStatement的最佳靶场。把原生拼接SQL注释掉换成上面的预编译写法再测试输入admin OR 11——修复前会登录成功修复后必然失败。3.3 UserDAO与DBUtilJDBC连接池缺失下的线程安全陷阱DBUtil.java是本项目的数据库工具类它用静态块加载驱动用静态方法getConnection()返回Connection。问题在于它没有连接池每次调用都新建连接且未关闭。查看UserDAO.java中findUserByUsernameAndPassword()方法末尾只有rs.close(); stmt.close();但conn.close()被注释掉了——作者留了个坑“此处conn不能close否则后续操作会报错”。这暴露了初学者对JDBC生命周期的误解Connection是线程不安全的若多个Servlet并发调用DBUtil.getConnection()可能拿到同一个被其他线程关闭的连接。真实解决方案是在DBUtil.getConnection()中增加try-with-resources包装或在UserDAO每个方法末尾都conn.close()并捕获SQLException更优解是引入commons-dbcp在web.xml中配置resource-ref用JNDI获取连接池。但本项目选择最简路径在DBUtil.java第35行getConnection()方法末尾添加conn.setAutoCommit(true);并在所有DAO方法中确保conn.close()被执行。我一般会这样改public static Connection getConnection() throws SQLException { try { Class.forName(DRIVER); Connection conn DriverManager.getConnection(URL, USER, PASSWORD); conn.setAutoCommit(true); // 显式开启自动提交 return conn; } catch (Exception e) { throw new SQLException(获取连接失败, e); } }然后在UserDAO.findUserByUsernameAndPassword()末尾补上} finally { if (rs ! null) rs.close(); if (stmt ! null) stmt.close(); if (conn ! null !conn.isClosed()) conn.close(); // 安全关闭 }3.4 index.jspJSTL标签库的零配置接入与EL表达式失效排查index.jsp用了JSTLc:forEach遍历部门列表但项目未提供jstl.jar和standard.jar。这是因为Tomcat 7自带el-api.jar但JSTL需手动添加。解决方案下载jstl-1.2.jar注意是1.2非1.2.5因Servlet 2.5只支持JSTL 1.2放入WEB-INF/lib/在JSP顶部添加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。若仍报org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved说明URI不匹配。此时检查web.xml是否声明了web-app版本为2.5且jsp-config中未禁用EL。本项目web.xml无jsp-config故只需确认JAR包正确。另一个常见问题是EL表达式${user.username}不解析显示为字面量。原因通常是% page isELIgnoredtrue %被误设。检查所有JSP顶部确保没有该指令或显式设为isELIgnoredfalse默认即false。4. 常见问题排查五个真实踩坑记录与一行命令修复方案4.1 现象登录后跳转到空白页浏览器地址栏显示http://localhost:8080/hrms/index.jsp但页面无任何HTML内容原因index.jsp中c:forEach items${deptList} vardept的deptList在DeptListServlet中未正确存入request域。查看DeptListServlet.java第28行request.setAttribute(deptList, list);写成了request.setAttribute(deptlist, list);变量名大小写不一致。EL表达式${deptList}找不到对应keyc:forEach不执行导致循环体内的HTML不渲染。解决统一变量名将request.setAttribute(deptlist, list);改为request.setAttribute(deptList, list);并检查所有JSP中引用该变量的地方。4.2 现象添加新员工时姓名、部门等字段保存成功但entry_date入职日期始终为0000-00-00原因EmpAddServlet.java中获取日期参数用的是request.getParameter(entry_date)但HTML表单中日期输入框input typedate在IE/Edge中可能返回空字符串而MySQLDATE类型无法接受空字符串JDBC驱动将其转为0000-00-00。解决在EmpAddServlet.java中增加空值判断String entryDateStr request.getParameter(entry_date); java.sql.Date entryDate null; if (entryDateStr ! null !entryDateStr.trim().isEmpty()) { SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); try { java.util.Date utilDate sdf.parse(entryDateStr); entryDate new java.sql.Date(utilDate.getTime()); } catch (ParseException e) { // 日期格式错误设为今天 entryDate new java.sql.Date(System.currentTimeMillis()); } } // 后续setDate(3, entryDate)时entryDate可能为nullMySQL允许DATE NULL4.3 现象考勤页面attendance.jsp中c:forEach遍历attList正常但每条记录的“状态”列显示null而非“正常/迟到/早退”原因AttendanceDAO.java中查询SQL为SELECT *, CASE WHEN late_time 0 THEN 迟到 ... END AS status FROM attendance但ResultSet.getString(status)在MySQL驱动中无法识别AS别名必须用getString(8)假设status是第8列或getString(CASE WHEN late_time 0 THEN 迟到 ... END)。解决修改SQL给CASE语句起明确别名SELECT *, CASE WHEN late_time 0 THEN 迟到 WHEN leave_early_time 0 THEN 早退 ELSE 正常 END AS att_status FROM attendance然后在AttendanceDAO.java中用rs.getString(att_status)获取。4.4 现象修改员工信息后页面跳转回emp_list.jsp但列表未更新仍显示旧数据原因EmpUpdateServlet.java执行更新后重定向代码为response.sendRedirect(emp_list.jsp)但浏览器缓存了emp_list.jsp的GET响应。按F5刷新才看到新数据。解决在emp_list.jsp顶部添加禁止缓存头% response.setHeader(Cache-Control, no-cache, no-store, must-revalidate); response.setHeader(Pragma, no-cache); response.setDateHeader(Expires, 0); %4.5 现象部署到Linux服务器后中文菜单显示为方框□□□但数据库查询结果正常原因Tomcat启动脚本catalina.sh中未设置JVM参数-Dfile.encodingUTF-8导致JSP编译时用系统默认编码如ISO-8859-1解析GBK编码的JSP文件。解决编辑$CATALINA_HOME/bin/catalina.sh在JAVA_OPTS行追加JAVA_OPTS$JAVA_OPTS -Dfile.encodingUTF-8同时确认login.jsp等文件本身是GBK编码用file -i login.jsp检查若为UTF-8则需同步修改pageEncodingUTF-8。5. 进阶技巧用Chrome DevTools逆向分析JSP页面行为快速定位前端逻辑缺陷5.1 通过Network面板捕获AJAX请求验证DeptListServlet返回数据真实性index.jsp中部门下拉框是通过AJAX加载的$.get(servlet/DeptListServlet, function(data) { var select $(#deptSelect); $.each(data, function(i, dept) { select.append(option valuedept.iddept.name/option); }); });但实际运行时下拉框为空。此时打开Chrome DevTools → Network选项卡 → 刷新页面 → 找到DeptListServlet请求 → 查看Response。若返回[]空数组说明后端未查到数据若返回{error:db connection failed}说明数据库连接失败若返回[{id:1,name:技术部}]但前端没渲染则问题在JS。此时在Console中手动执行$.get(servlet/DeptListServlet)观察Promise状态。若报SyntaxError: Unexpected token in JSON at position 0说明后端返回了HTML如500错误页而非JSON——这通常是因为DeptListServlet中response.setContentType(application/json;charsetGBK)写成了text/html或PrintWriter未设置编码。修复方法是在DeptListServlet.java的doGet()开头添加response.setContentType(application/json;charsetGBK); response.setCharacterEncoding(GBK); PrintWriter out response.getWriter();5.2 用Elements面板实时修改DOM测试权限控制逻辑的健壮性项目声称“HR专员只能查看本部门员工”但emp_list.jsp中员工列表是服务端渲染的前端无权限过滤。为验证服务端是否真做过滤可手动修改HTML在Elements面板中找到员工表格table idempTable右键Edit as HTML将某行tr>String sql SELECT * FROM employee WHERE dept_id ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, ((User)session.getAttribute(user)).getDeptId());5.3 用Console执行JavaScript绕过前端校验测试后端安全性emp_add.jsp中手机号输入框有HTML5校验input typetel pattern^1[3-9]\d{9}$ required但这只是前端提示。攻击者可禁用JS或直接发POST请求绕过。测试方法在Console中执行fetch(servlet/EmpAddServlet, { method: POST, headers: {Content-Type: application/x-www-form-urlencoded}, body: name张三phoneabcdept_id1entry_date2023-01-01 }).then(r r.text()).then(console.log);若返回success说明后端未校验手机号格式此时应修改EmpAddServlet.java在doPost()中增加String phone request.getParameter(phone); if (phone null || !phone.matches(^1[3-9]\\d{9}$)) { request.setAttribute(msg, 手机号格式错误); request.getRequestDispatcher(emp_add.jsp).forward(request, response); return; }5.4 用Application面板导出localStorage分析用户登录态持久化机制本项目用session.setAttribute(user, user)存储用户信息但未实现“记住我”功能。若想扩展需结合Cookie登录成功后生成Token如UUID.randomUUID().toString()将Token存入数据库user_token表关联user_id设置CookieCookie cookie new Cookie(rememberMe, token); cookie.setMaxAge(60*60*24*7); response.addCookie(cookie);在LoginFilter.java中若session.getAttribute(user) null则读取Cookie中的Token查库恢复用户。从那以后我每次复现老项目都强制走一遍Chrome DevTools的Network→Console→Elements三板斧Network看请求是否发出、Console看JS是否报错、Elements看DOM是否被篡改。这比盯着日志猜三天更高效。希望帮到你。本文还有配套的精品资源点击获取