JSP人事管理系统源码解析:从MVC分层到部署排错
2026/9/15 17:07:08 网站建设 项目流程

简介:基于JSP的企业人事管理系统毕业设计项目,面向Java Web初学者与毕业设计学生,提供从需求分析到编码实现的完整参考。系统覆盖用户登录与权限管理、人事档案、考勤、薪酬福利、绩效考核、培训发展和报表统计等模块,采用JSP+Servlet+JavaBean的MVC分层思想,搭配MySQL数据库,适合用来理解真实业务系统的开发全流程。压缩包共277个文件,大小仅3.18MB,以104个JSP页面、14个Java源码、28个class编译文件和16个CSS样式文件为核心,并包含JPG/GIF图片素材、SQL数据库文件、项目文档及配置文件,结构清晰、便于对照学习。目前已有51人学习下载。借助这份资源,读者可获得可直接运行的工程源码和详尽的项目报告,报告涵盖需求分析、数据库设计、功能实现与测试优化等关键内容,能有效提升Web项目开发与文档撰写能力。

1. JSP人事管理系统到底怎么从源码包立起来

拿到手一个“基于JSP的企业人事管理系统(源代码+项目报告).zip”,解压之后大概率和很多课程设计包一样:一堆.java、.class、JSP页面、lib目录里躺着几个jar包,项目报告里则是从需求分析一路写到测试的PPT式文档。这个项目的价值不在“企业级”,而在“完整闭环”——它把JSP+Servlet+JDBC+MySQL这套老牌Java Web技术栈从头到尾走了一遍,对于正在做毕业设计、或者想补JSP/Servlet底层原理的开发者来说,是一个能拆开看内部结构的标本。

说几个这个项目里值得盯住的点:AdminServlet和AdminBean暴露了它的分层方式——Servlet做控制层,JavaBean做业务/数据模型,JSP只负责渲染页面;UserForm这个类名则说明系统里用了表单对象封装前端提交的数据。这让项目天然是教科书式的MVC形态,但也正是这种形态,让“配置文件写在哪”“Session在过滤器里怎么拿”“数据库连接用不用池”这些坑全部暴露出来。适合人群很明确:Java Web初学者、要做课设/毕设的学生、以及想找一个“麻雀虽小五脏俱全”的代码库来复盘老技术的人。

2. Servlet与JSP的协作机制:从class文件反推系统骨架

2.1 项目包里的类名暴露了MVC分层结构

解压源码包后,你会看到类似这样的文件列表:

src/com/company/admin/AdminBean.java src/com/company/admin/AdminServlet.java src/com/company/user/UserForm.java src/com/company/user/UserServlet.java WebRoot/WEB-INF/web.xml WebRoot/login.jsp WebRoot/admin/main.jsp

可以看到AdminBean、AdminServlet、UserForm这样的类名组合。简单说:AdminBean是管理员的数据模型,通常对应admin表的一行记录;AdminServlet是管理员相关请求的入口;UserForm是前端表单提交数据的封装类,用来接收JSP页面传过来的输入参数。

这种命名方式不是随意的。它隐含了JSP/Servlet时代的经典分工:JSP页面负责把数据展示成HTML,Servlet负责接收HttpServletRequest、调用业务逻辑、再转发或重定向到JSP页面,JavaBean负责承载数据并可能封装一部分业务方法。后台管理人员登录、员工信息增删改查、考勤记录登记,这些操作都走“JSP提交表单 → Servlet拿到参数 → 操作数据库 → 返回结果页面”这条链路。

2.2 web.xml中的Servlet映射与load-on-startup

要在Tomcat里让Servlet生效,web.xml的配置是关键。如果项目用的是注解方式(@WebServlet),web.xml里可以不放Servlet声明;但很多课设项目还是传统写法,配置示例如下:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>HRMS</display-name> <servlet> <servlet-name>AdminServlet</servlet-name> <servlet-class>com.company.admin.AdminServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>AdminServlet</servlet-name> <url-pattern>/admin/login</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>

