背景痛点:为什么“能跑”≠“能毕业”
做毕设时,很多同学把“工资管理系统”当成“增删改查”大合集:一个EmployeeServlet里塞满 SQL 拼接,一个if-else搞定个税计算,页面用 JSP 直接<% %>写业务。功能看似能跑,但答辩现场常被老师三连问:
- 员工调薪时,加班费和津贴规则变一下,你要改几处代码?
- 年终奖单独计税,事务怎么保证“发钱”与“更新已发标志”同时成功?
- 财务部和人事部查询工资权限不同,你现在的代码怎么加拦截?
痛点总结成一句话:紧耦合、硬编码、无事务、无扩展。下面用一张图先直观感受“坏味道”:
技术选型:Spring Boot + MyBatis 不是“赶时髦”,是“省命”
| 方案 | 优点 | 缺点 | 毕设场景结论 |
|---|---|---|---|
| 纯 Servlet + JSP | 零依赖,老师教学资料多 | SQL 散落各处,事务靠手动 commit,一学期后自己看不懂 | 维护地狱 |
| Spring + JDBC Template | 注入解耦,事务模板化 | 手写 RowMapper 枯燥,字段多时常出 Bug | 写腻了 |
| Spring Boot + MyBatis | 自动配置、接口绑定、SQL 与 Java 分离、插件丰富(分页、乐观锁) | 需要理解 Starter 与 YAML 配置 | 毕设最均衡 |
一句话:Spring Boot 让你专注业务,MyBatis 让你保留对 SQL 的细粒度控制,两者加一起就是“写得快、改得少、答辩稳”。
核心实现:从“大杂烩”到“三层清流”
1. 模块划分
- 员工中心(Employee)
- 薪资计算(Payroll)
- 系统权限(Security)
2. 分层数据流
浏览器 → Controller → Service → Mapper → MySQL- Controller 只做格式转换与参数校验
- Service 放业务规则(个税、社保比例)
- Mapper 只给 CRUD SQL
3. 接口设计示例
员工调薪接口:
POST /api/employees/{id}/salary { "baseSalary": 8000, "allowance": 1500, "effectiveDate": "2024-07-01" }返回:
{ "code": 0, "message": "调薪已生效,7 月薪资将按新标准计算" }4. 关键 Service 代码(含注释)
@Service public class PayrollService { @Resource private EmployeeMapper employeeMapper; @Resource private SalaryRecordMapper recordMapper; /** * 计算单月工资并落库,事务保证“计算+写记录”原子性 */ @Transactional(rollbackFor = Exception.class) public PaySlip calculateMonthly(Long empId, YearMonth month) { // 1. 查员工最新工资标准 Employee emp = employeeMapper.selectById(empId); Validate.notNull(emp, "员工不存在"); // 2. 组装 SalaryContext,把各种规则参数丢进去 SalaryContext ctx = SalaryContext.builder() .base(emp.getBaseSalary()) .allowance(emp.getAllowance()) .insuranceRate(0.08) // 社保 .taxThreshold(5000) .build(); // 3. 计算 BigDecimal gross = ctx.getBase().add(ctx.getAllowance()); BigDecimal insurance = gross.multiply(BigDecimal.valueOf(ctx.getInsuranceRate())); BigDecimal taxable = gross.subtract(insurance).subtract(BigDecimal.valueOf(ctx.getTaxThreshold())); BigDecimal tax = TaxUtil.calc(taxable); // 分档速算扣除 BigDecimal net = gross.subtract(insurance).subtract(tax); // 4. 落库 SalaryRecord record = SalaryRecord.builder() .empId(empId) .month(month.toString()) .gross(gross).insurance(insurance).tax(tax).net(net) .build(); recordMapper.insertSelective(record); return new PaySlip(month.toString(), gross, insurance, tax, net); } }注意:
- 金额全用
BigDecimal,避免double二进制误差 - 事务加在 Service 层,保证“计算+落库”原子性
- 工具类
TaxUtil单独封装,改政策只改一处
安全与性能:别让“小系统”成“靶子”
- SQL 注入:MyBatis
#{}预编译占位,拒绝${}拼接 - 连接池:默认 HikariCP,毕设数据量小也别乱设
maximum-pool-size=100,压测后 5~10 足够 - 敏感字段:工资表
net字段若需展示,接口做脱敏,返回*.*或 AES 加密 - 慢 SQL:给
emp_id + month建联合索引,避免全表扫描
生产环境避坑指南
- 事务失效:同类方法内部调用,
@Transactional会失效,用@Autowired自身代理或拆 Service - 日期格式:
effectiveDate存DATE类型,Java 用LocalDate,前后端统一yyyy-MM-dd,拒用java.util.Date - 浮点精度:工资分、个税速算扣除全部转“分”单位整数或
BigDecimal,杜绝double - 批量发薪:MyBatis 批量插入用
<foreach>,一次 500 条,防止大事务锁表 - 缓存误用:Redis 缓存工资结果前,先评估“财务已锁定”状态,避免员工看到过期数据
可扩展方向:把“毕设”变“作品”
- 多部门薪资策略:把
SalaryContext抽成策略接口,不同部门implements自定义社保、津贴算法,再用工厂模式注入 - Excel 一键导出:引入 Alibaba EasyExcel,写 10 行代码实现
List<SalaryRecord>→.xlsx,财务小姐姐点赞 - 微服务拆分:若团队演示,可把“员工中心”“薪资计算”“报表服务”拆三个 Spring Boot 模块,用 OpenFeign 调用,答辩加分
结尾思考
把工资管理系统从“能跑”做成“能讲”,核心就是分层+解耦+可测试。今天给出的代码骨架,你可以直接丢进毕设仓库,再花一下午把 Excel 导出和多部门策略工厂补齐,足以应对 90% 的答辩提问。下一步,不妨思考:如果公司突然新增“海外员工”且税费规则完全独立,你的策略模式还能 cover 住吗?动手改一改,你会发现——毕业设计不是终点,是代码生涯的第一块积木。