1. 项目概述
"Spring Boot企业员工薪酬关系系统信息管理系统"是一套基于现代主流技术栈开发的企业级应用解决方案。这个系统采用了前后端分离架构,后端使用Spring Boot框架,前端基于Vue.js,数据库选用MySQL,实现了从员工信息管理到薪酬计算发放的全流程数字化管理。
这套系统的核心价值在于:
- 完整实现了企业人力资源管理中的薪酬管理闭环
- 采用主流技术栈,便于企业技术团队维护和二次开发
- 提供可直接运行的完整代码,降低企业信息化建设门槛
2. 系统架构设计
2.1 技术选型分析
后端技术栈:
- Spring Boot 2.7.x:简化了Spring应用的初始搭建和开发过程
- Spring Security:提供完善的身份认证和授权机制
- MyBatis-Plus:简化数据库操作,提高开发效率
- Lombok:通过注解减少样板代码
- Hutool:Java工具类库,提供各种实用功能
前端技术栈:
- Vue 2.6.x:渐进式JavaScript框架
- Element UI:基于Vue的组件库,提供丰富的UI组件
- Axios:处理HTTP请求
- Vue Router:实现前端路由
- Vuex:状态管理
数据库:
- MySQL 5.7/8.0:关系型数据库
- Redis(可选):缓存高频访问数据
2.2 系统模块划分
系统主要分为以下几个功能模块:
-
员工信息管理模块
- 员工基本信息CRUD
- 部门组织结构管理
- 职位职级管理
-
薪酬管理模块
- 薪资结构配置
- 薪资项管理(基本工资、绩效工资、补贴等)
- 个税计算
- 社保公积金计算
-
考勤管理模块
- 考勤记录导入
- 请假/加班审批
- 考勤异常处理
-
报表统计模块
- 薪资发放明细表
- 部门薪资汇总表
- 历史薪资对比分析
-
系统管理模块
- 用户权限管理
- 系统参数配置
- 操作日志审计
3. 核心功能实现
3.1 后端关键实现
员工信息管理实现:
java复制// 员工实体类
@Data
@TableName("employee")
public class Employee {
@TableId(type = IdType.AUTO)
private Long id;
private String employeeNo; // 员工编号
private String name; // 姓名
private Integer gender; // 性别
private Date birthDate; // 出生日期
// 其他字段...
}
// 员工服务层
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee>
implements EmployeeService {
@Override
public Page<Employee> queryPage(EmployeeQuery query) {
return this.lambdaQuery()
.eq(StringUtils.isNotBlank(query.getName()), Employee::getName, query.getName())
.eq(query.getDeptId() != null, Employee::getDeptId, query.getDeptId())
.page(new Page<>(query.getPage(), query.getSize()));
}
}
薪资计算服务实现:
java复制@Service
public class SalaryCalculateService {
@Autowired
private EmployeeService employeeService;
@Autowired
private AttendanceService attendanceService;
public SalaryDetail calculateSalary(Long employeeId, Date month) {
// 获取员工基本信息
Employee employee = employeeService.getById(employeeId);
// 获取考勤数据
AttendanceRecord attendance = attendanceService.getByEmployeeAndMonth(employeeId, month);
// 计算基本工资
BigDecimal baseSalary = calculateBaseSalary(employee);
// 计算绩效工资
BigDecimal performanceSalary = calculatePerformanceSalary(employee, month);
// 计算社保公积金
SocialInsurance socialInsurance = calculateSocialInsurance(baseSalary);
// 计算个人所得税
BigDecimal tax = calculateTax(baseSalary.add(performanceSalary), socialInsurance);
// 构建薪资明细
SalaryDetail detail = new SalaryDetail();
detail.setEmployeeId(employeeId);
detail.setBaseSalary(baseSalary);
detail.setPerformanceSalary(performanceSalary);
detail.setSocialInsurance(socialInsurance);
detail.setTax(tax);
detail.setNetSalary(baseSalary.add(performanceSalary)
.subtract(socialInsurance.getTotal())
.subtract(tax));
return detail;
}
// 其他计算方法...
}
3.2 前端关键实现
员工列表页面:
vue复制<template>
<div class="employee-container">
<el-card>
<div slot="header" class="clearfix">
<span>员工列表</span>
<el-button type="primary" @click="handleAdd" style="float: right;">新增员工</el-button>
</div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="employeeNo" label="工号" width="120"></el-table-column>
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="gender" label="性别" width="80">
<template slot-scope="scope">
{{ scope.row.gender | genderFilter }}
</template>
</el-table-column>
<el-table-column prop="deptName" label="部门"></el-table-column>
<el-table-column prop="position" label="职位"></el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="query.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="query.size"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</el-card>
</div>
</template>
<script>
import { getEmployeeList } from '@/api/employee'
export default {
data() {
return {
query: {
page: 1,
size: 10,
name: '',
deptId: null
},
tableData: [],
total: 0
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
getEmployeeList(this.query).then(response => {
this.tableData = response.data.records
this.total = response.data.total
})
},
handleAdd() {
this.$router.push('/employee/add')
},
handleEdit(row) {
this.$router.push(`/employee/edit/${row.id}`)
},
handleDelete(row) {
this.$confirm('确认删除该员工?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteEmployee(row.id).then(() => {
this.$message.success('删除成功')
this.fetchData()
})
})
},
handleSizeChange(val) {
this.query.size = val
this.fetchData()
},
handleCurrentChange(val) {
this.query.page = val
this.fetchData()
}
}
}
</script>
薪资计算页面:
vue复制<template>
<div class="salary-calculate">
<el-card>
<div slot="header" class="clearfix">
<span>薪资计算</span>
</div>
<el-form :model="query" :inline="true" class="search-form">
<el-form-item label="月份">
<el-date-picker
v-model="query.month"
type="month"
placeholder="选择月份"
value-format="yyyy-MM">
</el-date-picker>
</el-form-item>
<el-form-item label="部门">
<el-select v-model="query.deptId" placeholder="请选择部门" clearable>
<el-option
v-for="item in deptOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleCalculate">计算薪资</el-button>
<el-button type="success" @click="handleExport">导出Excel</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="employeeNo" label="工号" width="120"></el-table-column>
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="deptName" label="部门"></el-table-column>
<el-table-column prop="baseSalary" label="基本工资" align="right">
<template slot-scope="scope">
{{ scope.row.baseSalary | currency }}
</template>
</el-table-column>
<el-table-column prop="performanceSalary" label="绩效工资" align="right">
<template slot-scope="scope">
{{ scope.row.performanceSalary | currency }}
</template>
</el-table-column>
<el-table-column prop="socialInsurance" label="社保公积金" align="right">
<template slot-scope="scope">
{{ scope.row.socialInsurance | currency }}
</template>
</el-table-column>
<el-table-column prop="tax" label="个人所得税" align="right">
<template slot-scope="scope">
{{ scope.row.tax | currency }}
</template>
</el-table-column>
<el-table-column prop="netSalary" label="实发工资" align="right">
<template slot-scope="scope">
{{ scope.row.netSalary | currency }}
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import { calculateSalary, exportSalary } from '@/api/salary'
import { getDeptList } from '@/api/dept'
export default {
data() {
return {
query: {
month: '',
deptId: null
},
tableData: [],
deptOptions: []
}
},
created() {
this.getDeptOptions()
},
methods: {
getDeptOptions() {
getDeptList().then(response => {
this.deptOptions = response.data
})
},
handleCalculate() {
if (!this.query.month) {
this.$message.warning('请选择月份')
return
}
calculateSalary(this.query).then(response => {
this.tableData = response.data
})
},
handleExport() {
if (!this.query.month) {
this.$message.warning('请选择月份')
return
}
exportSalary(this.query).then(response => {
const blob = new Blob([response.data], { type: 'application/vnd.ms-excel' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `薪资表_${this.query.month}.xlsx`
link.click()
})
}
}
}
</script>
4. 数据库设计
4.1 主要表结构
员工表(employee):
sql复制CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`employee_no` varchar(20) NOT NULL COMMENT '员工编号',
`name` varchar(50) NOT NULL COMMENT '姓名',
`gender` tinyint(1) DEFAULT NULL COMMENT '性别:0-女,1-男',
`birth_date` date DEFAULT NULL COMMENT '出生日期',
`id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`address` varchar(200) DEFAULT NULL COMMENT '住址',
`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`position_id` bigint(20) DEFAULT NULL COMMENT '职位ID',
`entry_date` date DEFAULT NULL COMMENT '入职日期',
`leave_date` date DEFAULT NULL COMMENT '离职日期',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0-离职,1-在职',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_employee_no` (`employee_no`),
KEY `idx_dept_id` (`dept_id`),
KEY `idx_position_id` (`position_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表';
部门表(department):
sql复制CREATE TABLE `department` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '部门名称',
`code` varchar(20) DEFAULT NULL COMMENT '部门编码',
`parent_id` bigint(20) DEFAULT NULL COMMENT '父部门ID',
`manager_id` bigint(20) DEFAULT NULL COMMENT '部门经理ID',
`order_num` int(11) DEFAULT '0' COMMENT '排序号',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';
薪资表(salary):
sql复制CREATE TABLE `salary` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`employee_id` bigint(20) NOT NULL COMMENT '员工ID',
`year_month` varchar(7) NOT NULL COMMENT '年月,格式:yyyy-MM',
`base_salary` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '基本工资',
`performance_salary` decimal(10,2) DEFAULT '0.00' COMMENT '绩效工资',
`bonus` decimal(10,2) DEFAULT '0.00' COMMENT '奖金',
`subsidy` decimal(10,2) DEFAULT '0.00' COMMENT '补贴',
`social_insurance` decimal(10,2) DEFAULT '0.00' COMMENT '社保公积金',
`tax` decimal(10,2) DEFAULT '0.00' COMMENT '个人所得税',
`net_salary` decimal(10,2) DEFAULT '0.00' COMMENT '实发工资',
`status` tinyint(1) DEFAULT '0' COMMENT '状态:0-未发放,1-已发放',
`remark` 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_employee_month` (`employee_id`,`year_month`),
KEY `idx_year_month` (`year_month`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='薪资表';
4.2 数据库关系图
系统主要表之间的关系如下:
- 员工-部门关系:多对一,一个员工属于一个部门,一个部门有多个员工
- 员工-职位关系:多对一,一个员工有一个职位,一个职位可以被多个员工拥有
- 员工-薪资关系:一对多,一个员工有多个月的薪资记录
- 部门-部门关系:自引用,实现部门树形结构
5. 系统部署与运行
5.1 环境准备
开发环境要求:
- JDK 1.8+
- Maven 3.6+
- Node.js 12+
- MySQL 5.7+
- Redis 5.0+ (可选)
生产环境建议:
- Linux服务器(CentOS 7+/Ubuntu 18.04+)
- JDK 11+
- Nginx 1.18+
- MySQL 8.0+
- Redis 6.0+
5.2 后端部署步骤
- 数据库初始化
bash复制# 创建数据库
mysql -uroot -p -e "CREATE DATABASE hr_salary CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 导入SQL脚本
mysql -uroot -p hr_salary < db/hr_salary.sql
- 修改配置文件
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/hr_salary?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: yourpassword
redis:
host: localhost
port: 6379
password:
- 打包部署
bash复制# 打包
mvn clean package -DskipTests
# 运行
java -jar target/hr-salary-system.jar
5.3 前端部署步骤
- 安装依赖
bash复制npm install
- 修改配置
javascript复制// .env.production
VUE_APP_BASE_API = 'http://your-server-ip:8080'
- 构建部署
bash复制# 构建生产环境代码
npm run build
# 将dist目录内容部署到Nginx
cp -r dist/* /usr/share/nginx/html/
- Nginx配置示例
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
6. 系统特色与优化建议
6.1 系统特色功能
-
灵活的薪资结构配置
- 支持自定义薪资项
- 支持公式计算
- 支持多套薪资方案
-
完善的权限控制
- 基于角色的访问控制(RBAC)
- 数据权限过滤
- 操作日志审计
-
高效的批量处理
- Excel导入导出
- 批量计算薪资
- 批量发放薪资
-
可视化数据分析
- 薪资分布图表
- 部门薪资对比
- 历史趋势分析
6.2 性能优化建议
-
数据库优化
- 为常用查询字段添加索引
- 对大表进行分表处理
- 使用读写分离架构
-
缓存策略优化
- 高频访问数据缓存
- 多级缓存设计
- 缓存预热机制
-
计算性能优化
- 薪资计算异步化
- 分布式计算
- 计算结果缓存
-
前端性能优化
- 组件懒加载
- 路由懒加载
- 代码分割
7. 常见问题与解决方案
7.1 部署问题
问题1:启动时报数据库连接失败
解决方案:
- 检查application.yml中的数据库配置是否正确
- 确认MySQL服务已启动
- 检查数据库用户是否有权限访问指定数据库
- 检查防火墙是否开放了MySQL端口(默认3306)
问题2:前端访问接口404
解决方案:
- 检查VUE_APP_BASE_API配置是否正确
- 确认后端服务已启动
- 检查Nginx代理配置是否正确
- 查看浏览器开发者工具中的网络请求,确认请求URL
7.2 开发问题
问题1:薪资计算精度问题
解决方案:
- 使用BigDecimal进行金额计算,避免使用double
- 设置统一的金额精度和舍入模式
- 数据库中使用DECIMAL(10,2)类型存储金额
示例代码:
java复制// 设置金额计算精度
BigDecimal a = new BigDecimal("100.23");
BigDecimal b = new BigDecimal("50.10");
BigDecimal result = a.add(b).setScale(2, RoundingMode.HALF_UP);
问题2:并发操作导致数据不一致
解决方案:
- 使用数据库乐观锁
- 对关键操作添加事务注解
- 使用分布式锁控制并发
示例代码:
java复制@Service
public class SalaryService {
@Transactional
public void distributeSalary(Long salaryId) {
Salary salary = salaryMapper.selectById(salaryId);
if (salary.getStatus() == 1) {
throw new RuntimeException("薪资已发放,不能重复操作");
}
// 发放薪资逻辑...
salary.setStatus(1);
salaryMapper.updateById(salary);
}
}
7.3 使用问题
问题1:Excel导入数据格式错误
解决方案:
- 下载系统提供的模板文件
- 严格按照模板格式填写数据
- 检查数据是否符合验证规则
- 数值型数据不要包含特殊字符
问题2:薪资计算结果异常
解决方案:
- 检查员工基本信息是否正确
- 检查考勤数据是否完整
- 检查薪资项配置是否正确
- 检查计算公式是否有误
8. 扩展功能建议
-
移动端适配
- 开发微信小程序版本
- 员工自助查询功能
- 移动审批流程
-
第三方集成
- 与OA系统集成
- 与财务系统对接
- 与考勤机数据对接
-
智能分析
- 薪资异常检测
- 人力成本预测
- 离职风险预警
-
多租户支持
- SaaS化改造
- 租户数据隔离
- 个性化配置
9. 技术难点与解决方案
9.1 薪资计算引擎设计
难点:
- 不同企业薪资结构差异大
- 计算规则复杂多变
- 需要支持灵活的公式配置
解决方案:
- 采用规则引擎设计
- 使用脚本语言(如Groovy)实现动态公式
- 设计可配置的薪资项和计算规则
示例代码:
java复制public interface SalaryCalculator {
BigDecimal calculate(SalaryContext context);
}
public class BasicSalaryCalculator implements SalaryCalculator {
@Override
public BigDecimal calculate(SalaryContext context) {
// 基本工资计算逻辑
Employee employee = context.getEmployee();
Position position = employee.getPosition();
return position.getBaseSalary();
}
}
public class PerformanceSalaryCalculator implements SalaryCalculator {
@Override
public BigDecimal calculate(SalaryContext context) {
// 绩效工资计算逻辑
BigDecimal base = context.getBaseSalary();
BigDecimal ratio = context.getPerformanceRatio();
return base.multiply(ratio);
}
}
// 使用计算引擎
public class SalaryCalculateEngine {
private Map<String, SalaryCalculator> calculators;
public SalaryDetail calculate(SalaryContext context) {
SalaryDetail detail = new SalaryDetail();
// 基本工资
detail.setBaseSalary(calculators.get("basic").calculate(context));
// 绩效工资
detail.setPerformanceSalary(calculators.get("performance").calculate(context));
// 其他计算...
return detail;
}
}
9.2 大数据量导出优化
难点:
- 薪资数据量大时导出耗时长
- 内存容易溢出
- 用户体验差
解决方案:
- 采用分页查询+流式导出
- 使用多线程提高导出速度
- 添加导出任务队列,支持异步导出
示例代码:
java复制@RestController
@RequestMapping("/api/salary")
public class SalaryExportController {
@Autowired
private SalaryService salaryService;
@GetMapping("/export")
public void exportSalary(SalaryQuery query, HttpServletResponse response) throws IOException {
String fileName = URLEncoder.encode("薪资表.xlsx", "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
try (ExcelWriter writer = ExcelUtil.getWriter(true);
OutputStream out = response.getOutputStream()) {
// 设置标题
writer.merge(6, "薪资表");
writer.writeHeadRow(Arrays.asList("工号", "姓名", "部门", "基本工资", "绩效工资", "实发工资"));
// 分页查询数据
int page = 1;
int size = 1000;
while (true) {
query.setPage(page);
query.setSize(size);
Page<SalaryVO> data = salaryService.queryPage(query);
if (data.getRecords().isEmpty()) {
break;
}
// 写入数据
for (SalaryVO vo : data.getRecords()) {
writer.writeRow(Arrays.asList(
vo.getEmployeeNo(),
vo.getName(),
vo.getDeptName(),
vo.getBaseSalary(),
vo.getPerformanceSalary(),
vo.getNetSalary()
));
}
if (!data.hasNext()) {
break;
}
page++;
}
writer.flush(out, true);
}
}
}
9.3 前后端数据交互优化
难点:
- 薪资数据字段多,传输量大
- 频繁请求导致性能问题
- 数据实时性要求高
解决方案:
- 设计合理的API接口
- 使用DTO对象控制返回字段
- 采用WebSocket实现实时通知
示例代码:
java复制// 薪资DTO对象
@Data
public class SalaryDTO {
private Long id;
private String employeeNo;
private String name;
private String deptName;
private BigDecimal baseSalary;
private BigDecimal performanceSalary;
private BigDecimal netSalary;
private String status;
public static SalaryDTO fromEntity(Salary salary) {
SalaryDTO dto = new SalaryDTO();
BeanUtils.copyProperties(salary, dto);
return dto;
}
}
// WebSocket配置
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
}
// WebSocket控制器
@Controller
public class SalaryWebSocketController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Async
public void notifySalaryCalculated(Long salaryId) {
Salary salary = salaryRepository.findById(salaryId).orElse(null);
if (salary != null) {
messagingTemplate.convertAndSend(
"/topic/salary/" + salary.getEmployeeId(),
SalaryDTO.fromEntity(salary)
);
}
}
}
10. 安全考虑与最佳实践
10.1 系统安全措施
-
认证与授权
- 使用Spring Security实现RBAC
- JWT令牌认证
- 密码加密存储
-
数据安全
- 敏感字段加密
- SQL注入防护
- XSS攻击防护
-
接口安全
- 接口限流
- 防重放攻击
- 参数校验
10.2 薪资数据安全
敏感数据处理:
- 员工身份证号等敏感信息加密存储
- 薪资数据访问权限严格控制
- 操作日志完整记录
示例代码:
java复制// 敏感数据加密
public class SensitiveDataEncryptor {
private static final String KEY = "your-encryption-key";
public static String encrypt(String data) {
// 使用AES加密算法
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
public static String decrypt(String encryptedData) {
// 解密逻辑...
}
}
// 在实体类中使用
@Data
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Convert(converter = EncryptConverter.class)
private String idCard;
// 其他字段...
}
// 自定义属性转换器
public class EncryptConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
return SensitiveDataEncryptor.encrypt(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return SensitiveDataEncryptor.decrypt(dbData);
}
}
10.3 审计日志实现
操作日志记录:
- 使用AOP记录关键操作
- 保存操作人、时间、IP等信息
- 支持操作记录查询
示例代码:
java复制@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogService logService;
@Pointcut("@annotation(com.example.hr.annotation.OperationLog)")
public void operationLogPointCut() {}
@Around("operationLogPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog annotation = method.getAnnotation(OperationLog.class);
String operation = annotation.value();
String className = joinPoint.getTarget().getClass().getName();
String methodName = method.getName();
Object[] args = joinPoint.getArgs();
// 获取当前用户
String username = SecurityUtils.getCurrentUsername();
// 记录开始时间
long beginTime = System.currentTimeMillis();
try {
// 执行方法
Object result = joinPoint.proceed();
// 记录成功日志
logService.saveLog(
username,
operation,
className + "." + methodName,
JsonUtils.toJson(args),
System.currentTimeMillis() - beginTime,
true,
""
);
return result;
} catch (Exception e) {
// 记录失败日志
logService.saveLog(
username,
operation,
className + "." + methodName,
JsonUtils.toJson(args),
System.currentTimeMillis() - beginTime,
false,
e.getMessage()
);
throw e;
}
}
}
// 使用注解记录操作日志
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value() default "";
}
// 在Controller中使用
@RestController
@RequestMapping("/api/employee")
public class EmployeeController {
@OperationLog("新增员工")
@PostMapping
public Result addEmployee(@RequestBody Employee employee) {
// 业务逻辑...
}
}