逻辑说明:<servlet-class>必须写全限定类名,路径要和src下的包结构一致;<url-pattern>是浏览器访问的路径,比如http://localhost:8080/hrms/admin/loginload-on-startup值设为1表示服务器启动时就初始化这个Servlet,适合登录入口这种最先用到的控制器。若项目里用了@WebServlet注解,web.xml则只保留欢迎页和过滤器等配置。

2.3 JSP页面中的Java代码为什么越少越好

很多初学者直接把Java代码写在JSP里,比如在页面顶部用<% ... %>写循环和数据库查询。这个项目在不同页面里的写法可能并不统一——报告里强调MVC,但局部JSP里仍可能藏着脚本片段。JSP编译成Servlet后,这些脚本片段会出现在_jspService()方法体内,逻辑复杂时页面会变得难以维护,排错时也更容易定位到错误行。

正确姿势是在Servlet里完成业务处理,把结果放进request或session作用域,再转发给JSP显示:

request.setAttribute("empList", employeeDao.findAll()); request.getRequestDispatcher("/admin/employee_list.jsp").forward(request, response);

在JSP中只用EL表达式和JSTL取值:

<c:forEach var="emp" items="${empList}"> <tr> <td>${emp.empNo}</td> <td>${emp.name}</td> <td>${emp.dept}</td> </tr> </c:forEach>

逻辑说明:Servlet中把查询结果放到request作用域后通过forward转发,JSP侧用${empList}取到集合,<c:forEach>配合var属性迭代输出员工列表。注意items对应的是Servlet里设置的attribute名,必须一致;emp.empNo会调用UserForm或EmployeeBean的getEmpNo()方法,不要手写数据库字段名去对。这样做的好处是页面标签清晰,后端改了查询逻辑不影响页面结构。

3. 数据库设计与JDBC持久层封装

3.1 五张核心表的关系和字段约束

人事管理系统无论做成什么样子,表设计都是最见功底的部分。这个项目的报告里通常给出了E-R图,转为建表语句后核心表可以归结为五张:用户表(管理员)、员工表、考勤表、薪资表、培训表。员工表和用户表分开是必要的——员工可能是被管理人员,管理员是系统操作者,二者登录权限不能混用。

举个例子,员工表与考勤表、薪资表就是一对多关系。员工表的主键成为考勤表和薪资表的外键,这样设计能确保一条考勤记录或薪资记录必然指向一个真实存在的员工。建议建表时加外键约束,并且在emp_id列上建索引,避免后期数据量大时连表查询变慢。

CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID', emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '工号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender CHAR(1) DEFAULT '男' COMMENT '性别', dept VARCHAR(50) COMMENT '部门', position VARCHAR(50) COMMENT '职位', hire_date DATE COMMENT '入职日期', phone VARCHAR(20) COMMENT '联系电话' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, work_date DATE NOT NULL, check_in_time DATETIME, check_out_time DATETIME, status TINYINT DEFAULT 1 COMMENT '1正常 2迟到 3早退 4请假 5缺勤', FOREIGN KEY (emp_id) REFERENCES employee(emp_id), UNIQUE KEY uk_emp_date (emp_id, work_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考勤表';

参数说明:AUTO_INCREMENT用于自增主键,COMMENT为字段注释,UNIQUE KEY uk_emp_date (emp_id, work_date)保证同一天一名员工只能有一条考勤记录,避免重复打卡数据。外键约束在插入数据时会校验emp_id是否存在于employee表,如果报错Cannot add or update a child row,说明员工ID填错了,先查employee表确认。utf8mb4编码能存中文和特殊符号,比utf8更稳。

3.2 JDBC连接池:DBCP还是C3P0

大多数课设项目直接DriverManager.getConnection(),但这套写法在高并发下必挂。数据库连接的创建和销毁很耗时,如果系统里同时有几百人登录,每条查询都新建连接,数据库瞬间就会被拖垮。项目报告里如果没写连接池,建议实现时自己加上,这是答辩时能加分的点。

DBCP配置方式相对简单,适合老项目改造:

BasicDataSource ds = new BasicDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8"); ds.setUsername("root"); ds.setPassword("123456"); ds.setInitialSize(5); ds.setMaxTotal(20); ds.setMaxWaitMillis(3000);

逻辑说明:setInitialSize(5)表示启动时预创建5个空闲连接;setMaxTotal(20)是连接池中最大连接数;setMaxWaitMillis(3000)是连接不够时最多等3秒,超时即抛异常。实际项目里不要用root用户连数据库,最好单独建一个账号只授予hrms库的增删改查权限,避免SQL注入时把整库拖走。

这之后DAO层写法就统一了:

public class EmployeeDao { private BasicDataSource ds; public EmployeeDao(BasicDataSource ds) { this.ds = ds; } public List<Employee> findAll() throws SQLException { String sql = "SELECT emp_id, emp_no, name, dept, position FROM employee ORDER BY emp_id"; List<Employee> list = new ArrayList<>(); try (Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Employee emp = new Employee(); emp.setEmpId(rs.getInt("emp_id")); emp.setEmpNo(rs.getString("emp_no")); emp.setName(rs.getString("name")); emp.setDept(rs.getString("dept")); emp.setPosition(rs.getString("position")); list.add(emp); } } return list; } }

