点狮HRM-企业级HRM系统参数化配置架构设计
·
一、业务背景与挑战
1.1 HRM系统的复杂性挑战
在企业级HRM(人力资源管理)系统中,薪资计算是最复杂、最敏感的核心业务。不同企业、不同地区、不同行业的薪资规则千差万别,呈现出以下特点:
多维度差异化需求:
- 企业差异:国企、民企、外企的薪资结构完全不同
- 地区差异:不同城市的社保基数、公积金比例不同
- 行业差异:制造业、互联网、服务业的薪资项目不同
- 岗位差异:管理岗、技术岗、销售岗的薪资计算方式不同
- 政策变化:个税政策、社保政策频繁调整
相关链接:
- 🌐 官网:http://www.dianshixinxi.com/
- 📱 演示站:http://cloud.dianshixinxi.com:90/
- 🎨 Gitee:https://gitee.com/glorylion/JFinalOA
- 💻 GitCode:https://gitcode.com/Glory_Lion/pointlion-cloud

1.2 传统硬编码方案的痛点
传统方案的典型代码:
// 硬编码的薪资计算 - 痛点明显
public BigDecimal calculateSalary(Employee employee) {
BigDecimal baseSalary = employee.getBaseSalary();
// 硬编码的社保计算 - 不同地区需要修改代码
BigDecimal socialInsurance = baseSalary.multiply(new BigDecimal("0.08"));
// 硬编码的公积金计算 - 政策调整需要重新发布
BigDecimal housingFund = baseSalary.multiply(new BigDecimal("0.12"));
// 硬编码的个税计算 - 税法更新需要修改代码
BigDecimal tax = calculateTaxHardcoded(baseSalary.subtract(socialInsurance));
return baseSalary.subtract(socialInsurance).subtract(tax);
}
痛点分析:
- 扩展性差:每次规则变化都需要修改代码、重新发布
- 维护成本高:大量if-else分支难以理解和维护
- 业务耦合:业务规则与技术实现强耦合
- 响应速度慢:政策调整无法快速响应,存在合规风险
- 测试困难:规则变更需要完整的回归测试

