Spring Boot高校人事管理系统:从需求设计到权限部署的完整方案
2026/9/8 15:12:57 网站建设 项目流程

每年到了毕业设计选题交流的时候,总会被同一类问题刷屏:“Spring Boot 能做什么题?怎么做才不像网上烂大街的增删改查?”我给得最多的建议之一,就是高校人事管理系统。原因很实际:Spring Boot 技术栈在它身上几乎每一个常用组件都能派上用场,而高校人事管理又天然带着多角色、多流程、多状态这些软件工程里最值得写的特征。这篇文章会把这个系统从需求拆分、数据库设计、后端工程组织、核心业务实现,到 Spring Security 权限控制和最后部署答辩的完整链路讲清楚,给正在做选题或者想完整走一遍业务系统的同学一条可以直接照搬的思路。

1. 选题逻辑与需求解构:为什么这题既是经典题又是能力分水岭

1.1 系统名称里的三个关键词,直接决定了你的工作量

“高校人事管理系统”这个标题看似老套,实际上信息量非常大,把它拆开看,每个词都在给系统提要求。

“高校”意味着组织形态比普通企业更复杂。一所高校里面通常有教学单位、行政单位、教辅单位、科研机构,部门层级往往不止两级,而且教师群体又分专任教师、行政管理人员、辅导员、实验技术人员、外聘教师等,人员身份属性特别多。如果你的系统把人员只设计成一张“员工表”,后面做职称评定、课时统计、部门归属调整时会非常别扭。

“人事管理系统”告诉你业务主线的核心不是审批流本身,而是“人”在组织里的生命周期。从入职建档开始,到部门分配、岗位任命、职称变动、请假考勤、工资核算,最后是离职或退休,中间每一步都会产生状态和关联记录。所以在设计阶段就要想清楚:哪些数据适合存“最新值”,哪些数据必须留“历史轨迹”。

“Spring Boot”这个技术关键词决定了实现层面的风格,也基本框定了你的技术选型范围。我的看法是,只要题目里明确写了 Spring Boot,你就应该有意识地在系统里体现 Spring Boot 的生态能力:Spring Security 负责认证授权,Spring Data JPA 或 MyBatis-Plus 负责持久层,Spring Schedule 做定时任务,Redis 做缓存或者消息,Spring Boot Actuator 做运行状态监控。哪怕某些模块做得不深,只要链路完整,答辩时也有东西可讲。

1.2 角色和用例先行:人事系统不是管理员一个人的系统

很多毕业生做人事系统时,只设计了一种角色:管理员。这是最大的设计失误。高校人事系统真正的使用场景是多角色协作,不同的人在系统里看到的内容、能执行的操作完全不同。

我建议至少划分四类角色:

  • 系统管理员:负责用户账号、角色权限、数据字典等基础配置,不参与人事业务。
  • 人事处管理员:核心业务操作者,负责教职工档案管理、入职离职审核、工资核算、人事统计。
  • 学院/部门管理员:一般由教学秘书或办公室主任担任,只能维护本部门的教职工信息和请假审批。
  • 普通教职工:可以查看个人档案、发起请假申请、查看工资条。

由这四类角色倒推出来的功能模块就非常清晰了,系统管理、部门管理、教职工档案管理、请假审批、考勤登记、工资管理、统计看板分别服务谁一目了然。这里还是提醒一句,不要在毕业设计里把招聘管理、培训管理、绩效考评全部塞进来,那样工作量会失控,而且每个模块都只能做出皮囊。把教职工档案、请假审批、工资统计这三条线做扎实,系统就已经有很好的完成度了。

2. 数据库设计才是隐形评分点:用“档案+流水”的思路建模

2.1 一张主表加上若干流水的设计逻辑

人事系统最核心的对象是教职工,很多人会直接建一张大宽表,把姓名、性别、学历、职称、部门、工资等全部塞进去。这种表一多了就会变成“字段垃圾桶”,后面想加一个职称变动历史,只能在原表加字段或者在论文里写一段牵强的说明。

正确的建模方式是分成两层:

第一层是当前状态表,也就是teacher_archive,它只存教职工在当前时刻最新的基本信息,工号、姓名、性别、出生日期、入职日期、当前部门ID、当前岗位、当前职称、在职状态等。系统查询列表、导出花名册、统计人数时都走这张主表,速度最快。