说明:try-with-resources写法让Connection、PreparedStatement、ResultSet在每次执行完后自动关闭,防止连接泄漏。rs.getXxx("列名")用的是SQL中查出来的列名而不是表原名,如果你用了SELECT emp_id AS id,这里就得换成rs.getInt("id")。常见报错是Column 'xxx' not found,对照SQL语句检查列名是否拼写一致即可。

4. 登录鉴权、考勤统计与薪酬计算实战

4.1 用Filter统一拦截未登录请求

用户权限控制是人事管理系统的核心安全点。如果只在每个Servlet里判断session,很容易遗漏某个入口;更合理的做法是写一个Filter,在请求到达Servlet之前统一检查。注意放行登录页、静态资源和错误页,避免死循环跳转。

@WebFilter(urlPatterns = "/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; HttpSession session = req.getSession(false); String uri = req.getRequestURI(); if (uri.endsWith("login.jsp") || uri.contains("/login") || uri.endsWith(".css") || uri.endsWith(".js")) { chain.doFilter(request, response); return; } if (session != null && session.getAttribute("admin") != null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() + "/login.jsp"); } } }

参数说明:urlPatterns="/*"拦截所有请求;req.getSession(false)不会自动创建新session,避免页面被非法刷新时凭空生成一大堆无用的Session对象;req.getContextPath()用来拼接项目根路径,这样即使部署时改了应用名也不会跳错地方。拦截到未登录请求时,用sendRedirect跳到登录页,注意这里不能用forward,否则浏览器地址栏不会变化,刷新时会重复提交表单。

4.2 考勤数据统计的边界情况处理

考勤模块的逻辑集中在计算“迟到/早退/加班”上。项目中通常会有一张attendance表,包含上班打卡时间、下班打卡时间和日期状态。计算工时最稳妥的方式是把打卡时间转换成时间戳后再相减,而不是直接拿字符串做减法,否则跨天打卡(晚上23点打上班卡,凌晨2点打下班卡)就会出现负数。

public double calculateWorkHours(Date checkIn, Date checkOut) { if (checkIn == null || checkOut == null) { return 0.0; } long diff = checkOut.getTime() - checkIn.getTime(); double hours = diff / (1000.0 * 60 * 60); if (hours < 0) { hours += 24; } return Math.round(hours * 100.0) / 100.0; }

参数说明:getTime()返回从1970年1月1日00:00:00以来的毫秒数;diff / (1000.0 * 60 * 60)得到小时数,保留两位小数;如果跨天打卡,hours会是负数,加24得到实际时长。这个逻辑没有考虑用餐休息扣减等规则,正式系统还需要配置班次表。判断迟到时不要拿“字符串大于9:00”去比,格式化时区不同会造成误判,最稳妥的比较方式是calendar.set(Calendar.HOUR_OF_DAY, 9),然后把打卡时间与这个Calendar对象比较。

4.3 薪资计算的公式拆分方法

薪酬福利管理是最容易出逻辑错的模块。基本工资、绩效奖金、社保扣除、个税计算各有独立公式,把它们混合写在一个DAO方法里会很难测试。这块在人事管理系统的项目报告里通常会单独画一个计算流程图。建议把薪资计算拆成三个步骤:先算应发工资(基本工资+绩效+奖金),再算扣除项(社保+公积金),最后算实发(应发-扣除-个税)。

public double calcNetPay(double baseSalary, double performance, double bonus, double socialInsurance, double housingFund) { double grossPay = baseSalary + performance + bonus; double deductions = socialInsurance + housingFund; double taxableIncome = grossPay - deductions - 5000; double tax = 0.0; if (taxableIncome > 0) { // 简化版个税计算:超出部分按10%征收,速算扣除210 tax = taxableIncome * 0.10 - 210; } return grossPay - deductions - tax; }

参数说明:grossPay为应发工资合计,deductions为五险一金个人扣款合计,taxableIncome是扣除起征点5000元后的应纳税所得额。这里的个税算法是简化的,2024年标准下工资薪金所得是累进税率,3%档适用于应纳税所得额不超过3000元的部分,10%档对应3000-12000元,公式中的速算扣除数并不能完全套到所有收入区间。毕业设计里建议写一个专用的TaxCalculator类,把每个税率档位用数组存起来,循环判断落在哪个区间,这样报告里能展示算法设计,不会在答辩时被追问“你的税算错了”。

5. 部署到Tomcat后验证与三个高频排错点

最后落在整个系统的运行验证上。把项目放到Tomcat时,最直接的验证路径是:登录页面能打开 → 用报告里预设的管理员账号登录 → 成功跳转到后台主页 → 能对一个员工做新增和查询。如果中途失败,排错优先级是:先看启动日志,再看数据库连没连上,最后查JSP编译错误。

第一个高频坑是数据库驱动版本不一致。项目lib目录里如果放的是mysql-connector-java-5.1.x.jar,而本机MySQL是8.0+,连接时会报Public Key Retrieval is not allowed。应对方法是在JDBC URL上追加allowPublicKeyRetrieval=true&useSSL=false,这两个参数是MySQL 8.0之后才必须显式指定的。useSSL=false是因为本地开发没有配置证书,如果连的是云端数据库且要求加密传输,这条要改成useSSL=true并配置证书,否则可能被服务端拒绝。

第二个高频坑是JSP编译后的class文件找不到或更新不及时。Tomcat会在work/Catalina/localhost/项目名/org/apache/jsp目录下存放JSP编译出的Java和class文件。修改JSP后如果刷新页面仍显示旧内容,去这个目录里看对应文件的时间戳是否更新了;如果目录不存在,说明JSP根本没有被访问触发编译。处理办法是先clean一下Tomcat的work目录再重启,或者确认Tomcat的conf/web.xmldevelopment参数没有被改成false,那个参数控制是否检测JSP文件变化并重新编译。

第三个高频坑是页面跳转路径404。因为Servlet映射和JSP物理路径不在同一个目录,常见的错误是response.sendRedirect("admin/main.jsp")少了项目上下文。统一做法是写一个工具方法:

public static String path(HttpServletRequest req, String page) { return req.getContextPath() + page; }

调用时:resp.sendRedirect(path(req, "/admin/main.jsp")),这样无论部署到哪个Tomcat,应用名都不影响跳转路径。浏览器F12看一下网络面板,404出现的请求地址和项目实际路径对比,能快速确认是少了一层目录还是拼错了文件名。以上三步排查完,这个基于JSP+Servlet的人事管理系统基本就能在本地稳定跑起来了。

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

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

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

立即咨询