
简介这份资源是面向高校计算机相关专业毕业生的人事管理系统毕业设计完整资料包包含可运行的JSP源代码与配套论文适合正在准备毕设、需要参考企业级信息管理项目实现的学生及Java Web初学者。压缩包共140个文件约2.21MB以52个jsp页面文件、22张jpg与20张gif界面素材、14个class与7个java源码文件为主另含8个css样式、4份doc文档及xml、js等配置脚本覆盖前端页面、业务逻辑与数据库设计各层。论文部分从人事管理信息系统初步调查入手详细阐述需求分析、数据流程分析、系统总体结构设计、数据结构与数据库设计及输入输出设计源代码则实现了登录、用户表单、管理后台等核心模块。目前已有82人学习下载读者可借此获得一套结构完整的毕设参考方案用于理解JSP开发流程、梳理论文写作框架并对照改进自身项目。1. 企业人事管理系统JSP 毕设里最容易被低估的硬骨头很多同学拿到「基于JSP的企业人事管理系统设计」这个题目时第一反应是“老掉牙的技术栈随便糊弄一下就能过”。但真正动手做过的都知道这套系统里藏着不少能让你卡上两三天甚至一周的细节——从 JSP 页面里表单数据的乱码处理到员工照片上传后的路径映射再到不同角色登录后菜单权限的动态渲染每一个点单拎出来都不算难凑在一起就是一台“翻车制造机”。这篇文章面向的是正在做计算机毕业设计、选了 JSP 技术路线的同学也适合想用一套完整业务系统练手 Java Web 基础的开发者。我会按实际开发顺序把企业人事管理系统从数据库设计到核心模块实现再到部署排错的完整路径拆开讲代码可以直接抄改参数会说明为什么这么设。读完你至少能拿到一套能跑起来、能答辩、能写进论文的系统骨架。2. 先把数据库和项目骨架定下来别急着写 JSP2.1 企业人事管理系统的数据表怎么切分才不返工很多同学一上来就打开 Eclipse 或 IDEA 新建 JSP 文件写到一半发现部门表和员工表的关系没理清又回头改数据库改完数据库发现实体类对不上实体类改完发现 JSP 页面里取的字段名全错了。这种返工链条的根源就是数据库设计没前置。企业人事管理系统的核心实体其实不多但关系要理清楚。我一般会切成这几张表部门表department、职位表position、员工表employee、用户表user、考勤记录表attendance、薪资表salary。其中员工表和部门表是多对一员工表和职位表也是多对一用户表和员工表是一对一用于登录考勤和薪资都是员工的一对多。建表时有两个容易忽略的点。第一员工表里不要直接存部门名称和职位名称只存 department_id 和 position_id展示的时候用 JOIN 查出来。第二用户表要单独建不要把登录密码塞进员工表否则后面做权限控制时你会后悔。下面是我常用的建表 SQL以 MySQL 为例-- 部门表只存最基础的层级信息 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, dept_desc VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 职位表和部门弱关联一个部门可以有多个职位 CREATE TABLE position ( pos_id INT PRIMARY KEY AUTO_INCREMENT, pos_name VARCHAR(50) NOT NULL, dept_id INT, base_salary DECIMAL(10,2), FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 员工表核心表注意字段长度和默认值 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name VARCHAR(30) NOT NULL, gender CHAR(2) DEFAULT 男, birth_date DATE, phone VARCHAR(20), email VARCHAR(50), dept_id INT, pos_id INT, hire_date DATE, photo_path VARCHAR(200), status TINYINT DEFAULT 1 COMMENT 1在职 0离职, FOREIGN KEY (dept_id) REFERENCES department(dept_id), FOREIGN KEY (pos_id) REFERENCES position(pos_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户表登录用和员工表一对一 CREATE TABLE user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT employee COMMENT admin/hr/employee, emp_id INT, FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字符集统一用 utf8mb4不要用 utf8否则员工姓名里如果有生僻字会存不进去。密码字段给 64 位是留了余地后面如果做 MD5 或 SHA-256 加密长度够用。status 字段用 TINYINT 而不是 BOOLEAN方便后面扩展更多状态比如试用期、停薪留职。2.2 JSP Servlet JDBC 的分层结构怎么搭数据库定好之后项目结构要按分层来。我见过太多毕设项目把所有 Java 代码堆在 src 根目录下JSP 页面散落在 WebContent 各个角落最后自己都找不到文件。推荐的结构是这样的src/ com.hr.entity/ -- 实体类和数据库表一一对应 com.hr.dao/ -- 数据访问层只做增删改查 com.hr.service/ -- 业务逻辑层处理事务和复杂判断 com.hr.servlet/ -- 控制层接收请求、调 service、跳转页面 com.hr.util/ -- 工具类数据库连接、编码过滤、分页等 WebContent/ css/ js/ images/ WEB-INF/ web.xml lib/ -- 放 mysql-connector-java.jar 等依赖 admin/ -- 管理员页面 employee/ -- 普通员工页面 login.jsp index.jsp这个结构的好处是改数据库字段时只需要动 entity 和 daoJSP 页面里用的属性名通过 EL 表达式取值不会因为表结构微调就大面积崩。Servlet 里不要写 SQLDAO 里不要写 request.getParameter各层职责分明答辩的时候老师问起来也能说清楚。数据库连接用 JDBC 直接连就行毕设不需要上连接池虽然 Druid 也不复杂但多一个配置文件就多一个出错点。写一个 DBUtil 类把驱动加载、连接获取、资源关闭封装好public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/hr_system?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭每个都 try-catch不要图省事只写一个 } }URL 里的 characterEncoding 要写 utf8mb4serverTimezone 必须加否则 MySQL 8.x 会报时区错误。这个坑每年都有大量同学踩现象是“明明数据库里有数据Java 查出来就是空”其实就是时区没配对导致连接失败但异常被吞了。3. 核心模块逐个击破登录、员工 CRUD、权限控制3.1 登录模块从 JSP 表单到 Session 的完整链路登录是企业人事管理系统的入口也是答辩时老师必看的功能。很多同学写登录只写一个 login.jsp 和一个 LoginServlet验证完密码就跳转结果后面每个页面都要重新验证一遍用户有没有登录代码重复得让人头皮发麻。正确的做法是用 Filter 做统一拦截。先写登录的 ServletWebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao userDao new UserDao(); User user userDao.findByUsername(username); if (user null) { req.setAttribute(msg, 用户名不存在); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 实际项目里密码要加密比对毕设至少做个 MD5 if (!user.getPassword().equals(MD5Util.encode(password))) { req.setAttribute(msg, 密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(currentUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 // 按角色跳转到不同首页 if (admin.equals(user.getRole())) { resp.sendRedirect(req.getContextPath() /admin/index.jsp); } else { resp.sendRedirect(req.getContextPath() /employee/index.jsp); } } }这里有几个参数要说明。setMaxInactiveInterval 设 1800 秒是毕设的合理值太短了演示时容易掉线太长了不安全。密码比对用 MD5 是为了演示加密流程实际生产要用 BCrypt但毕设用 MD5 足够论文里可以写“采用 MD5 摘要算法存储密码避免明文泄露”。然后是登录拦截 FilterWebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 放行登录页、登录接口、静态资源 if (uri.endsWith(login.jsp) || uri.endsWith(/login) || uri.contains(/css/) || uri.contains(/js/) || uri.contains(/images/)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if (session null || session.getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }Filter 的 urlPattern 写 /* 表示拦截所有请求然后在代码里手动放行白名单。注意 getSession(false) 不要传 true否则每个未登录请求都会创建一个新 Session白白消耗内存。3.2 员工信息 CRUD分页查询和文件上传的落地细节员工管理模块是企业人事管理系统里代码量最大的部分包含列表展示、新增、编辑、删除、搜索、分页。其中分页和文件上传是两个最容易出问题的地方。先看分页查询。不要用 SELECT * FROM employee 一次性查出来再在 Java 里 subList数据量一大就内存溢出。正确做法是用 LIMIT 在数据库层面分页public ListEmployee findByPage(int pageNum, int pageSize, String keyword) { ListEmployee list new ArrayList(); StringBuilder sql new StringBuilder( SELECT e.*, d.dept_name, p.pos_name FROM employee e LEFT JOIN department d ON e.dept_id d.dept_id LEFT JOIN position p ON e.pos_id p.pos_id WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND e.emp_name LIKE ? ); params.add(% keyword.trim() %); } sql.append(ORDER BY e.emp_id DESC LIMIT ?, ?); params.add((pageNum - 1) * pageSize); params.add(pageSize); // 执行查询封装 ResultSet 到 Employee 对象 // ... return list; }pageNum 从 1 开始pageSize 一般设 10 或 15。LIMIT 的第一个参数是偏移量计算方式是 (pageNum - 1) * pageSize这个公式写错的话第一页会跳过数据或者最后一页出现空记录。总记录数要单独写一个 count 方法SQL 是 SELECT COUNT(*) FROM employee WHERE 同样的条件不要用 list.size() 代替。文件上传员工照片是另一个高频翻车点。JSP 表单必须加 enctypemultipart/form-dataServlet 里不能用 request.getParameter 直接取普通字段要用 Servlet 3.0 的 Part APIWebServlet(/employee/add) MultipartConfig(maxFileSize 2 * 1024 * 1024, maxRequestSize 5 * 1024 * 1024) public class EmployeeAddServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String empName req.getParameter(empName); String phone req.getParameter(phone); // ... 其他字段 Part photoPart req.getPart(photo); String photoPath null; if (photoPart ! null photoPart.getSize() 0) { String fileName System.currentTimeMillis() _ photoPart.getSubmittedFileName(); String saveDir getServletContext().getRealPath(/upload/photo); File dir new File(saveDir); if (!dir.exists()) dir.mkdirs(); photoPart.write(saveDir File.separator fileName); photoPath upload/photo/ fileName; } Employee emp new Employee(); emp.setEmpName(empName); emp.setPhotoPath(photoPath); // ... 调 service 保存 } }MultipartConfig 的 maxFileSize 设 2MB 是合理的员工照片不需要太大。getSubmittedFileName 在 Servlet 3.1 才有如果用的 Tomcat 7 要换成 part.getHeader(content-disposition) 手动解析。保存路径用 getRealPath 拿到的是项目部署目录重启 Tomcat 后上传的文件可能会丢毕设演示时要注意这一点或者把文件存到项目外的固定目录。3.3 角色权限控制菜单动态渲染和 URL 级拦截企业人事管理系统一般有三种角色管理员admin、人事专员hr、普通员工employee。管理员什么都能看人事专员能管员工和考勤但不能管用户普通员工只能看自己的信息和考勤。权限控制分两层。第一层是菜单动态渲染在 JSP 里根据 Session 中的角色决定显示哪些菜单项% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % User currentUser (User) session.getAttribute(currentUser); String role currentUser ! null ? currentUser.getRole() : ; % ul classnav lia hrefindex.jsp首页/a/li c:if test% \admin\.equals(role) || \hr\.equals(role) % lia hrefemployee/list员工管理/a/li lia hrefattendance/list考勤管理/a/li /c:if c:if test% \admin\.equals(role) % lia hrefuser/list用户管理/a/li lia hrefdept/list部门管理/a/li /c:if lia hrefemployee/myInfo个人信息/a/li /ul注意 JSTL 的 c:if 里嵌 Java 变量需要用 % % 输出写法比较别扭但能用。更干净的方式是在 Servlet 里把权限布尔值放进 request 域JSP 里直接取。第二层是 URL 级拦截。光隐藏菜单不够普通员工如果直接在浏览器地址栏输入 /admin/user/list 照样能访问。需要在 Filter 里加角色判断// 在 LoginFilter 的登录校验之后追加 String role (String) session.getAttribute(currentUser) ! null ? ((User) session.getAttribute(currentUser)).getRole() : null; if (uri.contains(/admin/) !admin.equals(role)) { response.sendRedirect(request.getContextPath() /noPermission.jsp); return; } if (uri.contains(/hr/) !(admin.equals(role) || hr.equals(role))) { response.sendRedirect(request.getContextPath() /noPermission.jsp); return; }这种基于 URL 路径的权限判断虽然粗糙但毕设够用而且逻辑清晰论文里可以画一张角色-资源访问矩阵表来说明。4. 避坑与排查JSP 毕设里那些让人抓狂的瞬间4.1 中文乱码三个位置必须同时设对现象表单提交后数据库里存的是问号或者页面显示的是乱码。原因JSP 页面的 pageEncoding、Servlet 的 request/response 编码、数据库连接的 characterEncoding 三者不一致。解决JSP 页面头部写 % page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里 doPost 第一行写 req.setCharacterEncoding(UTF-8)doGet 如果也要处理中文得手动 new String(req.getParameter(x).getBytes(ISO-8859-1), UTF-8)数据库 URL 里加 characterEncodingutf8mb4。三处缺一处都会出问题这是血泪经验。4.2 数据库连不上但异常被吞了现象页面一直转圈或者报 500但控制台没有明显的 SQLException 堆栈。原因DBUtil 的 close 方法里把异常 catch 了但没打印或者 getConnection 返回 null 后直接使用导致 NullPointerException真正的连接失败信息被掩盖。解决在 getConnection 的 catch 块里必须 e.printStackTrace()不要写空 catch。另外可以在获取连接后加一句 if (conn null) throw new SQLException(数据库连接为空)让问题尽早暴露。4.3 上传文件后重启 Tomcat 图片就没了现象上传员工照片时能看到重启 Tomcat 后图片变成裂图。原因文件保存在 getRealPath(/upload) 返回的目录里这个目录在 Tomcat 重启或重新部署时会被清空。解决把上传目录配到项目外部比如 D:/hr_upload/数据库里存相对路径JSP 里用访问。或者在 Tomcat 的 server.xml 里配一个 Context 指向外部目录。毕设演示时如果只在一次会话内展示用 getRealPath 也能凑合但论文里最好写清楚生产环境的做法。4.4 JSTL 标签不解析直接原样输出现象JSP 页面里写了 c:forEach 但浏览器里显示的就是这行代码本身。原因缺少 jstl.jar 和 standard.jar或者 web.xml 的版本太低不支持 JSTL。解决在 WEB-INF/lib 下放入 jstl-1.2.jar一个 jar 就够不需要 standard.jar并且在 JSP 头部加上 % taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。如果用的是 Maven 项目加 javax.servlet:jstl:1.2 依赖。4.5 分页查询最后一页显示空数据现象列表总共 23 条每页 10 条点第三页时显示空白。原因LIMIT 偏移量算错了或者总页数计算时用了整数除法丢精度。解决总页数用 int totalPages (totalCount pageSize - 1) / pageSize; 这个公式保证向上取整。偏移量用 (pageNum - 1) * pageSizepageNum 从 1 开始。另外在 Servlet 里要判断 pageNum 不能小于 1也不能大于 totalPages否则直接重定向到第一页或最后一页。5. 论文与源码的收尾技巧让答辩老师挑不出硬伤走到这一步系统功能基本跑通了但毕设还有一半分数在论文上。很多同学系统做得不错论文却写得像说明书老师一看就皱眉头。这里说几个我反复验证过的收尾技巧。第一论文里的系统架构图不要用网上随便找的模板。用 Visio 或 draw.io 画一张三层架构图标注清楚 JSP 负责表现层、Servlet 负责控制层、DAO 负责数据访问层每一层之间用箭头标出数据流向。这张图放在第三章老师一眼就能看出你理解了自己的代码结构。第二数据库设计章节要放 E-R 图和表结构说明。E-R 图用矩形表示实体、菱形表示关系、椭圆表示属性不要直接用数据库表截图代替。表结构说明用表格列出字段名、类型、约束、说明下面这张表可以直接参考字段名类型约束说明emp_idINT主键自增员工编号emp_nameVARCHAR(30)非空员工姓名genderCHAR(2)默认男性别dept_idINT外键所属部门statusTINYINT默认11在职 0离职第三核心代码不要整段贴进论文。选两三段有代表性的比如登录的密码加密比对、分页查询的 LIMIT 拼接、Filter 的权限拦截每段代码配一段文字说明“这段代码解决了什么问题、关键参数为什么这么设”。贴太多代码会被查重贴太少又显得工作量不够三到五段刚好。第四测试章节要写具体的测试用例和结果。不要只写“系统运行正常”要列出输入正确的用户名密码能登录成功、输入错误的密码提示“密码错误”、未登录直接访问员工列表会跳转到登录页、上传超过 2MB 的照片会提示文件过大。每条用例写清楚操作步骤、预期结果、实际结果用表格呈现。最后说一个我自己的习惯答辩前把系统在另一台电脑上完整部署一遍。从安装 JDK、配置 Tomcat、导入数据库、改 DBUtil 里的密码到启动后走一遍所有功能。这个过程能暴露 90% 的环境问题比如 JDK 版本不兼容、MySQL 驱动包没放对位置、端口被占用。我见过太多同学在自己电脑上跑得好好的一到答辩教室就各种报错提前在陌生环境跑一遍能省下很多后悔药。希望帮到你。本文还有配套的精品资源点击获取