第二层是流水表,比如学历变更记录表、职称变动记录表、部门调动记录表等。每一次变化,不是去修改主表已经做过的事情记录,而是往对应的流水表里插一条新纪录,同时更新主表的当前字段。这样系统既能回答“这个老师现在是什么职称”,也能回答“2018年到2023年之间他的职称是怎么一步步升上来的”。

这两层设计对应到实时业务里非常直观。人事处老师录入一个新进教师时,先写teacher_archive主档,再插入一条“入职分配”记录;教师评上副教授之后,主表职称字段更新为副教授,同时在职称流水表里留一条带生效日期的记录。这种设计在数据库课程里是好范式,在真实系统里是好维护性,在答辩时也能解释得非常有底气。

2.2 核心表清单与建表的关键细节

这里给一份我在类似项目中整理过的核心表清单,你可以直接用来指导自己建表:

表名用途设计要点
sys_department部门机构表parent_id 自关联成树,适合高校多级部门
sys_user登录用户表与教职工档案 teacher_id 关联,用于账号登录
sys_role / sys_user_role角色及关系表RBAC 权限模型基础
teacher_archive教职工档案主表工号唯一,保存当前最新状态
teacher_title_change职称变动流水记录晋升时间、原职称、新职称、审批操作人
leave_order请假申请单核心审批流,状态字段驱动
attendance_record考勤登记表按人按天记录出勤情况
salary_account_main工资单头表记录某年某月某人的工资汇总
salary_account_item工资单明细表收入、扣款项目单独成行,便于扩展

在主档案表上,有一些字段是安全底线级别的,务必注意。工号必须加唯一约束,因为高校里工号在人员在职期间唯一,如果业务上允许工号重新分配,至少也要在逻辑层面做校验。身份证号、手机号属于个人敏感信息,开发测试环境可以脱敏,生产环境应当加密存储,至少不能在接口返回值里完整输出。

teacher_archive的简化建表脚本可以这样写:

CREATE TABLE `teacher_archive` ( `id` bigint NOT NULL AUTO_INCREMENT, `teacher_no` varchar(32) NOT NULL COMMENT '工号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint DEFAULT NULL COMMENT '性别 1男 2女', `birth_date` date DEFAULT NULL COMMENT '出生日期', `department_id` bigint DEFAULT NULL COMMENT '当前所属部门', `position_name` varchar(50) DEFAULT NULL COMMENT '当前岗位', `title_name` varchar(50) DEFAULT NULL COMMENT '当前职称', `title_date` date DEFAULT NULL COMMENT '职称评定日期', `hire_date` date DEFAULT NULL COMMENT '入职日期', `status` tinyint DEFAULT '1' COMMENT '状态 1试用期 2在职 3离职 4退休', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_teacher_no` (`teacher_no`), KEY `idx_department_id` (`department_id`) ) ENGINE = InnoDB COMMENT ='教职工档案主表';

这里最容易被忽略的是索引设计。department_id是“按部门查人”的高频条件,必须单独建索引,否则数据量上来之后,部门列表页会越来越慢。status字段如果经常作为筛选条件,可以和department_id做成联合索引。工号已经建了唯一索引,按工号查询就直接走索引。

职称变动流水表的思路是,不为每一次职称变动去修改某一行的历史数据,而是新增行:

CREATE TABLE `teacher_title_change` ( `id` bigint NOT NULL AUTO_INCREMENT, `teacher_id` bigint NOT NULL COMMENT '教职工档案ID', `old_title` varchar(50) DEFAULT NULL COMMENT '原职称', `new_title` varchar(50) NOT NULL COMMENT '新职称', `change_date` date NOT NULL COMMENT '变动生效日期', `change_reason` varchar(200) DEFAULT NULL COMMENT '变动原因', `operator_id` bigint DEFAULT NULL COMMENT '操作人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE = InnoDB COMMENT ='职称变动记录表';

职称变动的流水一旦留住了,后面统计“过去五年学校副高以上人员增幅”“各学院职称结构占比”这类报表时,就可以直接基于流水表做时间维度分析,而不需要靠什么历史快照表。人事处老师非常喜欢这种能看得出来龙去脉的功能。

2.3 工资表不要设计成一堆并列字段

工资单是另一个典型的“看起来简单、设计起来容易翻车”的表。常规做法是建一个工资表,字段依次是基本工资、岗位津贴、绩效工资、住房补贴、公积金、养老保险……如果学校工资项目调整了,比如新增一项“人才补贴”,你就要去改表结构、改 SQL、改前端页面,成本非常高。

更合理的做法是把工资拆成头表和明细表。头表salary_account_main记录某年某月某位教职工的工资单头,包括总应发、总扣款、实发金额、状态;明细表salary_account_item把每一个收入项和扣款项单独做成一行,通过 item_type 区分收入还是扣款,通过 item_code 区分项目类型。

统计某个月全校工资结构时,写一条 SQL 用 CASE WHEN 分组聚合就行:

SELECT teacher.department_id, SUM(CASE WHEN item.item_type = 1 THEN item.amount ELSE 0 END) AS total_income, SUM(CASE WHEN item.item_type = 2 THEN item.amount ELSE 0 END) AS total_deduction, SUM(CASE WHEN item.item_type = 1 THEN item.amount ELSE 0 END) - SUM(CASE WHEN item.item_type = 2 THEN item.amount ELSE 0 END) AS net_salary FROM salary_account_main main JOIN salary_account_item item ON main.id = item.account_id JOIN teacher_archive teacher ON main.teacher_id = teacher.id WHERE main.account_year = 2025 AND main.account_month = 6 GROUP BY teacher.department_id;

这种“一插入明细、聚合出结果”的思路,和电商系统里订单加订单项的设计一致。数据库范式好的系统,后续做统计报表会舒服非常多。很多毕业设计丢分不是丢在功能没有做完,而是丢在一开始建表时没有留出扩展余地。这一段数据模型能讲顺,整篇设计论文的核心逻辑就有了。

3. Spring Boot 工程骨架:包结构和基础设施别等写代码时再补

3.1 技术选型怎么搭配最不容易出问题

这一节是很多学生最容易掉进去的坑。我先给一套稳妥的组合,再说明为什么。

  • JDK:建议 JDK 8 或 JDK 17。
  • Spring Boot:如果 JDK 8,选 Spring Boot 2.7.18;如果 JDK 17,选 Spring Boot 3.2.x。
  • 持久层:MyBatis-Plus 3.5.x,注意 Spring Boot 3 要引入mybatis-plus-spring-boot3-starter
  • 数据库:MySQL 8.0。
  • 权限:Spring Security + JWT,或者 Sa-Token。Spring Security 是题目相关技术关键词中更标准的方案,毕业设计使用它更合理。
  • Redis:做验证码缓存、登录状态存储,不是必须,但有它会让系统的技术层次完整很多。
  • 构建工具:Maven。

为什么不直接推荐最新版?因为毕业设计最重要的指标是稳定和可查。Spring Boot 3.0 刚出的时候很多人跟风用,结果很多老教程里的配置全失效,网上问答又少,一个依赖问题能卡一天半。Spring Boot 2.7.18 是 2.x 的最终维护版本,三年内你搜到的问题解决方案几乎全部适用。如果学校明确规定要用新版,那就老老实实从 Spring Boot 3.2 起步,不要混着 2.x 的代码抄。

3.2 分包结构:按“表现层、业务层、数据层”的分层思想来

很多学生的 Spring Boot 项目只有一个 controller 包和一个 mapper 包,Service 层直接省略,所有业务逻辑都堆在 Controller 方法里。这种代码在只有两三个页面时没有问题,一旦加入请假审批、权限判断、Excel 导入,Controller 就会膨胀到几百行,连自己都难维护。

我建议包结构按下面的方式组织:

com.example.hrms ├── HrmsApplication.java ├── common │ ├── Result.java // 统一返回体 │ ├── BizException.java // 业务异常 │ └── GlobalExceptionHandler.java ├── config │ ├── SecurityConfig.java │ ├── MybatisPlusConfig.java │ └── RedisConfig.java ├── controller // 只有参数接收和简单校验 ├── service // 业务逻辑和事务控制都在这 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库表对应的实体 ├── dto // 请求参数对象 └── vo // 响应结果对象

Controller 方法里不应该出现复杂的 if else 和状态判断,一个接口尽量只做一件事:接收参数,转给 Service,把 Service 返回结果包成 Result 返回。业务判断全部下沉到 Service。什么时候用事务?只要涉及多张表更新就要思考事务,比如人事处审核通过一条请假申请时,既要把请假单状态改成通过,又要修改请假人的剩余假期额度,这两个操作必须有同一个事务保护,否则中途报错就会出现“状态成功但假期扣了”的数据不一致。

3.3 统一返回体和全局异常是必须提前铺的基础设施

前后端分离的项目里,每一个接口的返回格式都应该一致。我构建项目时习惯从一开始就写一个 Result 类:

public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(int code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

然后定义一个业务异常类 BizException,再加上 @RestControllerAdvice 全局异常处理器。后面所有需要报错的场景,比如“工号重复”“审批单已被处理”,直接在 Service 里throw new BizException(400, "工号重复")就可以,异常处理器会自动把错误信息包装成标准响应返回前端。前端也不再需要逐个接口处理 data 为空和 error 的情况,拦截器统一判断 code 是否为 200 即可。

这不是额外工作量,这是替后面所有接口省时间。没有统一返回和异常体系的时候,你每写一个接口都要手写 try catch 和 Map 返回,代码会非常脏。

4. 核心业务接口的三个突破口:批量导入、审批状态机、定时提醒

4.1 Excel 批量导入教职工:不要边查边插,更不要遇错就停

高校人事处的老师手里通常维护着一份全校教职工 Excel 花名册,系统上线后让你手动一个个录几百条数据,既不现实也毫无体验。所以教职工档案模块的 Excel 导入功能必须是标配,这也是展示 Java 处理办公文档能力的好场景。

Excel 解析组件的选型上,首选阿里 EasyExcel,它对内存的占用比传统 Apache POI 要低一个量级。导入逻辑可以拆成五个步骤:

  1. 校验上传文件的格式和后缀,避免不是正确的 Excel 文件导致解析异常。
  2. 读取全部行数据,解析成 DTO 列表。
  3. 逐行做基础校验:工号是否为空、姓名是否为空、部门名称是否能在部门表中找到、日期格式是否正确。
  4. 对于校验通过的行,按工号判断是否已经存在,不存在则插入主档,存在则收集到错误清单,不直接覆盖。
  5. 最终返回一个结果对象:成功导入多少条,第几行哪一列为什么失败。

关于第 4 点的设计,很多新手容易走两个极端。一个极端是每一行遇到错误就抛异常结束整个导入,结果用户每次都只能改一条再导入一次;另一个极端是直接无视重名和重复工号强行插入,最后数据乱掉。正确做法是把失败原因收集进列表,比如:

List<String> errorMessages = new ArrayList<>(); for (int i = 0; i < rows.size(); i++) { TeacherImportDTO dto = rows.get(i); if (StringUtils.isBlank(dto.getTeacherNo())) { errorMessages.add("第" + (i + 2) + "行:工号不能为空"); continue; } if (teacherMapper.selectByTeacherNo(dto.getTeacherNo()) != null) { errorMessages.add("第" + (i + 2) + "行:工号 " + dto.getTeacherNo() + " 已存在"); continue; } // 插入逻辑 }

这么做的用户体验是最合理的:人事处老师拿到错误清单后可以一次性修正全部问题,再重新导入一次。你在做项目演示的时候也更容易讲清楚,直接向评委展示“出现错误行的文件会怎么提示”,比单纯展示一张导入成功页更有说服力。

4.2 请假审批:用状态字段驱动单表单流程,防并发就靠乐观更新

高校教职工请假流程通常不超过三级:教师提交申请,部门负责人审核,最后人事处备案。在数据库里,这其实就是一张 leave_order 表加一个状态字段的问题。但别小看这个状态机,它是练习“表驱动的业务流程”的最好场景。

请假单的状态可以设置为:

状态码含义
0待部门负责人审核
1待人事处审核
2审核通过
3审核驳回
4已撤销

部门负责人通过之后,把状态从 0 改成 1;人事处通过之后,把状态从 1 改成 2;任何一级不同意,都改成 3。这个流程简单,却有几个容易踩坑的细节。

第一个坑是“谁可以处理待办”。不能只查所有状态为 0 的单子,必须结合当前登录人的部门和岗位。部门负责人看的是本部门教师的请假单,人事处看的是所有部门已通过一级审核的单子。所以查询待办接口的 SQL 必须把部门 ID 关联条件做进去,这就是前面数据权限设计在业务层的具体体现。

第二个坑是重复审批。两个负责人在不同窗口同时打开同一条请假单,都点了通过,如果不做控制,状态可能被覆盖两次。解决办法不是加锁,而是用乐观更新:

int rows = leaveOrderMapper.updateStatusByIdAndStatus( order.getId(), LeaveStatus.PENDING_DEPT, // 当前状态,作为条件 LeaveStatus.PENDING_HR // 目标状态 ); if (rows == 0) { throw new BizException(400, "该请假单已被其他审批人处理,请刷新后重试"); }

UPDATE 语句把当前状态放进 WHERE 条件里,只有符合条件的行才会更新成功,影响行数等于 1。这个设计在答辩时讲出来,老师会立刻意识到你并不是只会写 CRUD,而是真正考虑过并发场景。

4.3 合同到期和退休提醒:Spring Schedule 加一张待办消息表

人事系统除了被人操作的功能,还应该有主动提醒能力。比如某位外聘教师合同 90 天后到期需要提示人事处续签,某位教师下个月到法定退休年龄需要提前准备退休手续。这类需求用 Spring 定时任务实现非常合适。

不要一开始就上来技术难度非常高的分布式任务调度平台。毕业设计和单体应用阶段,一个@Scheduled注解加一张消息表就足够。每天凌晨定时扫描一次相关人员的日期字段,把产生的结果写入一张消息通知表,用户登录后就能在首页看到待办提醒。

@Component public class ContractExpireRemindTask { @Resource private TeacherArchiveMapper teacherArchiveMapper; @Scheduled(cron = "0 0 8 * * ?") public void scanExpiredContract() { LocalDate today = LocalDate.now(); LocalDate remindDate = today.plusDays(90); // 查询合同到期日在未来 90 天内的在职教职工 List<TeacherArchive> list = teacherArchiveMapper.selectByContractExpireDate(remindDate); // 写入消息通知表 } }

如果以后系统要部署多个实例,这种定时任务会出现每个实例都执行一遍的问题。这时就可以考虑引入 Redis Stream,把任务扫描到的提醒事件先写入消息队列,再由指定节点消费处理。Redis Stream 的消费者组机制可以保证同一条消息只被一个节点消费。在 Spring Boot 中使用 Redis Stream 拉取队列消息时,核心是配置一个StreamMessageListenerContainer,消费者注册到 group 里,然后通过@EventListener或 listener 接口处理消息。没有多实例部署需求时,消息表方案完全够用,这个技术升级可以作为论文里的“后续扩展方向”来写。

5. Spring Security 权限控制:从认证到接口鉴权再到数据隔离,三道关都不能省

5.1 基于 RBAC 设计:用户、角色、菜单、按钮之间的关联

权限是人事系统里绝对不能回避的内容。不用 Spring Security,只靠一个管理员权限判断登录用户是否 admin,会导致你写每一个接口时都要手写大量重复的鉴权代码,而且很容易漏。对于高校人事系统这种天然多角色的场景,应该直接采用 RBAC 模型。

RBAC 的表设计在数据库部分已经提过:sys_usersys_role是多对多,sys_rolesys_menu是多对多,sys_menu表里可以定义三类数据:目录(顶级导航)、菜单(左侧菜单项)、按钮(页面上的具体操作权限)。

按钮权限是很多人容易忽略的。比如“删除教职工”这个动作,不是所有有菜单权限的人都能执行,如果菜单权限下放给了人事处管理员,就应该对“删除按钮”做更细的控制。在sys_menu表里给按钮记录一个 permission 字段,例如system:teacher:delete,然后在后端接口加上 Spring Security 的方法级权限注解:

@PreAuthorize("hasAuthority('system:teacher:delete')") @DeleteMapping("/teacher/{id}") public Result<Void> deleteTeacher(@PathVariable Long id) { teacherService.deleteTeacher(id); return Result.success(null); }

这里有一个关键认知:前端按钮权限只能隐藏入口,不能真正防越权。一个懂技术的人完全可以绕过页面直接调用后端接口,所以后端接口必须有注解或过滤器的校验,这是毕业设计答辩时老师的灵魂拷问点。

5.2 登录认证链路:BCrypt 加密、JWT 签发和过滤器链的配合

Spring Security 的认证流程并不复杂,我用一句话概括就是:系统从请求里取出用户名和密码,交给 AuthenticationManager 去做认证,成功后把用户信息封装成一个 Token 对象放进 SecurityContext 里,后续的接口就能从 SecurityContext 里拿到当前登录用户。

实战中的做法是登录成功之后生成 JWT 返回给前端,前端在后续请求的 Header 里带上 token。后端写一个 JwtAuthenticationFilter,加载在 Spring Security 过滤器链的用户名密码过滤器之前,每次请求都从 Header 取出 token、解析用户信息、填入 SecurityContext。

密码存储一定要用 BCrypt 加密,不能明文存库。Spring Security 自带的 BCryptPasswordEncoder 足够可靠,它的 encode 方法会把盐一起编码进哈希串,不需要单独维护盐字段。有的同学担心 BCrypt 慢,其实单次登录的毫秒级延迟完全可以接受,换来的是数据库泄露时用户密码不会裸奔,这个取舍非常值。

关于 JWT 的时间戳字段,token 有效期不要设置得过长,我通常设 2 小时,并配合 Redis 保存登录态,用户“记住我”这个需求通过在 Redis 里维持一个有效会话来实现。这样即使用户退出或者管理员将其强制下线,后端只需要删 Redis 里的 key,token 再有效也进不来。

5.3 数据权限:只看得到自己学院,不是简单条件查询

人事系统最容易被忽略的一层权限是“数据范围”。普通管理员如果可以看到全校教职工的工资,哪怕他没有修改权限,也是相当敏感的安全风险。所以资源权限之外,必须在业务查询层面做数据隔离。

数据权限的实现有两种典型做法。一种是直接在每个 Service 里根据当前用户角色追加查询条件,比如学院管理员只能查本部门:

if (currentUser.isCollegeAdmin()) { queryWrapper.eq(TeacherArchive::getDepartmentId, currentUser.getDepartmentId()); }

这个方案最直观,代码写起来也不复杂,缺点是每个查询方法都要记得加这个条件,一旦忘了就是越权漏洞。

另一种是使用 MyBatis-Plus 的 DataPermissionInterceptor 做拦截器,根据当前用户动态地给所有 SQL 拼上部门条件。这个方案更优雅但是理解门槛较高。我的建议是:如果你想在答辩时展示技术深度,可以在 Service 层封装一个getDataScopeWrapper()方法,然后故意在查询时统一调用,这样代码既有实际防御力,又不需要动到 MyBatis 底层的 SQL 注入,对多数本科生来说更可控。

有一个误区需要特别提醒:数据权限不能只靠后端查询条件,前端在展示菜单和页面时也要配合。但前端的控制只是体验层面的,千万别在前端写死“不显示其它学院数据”就当数据权限完成了,直接调 Api 一样能看到,真正的防线必须放在后端。

6. 开发部署中的排错记录和答辩前最容易忽略的细节

6.1 Lombok 环境报错、日期时区问题和 JSON 序列化不一致

越是基础的环境问题越容易让人崩溃。我在配置项目时遇到过几次 Lombok 的诡异报错,报错内容是You aren't using a compiler supported by lombok, so lombok will not work。这个提示在 IDEA 里出现,排查顺序应该是这样:

先检查项目 JDK 版本和 Lombok 版本的兼容性。Java 8 配 Lombok 1.18.20 以上基本没问题;Java 17 配旧版 Lombok 经常出问题,推荐升级到 1.18.30 以上。如果没有升级条件,就在 IDEA 的 Settings 里找到 Annotation Processors,勾选 Enable annotation processing,然后清缓存重启。多模块 Maven 项目要注意父模块是否引入了 Lombok 依赖,子模块单独编译时可能找不到注解处理器,建议所有子模块的 pom 都显式声明或统一通过 parent 的 dependencies 管理。

还有一个高频问题是后端的日期时间返回给前端时少了 8 小时,或者格式成了时间戳数组。Spring Boot 处理 JSON 时间序列化的核心配置如下:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

MySQL 连接串上也务必加上serverTimezone=Asia/Shanghai,否则 JDBC 驱动读取 DATETIME 时会按服务器默认时区解析,出现时区错乱。这类问题不是代码逻辑 bug,而是链路中每一层的时区配置不一致导致的,排查时从数据库连接串、Jackson 配置、前端格式化器三个方向一起看。

6.2 启动内存溢出和构建命令

用 IDEA 开发的时候,很多同学会遇到java: OutOfMemoryError: insufficient memory。这通常不是代码堆内存溢出,而是 IDEA 的构建进程 JVM 内存不够了。解决方案是在 IDEA 的 Help -> Change Memory Settings 里调大 IDE 内存,并且在 Settings -> Build Tools -> Maven -> Runner 里的 VM Options 添加-Xmx1024m。如果是命令行 Maven 构建报错,就设置MAVEN_OPTS环境变量。

如果代码层面确实有大量数据在内存中操作,比如 Excel 全量读取列表到内存,那就要从代码层想办法。导出大数据量 Excel 时使用 EasyExcel 或 POI 的 SXSSFWorkbook 流式写入,而不是一次性把所有行加载到内存。报表统计的列表查询也始终记得分页,或者只查出需要的字段而不是大字段。

Maven 方式构建 Spring Boot 项目是通用场景,常用命令列在这里:

场景命令
本地开发直接运行mvn spring-boot:run
打包跳过测试mvn clean package -DskipTests
指定环境启动 jarjava -jar target/hrms.jar --spring.profiles.active=prod
部署到 Tomcat改 packaging 为 war,让启动类继承 SpringBootServletInitializer

需要指出的细节是,如果打成 war 包部署到外部 Tomcat,原来的内置 Tomcat 要配置为 provided 范围,否则会和外部容器冲突。如果打成 jar 包直接部署,则在服务器上只需要有 JDK 环境即可。答辩演示的环境经常不是一台全新机器,建议提前把“命令行启动项目”操作练熟练,因为现场用 IDEA 打开一个大型项目来启动,等待构建的时间可能会让气氛非常尴尬。

6.3 Actuator 监控和演示数据准备是答辩里的小型加分项

引入 Spring Boot Actuator 是成本极低但效果很好的做法。在 pom 中增加 spring-boot-starter-actuator 依赖,然后配置暴露的端点:

management: endpoints: web: exposure: include: health,info,metrics

启动之后,访问/actuator/health会返回服务是否正常的 JSON,这也是线上服务做健康检查的基础。如果你愿意再用 Micrometer 把 JVM 内存、线程数、接口调用耗时暴露出来,项目就具备了最基本的可观测性。毕设演示的时候,可以现场打开这个地址,向评委展示“系统具备运行状态自检能力”,比口述系统用了什么框架有说服力得多。

演示前还有一个容易被忽略的实操问题:准备一套干净、真实感强且没有隐私风险的演示数据。人事系统的数据量不要求大,但部门层级要全,职称结构要多样,请假单要有不同状态的记录,工资数据要有连续三个月的明细。很多同学在开发时数据库里数据乱得没法看,比如所有人的姓名都叫 test,部门都是空的,演示一打开页面就非常出戏。我自己的习惯是导出一份固定的初始化 SQL,在演示前重建一次数据库,确保任何时候打开系统,看到的都是一套逻辑完整的数据。这个细节看上去很简单,但每次都能让现场的演示流畅度提高一个档次。

最后再分享一个私人经验:做这种管理系统,最花时间的往往不是代码本身,而是想明白“每个角色的真实处境”。你在演示请假审批时,不要只演示流程走通,可以故意造一条被人事处驳回的数据,展示申请人在“我的申请”里看到驳回原因的完整链路;你在演示数据权限时,用学院管理员账号登录,证明他确实查不到别的学院数据。把这些边界情况和反例

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

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

立即咨询