JSP人事管理系统毕设全流程:技术栈、数据库设计与Tomcat部署
发布时间:2026/9/16 14:46:57 锦皓数字建站

简介面向毕业设计或课程设计的人事人力资源管理系统完整项目包基于JSPMySQL的B/S架构涵盖系统管理员、部门信息、员工信息、合同信息、绩效管理含工资与奖惩记录及考勤管理等模块适合Java Web初学者、毕业生快速搭建与二次开发。压缩包共116个文件以69个JSP页面为核心配合4个CSS样式、6个JavaScript脚本及6个JAR依赖库还包括项目配置文件和SQL数据库脚本另有15个GIF和5个JPG图片素材整体仅2.99MB结构紧凑。资源内含源代码、说明文档及演示录像方便对照学习环境搭建、数据库初始化与功能实现流程可快速理解人事管理系统各模块的业务逻辑和代码组织方式。目前已有58人学习下载适合用于毕业设计参考、课程设计实践或入职前项目复习。1. 基于JSP的人事人力资源管理系统毕设先想清楚它要证明什么毕业设计选题“人事人力资源管理系统”用 JSP 做技术底座几乎是国内高校 Java Web 方向最经典的组合之一。它不像 Spring Boot 全家桶那样有大量自动化配置也不像 PHP 那样一行 echo 就能出页面它逼着你把 HTTP 请求、Servlet 生命周期、Session 状态管理、JDBC 数据库访问这些 Web 底层的骨架亲手搭一遍。这正是多数院校答辩时评委最想看的东西你不是只会调用框架注解而是真明白一次表单提交从浏览器到数据库再回到浏览器中间发生了什么。这套系统的业务边界很清晰员工信息维护、部门管理、考勤记录、薪资查询、公告发布再加一个管理员登录入口。每个模块都不大但合起来刚好覆盖了一个 Web 应用最常见的功能形态——增删改查、条件检索、分页展示、会话拦截。用来做毕业设计工作量适中论文有话可写演示时有画面感。对正在选型的同学来说判断一个 JSP 毕设项目质量的高低核心就看三件事数据库表设计是否规范、Servlet 与 JSP 的分工是否清楚、登录后的权限控制是否真的拦住了未授权访问而不是只在页面上藏了个链接。2. 拆解 JSP 人事系统的技术栈与项目结构JDK、Tomcat、MySQL 的配套方案2.1 JSPServletJavaBean 的分层架构在人事系统里怎么落JSP 项目的经典三层结构是 JSP视图、Servlet控制器、JavaBean/DAO模型。人事系统里最常见的错误是把业务逻辑全写进 JSP 的% %脚本块里比如直接在页面里写 JDBC 查询、声明Connection、拼 SQL 字符串。这样做的后果是页面一打开就查数据库页面里全是 Java 代码前端改个样式都会碰到业务逻辑答辩时老师问“Servlet 用来干什么”根本答不上来。正确做法是让 JSP 只负责接收后端放好的数据并展示所有请求先到 Servlet由 Servlet 调用 DAO 层的方法操作数据库最后把结果放进 request 或 session 再转发到 JSP 页面。以员工列表为例请求路径是employeeList.jsp不是应该是EmployeeServlet?actionlist。Servlet 里根据 action 参数决定调用哪个 DAO 方法查询完成后request.setAttribute(empList, list)再request.getRequestDispatcher(/employee_list.jsp).forward(...)。JSP 页面通过 JSTL 的c:forEach或简单的for循环取出列表渲染成表格。这个请求链路的清晰程度直接决定了论文里“系统设计”这一章能写出多少内容。2.2 zip 源码包解压后的标准目录结构解读毕设项目通常以 zip 形式分发解压后你要能快速看懂目录归属哪里。一个规范的 JSP 人事系统打包后核心目录是 WebContent有些叫 webapp 或 WebRoot和 src对应关系如下hr_system/ ├── src/ // Java 源码根目录 │ ├── com/hr/dao/ // 数据访问层EmployeeDao.java、DeptDao.java │ ├── com/hr/servlet/ // 控制器层LoginServlet.java、EmployeeServlet.java │ ├── com/hr/entity/ // 实体类Employee.java、Department.java │ ├── com/hr/util/ // 工具类DBUtil.java、MD5Util.java │ └── com/hr/filter/ // 过滤器LoginFilter.java ├── WebContent/ │ ├── index.jsp // 登录页面或首页入口 │ ├── WEB-INF/ │ │ ├── web.xml // Web 应用描述符配置 Servlet 映射和过滤器 │ │ └── lib/ // 依赖 jar 包mysql-connector、jstl 等 │ ├── css/ // 样式文件 │ ├── js/ // 前端脚本 │ ├── employee_list.jsp // 员工列表页面 │ ├── employee_add.jsp // 新增员工页面 │ └── department_list.jsp // 部门列表页面 ├── sql/ │ └── hr_system.sql // 数据库初始化脚本表和测试数据 └── 说明文档.docx有几个位置要特别留意。第一WEB-INF/lib下必须有 mysql 驱动 jar 包没有它系统启动后一查数据库就报ClassNotFoundException: com.mysql.jdbc.Driver。第二web.xml里如果没有配置 Servlet 映射你访问EmployeeServlet会得到 404。第三如果项目里没有sql/目录或 SQL 脚本说明你要自己建库建表那就得从代码里反推表结构工作量会大不少。拿到 zip 后先按这个结构核对一遍缺哪块补哪块再往下走。2.3 环境版本搭配与常见版本冲突JSP 系统对环境版本极其敏感不是越新越好。常见的可靠组合是 JDK 8 Tomcat 8.5 MySQL 5.7这套组合在绝大多数国产生态下验证充分。JDK 8 的com.sun.mirror等内部 API 在后续版本被移除Tomcat 9 之后的 Servlet 规范升级为 4.0部分老项目里的javax.servlet引用如果不更新依赖会直接编译失败。MySQL 8.0 虽然往下兼容但驱动类的名字从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver连接串还需要额外指定时区参数serverTimezoneUTC老项目里的代码如果写的是旧驱动名就必须同步调整。组件推荐版本说明JDK1.8编译级别设为 1.8避免 lambda 语法兼容问题Tomcat8.5.x支持 Servlet 3.1JSP 2.3企业环境存量最大MySQL5.7.x稳定兼容旧驱动默认字符集 utf8mb4Eclipse2020-06 及以上自带 JST 适配器可以直接跑 Dynamic Web ProjectIDEA2021.x需要配置 Tomcat 的 Local Server 方式mysql-connector-java5.1.49对应 MySQL 5.7驱动名为 com.mysql.jdbc.Driver不建议在没有把握的情况下直接上 JDK 17 Tomcat 10因为 Tomcat 10 把javax.servlet包名换成了jakarta.servlet整个项目的 import 语句要批量替换或者更换所有依赖 jar。这属于额外的工作量对毕设来说收益不高。如果项目的说明文档.txt里写了版本要求优先信文档其次信上面的表。3. 人事系统数据库设计与核心功能实现登录、增删改查、分页查询3.1 人事管理系统数据库表结构设计人事系统的数据库设计建议不少于 5 张表管理员表、部门表、员工表、考勤表、公告表。如果项目里还包含薪资模块再加一张薪资表。第一次做容易犯的错是只有一张员工表把部门、职位全设计成字符串字段。这种设计前期看起来很省事但后期统计“每个部门有多少人”时只能写 Java 代码去内存里数论文里的 E-R 图也画不出来答辩分数会打折扣。以下是一套标准的建表方案可以直接作为基础 SQL 脚本使用-- 部门表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 部门ID, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称, dept_manager VARCHAR(20) DEFAULT NULL COMMENT 部门负责人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT部门表; -- 员工表 CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 员工ID, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号唯一索引, emp_name VARCHAR(20) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别, birthday DATE DEFAULT NULL COMMENT 出生日期, dept_id INT NOT NULL COMMENT 所属部门ID, position VARCHAR(30) DEFAULT NULL COMMENT 职位, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, entry_date DATE DEFAULT NULL COMMENT 入职日期, status TINYINT DEFAULT 1 COMMENT 状态1在职 0离职, KEY idx_dept (dept_id), CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES department (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工表; -- 管理员表 CREATE TABLE sys_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 密码建议存MD5值, real_name VARCHAR(20) DEFAULT NULL COMMENT 真实姓名, role TINYINT DEFAULT 1 COMMENT 角色1管理员 2普通用户 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 考勤表 CREATE TABLE attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, att_date DATE NOT NULL COMMENT 考勤日期, status VARCHAR(10) DEFAULT 正常 COMMENT 状态正常/迟到/早退/请假/缺勤, remark VARCHAR(100) DEFAULT NULL, UNIQUE KEY uk_emp_date (emp_id, att_date), CONSTRAINT fk_att_emp FOREIGN KEY (emp_id) REFERENCES employee (emp_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考勤表;需要注意几个设计细节。emp_no工号字段必须加UNIQUE约束防止同一工号被录入两次。所有表加上create_time或类似时间字段这几乎不需要额外成本但答辩时“数据审计”这个点就能拿来讲。外键约束建议加上JSP 毕设的数据库规模不会出现性能瓶颈保持引用的完整性比什么都重要。字符集统一用utf8mb4不要用latin1或utf8否则录个生僻字姓名就变成问号演示时很尴尬。3.2 基于 JDBC 的连接工具类与防注入查询JSP 项目里最稳定、最容易被答辩老师接受的数据库访问方案是 JDBC DAO 模式而不是在 Servlet 里直接 open Connection。先写一个连接工具类统一管理驱动加载和连接获取package com.hr.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class DBUtil { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/hr_system?useUnicodetruecharacterEncodingutf8; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (Exception e) { } try { if (stmt ! null) stmt.close(); } catch (Exception e) { } try { if (conn ! null) conn.close(); } catch (Exception e) { } } }这段代码要点在于useUnicodetruecharacterEncodingutf8拼在连接串上少了它中文写进数据库就成了问号页面输入“张三”存成“???”。Class.forName放在静态代码块里保证类第一次加载时驱动只注册一次避免每次连接都重复注册。连接、语句、结果集三个资源的关闭顺序要从里到外先关 ResultSet 再关 Statement 最后关 Connection直接只关 Connection 在 MySQL 5.7 下可能因连接池未及时释放导致Too many connections报错。查询部门列表并统计人数的典型 DAO 写法如下public ListDepartment findAllWithCount() { ListDepartment list new ArrayList(); String sql SELECT d.*, COUNT(e.emp_id) AS emp_count FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id GROUP BY d.dept_id, d.dept_name, d.dept_manager, d.create_time; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); rs ps.executeQuery(); while (rs.next()) { Department dept new Department(); dept.setDeptId(rs.getInt(dept_id)); dept.setDeptName(rs.getString(dept_name)); dept.setEmpCount(rs.getInt(emp_count)); list.add(dept); } } catch (Exception e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return list; }这里用了LEFT JOIN而不是INNER JOIN效果是查询结果里会包含还没有员工的新部门统计数显示为 0。PreparedStatement是强制要求不能直接用Statement拼接字符串否则一个姓名里带引号或 SQL 关键字就会注入报错。代码中的emp_count是数据库聚合出来的列不是实体类原始属性所以在 Department 实体里要单独加一个empCount字段来承接。3.3 登录验证、Session 会话与登录过滤器的实现登录模块是整个系统的门面。做 JSP 毕设登录功能最容易出现的问题是输入密码后跳转到页面刷新一下又要重新登录——这其实是 Session 域设置的问题。正确的处理流程是验证账号密码成功后把用户信息放进 session再在web.xml里配置过滤器统一拦截。登录 Servlet 的核心代码WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 调用 DAO 验证用户 UserDao dao new UserDao(); SysUser user dao.findByUsernameAndPassword(username, MD5Util.md5(password)); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(1800); // 会话超时时间单位秒 response.sendRedirect(request.getContextPath() /dashboard.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/index.jsp).forward(request, response); } } }这里有两个关键点。第一密码入库前用MD5Util.md5(password)做了一次摘要数据库中不存明文密码这是答辩时安全性的讨论点。第二验证失败使用forward而不是sendRedirect区别在于前者是服务器内部转发可以将 errorMsg 通过 request 属性带回页面回显浏览器 URL 不变后者是浏览器重新发起请求request 域的内容就丢了。成功时用sendRedirect则有利于避免表单重复提交。加上登录过滤器后在web.xml中做如下配置filter filter-nameLoginFilter/filter-name filter-classcom.hr.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern*.jsp/url-pattern /filter-mapping过滤器的拦截逻辑是判断 session 里有没有loginUser属性没有就重定向回登录页。注意要放行登录页本身和静态资源可以在过滤器里加白名单判断。web.xml中的url-pattern配成*.jsp拦截所有页面时还要确认index.jsp也能被放行否则会出现用户已经站在登录页上却因没有 session 被过滤器循环重定向的“死循环跳转”。3.4 员工列表分页与条件查询的 JSP 实现分页查询是毕设中必讲的一环也是证明你没停留在“全表查询然后 List.subList”这个层次的关键。正确方案是 SQL 层分页用LIMIT控制偏移量。员工 Servlet 中的分页计算如下int pageSize 10; int currentPage request.getParameter(pageNum) null ? 1 : Integer.parseInt(request.getParameter(pageNum)); String keyword request.getParameter(keyword); // 拼 SQL 查询总条数 String countSql SELECT COUNT(*) FROM employee WHERE 11 ; if (keyword ! null !keyword.trim().isEmpty()) { countSql AND emp_name LIKE ?; } int totalCount dao.getTotalCount(countSql, keyword); // 计算总页数保证至少为 1 int totalPages (totalCount % pageSize 0) ? (totalCount / pageSize) : (totalCount / pageSize 1); int offset (currentPage - 1) * pageSize; // 列表查询加分页条件 String listSql SELECT * FROM employee WHERE 11 ; ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { listSql AND emp_name LIKE ?; params.add(% keyword %); } listSql LIMIT ? , ?; params.add(offset); params.add(pageSize); ListEmployee empList dao.findByPage(listSql, params); request.setAttribute(empList, empList); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPages, totalPages); request.setAttribute(keyword, keyword);LIMIT ? , ?中的两个参数分别是起始行索引和每页条数第一页的 offset 是 0第二页是 10以此类推。WHERE 11是个取巧写法它不参与实际运算作用是方便在后续动态拼接条件时不用先判断“前面有没有 WHERE”在毕设项目里可读性不差。搜索关键词用LIKE ?预编译配合%通配符放入参数杜绝 SQL 注入风险。总条数查询用了独立的COUNT(*)不能跟列表查询合在一起否则数据集中在内存里分页数据量一大就卡死。JSP 页面的分页条用 JSTL 输出div classpagination c:if test${currentPage 1} a hrefEmployeeServlet?actionlistpageNum${currentPage - 1}keyword${keyword}上一页/a /c:if span第 ${currentPage} / ${totalPages} 页/span c:if test${currentPage totalPages} a hrefEmployeeServlet?actionlistpageNum${currentPage 1}keyword${keyword}下一页/a /c:if /div分页条里的链接必须把keyword参数带上下次翻页时保持搜索条件不然一翻页关键词就丢了。这是 JSP 项目里最常见的“分页丢条件”坑很多同学自查半天找不出来。${totalPages}是 EL 表达式在控制器放入 request 后可以直接用不需要额外引入脚本段。4. 把 zip 里的 JSP 人事系统部署到 Tomcat从导入到调通的完整流程4.1 将项目导入 Eclipse 和 IDEA 的不同方式拿到 zip 后先解压。多数毕设项目是标准的 Dynamic Web Project动态 Web 项目结构里面通常没有.project和.classpath文件所以不能直接File - Open Projects from File System。Eclipse 里的正确操作是File - Import - Existing Projects into Workspace再选择解压后的目录。如果项目没有.project文件Eclipse 会提示“No projects are found to import”这时需要先选择General - Existing Projects into Workspace之外的另一种方式创建新的 Dynamic Web Project把源码和 WebContent 分别拷贝到对应目录。IDEA 的导入路径是File - New - Project from Existing Sources选择解压目录后IDEA 会弹出提示框让你选择项目类型选Web或Java Enterprise然后指定好 JDK 和依赖库。项目带的src目录会被自动识别为源码根目录WebContent需要手动在 Project Structure 里标记为 Web 资源目录否则部署时 JSP 页面不会被打进 war 包Tomcat 会 404。要注意一点很多毕设 zip 包解压后默认编码是 GBKWindows 下用记事本写的而 IDEA 默认项目编码是 UTF-8。导入后如果 JSP 页面中文全部变成乱码先别急着改代码在 IDEA 右下角把文件编码切换成 GBK再在 Settings - File Encodings 里设置全局编码。先检查再动手改代码能省掉一晚上时间。4.2 JSP 修改不生效时编译后的 class 文件去哪找JSP 不是像 HTML 那样直接被 Tomcat 返回给浏览器的它在第一次被访问时会被翻译成 Java 文件再编译成 class 文件。很多人改了 JSP 刷新页面没变化就是因为 Tomcat 判断文件时间戳后没有重新编译或者你改错了文件。JSP 编译后的文件默认存在 Tomcat 的work目录下。结构路径apache-tomcat-8.5.xx/ └── work/ └── Catalina/ └── localhost/ └── hr_system/ // 项目名 └── org/ └── apache/ └── jsp/ ├── employee_list_jsp.java ├── employee_list_jsp.class ├── index_jsp.java └── index_jsp.class如果访问employee_list.jsp时报错打开对应的employee_list_jsp.java能看到翻译后的 Servletservice 方法里具体的出错行号这比 JSP 页面上的堆栈信息更接近真实原因。当出现“JSP 改了不生效”时按顺序排查确认改的是WebContent下被部署的那个文件而不是 src 同级下的副本检查 Tomcat 的server.xml里autoDeploy是否为 true最后清理work/Catalina/localhost下的缓存目录重启后强制重新编译。4.3 部署期高频报错定位和修复参数JSP 项目部署过程中的报错九成集中在 404、500、端口占用、驱动缺失四类。下面是按出现频率排列的遇错排查表报错症状直接原因定位方法修复手段404访问 Servlet 报错web.xml 缺少映射或注解没生效查看 server.xml 中 Context 路径与文件路径核对WebServlet(/xxx)与请求 URL 是否一致报ClassNotFoundException: com.mysql.jdbc.Driverlib 下没有 mysql 驱动 jar解压 war 包检查 WEB-INF/lib把 mysql-connector 的 jar 放入 WEB-INF/lib 并 Build PathHTTP 500 页面第一行报错JSP 脚本里 Java 语法或逻辑错误打开 work 目录对应 java 文件查看行号修正对应行代码后清理缓存重启端口被占用Address already in use另一个 Tomcat 或应用占用了 8080netstat -anofindstr 8080中文全是???数据库连接串没有指定编码查看数据库表字符集和连接 URLURL 加characterEncodingutf8并重建数据页面 404 但 Tomcat 正常启动项目未部署到 webapps 下检查 Eclipse Server 面板的 Modules 列表右键 Tomcat - Add and Remove 确认已添加这里面最容易踩的是端口占用。Eclipse 里开了多个 Tomcat 实例或者上一个项目没正常关闭8080 被孤儿进程占住。改端口的命令是修改apache-tomcat-8.5.xx/conf/server.xmlConnector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /将port改为8081或 9090 后重启 Tomcat再访问http://localhost:8081/项目名/。改端口是应急手段根治还是把占用的进程找到并结束掉。在 Windows 下用netstat -ano | findstr 8080查 PID再在任务管理器里结束进程。5. 答辩演示与三个低成本的进阶改造方向5.1 项目录像和说明文档要做到什么程度毕业设计附带的录像一般是演示现场操作的录屏录制时最容易犯的错是从登录开始一路鼠标乱点没有讲解逻辑。一个合格的演示录像按这个顺序走介绍数据库和表结构打开 Navicat 或命令行展示几张表和主外键关系→ 启动 Tomcat → 演示登录包括一次错误密码输出提示→ 进入员工模块执行一次新增、一次编辑、一次删除 → 展示搜索和分页切换 → 最后关闭服务器。整体控制在 5 到 8 分钟录的时候用 OBS 或者 Windows 自带的 WinG 录屏都行不用追求高画质但要保证鼠标移动轨迹清晰数据库里的中文不乱码。说明文档的核心内容除了环境配置和用户手册一定要有“如何导入项目”这一节把第 4 章的目录结构和 eclipse 导入步骤截图贴进去。将来评阅老师和查重系统都会重点看这一节写不清楚直接影响初审印象。5.2 用 POI 给员工列表加一个导出 Excel 的按钮JSP 系统里导出 Excel 是最能体现“我考虑了实际应用”的功能点而且是毕设答辩时动态演示效果最直观的一步。手工撸 Excel 可以用 Apache POI也可以用 JSP 直接输出 CSV 格式但 CSV 在中文环境下经常出现编码问题POI 生成的 .xls 更稳妥。给员工列表页面加一个a标签指向导出 Servlet。导出核心代码protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListEmployee empList new EmployeeDao().findAll(); response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenameemployee_list.xls); HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(员工信息); String[] headers {工号, 姓名, 性别, 部门, 职位, 入职日期}; HSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowIndex 1; for (Employee emp : empList) { HSSFRow row sheet.createRow(rowIndex); row.createCell(0).setCellValue(emp.getEmpNo()); row.createCell(1).setCellValue(emp.getEmpName()); row.createCell(2).setCellValue(emp.getGender()); row.createCell(3).setCellValue(emp.getDeptName()); row.createCell(4).setCellValue(emp.getPosition()); row.createCell(5).setCellValue(emp.getEntryDate().toString()); } workbook.write(response.getOutputStream()); workbook.close(); }这段代码里两个参数值得记住。response.setContentType必须指定为application/vnd.ms-excel浏览器才会触发下载而不是在页面里乱码显示。Content-Disposition头里的attachment是强制下载文件名后面的.xls后缀不能省否则 Windows 下打开会提示格式不匹配。注意 POI 的 HSSFWorkbook 适用于 .xls 格式数据量在 65535 行内没问题人事系统足够用了。导出前绕过了权限拦截建议在 Servlet 里用 session 验证用户是否登录再执行导出避免暴露在无鉴权场景下。5.3 给密码列加盐哈希改造讲完功能顺手把安全性升级一点。第 3 章的登录代码里密码存的是单次 MD5这在答辩时会被追问“MD5 可以被彩虹表反查怎么办”。常见的做法是加盐即每个用户生成一个随机盐值保存密码时存MD5(盐值 密码)盐值本身存在用户表里。用户注册或修改密码时的伪代码是String salt UUID.randomUUID().toString().substring(0, 8); String hashedPwd MD5Util.md5(salt rawPassword); user.setSalt(salt); user.setPassword(hashedPwd);登录校验时从用户表先查出盐值再用同一公式计算出哈希值对比就能以极低改造成本挡住彩虹表攻击。这个方案好在不需要引入 Spring Security 这类重量级依赖代码量不到 20 行但涉及数据库表结构调整和登录验证逻辑两处改动答辩时能展开讲很长时间。顺便把MD5Util里的算法从 JDK 自带的MessageDigest换成兼容性好一点的实现就是把getInstance(MD5)的异常处理补全避免算法不存在时整个页面直接抛 500。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。