资讯动态

JavaWeb HR系统实战:SpringMVC+MyBatis+Bootstrap部署与权限落地

发布时间:2026/10/27 15:57:11 来源:尧图企业网站定制
简介这是一套基于JavaWeb技术实现的完整人力资源管理系统源码面向Java初学者与Web开发入门者适用于课程设计、毕业设计或小型企业HR管理模块快速搭建。系统采用JSPServletJDBC经典架构涵盖员工信息、招聘、考勤、培训、薪资、简历管理等核心业务模块Controller层命名清晰如ApplyController、SalaryController、RecruitController实体类与业务逻辑分层明确便于理解MVC模式实践。资源包共268个文件含71个Java源文件、71个编译后Class文件、40个JSP页面、38个XML配置文件含Spring/MyBatis配置及SQL映射、22个Jar依赖库辅以CSS、JS、图片及SQL脚本等整体压缩包仅24MB轻量易部署。目前已有4765人学习下载读者可直接导入Eclipse/IDEA运行调试获取完整可执行项目结构、典型Web层交互逻辑、数据库建表语句及基础权限控制雏形是掌握JavaWeb全栈开发流程的优质练手案例。1. 这不是又一个“学生作业级”JavaWeb系统它能真正在中小公司跑通考勤、薪资、组织架构三件套你搜“JavaWeb 人力资源管理系统源码”刷出来的大多是毕业设计模板——登录页带雪花动效、员工列表用table硬写、数据库字段叫user_name和pass_word、连密码都没加盐。但真正让HR每天打开、录入入职信息、导出个税申报表、同步给财务系统的系统必须过三关数据不能丢、权限不能乱、流程不能断。这个标题下的源码核心价值不在“能跑”而在它把SpringMVC MyBatis Bootstrap 3.3.7 MySQL 5.7这套被低估的“稳态技术栈”用到了骨子里用Transactional兜底薪资计算的原子性用Filter链实现部门-角色-菜单三级权限隔离用c:forEach嵌套生成带折叠功能的组织架构树——不是炫技是为了解决“张经理只能看本部门员工但能审批跨部门调岗单”这种真实冲突。适合两类人刚从培训班出来想补全真实项目经验的开发者以及IT资源有限、需要快速上线轻量HR系统的中小企运维/行政人员。它不承诺替代北森或Moka但能让你在3天内部署、7天内配好自己公司的岗位体系和审批流。2. 用 IDEA 跑通 JavaWeb HR 系统从解压到登录页的最小闭环2.1 解压后第一眼该盯什么三个文件夹决定成败拿到源码包常见命名如hrms-java-web.zip或javaweb-hr-system-src.zip别急着导入IDEA。先解压用文本编辑器打开根目录下的pom.xml和src/main/webapp/WEB-INF/web.xml再扫一眼src/main/resources/jdbc.properties。这三处是生死线pom.xml里spring.version4.3.29.RELEASE/spring.version和mybatis.version3.4.6/mybatis.version必须匹配——新版Spring Boot 3.x 的注解在这里会直接报错web.xml中servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class说明这是传统XML配置驱动不是Spring Bootjdbc.properties里jdbc.urljdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8提示你必须提前装好MySQL 5.7不是8.0字符集参数在8.0里已废弃。提示如果pom.xml里出现spring-boot-starter-web立刻停手——这不是标题所指的“JavaWeb”系统而是Spring Boot项目后续配置路径完全不同。2.2 IDEA 导入选对模块类型少踩80%的坑在 IDEA 中选择File → Open → 选中解压后的根目录 → 弹窗点 “Open as Project”不是“Import Project”。关键步骤在弹出的向导中务必勾选 “Maven”取消勾选 “Auto-import”避免网络卡顿导致依赖下载失败等待 Maven 自动解析完pom.xml后右键项目根目录 →Add Framework Support → 选中 “Web Application” → Version 3.1不是4.0Bootstrap 3.x 与Servlet 4.0存在JS兼容问题手动指定webapp目录为 Web Resource Root右键src/main/webapp→Mark Directory as → Resources Root否则JSP里的c:forEach标签会标红。# 验证Maven依赖是否完整终端执行 mvn dependency:tree -Dincludesorg.springframework:spring-webmvc,org.mybatis:mybatis-spring输出应包含spring-webmvc:4.3.29.RELEASE和mybatis-spring:1.3.2—— 若显示spring-webmvc:5.3.30说明本地Maven仓库混入了高版本需删掉~/.m2/repository/org/springframework/spring-webmvc/下所有非4.3.x的文件夹。2.3 数据库初始化别信SQL脚本里的“自动建库”源码包里通常有sql/hrms_init.sql或db/hrms_schema.sql。绝不能直接执行。打开它你会发现开头是CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hrms; -- 以下建表语句...问题在于utf8mb4在MySQL 5.7默认未启用。必须先手动创建数据库并指定字符集-- 在MySQL命令行或Navicat中执行 CREATE DATABASE hrms CHARACTER SET utf8 COLLATE utf8_general_ci; -- 注意这里用 utf8不是utf8mb4因为MyBatis 3.4.6的驱动不识别utf8mb4的排序规则然后复制hrms_init.sql中USE hrms;之后的所有建表和INSERT语句逐段执行。重点检查t_user表的password字段是否为VARCHAR(64)对应SHA-256加密若为VARCHAR(32)说明是MD5加密需同步修改UserServiceImpl.java中的密码加密逻辑。2.4 Tomcat 配置端口、上下文路径、JVM参数一个都不能少在 IDEA 中配置 Tomcat ServerApplication context必须设为/hrms不是/否则登录跳转会指向根路径VM options加入-Dfile.encodingUTF-8 -Xms512m -Xmx1024m中文乱码和内存溢出是高频翻车点Deployment标签页下点击→Artifact→ 选hrms:war exploded不是hrms:warJSP修改后需热更新Before launch中删掉所有Build任务只保留Build hrms:war exploded避免重复编译导致class文件冲突。启动后访问http://localhost:8080/hrms/login.jsp若看到登录框但CSS失效说明静态资源路径错了——检查web.xml中welcome-file-list是否指向login.jsp且login.jsp里link relstylesheet hrefcss/bootstrap.min.css的css/前不能有/因为上下文路径是/hrms绝对路径/css/会请求http://localhost:8080/css/。3. 权限模型落地为什么“张经理看不到李工工资”不是靠if-else硬写3.1 三级权限表结构department → role → menu 的真实约束力这套源码的权限不是靠session.getAttribute(role)判断而是用三张表建立动态映射t_department存部门ID、名称、上级部门ID支持多级树t_role存角色ID、名称如“部门经理”、“HR专员”t_role_menu关联角色与菜单ID如角色ID2 → 菜单ID101,102,103关键在t_user表的dept_id和role_id字段——用户登录后系统查出其部门ID再通过t_dept_role部门-角色关联表获取该部门下所有可分配角色最终拼出菜单权限。这意味着当张经理调任新部门只需改t_user.dept_id他能看到的菜单自动切换无需改代码。验证方法在MySQL中执行SELECT m.menu_name FROM t_menu m JOIN t_role_menu rm ON m.menu_id rm.menu_id JOIN t_user u ON rm.role_id u.role_id WHERE u.user_id 1001;结果应与登录后左侧菜单完全一致。3.2 JSP页面权限拦截用自定义TagLib代替JavaScript隐藏很多新手用if(session.getAttribute(role).equals(admin)){...}控制按钮显示这是重大安全漏洞——前端隐藏的按钮抓包发请求照样能调用。本系统用auth:hasPermission标签% taglib prefixauth uri/WEB-INF/tlds/auth.tld % !-- 只有拥有menu_id201的用户才显示 -- auth:hasPermission menuId201 button onclickopenSalaryDialog()查看薪资/button /auth:hasPermission背后是AuthTag.java实现public class AuthTag extends SimpleTagSupport { private String menuId; Override public void doTag() throws JspException, IOException { HttpServletRequest request (HttpServletRequest) getJspContext() .getAttribute(javax.servlet.jsp.jspRequest, PageContext.REQUEST_SCOPE); User user (User) request.getSession().getAttribute(user); // 查询t_role_menu确认user.role_id是否有menuId权限 if (permissionService.hasPermission(user.getRoleId(), Integer.parseInt(menuId))) { getJspBody().invoke(null); // 渲染标签体 } } }注意auth.tld文件必须放在WEB-INF/tlds/下且web.xml中要声明taglib否则JSP编译时报Cannot find tag library descriptor。3.3 薪资模块的事务边界为什么“发薪失败时员工状态不能变”薪资计算涉及三张表联动t_salary主记录、t_salary_item明细项、t_employee状态字段status从“在职”变“已发薪”。源码中SalaryService.java的关键方法Transactional(rollbackFor Exception.class) public void generateSalary(int month, int year) { // 1. 检查当月是否已生成薪资防止重复执行 if (salaryMapper.existsByMonthYear(month, year)) { throw new RuntimeException(本月薪资已生成); } // 2. 批量插入t_salary主记录 salaryMapper.insertBatch(salaryList); // 3. 批量插入t_salary_item明细 itemMapper.insertBatch(itemList); // 4. 更新t_employee.status SALARY_PROCESSED employeeMapper.updateStatusAfterSalary(); }血泪经验若第4步失败前两步必须回滚。但updateStatusAfterSalary()方法若用了Transactional注解会导致事务传播行为异常REQUIRES_NEW会开启新事务无法回滚。正确做法是去掉该方法的注解确保整个generateSalary()在同一事务中执行。4. 避坑那些让开发者凌晨三点还在查日志的典型故障4.1 现象登录成功后跳转到空白页浏览器控制台报Uncaught ReferenceError: $ is not defined原因login.jsp引用了 jQuery但webapp/js/下实际没有jquery.min.js文件或路径写成../js/jquery.min.js相对路径错误。解决检查login.jsp中script srcjs/jquery.min.js/script确认webapp/js/jquery.min.js存在若用CDN改为script srchttps://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.min.js/script注意版本号必须与Bootstrap 3.3.7兼容。4.2 现象新增员工时提示 “Data truncation: Data too long for column id_card at row 1”原因t_employee.id_card字段定义为VARCHAR(18)但用户输入了带X的18位身份证如11010119900307271XMySQL 5.7默认严格模式拒绝插入。解决在jdbc.properties的URL末尾添加jdbcCompliantTruncationfalse或修改表结构ALTER TABLE t_employee MODIFY id_card VARCHAR(18) CHARACTER SET utf8 COLLATE utf8_general_ci;4.3 现象Tomcat启动后控制台疯狂打印WARN o.s.w.c.s.XmlWebApplicationContext - Exception encountered during context initialization原因applicationContext.xml中bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean的configLocation属性指向classpath:mybatis-config.xml但该文件实际在src/main/resources/mybatis/下。解决将mybatis-config.xml移到src/main/resources/根目录或修改配置为property nameconfigLocation valueclasspath:mybatis/mybatis-config.xml/4.4 现象导出Excel时中文全是问号文件名乱码为?????.xls原因ExportController.java中response.setHeader(Content-Disposition, attachment; filename fileName);未对fileName编码。解决用URLEncoder.encode(fileName, UTF-8)包裹文件名并指定响应编码response.setContentType(application/vnd.ms-excel;charsetUTF-8); response.setHeader(Content-Disposition, attachment; filename URLEncoder.encode(fileName, UTF-8));4.5 现象修改员工信息后页面显示“操作成功”但数据库没变化原因EmployeeController.java的updateEmployee()方法接收参数用RequestParam但表单提交是POST且enctypemultipart/form-data含文件上传导致参数绑定失败。解决改用ModelAttribute Employee employee接收或确保表单enctype为application/x-www-form-urlencoded默认值。5. 让系统真正可用三个必须动手改的生产级配置5.1 日志分级把DEBUG日志关掉ERROR日志单独归档开发时log4j.properties通常是log4j.rootLoggerDEBUG, stdout, file上线必须改为log4j.rootLoggerINFO, stdout, file log4j.logger.com.hrmsERROR, errorFile # 单独归档ERROR日志 log4j.appender.errorFileorg.apache.log4j.DailyRollingFileAppender log4j.appender.errorFile.File${catalina.home}/logs/hrms-error.log log4j.appender.errorFile.DatePattern.yyyy-MM-dd log4j.appender.errorFile.layoutorg.apache.log4j.PatternLayout log4j.appender.errorFile.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%p] %c{1} - %m%n这样com.hrms.dao包下的SQL执行日志不会刷屏而com.hrms.service的异常堆栈会独立存到hrms-error.log方便运维排查。5.2 密码加密升级从明文到BCrypt一步到位源码中UserService.java的登录验证可能是// 危险明文比对 if (user.getPassword().equals(inputPassword)) { ... }必须替换为 BCrypt// 引入依赖 // dependency // groupIdorg.springframework.security/groupId // artifactIdspring-security-crypto/artifactId // version4.2.20.RELEASE/version // /dependency // 初始化加密器 private BCryptPasswordEncoder passwordEncoder new BCryptPasswordEncoder(); // 注册时加密 String encodedPassword passwordEncoder.encode(rawPassword); // 登录时验证 if (passwordEncoder.matches(inputPassword, user.getPassword())) { ... }注意BCryptPasswordEncoder的strength参数默认为10生成的密文长度固定为60位需将t_user.password字段改为VARCHAR(100)。5.3 静态资源缓存让Bootstrap CSS不再每次重载在web.xml中添加过滤器filter filter-nameExpiresFilter/filter-name filter-classorg.apache.catalina.filters.ExpiresFilter/filter-class init-param param-nameExpiresByType text/css/param-name param-valueaccess plus 1 month/param-value /init-param init-param param-nameExpiresByType application/javascript/param-name param-valueaccess plus 1 month/param-value /init-param /filter filter-mapping filter-nameExpiresFilter/filter-name url-pattern*.css/url-pattern url-pattern*.js/url-pattern /filter-mapping重启Tomcat后Chrome开发者工具Network面板中CSS文件的Response Headers会出现Cache-Control: max-age2592000大幅提升二次访问速度。6. 我的私藏技巧用Excel模板批量导入员工绕过页面逐条录入真实场景里HR最怕的不是写代码是面对200人的入职名单手动点200次“新增”。这个系统预留了importEmployee.jsp页面但默认没做校验和回滚——我把它升级成了生产可用方案。6.1 模板设计用Excel公式锁定必填项提供标准模板employee_import_template.xlsx其中A列姓名文本格式不允许空B列身份证号自定义格式###############防止Excel自动转科学计数法C列入职日期日期格式yyyy-mm-ddD列部门名称下拉列表数据源来自t_department.nameE列岗位文本F列基本工资数值小数位数0G列备注允许空。提示在Excel中选中D列 →数据 → 数据验证 → 序列 → 来源填INDIRECT(部门列表)再在另一张Sheet建“部门列表”表内容与数据库一致。6.2 后端解析用Apache POI 3.17避免OOMImportController.java中关键逻辑PostMapping(/import) ResponseBody public Result importEmployees(RequestParam(file) MultipartFile file) { try (InputStream is file.getInputStream()) { Workbook workbook WorkbookFactory.create(is); // 自动识别xls/xlsx Sheet sheet workbook.getSheetAt(0); ListEmployee employees new ArrayList(); for (int i 1; i sheet.getLastRowNum(); i) { // 跳过标题行 Row row sheet.getRow(i); if (row null) continue; Employee emp new Employee(); emp.setName(getCellValue(row.getCell(0))); emp.setIdCard(getCellValue(row.getCell(1))); emp.setHireDate(new SimpleDateFormat(yyyy-MM-dd).parse(getCellValue(row.getCell(2)))); emp.setDeptName(getCellValue(row.getCell(3))); // ... 其他字段 employees.add(emp); } // 批量校验非空、身份证格式、部门存在性 String errorMsg validateEmployees(employees); if (errorMsg ! null) { return Result.fail(errorMsg); } // 事务内批量插入 employeeService.batchInsert(employees); return Result.success(成功导入 employees.size() 条); } catch (Exception e) { return Result.fail(解析失败 e.getMessage()); } }关键点WorkbookFactory.create(is)比new XSSFWorkbook(is)更省内存validateEmployees()方法必须查库确认部门是否存在否则插入会因外键约束失败。6.3 错误反馈让用户知道哪一行哪一列错了不要只返回“第5行数据错误”要精确到单元格private String validateEmployees(ListEmployee list) { for (int i 0; i list.size(); i) { Employee emp list.get(i); if (StringUtils.isBlank(emp.getName())) { return String.format(第%d行姓名不能为空, i 2); // 2 因为标题占1行索引从0开始 } if (!IdCardUtil.isValidIdCard(emp.getIdCard())) { return String.format(第%d行身份证号格式错误, i 2); } if (!deptService.existsByName(emp.getDeptName())) { return String.format(第%d行部门【%s】不存在, i 2, emp.getDeptName()); } } return null; }这样HR拿到报错直接定位到Excel具体位置修改不用来回试错。我坚持在每个新项目里加这个Excel导入功能不是因为它多酷而是某次帮客户上线时他们HR拿着U盘来说“我们刚整理完372人的档案现在要一个个输……”——那一刻我就知道真正的工程价值永远在那些让业务人员少点一次鼠标的地方。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