ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

JavaWeb实战:企业员工信息管理系统从设计到部署全流程详解

JavaWeb实战:企业员工信息管理系统从设计到部署全流程详解 简介本资源是一套完整的JavaWeb企业级员工信息管理系统毕业设计实战资料面向计算机专业本科生、Java初学者及Web开发入门者解决中小企业员工信息化管理痛点覆盖部门管理、员工档案、考勤、薪资、请假审批等核心业务场景。压缩包共8个文件含2份Word文档中期检查表与毕业论文、1份PPT答辩稿、1个SQL数据库脚本、1个ZIP源码包、2段MP4讲解视频含项目部署与各模块功能演示总大小29.64MB。已有177人学习下载资料结构清晰、开箱即用论文详述需求分析与系统设计全过程视频手把手演示环境搭建与关键模块如请假审核、工资查询实现逻辑源码与数据库可直接导入运行配套文档完整支撑课程设计、毕业答辩与二次开发。1. 项目缘起为什么企业员工信息管理系统是JavaWeb的经典练手项目如果你正在学习JavaWeb或者想找一个能串联起前后端、数据库、服务器部署等核心技能的综合项目来巩固知识那么一个“企业员工信息管理系统”绝对是一个绕不开的经典选择。我当年学完Servlet和JSP后做的第一个像模像样的项目就是它。为什么它如此经典因为它麻雀虽小五脏俱全几乎涵盖了企业级应用开发的所有基础环节用户登录认证、部门与员工信息的增删改查CRUD、数据分页展示、简单的权限控制以及报表导出等。它不像电商或社交平台那样业务逻辑复杂但又足够让你把书本上的JDBC、Servlet、JSP、MVC这些概念真正“跑”起来形成一个完整的闭环。更重要的是这个项目有极强的现实映射。几乎任何一家公司都需要一个系统来管理其最核心的资产——人。因此从需求分析、数据库设计到功能实现你都能找到清晰的业务逻辑支撑而不是凭空捏造。通过实现它你不仅能学会技术更能初步理解一个真实业务系统是如何从零到一构建起来的。网络上流传着大量此类项目的“代码论文PPT数据库”资源包这恰恰说明了它的普适性和教学价值。但很多资源包要么代码老旧要么只给结果不讲过程。今天我就结合自己多年的开发和带新人的经验从头到尾拆解这个系统的设计与实现重点不是给你一堆代码而是告诉你每个环节“为什么”要这么做以及实际开发中会遇到哪些“坑”。2. 系统核心需求分析与技术选型不止于增删改查在动手写第一行代码之前我们必须明确这个系统要干什么。一个最基本的企业员工信息管理系统其核心需求远不止简单的增删改查。2.1 功能性需求拆解首先我们需要定义系统的角色。通常至少需要两类用户系统管理员和普通员工或部门经理。他们的权限不同系统管理员拥有最高权限可以进行部门信息管理、员工信息全功能管理增、删、改、查、系统用户管理、数据备份与恢复等。普通员工/部门经理通常只能查看和修改部分字段自己的信息或者查看本部门员工的基本信息如联系方式但无权删除或修改他人敏感信息。基于角色我们可以梳理出核心功能模块用户认证与授权模块这是系统的安全大门。包括登录、退出、Session管理以及基于角色的页面访问控制和操作权限校验。部门信息管理模块企业是有组织架构的。需要能对部门进行增删改查通常包括部门ID、部门名称、部门描述、成立时间等字段。这里要特别注意数据完整性约束比如删除一个部门时如果该部门下还有员工该如何处理是禁止删除还是将员工转移到其他部门员工信息管理模块系统的核心。字段通常包括工号主键、姓名、性别、出生日期、身份证号、所属部门、职位、入职日期、联系电话、邮箱、照片等。这里的需求复杂性体现在查询需要支持多条件组合查询例如按姓名模糊查询、按部门精确查询、按入职日期范围查询。分页当员工数据成千上万时必须实现分页展示这是Web应用的必备技能。数据验证前端JS验证和后端Java验证双重保障。身份证格式、邮箱格式、日期逻辑出生日期不能晚于入职日期等都需要校验。文件上传员工照片的上传与存储。系统管理模块包括操作日志记录谁在什么时候做了什么、基础数据字典维护如职位列表、民族列表、简单的数据统计如各部门人数统计、员工学历分布饼图等。2.2 技术栈选型与理由面对琳琅满目的Java技术生态如何为这个项目选择一个合适、易学且能体现主流思想的技术组合后端核心Servlet JSP 还是 Spring MVC对于初学者我强烈建议从Servlet JSP JDBC这个经典组合开始。为什么因为这是理解JavaWeb底层机制的基础。Servlet是Java处理HTTP请求的基石JSP是动态生成HTML的视图技术JDBC是操作数据库的标准接口。亲手用这个组合实现一遍MVCModel-View-Controller你会对请求转发RequestDispatcher、重定向sendRedirect、Session、Cookie等概念有刻骨铭心的理解。很多直接上Spring Boot的同学遇到问题往往不知道底层发生了什么基础不牢。对于希望项目更贴近当前企业应用的同学可以选择Spring Boot MyBatis/MyBatis-Plus Thymeleaf。Spring Boot极大地简化了配置MyBatis提供了灵活的SQL映射Thymeleaf是现代化的模板引擎。这个组合效率更高代码更简洁是当前主流选择。但请注意它隐藏了很多细节建议在掌握了Servlet/JSP后再学习。前端技术基础三件套HTML/CSS/JavaScript是必须的。为了快速构建美观的界面可以使用Bootstrap这类前端框架它能帮你搞定响应式布局和基础组件。jQuery在传统JavaWeb项目中依然常用用于简化DOM操作和Ajax请求。虽然现代前端流行Vue/React但对于一个以学习Java后端为核心的项目jQuery足以应对交互需求且学习成本低。Ajax用于实现局部刷新比如在员工列表页面点击删除按钮无需刷新整个页面即可移除该行数据提升用户体验。数据库MySQL是最佳选择。它免费、开源、流行社区资源丰富完全能满足这个项目的需求。MariaDB作为MySQL的分支兼容性极高也可选用。关键点在于数据库设计。你需要设计user用户、department部门、employee员工等核心表并建立正确的关联如employee.dept_id关联department.id。务必使用外键约束来保证数据的参照完整性尽管有些互联网公司因性能考虑在应用层保证但对于学习项目外键是理解关系型数据库的重要概念。开发工具与环境IDEIntelliJ IDEA是Java开发者的首选其对JavaEE和框架的支持远超Eclipse。社区版就足够完成这个项目。项目构建使用Maven来管理项目依赖Jar包它比手动导入Jar包要规范得多。你需要在pom.xml中配置Servlet API、JSTL、MySQL驱动、连接池如HikariCP或Druid等依赖。数据库工具Navicat、DBeaver或IDEA自带的数据库工具都可以用于直观地管理数据库、执行SQL语句。注意我见过很多新手拿到代码后第一件事就是导入工程然后被各种“找不到类”的报错劝退。这通常是因为缺少Maven依赖或Tomcat服务器配置不对。请务必先确保你的IDEA里配置好了Maven通常是自带的和一个Tomcat服务器版本8.5或9.0这是项目能运行起来的前提。3. 数据库设计与核心表结构详解数据库是系统的基石糟糕的设计会给后续编码带来无穷无尽的麻烦。我们来详细设计三张核心表。3.1 表结构设计1. 部门表 (department)这张表存储企业的组织架构。设计要考虑到扩展性比如未来可能需要支持多级部门树形结构。CREATE TABLE department ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 部门ID主键, dept_name varchar(50) NOT NULL COMMENT 部门名称, dept_desc varchar(200) DEFAULT NULL COMMENT 部门描述, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_dept_name (dept_name) COMMENT 部门名称唯一 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT部门信息表;设计理由id自增主键是关联员工表的外键依据。dept_name设为唯一约束防止创建重复部门。create_time和update_time是强烈建议添加的字段用于记录数据生命周期在排查问题时非常有用。使用utf8mb4字符集支持存储Emoji等所有Unicode字符避免未来出现乱码。使用InnoDB引擎支持事务和外键。2. 员工表 (employee)这是最核心的表字段多需要考虑各种约束和索引。CREATE TABLE employee ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 员工ID主键, emp_no varchar(20) NOT NULL COMMENT 员工工号业务唯一标识, emp_name varchar(50) NOT NULL COMMENT 员工姓名, gender tinyint(1) DEFAULT 1 COMMENT 性别1男0女, birth_date date DEFAULT NULL COMMENT 出生日期, id_card varchar(18) DEFAULT NULL COMMENT 身份证号, dept_id int(11) NOT NULL COMMENT 所属部门ID, position varchar(50) DEFAULT NULL COMMENT 职位, hire_date date NOT NULL COMMENT 入职日期, phone varchar(20) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 邮箱, photo_url varchar(500) DEFAULT NULL COMMENT 照片存储路径, status tinyint(1) DEFAULT 1 COMMENT 状态1在职0离职, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_emp_no (emp_no), UNIQUE KEY uk_id_card (id_card), KEY idx_dept_id (dept_id), KEY idx_emp_name (emp_name), CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES department (id) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工信息表;设计理由与避坑点id与emp_no这里设计了两个唯一标识。id是代理主键无业务含义用于内部关联和性能INT类型比VARCHAR的索引效率更高。emp_no是业务主键如“EMP20230001”展示给用户看。两者分离是常见的做法。外键约束dept_id字段通过外键fk_emp_dept关联到department.id。ON DELETE RESTRICT表示如果部门下有员工则禁止删除该部门防止出现“孤儿”数据。ON UPDATE CASCADE表示如果部门ID更新则自动更新所有相关员工的dept_id。索引策略除了主键和唯一键我为dept_id和emp_name创建了普通索引(idx_)。因为按部门查询和按姓名模糊查询LIKE ‘张%’会是高频操作加索引能大幅提升查询速度。id_card也加了唯一索引确保身份证号不重复。状态字段status字段用于软删除。即员工离职时并不真正从数据库删除这条记录而是将status置为0。这保留了历史数据便于审计和统计。查询在职员工时只需加上WHERE status 1即可。照片存储photo_url存储的是图片在服务器上的相对路径如/upload/photo/emp001.jpg而不是将图片以BLOB形式存入数据库。将文件存储在数据库外是更优的做法便于管理和CDN加速。3. 用户表 (user)用于系统登录认证。CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 加密后的密码, emp_id int(11) DEFAULT NULL COMMENT 关联的员工ID, role varchar(20) DEFAULT employee COMMENT 角色admin, manager, employee, is_active tinyint(1) DEFAULT 1 COMMENT 账户是否激活, last_login_time datetime DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username), KEY idx_emp_id (emp_id), CONSTRAINT fk_user_emp FOREIGN KEY (emp_id) REFERENCES employee (id) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;设计理由密码存储password字段长度设为100是为了存储加密后的字符串。绝对禁止明文存储密码通常使用BCrypt或MD5 Salt进行加密。BCrypt是目前最推荐的方式它内置了盐值处理且计算速度可调更安全。关联员工emp_id关联到employee.id这样用户就和一个具体的员工绑定可以获取其姓名、部门等信息。外键约束设为ON DELETE SET NULL意思是如果该员工记录被软删除则此用户的emp_id置为NULL但账户仍可保留可能用于审计。角色字段role字段用于简单的权限控制。更复杂的系统可能会使用RBAC基于角色的访问控制模型有单独的权限表、角色表、用户角色关联表。3.2 连接池配置为什么不用DriverManager在Java代码中我们不应该在每次执行SQL时都通过DriverManager.getConnection()来创建新的数据库连接。创建连接是昂贵的操作涉及网络IO、身份验证等。正确做法是使用数据库连接池。在pom.xml中引入Druid连接池阿里开源功能强大监控齐全dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.16/version /dependency然后在项目的资源目录如src/main/resources下创建druid.properties配置文件# 数据库连接信息 druid.driverClassNamecom.mysql.cj.jdbc.Driver druid.urljdbc:mysql://localhost:3306/employee_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai druid.usernameroot druid.passwordyour_password # 连接池配置 druid.initialSize5 druid.minIdle5 druid.maxActive20 druid.maxWait60000 druid.timeBetweenEvictionRunsMillis60000 druid.minEvictableIdleTimeMillis300000 druid.validationQuerySELECT 1 druid.testWhileIdletrue druid.testOnBorrowfalse druid.testOnReturnfalse配置解析initialSize池启动时创建的初始连接数。maxActive池中同时活动的最大连接数。根据你的应用负载和数据库承受能力设置不是越大越好。maxWait当池中连接耗尽时获取新连接的最大等待时间毫秒超时则抛异常。设置此值可以防止请求无限期挂起。testWhileIdle建议开启连接池会定时对空闲连接进行有效性检查剔除失效连接。在代码中我们通过一个工具类如DBUtil来初始化Druid数据源并提供获取连接和关闭连接的方法。注意这里“关闭连接”只是将连接归还给池子并非真正关闭。4. 后端核心实现从Servlet到MVC模式的应用我们以ServletJSP技术栈为例展示如何组织代码。核心是贯彻MVC模式让代码清晰、易于维护。4.1 模型层Model实体类与DAO模式首先创建与数据库表对应的Java实体类POJO。以Employee类为例package com.ems.model; import java.util.Date; public class Employee { private Integer id; private String empNo; private String empName; private Integer gender; // 1男0女 private Date birthDate; private String idCard; private Integer deptId; private String deptName; // 用于显示不直接映射到表字段 private String position; private Date hireDate; private String phone; private String email; private String photoUrl; private Integer status; private Date createTime; private Date updateTime; // 省略getter和setter方法以及toString方法 }注意这里我添加了一个deptName字段它并不存在于employee表中。这是因为在显示员工列表时我们通常需要显示部门名称而不是ID。这个字段会在DAO层查询时通过JOIN语句赋值。接下来是数据访问对象DAO层负责所有数据库操作。我们定义一个EmployeeDao接口及其实现类。package com.ems.dao; import com.ems.model.Employee; import java.util.List; public interface EmployeeDao { // 新增员工 int insert(Employee emp); // 根据ID删除实际是更新状态为0 int deleteById(Integer id); // 更新员工信息 int update(Employee emp); // 根据ID查询 Employee selectById(Integer id); // 根据工号查询 Employee selectByEmpNo(String empNo); // 多条件分页查询 ListEmployee selectByCondition(Employee condition, int pageNum, int pageSize); // 查询符合条件的总记录数用于分页计算总页数 int countByCondition(Employee condition); }实现类EmployeeDaoImpl中会使用JDBC和Druid连接池来执行SQL。这里以复杂的多条件分页查询为例展示其实现逻辑Override public ListEmployee selectByCondition(Employee condition, int pageNum, int pageSize) { Connection conn null; PreparedStatement ps null; ResultSet rs null; ListEmployee list new ArrayList(); try { conn DBUtil.getConnection(); // 动态构建SQL防止SQL注入 StringBuilder sql new StringBuilder(SELECT e.*, d.dept_name FROM employee e LEFT JOIN department d ON e.dept_id d.id WHERE e.status 1 ); ListObject params new ArrayList(); if (condition ! null) { if (condition.getEmpName() ! null !condition.getEmpName().trim().isEmpty()) { sql.append( AND e.emp_name LIKE ? ); params.add(% condition.getEmpName() %); } if (condition.getDeptId() ! null) { sql.append( AND e.dept_id ? ); params.add(condition.getDeptId()); } if (condition.getEmpNo() ! null !condition.getEmpNo().trim().isEmpty()) { sql.append( AND e.emp_no ? ); params.add(condition.getEmpNo()); } } sql.append( ORDER BY e.id DESC LIMIT ?, ? ); // 计算分页起始位置 params.add((pageNum - 1) * pageSize); params.add(pageSize); ps conn.prepareStatement(sql.toString()); for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } rs ps.executeQuery(); while (rs.next()) { Employee emp new Employee(); // 使用BeanUtils.populate或手动映射字段... emp.setId(rs.getInt(id)); emp.setEmpName(rs.getString(emp_name)); emp.setDeptName(rs.getString(dept_name)); // 从JOIN查询中获取 // ... 映射其他字段 list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return list; }关键点使用PreparedStatement这是防止SQL注入攻击的关键。永远不要用字符串拼接的方式来构造SQL。动态SQL构建根据前端传入的查询条件动态添加WHERE子句。params列表用来收集参数值确保顺序与?占位符对应。分页逻辑使用MySQL的LIMIT offset, size语法。offset (pageNum - 1) * pageSize。连接关闭务必在finally块中关闭ResultSet、PreparedStatement和Connection实际上是归还给连接池。4.2 控制层ControllerServlet处理请求Servlet充当控制器接收HTTP请求调用相应的DAO方法处理业务然后决定将请求转发到哪个JSP页面或重定向到哪里。以“添加员工”功能为例创建一个EmployeeAddServletpackage com.ems.web; import com.ems.dao.EmployeeDao; import com.ems.dao.impl.EmployeeDaoImpl; import com.ems.model.Employee; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Date; import java.util.List; WebServlet(/employee/add) public class EmployeeAddServlet extends HttpServlet { private EmployeeDao employeeDao new EmployeeDaoImpl(); // 文件上传保存路径 private static final String UPLOAD_DIRECTORY upload; Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 检查请求是否为multipart/form-data文件上传 if (!ServletFileUpload.isMultipartContent(req)) { req.setAttribute(errorMsg, 请求格式错误); req.getRequestDispatcher(/employee_add.jsp).forward(req, resp); return; } DiskFileItemFactory factory new DiskFileItemFactory(); ServletFileUpload upload new ServletFileUpload(factory); Employee emp new Employee(); String photoFileName null; try { // 2. 解析请求获取表单字段和文件 ListFileItem formItems upload.parseRequest(req); for (FileItem item : formItems) { if (item.isFormField()) { // 普通表单字段 String fieldName item.getFieldName(); String fieldValue item.getString(UTF-8); BeanUtils.setProperty(emp, fieldName, fieldValue); } else { // 文件字段照片 if (!item.getName().isEmpty()) { String fileName new File(item.getName()).getName(); // 生成唯一文件名防止覆盖 String fileExt fileName.substring(fileName.lastIndexOf(.)); photoFileName System.currentTimeMillis() _ (int)(Math.random()*1000) fileExt; String uploadPath getServletContext().getRealPath() File.separator UPLOAD_DIRECTORY; File uploadDir new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdir(); } String filePath uploadPath File.separator photoFileName; File storeFile new File(filePath); item.write(storeFile); emp.setPhotoUrl(UPLOAD_DIRECTORY / photoFileName); } } } // 3. 补充业务数据 emp.setEmpNo(generateEmpNo()); // 调用生成工号的方法 emp.setStatus(1); emp.setCreateTime(new Date()); emp.setUpdateTime(new Date()); // 4. 数据验证应在前端JS验证基础上后端再次验证 if (emp.getEmpName() null || emp.getEmpName().trim().isEmpty()) { throw new IllegalArgumentException(员工姓名不能为空); } // 可以添加更多验证如身份证格式、邮箱格式等 // 5. 调用DAO插入数据 int result employeeDao.insert(emp); if (result 0) { // 成功重定向到列表页防止表单重复提交 resp.sendRedirect(req.getContextPath() /employee/list); } else { req.setAttribute(errorMsg, 添加员工失败); req.getRequestDispatcher(/employee_add.jsp).forward(req, resp); } } catch (Exception e) { e.printStackTrace(); // 失败回显数据到表单页面 req.setAttribute(emp, emp); req.setAttribute(errorMsg, 系统错误 e.getMessage()); req.getRequestDispatcher(/employee_add.jsp).forward(req, resp); } } private String generateEmpNo() { // 简单的工号生成逻辑例如EMP 年月日 序列号 // 实际项目中可能从数据库获取最大序列号 return EMP System.currentTimeMillis(); } }代码详解与避坑文件上传员工照片上传需要使用commons-fileupload组件。表单的enctype必须设为multipart/form-data。ServletFileUpload负责解析这种格式的请求。BeanUtils使用Apache Commons BeanUtils可以方便地将表单字段名映射到JavaBean的同名属性上减少手动getParameter的代码。但要注意表单字段名必须和Employee类的属性名一致。文件名处理直接使用用户上传的文件名是危险的可能包含路径、特殊字符或中文乱码。通常做法是生成一个唯一的文件名如时间戳随机数后缀来存储而将原始文件名保存在数据库中另一个字段以备查。文件存储路径getServletContext().getRealPath()获取的是Web应用在服务器上的真实部署路径。将文件存在这里在应用重新部署时可能会丢失。生产环境中强烈建议将上传文件存储在一个独立的、与应用分离的目录或对象存储服务中。重定向Redirect vs 转发Forward添加成功后使用resp.sendRedirect()这是一种Post/Redirect/Get (PRG) 模式能有效防止用户刷新页面时重复提交表单。而失败时使用req.getRequestDispatcher().forward()可以将错误信息和用户已填的数据带回原表单页面实现回显。异常处理务必捕获所有异常并给用户一个友好的错误提示而不是将堆栈信息直接抛到页面上。4.3 视图层ViewJSP与EL/JSTLJSP页面负责展示数据。我们应该尽量让JSP只做展示逻辑处理交给Servlet。使用EL表达式和JSTL标签可以大大简化JSP代码。员工列表页 (employee_list.jsp) 关键部分% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html head title员工列表/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap4.6.0/dist/css/bootstrap.min.css /head body div classcontainer mt-4 h2员工信息管理/h2 !-- 查询条件表单 -- form action${pageContext.request.contextPath}/employee/list methodget classform-inline mb-3 input typetext nameempName value${param.empName} placeholder姓名 classform-control mr-2 select namedeptId classform-control mr-2 option value全部部门/option c:forEach items${deptList} vardept option value${dept.id} c:if test${param.deptId dept.id}selected/c:if${dept.deptName}/option /c:forEach /select button typesubmit classbtn btn-primary查询/button /form !-- 员工列表表格 -- table classtable table-bordered table-hover thead classthead-light tr th工号/thth姓名/thth部门/thth职位/thth入职日期/thth操作/th /tr /thead tbody c:forEach items${pageInfo.list} varemp tr td${emp.empNo}/td td${emp.empName}/td td${emp.deptName}/td td${emp.position}/td tdfmt:formatDate value${emp.hireDate} patternyyyy-MM-dd//td td a href${pageContext.request.contextPath}/employee/detail?id${emp.id} classbtn btn-sm btn-info查看/a a href${pageContext.request.contextPath}/employee/edit?id${emp.id} classbtn btn-sm btn-warning编辑/a a hrefjavascript:if(confirm(确定删除吗)) location.href${pageContext.request.contextPath}/employee/delete?id${emp.id} classbtn btn-sm btn-danger删除/a /td /tr /c:forEach /tbody /table !-- 分页组件 -- nav aria-labelPage navigation ul classpagination justify-content-center c:if test${pageInfo.hasPreviousPage} li classpage-itema classpage-link href?pageNum${pageInfo.prePage}empName${param.empName}deptId${param.deptId}上一页/a/li /c:if c:forEach begin1 end${pageInfo.pages} vari li classpage-item c:if test${pageInfo.pageNum i}active/c:if a classpage-link href?pageNum${i}empName${param.empName}deptId${param.deptId}${i}/a /li /c:forEach c:if test${pageInfo.hasNextPage} li classpage-itema classpage-link href?pageNum${pageInfo.nextPage}empName${param.empName}deptId${param.deptId}下一页/a/li /c:if /ul p classtext-center共 ${pageInfo.total} 条记录第 ${pageInfo.pageNum}/${pageInfo.pages} 页/p /nav /div /body /html关键点EL表达式${pageInfo.list}、${emp.empName}直接从Servlet设置的请求属性中取值比老式的% %脚本简洁安全得多。JSTL核心标签c:forEach遍历集合。c:if条件判断。fmt:formatDate格式化日期。分页参数传递分页链接中除了pageNum还必须带上当前的查询条件如empName,deptId否则翻页后查询条件会丢失。这是新手常犯的错误。Bootstrap引入了Bootstrap的CSS使用其表格和分页组件可以快速获得一个美观的界面。删除确认使用javascript:confirm()在删除前给用户一个确认提示是良好的用户体验。对应的Servlet (EmployeeListServlet) 需要处理查询参数调用DAO的分页查询方法并将结果PageInfo对象包含列表数据、当前页、总页数等信息和部门列表用于下拉框设置到请求属性中然后转发到JSP。5. 项目部署、常见问题排查与进阶思考5.1 项目部署到Tomcat开发完成后你需要将项目部署到Tomcat服务器进行测试或上线。打包使用Maven的package命令生成一个WAR文件如employee-management.war。部署将WAR文件复制到Tomcat的webapps目录下。启动启动Tomcatbin/startup.bat或bin/startup.shTomcat会自动解压WAR文件并部署应用。访问在浏览器中输入http://localhost:8080/employee-management/访问应用。常见部署问题404错误检查上下文路径Context Path是否正确。在IDEA中运行和在独立Tomcat中部署上下文路径可能不同。可以通过在web.xml中配置display-name或在Tomcat的conf/server.xml中配置Context来指定。500错误内部服务器错误查看Tomcat的logs/catalina.out或logs/localhost.yyyy-MM-dd.log日志文件这是排查问题的第一现场。常见原因有数据库连接失败、Jar包冲突、JSP编译错误等。中文乱码确保做到“四码合一”数据库、表字符集设为utf8mb4。连接字符串加上characterEncodingutf8。JSP页面顶部设置% page pageEncodingUTF-8%和meta charsetUTF-8。Servlet中处理请求和响应时设置request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)。5.2 从Servlet/JSP到Spring Boot的平滑过渡如果你用Servlet/JSP完成了这个项目恭喜你你已经掌握了JavaWeb的基石。接下来可以尝试用Spring Boot重写一遍感受现代Java开发的便捷。创建Spring Boot项目使用 start.spring.io 生成一个基础项目依赖选择Web,Thymeleaf,MyBatis,MySQL Driver。配置数据源和MyBatis在application.properties中配置数据库连接。使用MyBatis的注解或XML文件来定义Mapper接口替代手写JDBC的DAO。编写Controller使用Controller和RequestMapping注解替代Servlet方法参数可以直接绑定请求参数、表单对象返回值可以是视图名或JSON数据。使用Thymeleaf模板语法类似HTML比JSP更自然且天然支持与Spring集成。分页插件集成PageHelper这类分页插件一行代码实现复杂分页无需手动计算limit。重写的过程你会深刻体会到框架如何通过“约定大于配置”和依赖注入将你从繁琐的配置和样板代码中解放出来。5.3 项目扩展与思考一个基础的管理系统完成后可以考虑以下方向进行扩展这会让你的项目更有竞争力前端现代化将JSP页面替换为Vue.js或React构建的单页面应用SPA后端提供RESTful API。这是目前前后端分离的主流架构。权限系统深化实现完整的RBAC基于角色的访问控制模型设计user,role,permission,user_role,role_permission五张表实现动态菜单和按钮级权限控制。加入日志框架集成SLF4J Logback记录用户操作日志和系统异常日志便于审计和问题追踪。接口安全为REST API添加JWTJSON Web Token认证替代传统的Session管理。使用缓存对部门列表等不常变化的数据使用Redis进行缓存减轻数据库压力提升响应速度。代码优化Service层在Controller和DAO之间引入Service层处理复杂的业务逻辑和事务管理使用Transactional。全局异常处理使用ControllerAdvice和ExceptionHandler统一处理异常返回友好的错误信息。参数校验使用Hibernate Validator注解在实体类上进行声明式校验代替繁琐的if判断。这个“企业员工信息管理系统”就像一块敲门砖通过它你不仅学会了如何让代码跑起来更重要的是理解了Web应用从数据层到展示层的完整链路以及如何组织代码使其更健壮、更易维护。当你能够独立完成它并解决其中遇到的各种坑时你就已经具备了初级JavaWeb开发者的实战能力。接下来无论是学习更高级的框架还是去应对更复杂的业务系统你都有了坚实的底气。本文还有配套的精品资源点击获取
返回列表