1.3 参数化配置的价值
什么是参数化配置:
参数化配置是将业务规则从代码中分离出来,通过配置化的方式定义薪资计算规则。核心思想是:
- 规则配置化:通过数据库或配置文件存储规则
- 计算引擎化:统一的计算引擎解析并执行计算
- 参数标准化:标准化的参数模型和计算接口
- 审计可追溯:每次计算都有完整的参数版本和计算明细
参数化配置的价值:
- 灵活性:通过配置快速响应业务需求变化
- 可维护性:规则与代码分离,降低维护成本
- 可扩展性:新增参数无需修改核心代码
- 合规性:政策调整可快速适配,降低合规风险
- 可追溯性:完整记录参数变更历史和计算过程
二、整体架构设计
2.1 系统架构全景
┌─────────────────────────────────────────────────────────────────┐
│ HRM参数化配置系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 配置管理层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 参数定义管理 │ │ 参数分组管理 │ │ 参数版本管理 │ │ │
│ │ │(Definition) │ │(Group) │ │(Version) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 参数模板管理 │ │ 计算器管理 │ │ 规则引擎配置 │ │ │
│ │ │(Template) │ │(Calculator) │ │(Rule Engine) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 计算器引擎层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 计算器注册表 │ │ 计算器调度器 │ │ 计算上下文管理 │ │ │
│ │ │(Registry) │ │(Dispatcher) │ │(Context) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 计算器实现层 │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ 员工档案计算器 │ │ 考勤数据计算器 │ │ │
│ │ │(Profile) │ │(Attendance) │ │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ 绩效数据计算器 │ │ 系统配置计算器 │ │ │
│ │ │(Performance) │ │(SystemConfig) │ │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 数据存储层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ 参数定义表 │ │ 参数值表 │ │ 计算历史表 │ │ │
│ │ │(sys_param) │ │(sys_param_val)│ │(calc_history) │ │ │
│ │ └──────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 核心领域模型
系统参数定义(SystemParameterDO):
/**
* 系统参数定义
* 定义一个可配置的系统参数
*/
@Data
@TableName("hrm_system_parameter")
public class SystemParameterDO {
/**
* 参数ID
*/
private String id;
/**
* 参数编码(唯一)
* 示例:attendance_overtime_hours, performance_score
*/
private String code;
/**
* 参数名称
*/
private String name;
/**
* 参数分组
* 示例:attendance, performance, employee_profile
*/
private String groupCode;
/**
* 数据类型
* STRING, NUMBER, DATE, BOOLEAN, DECIMAL
*/
private String dataType;
/**
* 计算器类型
* 指定使用哪个计算器来计算参数值
*/
private String calculatorType;
/**
* 默认值
*/
private String defaultValue;
/**
* 是否必填
*/
private Boolean required;
/**
* 参数描述
*/
private String description;
/**
* 排序序号
*/
private Integer sortOrder;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 扩展配置(JSON格式)
*/
private String extConfig;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
系统参数值(SystemParameterValueDO):
/**
* 系统参数值
* 存储参数的计算结果
*/
@Data
@TableName("hrm_system_parameter_value")
public class SystemParameterValueDO {
/**
* 参数值ID
*/
private String id;
/**
* 参数ID
*/
private String parameterId;
/**
* 员工ID
*/
private String employeeId;
/**
* 计算月份
*/
private String calculationMonth;
/**
* 参数值(字符串格式)
*/
private String value;
/**
* 计算时间
*/
private LocalDateTime calculationTime;
/**
* 计算器类型
*/
private String calculatorType;
/**
* 是否有效
*/
private Boolean valid;
/**
* 失效原因
*/
private String invalidReason;
/**
* 扩展信息(JSON格式)
*/
private String extInfo;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
2.3 策略模式架构设计
计算器接口定义:
/**
* 系统参数计算器接口
* 所有参数计算器都需要实现此接口
*
* 这是一个典型的策略模式应用,每个参数对应一个计算策略
*/
public interface SystemParameterCalculator {
/**
* 获取支持的参数编码
*
* @return 参数编码
*/
String getSupportedParameterCode();
/**
* 计算参数值
*
* @param parameter 系统参数定义
* @param calculationMonth 计算月份
* @param employee 员工信息
* @return 参数值
*/
String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
);
/**
* 验证参数值
*
* @param value 参数值
* @param parameter 参数定义
* @return 验证结果
*/
default ValidationResult validate(String value, SystemParameterDO parameter) {
return ValidationResult.success();
}
/**
* 获取参数值的显示名称
*
* @param value 参数值
* @return 显示名称
*/
default String getDisplayName(String value) {
return value;
}
}
/**
* 验证结果
*/
@Data
@Builder
public class ValidationResult {
/**
* 是否验证通过
*/
private Boolean valid;
/**
* 错误消息
*/
private String errorMessage;
public static ValidationResult success() {
return ValidationResult.builder()
.valid(true)
.build();
}
public static ValidationResult error(String errorMessage) {
return ValidationResult.builder()
.valid(false)
.errorMessage(errorMessage)
.build();
}
}
三、核心模块实现
3.1 计算器注册与调度
计算器注册表:
/**
* 系统参数计算器注册表
* 管理所有参数计算器的注册、查找和调用
*/
@Component
@Slf4j
public class CalculatorRegistry {
/**
* 计算器缓存
* key: 参数编码, value: 计算器实例
*/
private final Map<String, SystemParameterCalculator> calculatorCache = new ConcurrentHashMap<>();
/**
* 自动注入所有计算器实现
*/
@Autowired
public void setCalculators(List<SystemParameterCalculator> calculators) {
for (SystemParameterCalculator calculator : calculators) {
registerCalculator(calculator);
}
log.info("系统参数计算器注册完成: total={}", calculators.size());
}
/**
* 注册计算器
*/
private void registerCalculator(SystemParameterCalculator calculator) {
String parameterCode = calculator.getSupportedParameterCode();
if (StringUtils.isBlank(parameterCode)) {
throw new CalculatorException(
"计算器支持的参数编码不能为空: " + calculator.getClass().getName()
);
}
// 检查是否已注册
if (calculatorCache.containsKey(parameterCode)) {
throw new CalculatorException(
"参数编码重复: " + parameterCode +
", existing: " + calculatorCache.get(parameterCode).getClass().getName() +
", new: " + calculator.getClass().getName()
);
}
calculatorCache.put(parameterCode, calculator);
log.debug("注册计算器: code={}, calculator={}",
parameterCode, calculator.getClass().getSimpleName());
}
/**
* 获取计算器
*
* @param parameterCode 参数编码
* @return 计算器实例
*/
public SystemParameterCalculator getCalculator(String parameterCode) {
SystemParameterCalculator calculator = calculatorCache.get(parameterCode);
if (calculator == null) {
throw new CalculatorException("未找到参数计算器: " + parameterCode);
}
return calculator;
}
/**
* 检查计算器是否存在
*
* @param parameterCode 参数编码
* @return 是否存在
*/
public boolean hasCalculator(String parameterCode) {
return calculatorCache.containsKey(parameterCode);
}
/**
* 获取所有已注册的参数编码
*
* @return 参数编码列表
*/
public Set<String> getSupportedParameterCodes() {
return calculatorCache.keySet();
}
}
/**
* 计算器异常
*/
public class CalculatorException extends RuntimeException {
public CalculatorException(String message) {
super(message);
}
public CalculatorException(String message, Throwable cause) {
super(message, cause);
}
}
计算器调度引擎:
/**
* 系统参数计算引擎
* 负责调度计算器进行参数计算
*/
@Service
@Slf4j
public class SystemParameterCalculationEngine {
@Autowired
private CalculatorRegistry calculatorRegistry;
@Autowired
private SystemParameterMapper parameterMapper;
@Autowired
private SystemParameterValueMapper parameterValueMapper;
/**
* 计算员工的所有参数值
*
* @param employeeId 员工ID
* @param calculationMonth 计算月份
* @return 参数值Map
*/
@Transactional(rollbackFor = Exception.class)
public Map<String, String> calculateEmployeeParameters(
String employeeId,
String calculationMonth
) {
log.info("开始计算员工参数: employeeId={}, month={}", employeeId, calculationMonth);
// 1. 获取员工信息
EmployeeDO employee = employeeMapper.selectById(employeeId);
if (employee == null) {
throw new BusinessException("员工不存在: " + employeeId);
}
// 2. 解析计算月份
LocalDate month = parseCalculationMonth(calculationMonth);
// 3. 获取所有启用的系统参数
List<SystemParameterDO> parameters = parameterMapper.selectList(
new LambdaQueryWrapper<SystemParameterDO>()
.eq(SystemParameterDO::getEnabled, true)
.orderByAsc(SystemParameterDO::getSortOrder)
);
// 4. 逐个计算参数值
Map<String, String> results = new LinkedHashMap<>();
List<SystemParameterValueDO> valuesToSave = new ArrayList<>();
for (SystemParameterDO parameter : parameters) {
try {
// 检查是否有对应的计算器
if (!calculatorRegistry.hasCalculator(parameter.getCode())) {
log.warn("未找到参数计算器: code={}", parameter.getCode());
continue;
}
// 获取计算器
SystemParameterCalculator calculator =
calculatorRegistry.getCalculator(parameter.getCode());
// 计算参数值
String value = calculator.calculateParameterValue(
parameter, month, employee
);
// 验证参数值
ValidationResult validationResult = calculator.validate(value, parameter);
if (!validationResult.getValid()) {
log.warn("参数值验证失败: code={}, value={}, error={}",
parameter.getCode(), value, validationResult.getErrorMessage());
continue;
}
// 保存结果
results.put(parameter.getCode(), value);
// 构建参数值记录
SystemParameterValueDO parameterValue = new SystemParameterValueDO();
parameterValue.setId(IdUtil.fastUUID());
parameterValue.setParameterId(parameter.getId());
parameterValue.setEmployeeId(employeeId);
parameterValue.setCalculationMonth(calculationMonth);
parameterValue.setValue(value);
parameterValue.setCalculationTime(LocalDateTime.now());
parameterValue.setCalculatorType(parameter.getCalculatorType());
parameterValue.setValid(true);
parameterValue.setCreateTime(LocalDateTime.now());
valuesToSave.add(parameterValue);
} catch (Exception e) {
log.error("计算参数失败: code={}, error={}",
parameter.getCode(), e.getMessage(), e);
// 记录失败信息
SystemParameterValueDO parameterValue = new SystemParameterValueDO();
parameterValue.setId(IdUtil.fastUUID());
parameterValue.setParameterId(parameter.getId());
parameterValue.setEmployeeId(employeeId);
parameterValue.setCalculationMonth(calculationMonth);
parameterValue.setValid(false);
parameterValue.setInvalidReason(e.getMessage());
parameterValue.setCreateTime(LocalDateTime.now());
valuesToSave.add(parameterValue);
}
}
// 5. 批量保存参数值
if (!valuesToSave.isEmpty()) {
// 先删除旧数据
parameterValueMapper.delete(
new LambdaQueryWrapper<SystemParameterValueDO>()
.eq(SystemParameterValueDO::getEmployeeId, employeeId)
.eq(SystemParameterValueDO::getCalculationMonth, calculationMonth)
);
// 插入新数据
parameterValueMapper.insertBatch(valuesToSave);
}
log.info("员工参数计算完成: employeeId={}, month={}, count={}",
employeeId, calculationMonth, results.size());
return results;
}
/**
* 批量计算多个员工的参数值
*
* @param employeeIds 员工ID列表
* @param calculationMonth 计算月份
* @return 计算结果Map
*/
public Map<String, Map<String, String>> calculateBatchParameters(
List<String> employeeIds,
String calculationMonth
) {
// 使用并行流提升性能
return employeeIds.parallelStream()
.collect(Collectors.toMap(
employeeId -> employeeId,
employeeId -> calculateEmployeeParameters(employeeId, calculationMonth)
));
}
/**
* 解析计算月份
*/
private LocalDate parseCalculationMonth(String calculationMonth) {
try {
return LocalDate.parse(calculationMonth + "-01");
} catch (Exception e) {
throw new BusinessException("无效的计算月份格式: " + calculationMonth);
}
}
}
3.2 员工档案参数计算器
员工档案基础信息计算器:
/**
* 员工档案-城市计算器
* 获取员工工作所在城市
*/
@Component
public class EmployeeProfileCityCalculator implements SystemParameterCalculator {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public String getSupportedParameterCode() {
return "employee_profile_city";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return null;
}
// 获取员工的工作城市
String city = employee.getWorkCity();
if (StringUtils.isBlank(city)) {
// 如果员工没有设置工作城市,使用组织所在城市
String orgId = employee.getOrgId();
if (StringUtils.isNotBlank(orgId)) {
city = getOrgCity(orgId);
}
}
return StringUtils.isBlank(city) ? "未知" : city;
}
/**
* 获取组织所在城市
*/
private String getOrgCity(String orgId) {
// 从组织服务获取
// 这里简化处理
return "北京";
}
}
/**
* 员工档案-学历计算器
*/
@Component
public class EmployeeProfileEducationCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "employee_profile_education";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return null;
}
// 获取员工学历
String education = employee.getEducation();
// 转换学历编码为显示名称
return convertEducationName(education);
}
/**
* 转换学历名称
*/
private String convertEducationName(String educationCode) {
if (StringUtils.isBlank(educationCode)) {
return "未知";
}
switch (educationCode) {
case "博士":
return "博士研究生";
case "硕士":
return "硕士研究生";
case "本科":
return "大学本科";
case "大专":
return "大学专科";
case "高中":
return "高中";
case "初中":
return "初中";
case "小学":
return "小学";
default:
return educationCode;
}
}
}
/**
* 员工档案-工龄计算器
*/
@Component
public class EmployeeProfileWorkYearsCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "employee_profile_work_years";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
// 获取员工入职日期
LocalDate hireDate = employee.getHireDate();
if (hireDate == null) {
return "0";
}
// 计算工龄(按月计算)
Period period = Period.between(hireDate, calculationMonth);
int years = period.getYears();
int months = period.getMonths();
// 转换为年(保留一位小数)
double workYears = years + months / 12.0;
return String.valueOf(workYears);
}
@Override
public ValidationResult validate(String value, SystemParameterDO parameter) {
try {
double workYears = Double.parseDouble(value);
if (workYears < 0 || workYears > 50) {
return ValidationResult.error("工龄值异常: " + value);
}
return ValidationResult.success();
} catch (NumberFormatException e) {
return ValidationResult.error("工龄格式错误: " + value);
}
}
}
/**
* 员工档案-岗位级别计算器
*/
@Component
public class EmployeeProfilePositionLevelCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "employee_profile_position_level";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return null;
}
// 获取员工的岗位级别
String positionLevel = employee.getPositionLevel();
return StringUtils.isBlank(positionLevel) ? "未知" : positionLevel;
}
}
3.3 考勤数据参数计算器
考勤数据计算器:
/**
* 考勤-应出勤天数计算器
*/
@Component
@Slf4j
public class AttendanceShouldAttendanceDaysCalculator implements SystemParameterCalculator {
@Autowired
private AttendanceDailyService attendanceDailyService;
@Autowired
private AttendanceGroupService attendanceGroupService;
@Override
public String getSupportedParameterCode() {
return "attendance_should_attendance_days";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
try {
// 1. 获取考勤月份的起止日期
LocalDate monthStart = calculationMonth.withDayOfMonth(1);
LocalDate monthEnd = calculationMonth.withDayOfMonth(
calculationMonth.lengthOfMonth()
);
// 2. 获取员工的考勤组
AttendanceGroupDO attendanceGroup =
attendanceGroupService.getByEmployeeId(employee.getId());
if (attendanceGroup == null) {
// 没有考勤组,使用标准工作日计算
return calculateStandardWorkingDays(monthStart, monthEnd);
}
// 3. 根据考勤组排班计算应出勤天数
int shouldDays = 0;
LocalDate currentDate = monthStart;
while (!currentDate.isAfter(monthEnd)) {
// 检查该日期是否需要出勤
if (shouldWorkOnDate(currentDate, attendanceGroup)) {
shouldDays++;
}
currentDate = currentDate.plusDays(1);
}
return String.valueOf(shouldDays);
} catch (Exception e) {
log.error("计算应出勤天数失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "0";
}
}
/**
* 计算标准工作日(周一到周五)
*/
private String calculateStandardWorkingDays(LocalDate start, LocalDate end) {
int workingDays = 0;
LocalDate currentDate = start;
while (!currentDate.isAfter(end)) {
DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
workingDays++;
}
currentDate = currentDate.plusDays(1);
}
return String.valueOf(workingDays);
}
/**
* 判断指定日期是否需要出勤
*/
private boolean shouldWorkOnDate(LocalDate date, AttendanceGroupDO group) {
// 1. 检查是否为节假日
if (isHoliday(date)) {
return false;
}
// 2. 检查该日期的排班
AttendanceGroupDailyScheduleDO schedule =
attendanceGroupService.getDailySchedule(group.getId(), date);
if (schedule == null) {
// 没有特殊排班,检查默认工作日
return isDefaultWorkDay(date, group);
}
// 有特殊排班,检查是否需要上班
return "1".equals(schedule.getIsWorkDay());
}
/**
* 判断是否为节假日
*/
private boolean isHoliday(LocalDate date) {
// 查询节假日表
AttendanceHolidayDO holiday = attendanceHolidayMapper.selectOne(
new LambdaQueryWrapper<AttendanceHolidayDO>()
.eq(AttendanceHolidayDO::getHolidayDate, date)
);
return holiday != null;
}
/**
* 判断是否为默认工作日
*/
private boolean isDefaultWorkDay(LocalDate date, AttendanceGroupDO group) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
// 考勤组的默认工作日配置
String defaultWorkDays = group.getDefaultWorkDays();
if (StringUtils.isBlank(defaultWorkDays)) {
// 未配置,使用周一到周五
return dayOfWeek != DayOfWeek.SATURDAY &&
dayOfWeek != DayOfWeek.SUNDAY;
}
// 解析配置的工作日
return defaultWorkDays.contains(String.valueOf(dayOfWeek.getValue()));
}
}
/**
* 考勤-加班时长计算器
*/
@Component
@Slf4j
public class AttendanceOvertimeHoursCalculator implements SystemParameterCalculator {
@Autowired
private AttendanceDailyService attendanceDailyService;
@Override
public String getSupportedParameterCode() {
return "attendance_overtime_hours";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
try {
// 1. 获取考勤月份的起止日期
LocalDate monthStart = calculationMonth.withDayOfMonth(1);
LocalDate monthEnd = calculationMonth.withDayOfMonth(
calculationMonth.lengthOfMonth()
);
// 2. 查询该员工在指定月份的所有考勤记录
List<AttendanceDailyDO> dailyRecords = attendanceDailyService.listByEmployeeAndDateRange(
employee.getId(),
monthStart,
monthEnd
);
// 3. 累计加班时长
BigDecimal totalOvertimeHours = BigDecimal.ZERO;
for (AttendanceDailyDO record : dailyRecords) {
if (record.getOvertimeDuration() != null) {
totalOvertimeHours = totalOvertimeHours.add(
record.getOvertimeDuration()
);
}
}
// 4. 保留两位小数
return totalOvertimeHours.setScale(2, RoundingMode.HALF_UP).toString();
} catch (Exception e) {
log.error("计算加班时长失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "0";
}
}
@Override
public ValidationResult validate(String value, SystemParameterDO parameter) {
try {
BigDecimal hours = new BigDecimal(value);
if (hours.compareTo(BigDecimal.ZERO) < 0) {
return ValidationResult.error("加班时长不能为负数: " + value);
}
if (hours.compareTo(new BigDecimal("500")) > 0) {
return ValidationResult.error("加班时长异常: " + value);
}
return ValidationResult.success();
} catch (NumberFormatException e) {
return ValidationResult.error("加班时长格式错误: " + value);
}
}
}
3.4 绩效数据参数计算器
绩效数据计算器:
/**
* 绩效-绩效得分计算器
*/
@Component
@Slf4j
public class PerformanceScoreCalculator implements SystemParameterCalculator {
@Autowired
private EvaluateEmployeeService evaluateEmployeeService;
@Override
public String getSupportedParameterCode() {
return "performance_score";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
try {
// 1. 获取该员工在指定月份的绩效评估
EvaluateEmployeeDO evaluate = evaluateEmployeeService.getByEmployeeAndMonth(
employee.getId(),
calculationMonth.toString()
);
if (evaluate == null) {
// 没有绩效评估,返回0或默认值
return "0";
}
// 2. 获取绩效总分
BigDecimal totalScore = evaluate.getTotalScore();
if (totalScore == null) {
return "0";
}
// 3. 保留两位小数
return totalScore.setScale(2, RoundingMode.HALF_UP).toString();
} catch (Exception e) {
log.error("计算绩效得分失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "0";
}
}
@Override
public ValidationResult validate(String value, SystemParameterDO parameter) {
try {
BigDecimal score = new BigDecimal(value);
if (score.compareTo(BigDecimal.ZERO) < 0) {
return ValidationResult.error("绩效得分不能为负数: " + value);
}
if (score.compareTo(new BigDecimal("100")) > 0) {
return ValidationResult.error("绩效得分不能超过100: " + value);
}
return ValidationResult.success();
} catch (NumberFormatException e) {
return ValidationResult.error("绩效得分格式错误: " + value);
}
}
}
/**
* 绩效-绩效等级计算器
*/
@Component
public class PerformanceLevelCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "performance_level";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "未知";
}
try {
// 1. 先获取绩效得分
String scoreStr = new PerformanceScoreCalculator().calculateParameterValue(
parameter, calculationMonth, employee
);
BigDecimal score = new BigDecimal(scoreStr);
// 2. 根据得分确定绩效等级
return calculateLevel(score);
} catch (Exception e) {
log.error("计算绩效等级失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "未知";
}
}
/**
* 根据分数计算等级
*/
private String calculateLevel(BigDecimal score) {
if (score.compareTo(new BigDecimal("90")) >= 0) {
return "S";
} else if (score.compareTo(new BigDecimal("80")) >= 0) {
return "A";
} else if (score.compareTo(new BigDecimal("70")) >= 0) {
return "B";
} else if (score.compareTo(new BigDecimal("60")) >= 0) {
return "C";
} else {
return "D";
}
}
@Override
public String getDisplayName(String value) {
switch (value) {
case "S":
return "优秀";
case "A":
return "良好";
case "B":
return "合格";
case "C":
return "待改进";
case "D":
return "不合格";
default:
return "未知";
}
}
}
/**
* 绩效-目标完成率计算器
*/
@Component
public class PerformanceTargetCompletionRateCalculator implements SystemParameterCalculator {
@Autowired
private PerformanceTargetService performanceTargetService;
@Override
public String getSupportedParameterCode() {
return "performance_target_completion_rate";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
try {
// 1. 获取该员工在指定月份的绩效目标
List<PerformanceTargetDO> targets = performanceTargetService
.listByEmployeeAndMonth(employee.getId(), calculationMonth);
if (targets.isEmpty()) {
return "0";
}
// 2. 计算目标完成率
BigDecimal totalCompletionRate = BigDecimal.ZERO;
int targetCount = 0;
for (PerformanceTargetDO target : targets) {
if (target.getTargetValue() != null &&
target.getTargetValue().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal completionRate = target.getActualValue()
.divide(target.getTargetValue(), 4, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"));
totalCompletionRate = totalCompletionRate.add(completionRate);
targetCount++;
}
}
if (targetCount == 0) {
return "0";
}
// 3. 计算平均完成率
BigDecimal avgCompletionRate = totalCompletionRate
.divide(new BigDecimal(targetCount), 2, RoundingMode.HALF_UP);
return avgCompletionRate.toString();
} catch (Exception e) {
log.error("计算目标完成率失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "0";
}
}
}
3.5 系统配置参数计算器
系统配置计算器:
/**
* 系统配置-当前月份计算器
*/
@Component
public class SystemConfigCurrentMonthCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "system_config_current_month";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
// 直接返回计算月份的年月格式
return calculationMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
}
}
/**
* 系统配置-当前季度计算器
*/
@Component
public class SystemConfigCurrentQuarterCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "system_config_current_quarter";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
// 计算当前季度
int month = calculationMonth.getMonthValue();
int quarter = (month - 1) / 3 + 1;
return String.valueOf(quarter);
}
}
/**
* 系统配置-当前年份计算器
*/
@Component
public class SystemConfigCurrentYearCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "system_config_current_year";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
// 直接返回年份
return String.valueOf(calculationMonth.getYear());
}
}
/**
* 系统配置-最低工资标准计算器
*/
@Component
public class SystemConfigMinimumWageCalculator implements SystemParameterCalculator {
@Autowired
private MinimumWageService minimumWageService;
@Override
public String getSupportedParameterCode() {
return "system_config_minimum_wage";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
try {
// 获取员工工作城市的最低工资标准
String city = employee.getWorkCity();
if (StringUtils.isBlank(city)) {
// 如果没有城市信息,使用默认标准
return "2000";
}
// 查询该城市的最低工资标准
BigDecimal minimumWage = minimumWageService.getByCityAndDate(
city, calculationMonth
);
if (minimumWage == null) {
return "2000";
}
return minimumWage.toString();
} catch (Exception e) {
log.error("获取最低工资标准失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "2000";
}
}
}
3.6 特殊标识参数计算器
特殊标识计算器:
/**
* 特殊标识-是否正式员工计算器
*/
@Component
public class SpecialFlagsIsFormalCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "special_flags_is_formal";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
// 判断是否为正式员工
// 1表示正式,0表示非正式
String employeeType = employee.getEmployeeType();
if ("正式".equals(employeeType)) {
return "1";
}
return "0";
}
@Override
public String getDisplayName(String value) {
return "1".equals(value) ? "是" : "否";
}
}
/**
* 特殊标识-是否管理人员计算器
*/
@Component
public class SpecialFlagsIsManagementCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "special_flags_is_management";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
// 判断是否为管理人员
// 管理人员通常有下级员工
if (hasSubordinates(employee.getId())) {
return "1";
}
return "0";
}
/**
* 判断是否有下级
*/
private boolean hasSubordinates(String employeeId) {
// 查询是否有上级为该员工的员工
Long count = employeeMapper.selectCount(
new LambdaQueryWrapper<EmployeeDO>()
.eq(EmployeeDO::getSuperiorId, employeeId)
);
return count != null && count > 0;
}
@Override
public String getDisplayName(String value) {
return "1".equals(value) ? "是" : "否";
}
}
/**
* 特殊标识-是否全勤计算器
*/
@Component
public class SpecialFlagsIsFullAttendanceCalculator implements SystemParameterCalculator {
@Autowired
private AttendanceDailyService attendanceDailyService;
@Override
public String getSupportedParameterCode() {
return "special_flags_is_full_attendance";
}
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
if (employee == null) {
return "0";
}
try {
// 1. 获取应出勤天数
String shouldDaysStr = new AttendanceShouldAttendanceDaysCalculator()
.calculateParameterValue(parameter, calculationMonth, employee);
int shouldDays = Integer.parseInt(shouldDaysStr);
if (shouldDays == 0) {
return "0";
}
// 2. 获取实际出勤天数
LocalDate monthStart = calculationMonth.withDayOfMonth(1);
LocalDate monthEnd = calculationMonth.withDayOfMonth(
calculationMonth.lengthOfMonth()
);
List<AttendanceDailyDO> dailyRecords = attendanceDailyService
.listByEmployeeAndDateRange(employee.getId(), monthStart, monthEnd);
// 3. 统计实际出勤天数(正常+迟到+早退,但不算缺勤)
int actualDays = 0;
for (AttendanceDailyDO record : dailyRecords) {
if (!"2".equals(record.getAttendanceStatus())) {
// 不是缺勤
actualDays++;
}
}
// 4. 判断是否全勤
if (actualDays >= shouldDays) {
return "1";
}
return "0";
} catch (Exception e) {
log.error("计算全勤状态失败: employeeId={}, month={}, error={}",
employee.getId(), calculationMonth, e.getMessage());
return "0";
}
}
@Override
public String getDisplayName(String value) {
return "1".equals(value) ? "是" : "否";
}
}
四、高级特性实现
4.1 参数缓存优化
参数缓存管理器:
/**
* 系统参数缓存管理器
* 使用多级缓存提升参数查询性能
*/
@Component
@Slf4j
public class SystemParameterCacheManager {
/**
* L1缓存:本地缓存(Caffeine)
*/
private final Cache<String, SystemParameterValueDO> localCache;
/**
* L2缓存:分布式缓存(Redis)
*/
@Autowired
private RedisTemplate<String, SystemParameterValueDO> redisTemplate;
public SystemParameterCacheManager() {
this.localCache = Caffeine.newBuilder()
.maximumSize(100_000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.recordStats()
.build();
}
/**
* 获取参数值
*
* @param employeeId 员工ID
* @param parameterCode 参数编码
* @param calculationMonth 计算月份
* @return 参数值
*/
public String getParameterValue(
String employeeId,
String parameterCode,
String calculationMonth
) {
String cacheKey = buildCacheKey(employeeId, parameterCode, calculationMonth);
// 1. 查询L1缓存
SystemParameterValueDO cached = localCache.getIfPresent(cacheKey);
if (cached != null && cached.getValid()) {
log.debug("L1缓存命中: key={}", cacheKey);
return cached.getValue();
}
// 2. 查询L2缓存
cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null && cached.getValid()) {
log.debug("L2缓存命中: key={}", cacheKey);
// 回写L1缓存
localCache.put(cacheKey, cached);
return cached.getValue();
}
// 3. 缓存未命中,返回null
log.debug("缓存未命中: key={}", cacheKey);
return null;
}
/**
* 缓存参数值
*
* @param employeeId 员工ID
* @param parameterCode 参数编码
* @param calculationMonth 计算月份
* @param value 参数值
*/
public void cacheParameterValue(
String employeeId,
String parameterCode,
String calculationMonth,
String value
) {
String cacheKey = buildCacheKey(employeeId, parameterCode, calculationMonth);
SystemParameterValueDO parameterValue = new SystemParameterValueDO();
parameterValue.setValue(value);
parameterValue.setValid(true);
parameterValue.setCalculationTime(LocalDateTime.now());
// 写入L1缓存
localCache.put(cacheKey, parameterValue);
// 写入L2缓存
redisTemplate.opsForValue().set(cacheKey, parameterValue, 1, TimeUnit.HOURS);
}
/**
* 批量预热缓存
*
* @param employeeId 员工ID
* @param calculationMonth 计算月份
*/
public void warmUpCache(String employeeId, String calculationMonth) {
log.info("预热参数缓存: employeeId={}, month={}", employeeId, calculationMonth);
// 查询该员工在该月份的所有参数值
List<SystemParameterValueDO> values = parameterValueMapper.selectList(
new LambdaQueryWrapper<SystemParameterValueDO>()
.eq(SystemParameterValueDO::getEmployeeId, employeeId)
.eq(SystemParameterValueDO::getCalculationMonth, calculationMonth)
.eq(SystemParameterValueDO::getValid, true)
);
// 批量写入缓存
for (SystemParameterValueDO value : values) {
SystemParameterDO parameter = parameterMapper.selectById(value.getParameterId());
if (parameter != null) {
cacheParameterValue(
employeeId,
parameter.getCode(),
calculationMonth,
value.getValue()
);
}
}
log.info("参数缓存预热完成: employeeId={}, month={}, count={}",
employeeId, calculationMonth, values.size());
}
/**
* 清除缓存
*
* @param employeeId 员工ID
* @param calculationMonth 计算月份
*/
public void invalidateCache(String employeeId, String calculationMonth) {
// 清除L1缓存
Set<String> keys = localCache.asMap().keySet().stream()
.filter(key -> key.startsWith(buildCachePrefix(employeeId, calculationMonth)))
.collect(Collectors.toSet());
localCache.invalidateAll(keys);
// 清除L2缓存
Set<String> redisKeys = redisTemplate.keys(
buildCachePrefix(employeeId, calculationMonth) + "*"
);
if (redisKeys != null && !redisKeys.isEmpty()) {
redisTemplate.delete(redisKeys);
}
log.info("清除参数缓存: employeeId={}, month={}", employeeId, calculationMonth);
}
/**
* 构建缓存键
*/
private String buildCacheKey(String employeeId, String parameterCode, String calculationMonth) {
return buildCachePrefix(employeeId, calculationMonth) + ":" + parameterCode;
}
/**
* 构建缓存键前缀
*/
private String buildCachePrefix(String employeeId, String calculationMonth) {
return "sys_param:value:" + employeeId + ":" + calculationMonth;
}
}
4.2 参数依赖计算
参数依赖计算引擎:
/**
* 参数依赖计算引擎
* 处理参数之间的依赖关系,确保按正确顺序计算
*/
@Component
@Slf4j
public class ParameterDependencyCalculationEngine {
@Autowired
private SystemParameterCalculationEngine calculationEngine;
@Autowired
private CalculatorRegistry calculatorRegistry;
/**
* 根据依赖关系计算参数
*
* @param employeeId 员工ID
* @param calculationMonth 计算月份
* @param parameterCode 参数编码
* @return 参数值
*/
public String calculateWithDependency(
String employeeId,
String calculationMonth,
String parameterCode
) {
// 1. 获取参数定义
SystemParameterDO parameter = parameterMapper.selectOne(
new LambdaQueryWrapper<SystemParameterDO>()
.eq(SystemParameterDO::getCode, parameterCode)
);
if (parameter == null) {
throw new BusinessException("参数不存在: " + parameterCode);
}
// 2. 解析依赖关系
List<String> dependencies = parseDependencies(parameter.getExtConfig());
// 3. 按顺序计算依赖参数
for (String dependencyCode : dependencies) {
// 检查是否已计算
String cachedValue = getParameterValueFromCache(
employeeId, dependencyCode, calculationMonth
);
if (cachedValue == null) {
// 递归计算依赖参数
calculateWithDependency(employeeId, calculationMonth, dependencyCode);
}
}
// 4. 计算目标参数
Map<String, String> results = calculationEngine.calculateEmployeeParameters(
employeeId, calculationMonth
);
return results.get(parameterCode);
}
/**
* 解析依赖关系
*
* @param extConfig 扩展配置
* @return 依赖的参数编码列表
*/
private List<String> parseDependencies(String extConfig) {
if (StringUtils.isBlank(extConfig)) {
return Collections.emptyList();
}
try {
JSONObject config = JSON.parseObject(extConfig);
String dependenciesStr = config.getString("dependencies");
if (StringUtils.isBlank(dependenciesStr)) {
return Collections.emptyList();
}
return Arrays.asList(dependenciesStr.split(","));
} catch (Exception e) {
log.warn("解析依赖关系失败: extConfig={}", extConfig);
return Collections.emptyList();
}
}
/**
* 从缓存获取参数值
*/
private String getParameterValueFromCache(
String employeeId,
String parameterCode,
String calculationMonth
) {
return cacheManager.getParameterValue(employeeId, parameterCode, calculationMonth);
}
}
4.3 参数变更审计
参数变更审计服务:
/**
* 系统参数变更审计服务
* 记录参数配置和值的变更历史
*/
@Service
@Slf4j
public class SystemParameterAuditService {
@Autowired
private SystemParameterChangeLogMapper changeLogMapper;
/**
* 记录参数定义变更
*
* @param parameter 参数定义
* @param changeType 变更类型
* @param operator 操作人
*/
public void logParameterDefinitionChange(
SystemParameterDO parameter,
ChangeType changeType,
String operator
) {
SystemParameterChangeLog changeLog = new SystemParameterChangeLog();
changeLog.setId(IdUtil.fastUUID());
changeLog.setParameterId(parameter.getId());
changeLog.setParameterCode(parameter.getCode());
changeLog.setChangeType(changeType.name());
changeLog.setChangeCategory("DEFINITION");
changeLog.setOldValue(null); // 从历史表获取
changeLog.setNewValue(JSON.toJSONString(parameter));
changeLog.setOperator(operator);
changeLog.setChangeTime(LocalDateTime.now());
changeLogMapper.insert(changeLog);
log.info("记录参数定义变更: code={}, type={}, operator={}",
parameter.getCode(), changeType, operator);
}
/**
* 记录参数值变更
*
* @param employeeId 员工ID
* @param parameterCode 参数编码
* @param calculationMonth 计算月份
* @param oldValue 旧值
* @param newValue 新值
* @param operator 操作人
*/
public void logParameterValueChange(
String employeeId,
String parameterCode,
String calculationMonth,
String oldValue,
String newValue,
String operator
) {
SystemParameterChangeLog changeLog = new SystemParameterChangeLog();
changeLog.setId(IdUtil.fastUUID());
changeLog.setEmployeeId(employeeId);
changeLog.setParameterCode(parameterCode);
changeLog.setCalculationMonth(calculationMonth);
changeLog.setChangeType(ChangeType.UPDATE.name());
changeLog.setChangeCategory("VALUE");
changeLog.setOldValue(oldValue);
changeLog.setNewValue(newValue);
changeLog.setOperator(operator);
changeLog.setChangeTime(LocalDateTime.now());
changeLogMapper.insert(changeLog);
log.info("记录参数值变更: employeeId={}, code={}, month={}, oldValue={}, newValue={}",
employeeId, parameterCode, calculationMonth, oldValue, newValue);
}
/**
* 查询参数变更历史
*
* @param parameterCode 参数编码
* @param startTime 开始时间
* @param endTime 结束时间
* @return 变更历史列表
*/
public List<SystemParameterChangeLog> queryChangeHistory(
String parameterCode,
LocalDateTime startTime,
LocalDateTime endTime
) {
return changeLogMapper.selectList(
new LambdaQueryWrapper<SystemParameterChangeLog>()
.eq(SystemParameterChangeLog::getParameterCode, parameterCode)
.ge(SystemParameterChangeLog::getChangeTime, startTime)
.le(SystemParameterChangeLog::getChangeTime, endTime)
.orderByDesc(SystemParameterChangeLog::getChangeTime)
);
}
}
/**
* 变更类型枚举
*/
public enum ChangeType {
/** 创建 */
CREATE,
/** 更新 */
UPDATE,
/** 删除 */
DELETE,
/** 启用 */
ENABLE,
/** 禁用 */
DISABLE
}
五、性能优化
5.1 批量计算优化
异步批量计算处理器:
/**
* 批量参数计算异步处理器
* 使用消息队列实现大规模批量计算
*/
@Service
@Slf4j
public class BatchParameterCalculationProcessor {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private SystemParameterCalculationEngine calculationEngine;
/**
* 异步批量计算参数
*
* @param employeeIds 员工ID列表
* @param calculationMonth 计算月份
*/
public void asyncBatchCalculate(
List<String> employeeIds,
String calculationMonth
) {
log.info("提交批量参数计算任务: count={}, month={}",
employeeIds.size(), calculationMonth);
// 分批发送消息
int batchSize = 100;
List<List<String>> batches = Lists.partition(employeeIds, batchSize);
for (int i = 0; i < batches.size(); i++) {
List<String> batch = batches.get(i);
ParameterCalculationMessage message = new ParameterCalculationMessage();
message.setBatchId(IdUtil.fastUUID());
message.setEmployeeIds(batch);
message.setCalculationMonth(calculationMonth);
message.setBatchNumber(i + 1);
message.setTotalBatches(batches.size());
rocketMQTemplate.asyncSend(
"PARAMETER_CALC_TOPIC",
message,
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("批量计算消息发送成功: batchNumber={}", i + 1);
}
@Override
public void onException(Throwable e) {
log.error("批量计算消息发送失败: batchNumber={}", i + 1, e);
}
}
);
}
}
}
/**
* 参数计算消息监听器
*/
@Component
@RocketMQMessageListener(
topic = "PARAMETER_CALC_TOPIC",
consumerGroup = "PARAMETER_CALC_CONSUMER_GROUP"
)
@Slf4j
public class ParameterCalculationConsumer implements
RocketMQListener<ParameterCalculationMessage> {
@Autowired
private SystemParameterCalculationEngine calculationEngine;
@Override
public void onMessage(ParameterCalculationMessage message) {
String batchId = message.getBatchId();
int batchNumber = message.getBatchNumber();
log.info("处理参数计算批次: batchId={}, batchNumber={}", batchId, batchNumber);
try {
// 并行处理本批次的所有员工
Map<String, Map<String, String>> results = message.getEmployeeIds()
.parallelStream()
.collect(Collectors.toMap(
employeeId -> employeeId,
employeeId -> calculationEngine.calculateEmployeeParameters(
employeeId, message.getCalculationMonth()
)
));
log.info("参数计算批次处理完成: batchId={}, batchNumber={}, count={}",
batchId, batchNumber, results.size());
} catch (Exception e) {
log.error("参数计算批次处理失败: batchId={}, batchNumber={}, error={}",
batchId, batchNumber, e.getMessage());
}
}
}
5.2 计算性能监控
计算性能监控服务:
/**
* 参数计算性能监控服务
*/
@Component
@Slf4j
public class ParameterCalculationMonitor {
@Autowired
private MeterRegistry meterRegistry;
/**
* 记录计算耗时
*
* @param parameterCode 参数编码
* @param duration 耗时(毫秒)
* @param success 是否成功
*/
public void recordCalculationTime(
String parameterCode,
long duration,
boolean success
) {
// 记录耗时分布
Timer.builder("parameter.calculation.duration")
.tag("parameter", parameterCode)
.tag("status", success ? "success" : "failure")
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
// 记录成功/失败计数
meterRegistry.counter("parameter.calculation.count",
"parameter", parameterCode,
"status", success ? "success" : "failure"
).increment();
}
/**
* 获取性能统计摘要
*
* @return 性能摘要
*/
public PerformanceSummary getPerformanceSummary() {
PerformanceSummary summary = new PerformanceSummary();
// 获取总体计算耗时统计
Timer totalTimer = meterRegistry.get("parameter.calculation.duration").timer();
if (totalTimer != null) {
summary.setAvgCalculationTime(totalTimer.mean(TimeUnit.MILLISECONDS));
summary.setMaxCalculationTime(totalTimer.max(TimeUnit.MILLISECONDS));
}
// 获取缓存命中率
double cacheHitRate = getCacheHitRate();
summary.setCacheHitRate(cacheHitRate);
// 获取计算成功率
double successRate = getSuccessRate();
summary.setSuccessRate(successRate);
return summary;
}
/**
* 获取缓存命中率
*/
private double getCacheHitRate() {
Counter hitCounter = meterRegistry.counter("parameter.cache", "result", "hit");
Counter missCounter = meterRegistry.counter("parameter.cache", "result", "miss");
double hitCount = hitCounter.count();
double missCount = missCounter.count();
double totalCount = hitCount + missCount;
if (totalCount > 0) {
return hitCount / totalCount;
}
return 0.0;
}
/**
* 获取计算成功率
*/
private double getSuccessRate() {
Counter successCounter = meterRegistry.counter("parameter.calculation.count", "status", "success");
Counter failureCounter = meterRegistry.counter("parameter.calculation.count", "status", "failure");
double successCount = successCounter.count();
double failureCount = failureCounter.count();
double totalCount = successCount + failureCount;
if (totalCount > 0) {
return successCount / totalCount;
}
return 0.0;
}
}
六、最佳实践
6.1 计算器设计原则
1. 单一职责原则
// ✅ 推荐:每个计算器只负责一个参数
@Component
public class EmployeeProfileCityCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "employee_profile_city"; // 单一参数
}
// ...
}
// ❌ 不推荐:一个计算器负责多个参数
@Component
public class EmployeeProfileAllCalculator implements SystemParameterCalculator {
@Override
public String getSupportedParameterCode() {
return "employee_profile_all"; // 多个参数
}
// 需要通过参数类型区分,违反单一职责
}
2. 幂等性原则
// ✅ 推荐:保证计算的幂等性
@Override
public String calculateParameterValue(...) {
// 每次相同的输入产生相同的输出
return employee.getCity();
}
// ❌ 不推荐:计算有副作用
@Override
public String calculateParameterValue(...) {
// 修改了数据库状态,不是幂等的
employee.setCity("北京");
employeeMapper.updateById(employee);
return employee.getCity();
}
3. 可测试性原则
// ✅ 推荐:便于单元测试
@Component
public class AttendanceShouldAttendanceDaysCalculator
implements SystemParameterCalculator {
// 依赖注入,便于Mock
@Autowired
private AttendanceGroupService attendanceGroupService;
@Override
public String calculateParameterValue(...) {
// 逻辑清晰,易于测试
return calculateDays(month, employee);
}
}
// ❌ 不推荐:难以测试
@Component
public class HardCalculator implements SystemParameterCalculator {
@Override
public String calculateParameterValue(...) {
// 直接调用静态方法,难以Mock
return SomeStaticClass.calculate();
}
}
6.2 参数编码规范
参数编码命名规范:
/**
* 参数编码命名规范
*
* 格式:{模块}_{子模块}_{具体参数}
*
* 示例:
* - employee_profile_city(员工档案-城市)
* - attendance_should_attendance_days(考勤-应出勤天数)
* - performance_score(绩效-得分)
* - system_config_current_month(系统配置-当前月份)
*/
public class ParameterCodeConstants {
// 员工档案类
public static final String EMPLOYEE_PROFILE_CITY = "employee_profile_city";
public static final String EMPLOYEE_PROFILE_EDUCATION = "employee_profile_education";
public static final String EMPLOYEE_PROFILE_WORK_YEARS = "employee_profile_work_years";
// 考勤类
public static final String ATTENDANCE_SHOULD_ATTENDANCE_DAYS = "attendance_should_attendance_days";
public static final String ATTENDANCE_OVERTIME_HOURS = "attendance_overtime_hours";
// 绩效类
public static final String PERFORMANCE_SCORE = "performance_score";
public static final String PERFORMANCE_LEVEL = "performance_level";
// 系统配置类
public static final String SYSTEM_CONFIG_CURRENT_MONTH = "system_config_current_month";
public static final String SYSTEM_CONFIG_MINIMUM_WAGE = "system_config_minimum_wage";
// 特殊标识类
public static final String SPECIAL_FLAGS_IS_FORMAL = "special_flags_is_formal";
public static final String SPECIAL_FLAGS_IS_MANAGEMENT = "special_flags_is_management";
}
6.3 错误处理最佳实践
优雅的错误处理:
/**
* 错误处理最佳实践
*/
@Component
@Slf4j
public class RobustCalculator implements SystemParameterCalculator {
@Override
public String calculateParameterValue(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
try {
// 1. 参数校验
if (employee == null) {
log.warn("员工信息为空: parameter={}", parameter.getCode());
return getDefaultValue(parameter);
}
// 2. 业务逻辑
String value = doCalculate(parameter, calculationMonth, employee);
// 3. 结果校验
if (StringUtils.isBlank(value)) {
log.warn("计算结果为空: parameter={}, employeeId={}",
parameter.getCode(), employee.getId());
return getDefaultValue(parameter);
}
return value;
} catch (BusinessException e) {
// 业务异常,记录日志并返回默认值
log.warn("业务异常: parameter={}, employeeId={}, error={}",
parameter.getCode(), employee.getId(), e.getMessage());
return getDefaultValue(parameter);
} catch (Exception e) {
// 系统异常,记录日志并返回默认值
log.error("系统异常: parameter={}, employeeId={}",
parameter.getCode(), employee.getId(), e);
return getDefaultValue(parameter);
}
}
/**
* 获取默认值
*/
private String getDefaultValue(SystemParameterDO parameter) {
String defaultValue = parameter.getDefaultValue();
return StringUtils.isNotBlank(defaultValue) ? defaultValue : "0";
}
/**
* 实际计算逻辑
*/
private String doCalculate(
SystemParameterDO parameter,
LocalDate calculationMonth,
EmployeeDO employee
) {
// 具体计算逻辑
return "calculated_value";
}
}
七、总结
本文详细介绍了企业级HRM系统参数化配置架构的设计与实现,主要内容包括:
核心技术点
- 策略模式应用:通过计算器接口实现参数计算的策略模式
- 计算器注册机制:自动注册和查找计算器,实现解耦
- 多级缓存架构:L1本地缓存+L2分布式缓存提升性能
- 异步批量计算:使用消息队列实现大规模批量计算
- 参数依赖管理:处理参数之间的依赖关系
- 变更审计追踪:完整记录参数变更历史
业务价值
- 灵活性:新增参数无需修改核心代码,只需添加计算器实现
- 可维护性:规则与代码分离,降低维护成本
- 可扩展性:支持企业自定义参数和计算规则
- 合规性:政策调整可快速适配
- 可追溯性:完整记录参数变更和计算过程
扩展方向
- 可视化配置:提供拖拽式参数配置界面
- 公式引擎:支持更复杂的计算公式
- 参数模板:支持参数模板的导入导出
- 智能推荐:根据历史数据推荐参数配置
- 实时计算:支持参数的实时计算和推送
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)