一、业务背景与挑战

1.1 HRM系统的复杂性挑战

在企业级HRM(人力资源管理)系统中,薪资计算是最复杂、最敏感的核心业务。不同企业、不同地区、不同行业的薪资规则千差万别,呈现出以下特点:

多维度差异化需求

  • 企业差异:国企、民企、外企的薪资结构完全不同
  • 地区差异:不同城市的社保基数、公积金比例不同
  • 行业差异:制造业、互联网、服务业的薪资项目不同
  • 岗位差异:管理岗、技术岗、销售岗的薪资计算方式不同
  • 政策变化:个税政策、社保政策频繁调整

相关链接

在这里插入图片描述

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);
}

痛点分析

  1. 扩展性差:每次规则变化都需要修改代码、重新发布
  2. 维护成本高:大量if-else分支难以理解和维护
  3. 业务耦合:业务规则与技术实现强耦合
  4. 响应速度慢:政策调整无法快速响应,存在合规风险
  5. 测试困难:规则变更需要完整的回归测试

在这里插入图片描述

1.3 参数化配置的价值

什么是参数化配置
参数化配置是将业务规则从代码中分离出来,通过配置化的方式定义薪资计算规则。核心思想是:

  • 规则配置化:通过数据库或配置文件存储规则
  • 计算引擎化:统一的计算引擎解析并执行计算
  • 参数标准化:标准化的参数模型和计算接口
  • 审计可追溯:每次计算都有完整的参数版本和计算明细

参数化配置的价值

  1. 灵活性:通过配置快速响应业务需求变化
  2. 可维护性:规则与代码分离,降低维护成本
  3. 可扩展性:新增参数无需修改核心代码
  4. 合规性:政策调整可快速适配,降低合规风险
  5. 可追溯性:完整记录参数变更历史和计算过程

二、整体架构设计

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系统参数化配置架构的设计与实现,主要内容包括:

核心技术点

  1. 策略模式应用:通过计算器接口实现参数计算的策略模式
  2. 计算器注册机制:自动注册和查找计算器,实现解耦
  3. 多级缓存架构:L1本地缓存+L2分布式缓存提升性能
  4. 异步批量计算:使用消息队列实现大规模批量计算
  5. 参数依赖管理:处理参数之间的依赖关系
  6. 变更审计追踪:完整记录参数变更历史

业务价值

  1. 灵活性:新增参数无需修改核心代码,只需添加计算器实现
  2. 可维护性:规则与代码分离,降低维护成本
  3. 可扩展性:支持企业自定义参数和计算规则
  4. 合规性:政策调整可快速适配
  5. 可追溯性:完整记录参数变更和计算过程

扩展方向

  1. 可视化配置:提供拖拽式参数配置界面
  2. 公式引擎:支持更复杂的计算公式
  3. 参数模板:支持参数模板的导入导出
  4. 智能推荐:根据历史数据推荐参数配置
  5. 实时计算:支持参数的实时计算和推送
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