做毕设选题的时候,不少同学看到“基于springboot的寿险公司人力资源管理系统”这个题目,第一反应就是:这不就是换了个壳的人事管理系统吗?跟普通的员工管理系统有什么区别?说实话,我刚开始动手之前也是这么想的,觉得自己已经见过太多类似的CRUD项目了。真正把需求理清、把数据库表设计完,才发现这个题目远没有字面上那么简单——寿险公司的“人力资源”不只是管员工,还有一支庞大的代理人队伍,他们的薪酬机制、考核方式、团队架构跟普通内勤员工完全是两套逻辑。如果只是照着通用的员工管理系统去做,答辩的时候老师问几个业务问题,基本就露馅了。
这篇文章我想完整复盘一下这个项目的设计与实现过程,包括技术选型、数据库设计、核心功能拆解、常见坑位排查,以及怎么在毕设的基础上做定制扩展。适合正在做这个选题、或者想做Spring Boot方向毕设的同学参考,也适合需要快速上手Spring Boot项目开发的朋友对照着理解。
1. 项目概述与核心思路拆解
1.1 寿险公司人力资源管理的业务特殊性
先说清楚一件事:寿险公司的HR系统和普通企业HR系统最大的不同,在于“双轨制”管理。公司内部有签劳动合同的内勤员工,包括行政、财务、技术、运营等岗位,这部分人跟其他行业没什么区别,考勤、请假、工资、绩效按常规来。但真正的主力其实是外勤代理人——就是大家常说的保险业务员,他们跟保险公司签订的并非劳动合同,而是代理合同,收入以佣金为主,基本工资要么没有,要么很低。
这就带来了一系列连锁的设计差异。第一,人员档案必须有“人员类型”这个维度,区分内勤和外勤,而且外勤人员的字段要多出好几项,比如执业证号、所属营业区、推荐人、职级(见习业务员、正式业务员、主管、经理等)。第二,薪酬计算完全不一样,内勤是“基本工资+绩效奖金-五险一金-个税”这种逻辑,外勤则是“佣金+津贴+奖金”,而且佣金比例跟险种、缴费年期、业绩档位挂钩,手算很容易出错,系统必须支持规则配置。第三,考核和晋升逻辑不同,外勤代理人每个考核季都有业绩指标,达标才能维持职级或晋升,这套东西一般企业根本用不上。
所以我在设计这套系统的时候,没有直接沿用通用人事系统的模型,而是单独拆出了代理人管理、佣金核算、团队归属关系这些模块。这也是这个毕设题目真正的得分点所在——评阅老师看的不只是你会不会写增删改查,而是你能否理解业务领域并把它转化成合理的系统设计。
1.2 技术选型:为什么是Spring Boot + MyBatis-Plus
技术选型是这个阶段最纠结的问题之一。Spring Boot本身没有太多争议,它是目前Java后端开发的事实标准,自动配置、内嵌服务器、起步依赖这三板斧能把项目从零跑到起来的成本压到很低。对于毕设来说,体量不大、周期短、单人开发,Spring Boot比传统的SSM(Spring MVC + Spring + MyBatis)配置少得多,也没有Spring Cloud那套微服务的复杂度,刚刚好。
需要认真权衡的是持久层框架。MyBatis、MyBatis-Plus、Spring Data JPA各有拥趸。我的建议是用MyBatis-Plus,理由很实在:它有现成的通用Mapper,单表CRUD基本不用写SQL,BaseMapper里就带insert、deleteById、selectPage这些方法,开发速度肉眼可见地快;复杂的多表关联查询又可以用注解或XML自己写,不会像JPA那样在复杂场景下“自动生成SQL翻车”。毕设周期就那么多,把时间花在业务逻辑上比花在反复调试ORM映射上划算得多。
前端部分我选了Vue + Element UI,前后端分离,接口用JSON交互。说实话,毕设做前后端分离是有一点风险的——意味着你要同时处理跨域、Token、接口联调这些问题。但好处也很明显:演示的时候体验更接近真实系统,而且Vue的生态和Element UI的表格、表单、弹窗组件确实省事。如果你之前没接触过Vue,也可以退而求其次用Thymeleaf模板引擎在服务端渲染,逻辑更简单但页面效果会朴素一些。两种方案我都跑通过,后面会提到各自的关键注意点。
1.3 功能模块全景图
完整的功能清单如下,基本覆盖了寿险公司HR管理的主要场景:
| 模块 | 核心功能 | 关键业务点 |
|---|---|---|
| 系统管理 | 用户登录、权限控制、菜单管理、操作日志 | JWT认证、RBAC角色权限 |
| 组织架构 | 部门管理、岗位管理、团队(营业区)管理 | 部门层级树、代理人团队归属 |
| 员工管理 | 内勤档案、代理人档案、入职离职、人员异动 | 人员类型区分、执业证管理、职级管理 |
| 考勤管理 | 打卡记录、请假审批、加班登记、考勤统计 | 内勤考勤为主,外勤出勤率统计 |
| 薪酬管理 | 内勤工资、代理人佣金、津贴奖金、薪资台账 | 佣金费率可配置、月度薪资汇总 |
| 招聘管理 | 招聘计划、简历库、面试安排、录用管理 | 代理人增员流程 |
| 培训管理 | 培训计划、培训记录、培训反馈 | 代理人合规培训必做 |
| 绩效与考核 | 考核指标、考核评分、结果查询 | 代理人职级升降依据 |
这八个模块互相之间有清晰的调用关系。比如人员异动会影响团队归属,薪酬计算要读取考勤结果和绩效结果,招聘录用的终点是员工档案的建立。我第一次设计的时候模块划分太散,后来做了收敛:把代理人相关的特殊字段合进统一的员工表里,通过person_type字段区分,而不是单独建一张代理人表——后者虽然字段隔离更干净,但跨模块的查询要反复join,也好不到哪去。这是设计层面一个挺重要的取舍。
2. 核心技术与设计细节解析
2.1 数据库设计:从建表到索引的一次完整思考
数据库是整个系统的地基,这块设计不好后面全是坑。我用的MySQL 8.0,字符集统一utf8mb4(别用utf8,遇到生僻字和特殊符号会报错)。核心表我大致列一下结构思路:
员工表emp是系统的中心表,字段包括:emp_id、emp_no(工号)、name、gender、birth_date、id_card、phone、email、person_type(1内勤 2外勤)、dept_id、position、hire_date、status(1在职 2离职 3试用)、教育背景等。对于外勤,额外有agency_no(执业证号)、agent_level(职级)、team_id(所属团队)、recommend_id(推荐人)等字段。把这些字段放同一张表里,最初有点担心“表太宽”,实际运行下来数据量几千条完全没有问题,反而省了很多麻烦。
部门表dept用parent_id做自关联,形成树形结构,查询子部门用递归或在业务层组装。代理人团队我单独建了team表,原因是团队的组织架构跟部门不一样——部门是固定的职能划分,团队则根据业务发展不断裂变、合并,团队主管本身就是代理人,他在“员工”和“管理者”两个身份间切换,单独建模更清晰。
薪酬相关的表我设计成三张:salary_standard(薪资标准)、salary_detail(月度薪资明细)、commission_config(佣金费率配置)。其中commission_config是关键,它记录了不同险种、不同缴费年期对应的佣金比例,比如某重疾险首年佣金率25%,某年金险20%,缴费期越长首年比例越低但后续续佣比例可能更高。佣金计算就是拿着这张配置表去算每一个代理人的当月业绩,这个逻辑后面会细讲。
索引方面我的原则是:外键字段和查询条件字段必加索引,写多读少的表索引克制一点。比如emp表上的person_type、dept_id、status都是查询热字段,建了普通索引;日期字段如果要按月份范围查询,建立组合索引(emp_no, salary_month)这种。不要迷信全表索引,写入性能会被拖垮。
还有两个设计习惯值得提一下。第一,业务数据表都加了create_time、update_time、deleted三个字段,deleted用逻辑删除——倒不是怕数据丢失,而是毕设答辩的时候,你删掉一条员工记录还能在数据库里翻出来,这种细节会让老师觉得你考虑问题周全。第二,工号、单号这类编号字段用程序生成,格式类似“YG20240101”,别用数据库自增id直接暴露给用户,自增id主键留着内部使用。
2.2 认证授权:JWT + 拦截器 + RBAC的完整落地
用户认证我用的JWT(JSON Web Token),没有引入Spring Security。这个决策我犹豫过——用Spring Security是“正规军”,代码更规范,但学习曲线确实陡,Filter链、UserDetailsService、SecurityConfig一堆概念,毕设阶段搞明白要花不少时间。轻量方案是自己写一个拦截器加JWT工具类,逻辑简单可控,出问题了自己能定位。
具体的流程是这样的:用户登录时,后端校验用户名密码,校验通过后生成一个JWT,把用户id、用户名、角色id放进去,签名用HMAC-SHA256密钥,有效期设置为2小时(前端定时刷新Token,避免用户用着用着就掉线)。前端把Token存在localStorage里,每次请求在axios拦截器中加上Authorization: Bearer 请求头。后端写一个拦截器继承HandlerInterceptor,在preHandle里解析Token,如果Token无效或过期直接返回401状态码,前端收到401统一跳回登录页。
角色权限这块用的是RBAC最简模型:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。菜单可以理解成“权限点”,一个角色能访问哪些页面、能操作哪些按钮,最终都落到菜单这个维度。后端在登录时把用户拥有的菜单列表查出来,返回给前端动态生成侧边栏。权限控制有两个层次:菜单级别控制在登录后返回的菜单数据里直接过滤;接口级别控制则通过自定义注解@RequirePermission,标注在Controller方法上,由拦截器检查当前用户是否有对应权限码。
这里有一个很容易踩的坑:JWT是无状态的,用户退出登录或者被禁用后,已经签发的Token在过期前仍然有效。解决方式是维护一个Token黑名单(用Redis存储被注销的Token,有效期等于Token剩余时间),或者妥协一点在改密码、禁用账号时把用户角色的权限版本号递增,让旧Token失效。毕设阶段我用的是Redis黑名单方案,顺便把“Redis缓存”也写进了技术亮点,一举两得。
2.3 MyBatis-Plus的正确打开方式
MyBatis-Plus用下来的核心心得是:单表CRUD交给BaseMapper,复杂查询交给XML,两者分工明确,千万别混着来。比如员工列表查询,筛选项可能有姓名、部门、类型、状态、入职日期范围,这种动态条件如果用XML写,每个条件都得配<if>标签,写起来很长;用MyBatis-Plus的LambdaQueryWrapper就非常舒服:
LambdaQueryWrapper<Emp> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(name), Emp::getName, name) .eq(personType != null, Emp::getPersonType, personType) .eq(status != null, Emp::getStatus, status) .between(startDate != null && endDate != null, Emp::getHireDate, startDate, endDate); Page<Emp> page = empMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);注意每个条件前面都带了一个boolean类型的判断参数,这个参数为false时该条件自动不生效,这样不用手写一堆if else拼条件。
但涉及多表关联的查询,比如“查询某部门下所有代理人的当月佣金明细,同时显示其团队名称和主管姓名”,这种用Wrapper就很别扭了,老老实实在XML里写JOIN。XML文件路径的配置是个经典坑位:mybatis-plus的mapper-locations要写对,classpath*:mapper/**/*.xml,少了那个星号经常扫不到XML文件,运行时报Invalid bound statement (not found),光是这个报错我见过群里同学问过不下十次。
分页直接用MyBatis-Plus内置的分页插件,配置一个MybatisPlusInterceptor,添加PaginationInnerInterceptor(DbType.MYSQL),然后在查询时传入Page对象就行。千万别自己写LIMIT手工分页,Spring Boot项目里用插件分页既标准又省事,还能顺便统计总记录数,前端表格的分页组件直接对接total字段即可。
3. 实操过程与核心环节实现
3.1 从零搭建项目骨架
项目搭建的第一步是去Spring Initializr(start.spring.io)生成基础工程,选择Java 8或Java 11、Spring Boot 2.7.x版本。这里特别提醒一下版本问题:现在Spring Initializr默认给的已经是Spring Boot 3.x了,需要Java 17以上,很多教程和网上找到的代码都是基于2.x写的,直接照抄会出现一堆不兼容。毕设求稳的话建议选Spring Boot 2.7.x + JDK 8或者11,这个组合的生态资料最丰富,遇到问题最容易搜到答案。
依赖方面我选择了Spring Web、MySQL Driver、Lombok,然后在pom.xml里手动引入MyBatis-Plus和JWT相关依赖。用Lombok可以少写一堆getter/setter,但有一个地方要注意:Lombok和某些版本的JDK配合不好,编译会报错,我遇到过Java 17 + 旧版Lombok的坑,解决方案是升级Lombok版本到1.18.30以上,或者安心用Java 11。
application.yml里有一些关键配置值得逐行说清楚:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/insurance_hr?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-256-bit-secret-key expire-hours: 2数据库连接串里的serverTimezone=Asia/Shanghai一定要加,MySQL 8.0驱动对时区敏感,不加会报一堆时间相关的异常。map-underscore-to-camel-case打开之后,数据库的snake_case字段能自动映射到Java的驼峰属性,少写很多resultMap。log-impl配成StdOutImpl可以在控制台打印SQL,调试阶段特别好用,上线前记得去掉。
3.2 核心模块实现:员工管理
员工管理模块是系统的基础,我以它为例说一下Controller-Service-Mapper三层是怎么协作的。Controller层不写业务逻辑,只做参数接收、调用Service、包装返回结果;Service层处理业务流程,比如新增员工时要同时校验工号唯一性、处理人员异动记录;Mapper层只管数据访问。
新增员工接口的简化代码如下:
@PostMapping("/emp") @RequirePermission("emp:add") public Result<Long> addEmp(@RequestBody EmpAddDTO dto) { Long empId = empService.addEmp(dto); return Result.success(empId); }Service里的核心方法:
@Transactional(rollbackFor = Exception.class) public Long addEmp(EmpAddDTO dto) { // 1. 工号唯一性校验 if (empMapper.selectCount(new LambdaQueryWrapper<Emp>() .eq(Emp::getEmpNo, dto.getEmpNo())) > 0) { throw new BusinessException("工号已存在: " + dto.getEmpNo()); } // 2. DTO转实体,设置默认状态 Emp emp = new Emp(); BeanUtils.copyProperties(dto, emp); emp.setStatus(1); emp.setCreateTime(LocalDateTime.now()); // 3. 入库 empMapper.insert(emp); // 4. 记录操作日志 logService.record("新增员工", dto.getEmpNo()); return emp.getId(); }这里有两个细节。第一,@Transactional注解一定要加,虽然新增员工看起来只有一个insert操作,但后续如果扩展了“新增员工同时创建账号、初始化薪酬标准”等关联操作,事务就能保证要么全部成功要么全部回滚。第二,抛业务异常而不是返回错误状态码,配合全局异常处理器统一转换成友好的错误信息返回给前端——用一个@RestControllerAdvice标注的类处理BusinessException,返回Result对象中code=500、message=具体原因,这样前端不用在每个接口里都做异常判断,体验好很多。
3.3 佣金计算:这个系统最见功夫的业务逻辑
外勤代理人的佣金计算是整个系统里最像“业务逻辑”的部分,也是答辩时老师最可能追问的地方。佣金的基本公式是:佣金 = 当月新单保费 × 佣金率 + 续期佣金 + 各种津贴奖金 - 扣款。
难点在佣金率不是固定值。同一款保险产品,不同缴费年期、不同缴费方式、不同职级的代理人,佣金率都不一样。我的方案是设计了commission_config表,字段包括:product_type(险种)、pay_period(缴费年期)、agent_level(职级)、first_year_rate(首年佣金率)、renewal_rate(续年佣金率)等。计算佣金时,根据代理人的职级、保单的险种和缴费年期,去这张配置表里查出对应的费率,乘以保单保费。
这个需求用MyBatis-Plus的BaseMapper也可以做:selectOne(条件构造器),配置数据量通常只有几十到几百条,性能没有压力。佣金计算的服务方法(简化版):
public BigDecimal calcCommission(AgentBusiness biz) { CommissionConfig config = commissionConfigMapper.selectOne( new LambdaQueryWrapper<CommissionConfig>() .eq(CommissionConfig::getProductType, biz.getProductType()) .eq(CommissionConfig::getPayPeriod, biz.getPayPeriod()) .eq(CommissionConfig::getAgentLevel, biz.getAgentLevel()) ); if (config == null) { throw new BusinessException("未找到佣金配置: " + biz.getProductType()); } BigDecimal commission = biz.getPremium() .multiply(config.getFirstYearRate()) .setScale(2, RoundingMode.HALF_UP); return commission; }计算时用BigDecimal而不用double,这是做金额计算的铁律——二进制浮点数无法精确表示0.1这种十进制小数,累加误差在佣金这种高频计算场景下会积累出让人尴尬的错误。所有涉及金额的字段在数据库里都用DECIMAL(10,2),Java里用BigDecimal,setScale时指定四舍五入模式,不要用默认的RoundingMode。
3.4 前端对接与联调
前端我用Vue 3 + Element Plus + Axios。工程结构大概是这样:views下面按模块分文件夹,每个页面由一个列表页和一个表单弹窗组成。列表页用el-table展示数据,配合el-pagination分页;新增/编辑用el-dialog包一个el-form;删除操作先弹个确认框再调接口。
联调阶段最容易出问题的是跨域。后端在开发环境配了一个CorsConfiguration,允许本地前端的访问地址。更省事的方案是在前端配Vite的devServer代理,把/api开头的请求代理到localhost:8080,这样浏览器看到的始终是同源请求,能避免大部分CORS问题。生产环境打包后,把dist目录里的静态文件放到后端resources/static下,同一个端口访问,跨域问题彻底不存在。
日期时间格式的传输也是个高频坑。前端传过来的日期字符串默认是“2024-01-15T10:30:00”这种ISO格式,后端LocalDateTime字段如果不配置全局Jackson格式化,解析会报错或者格式很难看。我在application.yml里加了:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8前端提交时把日期格式化成“yyyy-MM-dd HH:mm:ss”再传,后端返回也统一这个格式。虽然现在有更精细的LocalDateTime序列化器方案,但毕设阶段用全局配置图个省心,前提是项目里没有特殊的日期格式需求。
4. 常见问题与排查技巧实录
4.1 环境与启动类问题速查
毕设群里每天都能见到的问题,我整理了一个速查表:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 启动报端口被占用 | 8080端口被其他程序占用 | netstat -ano查看PID,任务管理器结束进程或改端口 |
| 数据库连接失败 | 密码不对、MySQL服务没启动、连接串错误 | 先用Navicat等客户端测试连接 |
| Invalid bound statement | mapper-locations路径错误 | 检查XML文件是否在target目录下 |
| 中文乱码 | 连接串缺characterEncoding参数 | 补上useUnicode=true&characterEncoding=utf8 |
| 前端请求跨域 | 后端没配CORS或代理未生效 | 先用Postman验证接口通不通,再排查跨域 |
环境问题大部分是惰性导致的——只要严格按照前面的配置步骤走,一次性通过的概率很高。真卡住了,先看控制台最前面的异常堆栈,不要只看最后一行。Spring Boot报错信息通常很详细,比如“APPLICATION FAILED TO START”下面会直接告诉你缺哪个Bean、哪个端口冲突,照着改就行。
4.2 业务代码里的经典坑位
除了环境问题,代码层面的坑更隐蔽,我踩过的几个典型:
第一个是MyBatis-Plus的逻辑删除与唯一索引冲突。我给员工表加了逻辑删除字段,又给emp_no建了唯一索引,结果删除一个员工后再新增同工号员工,直接报Duplicate entry。原因很简单:逻辑删除的记录还躺在表里,唯一索引照样生效。解决方案是emp_no不做数据库唯一索引,改在Service里用selectCount校验,查的时候排除掉已删除记录,业务上保证唯一。
第二个是Lombok的@Builder和@NoArgsConstructor冲突。用了@Builder注解的类如果没有显式添加无参构造,MyBatis反射创建对象时会失败。解决办法是同时加上@NoArgsConstructor和@AllArgsConstructor。这个坑报错很诡异,有时候不是启动时报,而是运行到某个查询才报,排查起来很费时间。
第三个是日期查询的边界问题。查“某月的考勤记录”,很多人直接写between月初和月末,但月份天数不一致,2月怎么算?我的习惯是查询条件用“大于等于月初且小于下月月初”,避免对当月天数的判断,也不会有23:59:59.999这种精度边界问题。
4.3 调试方法论:日志、断点与思路
调试Spring Boot项目,效率最高的三个手段:控制台SQL日志、全局异常堆栈、IDEA断点调试。控制台SQL日志能看到每次查询实际执行的SQL语句,配合参数值,十有八九的问题当场就能看出来——是条件写错了、参数没传进去,还是表名映射错了。这一步在开发阶段永远是第一个排查动作。
断点调试方面,在Controller入口、Service业务关键节点、Mapper调用处三处打断点,基本能定位到任何逻辑问题。用IDEA调试时注意,跳入方法用F7,步过用F8,从方法退出用Shift+F8。如果调试时发现某些变量的值看不出来,检查是不是Lombok生成的getter没编译到,先mvn clean一下。
更高层级的排查思路是“分层定位法”:接口报错了,先在Controller层看参数是否正常,再进Service看业务逻辑走到哪一步抛异常,最后查Mapper层的SQL和数据。每一层都能通过日志或断点确认是否正常,缩小问题范围。千万别拿着整个项目从头到尾读代码找bug,那是效率最低的方式。先复现、再定位、后修复,这是我在多个项目里验证过的经验。
4.4 打包部署与演示前检查
毕设演示前把项目打包成可运行的jar包,这步看着简单但出错率不低。在IDEA右侧Maven面板执行clean + package,如果之前配置正确,target目录下会生成xxx.jar。启动命令是java -jar xxx.jar。常见问题有几个:一是打出来的jar很小,那个不是最终包,Spring Boot的可执行jar通常有几MB到几十MB,如果只有几KB,检查pom里是否引入了spring-boot-maven-plugin;二是打包后静态资源404,前端dist内容要放在src/main/resources/static里再打包,或者用外部目录加静态资源映射;三是数据库连接写的是localhost,部署到服务器后要记得改成服务器的MySQL地址。
演示前我建议做一遍完整的流程走查:登录→新增员工→给员工分配角色→修改资料→做一次考勤登记→算一次工资→生成薪资台账→退出登录,每个环节都点一遍,确认没有报错、数据能正确展示。尤其要提前准备一些演示数据,比如不同职级的代理人、不同险种的佣金配置,现场现造数据很耽误时间,而且容易出意外。
5. 定制扩展与能力提升建议
5.1 三个性价比极高的扩展点
如果你的时间有余裕,下面三个扩展点能让项目的完成度上一个台阶,而且都有成熟的第三方组件可以用。
第一个是报表导出。把员工花名册、月度薪资表、佣金汇总表导出成Excel。Apache POI可以做,但代码写起来不够优雅;我推荐用EasyExcel(阿里巴巴的开源库),注解式定义表头,几行代码就能导出一个格式良好的Excel文件,还能设置列宽、样式、冻结表头。这个功能在HR领域的实际使用频率非常高,演示效果也很好。
第二个是消息提醒。比如员工生日提醒、合同到期提醒、代理人考核未达标提醒。实现方案用Spring的定时任务@Scheduled在每天固定时间扫描数据库,把符合条件的人查出来,生成提醒记录推送给相关管理者。这个功能代码量不大,但很能体现系统“智能”的一面。
第三个是审批流。请假、离职、晋升考核审批,需要一个人发起的申请经过多个节点流转。如果不想引入Flowable那套重量级工作流引擎,可以自己设计一张审批表,字段包括业务类型、申请人工号、当前审批人、审批状态、审批意见,配合一个简单的状态机模式,就能跑通“员工发起→主管审批→HR归档”的典型流程。这块写进论文里,工作量也够凑一篇不错的章节。
5.2 定制开发的通用套路
有的同学拿到的是别人写好的源码,需要二次开发,这种情况我分享一个通用套路。第一,先把项目跑起来,不要急着改代码,通过登录流程把项目从前到后梳理一遍,搞清楚请求是怎么从前端到后端再返回来的。第二,找到数据库初始化脚本,把表结构和初始数据读懂,这是理解整个系统的钥匙。第三,做任何修改前先在纸上画出调用链——页面在哪个vue文件、调哪个api、后端进哪个Controller、操作哪几张表,链路清晰了再动代码。第四,小步修改、频繁验证,每改一个功能就重启或者热部署验证一次,不要攒了一堆改动再一次性调试,那样出了问题根本不知道从哪查起。
强调一点:定制开发最忌讳的就是“上来就删”。看不懂的代码先留着,加注释标注“疑似无用”,研究明白了再决定去留。不少同学为了图省事,看到报错就直接注释掉一大块代码,结果报错更多,最后代码被改得面目全非,验收都过不了。
5.3 答辩时老师爱问的技术问题
最后聊几句答辩准备,这是我被问过以及旁观别人被问得最多的几个问题:
“为什么用JWT不用Session?”回答要点:无状态、适合前后端分离、天然支持跨域认证,而Session依赖服务端存储,分布式环境下要额外处理会话共享。
“MyBatis-Plus和MyBatis有什么区别?”回答要点:MyBatis-Plus是MyBatis的增强,不改变MyBatis的底层机制,提供通用CRUD、条件构造器、分页插件等,单表操作不用写SQL,复杂查询仍然使用MyBatis的XML方式。
“数据库为什么用逻辑删除?”回答要点:保留数据便于追溯,员工历史数据对HR系统有审计价值,查询时通过deleted字段过滤即可。
“遇到的最大的技术难点是什么?”这个问题不要凭空编,就从实际开发中挑一个真实问题来回答,比如佣金计算时的精度问题、逻辑删除与唯一索引的冲突等,把问题现象、排查过程、最终方案讲清楚,这就是最有说服力的回答。
答辩的核心其实不是炫技,是让老师相信这个项目确实是你自己做的,而且你理解每一个关键设计。所以平时开发中多留个心眼,记录一下自己踩过的坑,这些东西就是答辩时最宝贵的素材。
我个人做完这个项目的最大体会是:技术选型重要,但理解业务更重要。Spring Boot再熟练,如果理解不了寿险公司人力资源“内外勤双轨”的底层逻辑,做出来的东西就是一个换了名字的通用管理系统,交差容易,出彩很难。反过来,把佣金计算、职级考核这些业务细节真正消化掉,哪怕代码写得朴素一点,整个系统的设计合理性和完成度也会让老师眼前一亮。这也是为什么我建议想做这个题目的同学,先花一两天时间去把保险行业的代理人管理制度搞明白,这笔时间绝对花得值。
如果时间紧,至少把代理人薪酬、团队架构、考核晋升这三块业务吃透,围绕这三个点去设计和实现,你的论文和系统都会比“背模板CRUD”高一个档次。做毕设不只是一个编码任务,更是一个难得的综合训练机会,认真做下来,收获的不止是一份能通过的代码。