基于java实现规则引擎设计思路

背景

去年底换了公司,目前在做HR招聘系统,产品要求:管理端手动配置发布招聘要求(支持多个招聘条件AND与OR关系编排);当C端用户报名该职位时,系统执行该规则,返回报名成功或失败。根据技术调研,采用db存规则json串。然后报名时,执行该规则的方法。

前端样式

在这里插入图片描述
招聘岗位的多个规则关系是OR关系;同1个条件点击+号,可增加多个规则,是AND条件

代码入口

在这里插入图片描述
比如上述单元测试的场景:招java开发岗,要求:满足本科+35岁以下或本科+P7以上即可。
执行单测:
通过日志就能清晰的分析出,岗位配了什么规则,各规则的执行结果是什么
在这里插入图片描述

代码设计

类图

通过工厂+策略的设计模式,实现该规则,符合开闭原则,方便后续业务规则扩充,代码扩展
在这里插入图片描述

代码介绍

核心接口:InternalPositionRule 定义了规则执行的统一方法。
抽象类:AbstractPositionRule 提供了通用的比较和告警逻辑。
具体规则类:如 AgeRule、SexRule 等,继承自 AbstractPositionRule。
规则包装类:RuleWrapper 用于封装规则信息并动态创建规则实例。
规则组和规则集:RuleGroup 和 RuleSet 用于组织规则的逻辑关系(AND 和 OR)。
规则上下文:PositionRuleContext 是规则引擎的统一入口。
规则工厂:PositionRuleFactory 用于动态创建规则实例。
员工类:Employee 包含员工的基本信息,用于规则匹配。

核心代码

以下是脱敏后的核心代码
InternalPositionRule 岗位规则 接口 最顶层抽象,制定模版方法

* +-----------------------------------+
 * |  InternalPositionRule (接口)       |
 * |-----------------------------------|
 * |  + execute(Employee emp): boolean |
 * +-----------------------------------+
 *                 ^
 *                 |
 *                 |
 * +-----------------------------------+
 * |  AbstractPositionRule<T> (抽象类)  |
 * |-----------------------------------|
 * |  + compare(String, T, T): boolean |
 * |-----------------------------------|
 * |  - ruleValue: String              |
 * |  - forceFlag: String              |
 * |  - compareSymbol: String          |
 * +-----------------------------------+
 *                 ^
 *                 |
 *                 |
 * +-----------------------------------+
 * |  AgeRule                           |
 * |-----------------------------------|
 * |  + execute(Employee emp): boolean |
 * +-----------------------------------+
 **/
public interface InternalPositionRule {

    /**
     * 执行岗位条件规则
     *
     * @param emp 员工信息
     * @return true/false
     */
    boolean execute(Employee emp);

AbstractPositionRule 岗位规则 抽象类:业务上再扩展规则,继承该类即可

@Slf4j
@Component
public abstract class AbstractPositionRule<T extends Comparable<? super T>> implements InternalPositionRule {

    /**
     * 执行岗位条件规则
     *
     * @param emp 员工信息
     * @return
     */
    public abstract boolean execute(Employee emp);

    /**
     * 比较方法
     *
     * @param compareSymbol  比较符号
     * @param actualValue    实际值
     * @param thresholdValue 设定阈值
     * @return
     */
    public boolean compare(String compareSymbol, T actualValue, T thresholdValue) {
        boolean result;
        log.info("===比较开始,比较符号【{}】,配置阈值【{}】,实际值【{}】===", compareSymbol, thresholdValue, actualValue);
        //等于
        if (SymbolConstant.EQ.equalsIgnoreCase(compareSymbol)) {
            result = CompareUtil.equals(actualValue, thresholdValue);
            //大于
        } else if (SymbolConstant.GT.equalsIgnoreCase(compareSymbol)) {
            result = CompareUtil.gt(actualValue, thresholdValue);
            //小于
        } else if (SymbolConstant.LT.equalsIgnoreCase(compareSymbol)) {
            result = CompareUtil.lt(actualValue, thresholdValue);
            //大于等于
        } else if (SymbolConstant.GE.equalsIgnoreCase(compareSymbol)) {
            result = CompareUtil.ge(actualValue, thresholdValue);
            //小于等于
        } else if (SymbolConstant.LE.equalsIgnoreCase(compareSymbol)) {
            result = CompareUtil.le(actualValue, thresholdValue);
        } else {
            log.error(StrUtil.join("当前类型:", compareSymbol, InternalErrorCode.TYPE_UNSUPPORTED_ERROR.getMsg()));
            throw new ServiceException(InternalErrorCode.TYPE_UNSUPPORTED_ERROR);
        }
        log.info("==比较结束,结果【{}】===", result);
        return result;
    }

    /**
     * TODO 产品给出告警文案?怎么触达用户?是否持久化落库?
     * 告警逻辑:要求如果规则配置了告警,即使匹配失败,也允许通过,并返回告警文案
     *
     * @param compareResult 实际匹配结果
     * @param forceFlag     禁止或告警
     * @param ruleTypeEnum  规则类型
     * @return
     */
    public Optional<String> warn(Boolean compareResult, String forceFlag, RuleTypeEnum ruleTypeEnum) {
        if (BooleanUtil.isFalse(compareResult) && ForceTypeEnum.WARN.getType().equals(forceFlag)) {
            StringBuilder warnMsg = new StringBuilder().append("【").append(ruleTypeEnum.getName()).append("】").append("规则不符合;");
            log.warn("告警文案:::【{}】", warnMsg);
            return Optional.of(warnMsg.toString());
        }
        return Optional.empty();
    }
}

PositionRuleContext 内部招聘-职位规则上下文,执行规则统一入口

@Slf4j
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PositionRuleContext {

    /**
     * 规则集
     */
    private RuleSet ruleSet;

    /**
     * 执行规则(执行规则的统一入口)
     *
     * @param emp
     * @return
     */
    public boolean executeRule(Employee emp) {
        if (Objects.isNull(ruleSet)) {
            log.info("规则集是空,不走规则引擎");
            return true;
        }
        return ruleSet.execute(emp);
    }
}

RuleSet 规则集,须知:1个招聘职位对应1个规则集,db存json结构,
1个规则集里含n个规则组,规则组之间是OR关系,每个规则组内含n个规则条件,各个规则条件是AND关系

@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RuleSet {

    /**
     * 规则组
     */
    private List<RuleGroup> ruleGroup;

    public boolean execute(Employee emp) {
        return ruleGroup.stream().anyMatch(condition -> condition.execute(emp));
    }
}

RuleGroup 规则组

@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RuleSet {

    /**
     * 规则组
     */
    private List<RuleGroup> ruleGroup;

    public boolean execute(Employee emp) {
        return ruleGroup.stream().anyMatch(condition -> condition.execute(emp));
    }
}

RuleWrapper 规则Wrapper类 用于封装规则的配置信息

@Slf4j
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class RuleWrapper extends AbstractPositionRule {

    /**
     * 规则类型
     *
     * @see com.suning.ebrs.module.internal.enums.RuleTypeEnum
     */
    private String ruleType;

    /**
     * 比较符号
     */
    private String compareSymbol;


    //强制条件(1是,0不是)
    private String forceFlag;

    /**
     * 规则值
     */
    private String ruleValue;

    @Override
    public boolean execute(Employee emp) {
        AbstractPositionRule rule = PositionRuleFactory.getRule(ruleType, ruleValue, compareSymbol, forceFlag);
        return rule.execute(emp);
    }
    
}

AgeRule 年龄规则

@Slf4j
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class AgeRule extends AbstractPositionRule<Integer> {

    /**
     * 规则值(设置阈值)
     */
    private String ruleValue;

    /**
     * 强制条件(1是,0不是)
     * 不强制,也能过通过规则,并有告警提示;
     * 强制,返回实际通过结果
     */
    private String forceFlag;

    /**
     * 比较符号
     *
     * @see com.suning.ebrs.module.internal.constant.SymbolConstant
     */
    private String compareSymbol;

    @Override
    public boolean execute(Employee emp) {
        log.info("===【{}】开始===", this.getClass().getSimpleName());
        Integer thresholdValue = Integer.valueOf(ruleValue);
        //TODO 查SAP,根据员工id查员工实际年龄
        Integer actualValue = emp.getAge();
        boolean compareResult = compare(compareSymbol, actualValue, thresholdValue);
        Optional<String> opt = warn(compareResult, forceFlag, RuleTypeEnum.AGE_RULE);
        if (opt.isPresent()) {
            emp.setWarnMsg(StringUtils.isNotBlank(emp.getWarnMsg()) ? emp.getWarnMsg() + opt.get() : opt.get());
            compareResult = true;
        }
        log.info("===【{}】结束,结果【{}】===", this.getClass().getSimpleName(), compareResult);
        return compareResult;
    }
}

PostLevelRule 级别规则

@Slf4j
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class PostLevelRule extends AbstractPositionRule<Integer> {

    //属性值(设置阈值)
    private String ruleValue;

    //强制条件(1是,0不是)
    private String forceFlag;

    //比较符号(支持:等于,大于,小于,大于等于,小于等于)
    private String compareSymbol;

    @Override
    public boolean execute(Employee emp) {
        log.info("===【{}】开始===", this.getClass().getSimpleName());
        Integer thresholdValue = Integer.valueOf(ruleValue);
        //TODO 根据员工id查员工实际级别
        Integer actualValue = emp.getLevel();
        boolean compareResult = compare(compareSymbol, actualValue, thresholdValue);
        Optional<String> opt = warn(compareResult, forceFlag, RuleTypeEnum.AGE_RULE);
        if (opt.isPresent()) {
            emp.setWarnMsg(StringUtils.isNotBlank(emp.getWarnMsg()) ? emp.getWarnMsg() + opt.get() : opt.get());
            compareResult = true;
        }
        log.info("===【{}】结束,结果【{}】===", this.getClass().getSimpleName(), compareResult);
        return compareResult;
    }
}

Employee 员工类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private String name; //张三

    private Integer level; //6,7,8

    private Integer sex; //1男,0女

    private Integer age; //36

    private Integer edu; //4本科 5研究生 6博士

    private Integer topSchool; //重点高校:1是;0不是

    private Integer political; //政治面貌 3群众,4预备党员,5党员

    private List<String> certificates; //系统架构师证书,高级软考证书

    //执行规则后的告警信息
    private String warnMsg;

}

PositionRuleContext 职位规则上下文,执行规则统一入口

@Slf4j
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PositionRuleContext {

    /**
     * 规则集
     */
    private RuleSet ruleSet;

    /**
     * 执行规则(执行规则的统一入口)
     *
     * @param emp
     * @return
     */
    public boolean executeRule(Employee emp) {
        if (Objects.isNull(ruleSet)) {
            log.info("规则集是空,不走规则引擎");
            return true;
        }
        return ruleSet.execute(emp);
    }
}

PositionRuleFactory 职位规则工厂

@Slf4j
@Component
public class PositionRuleFactory {

    private static final ConcurrentHashMap<String, AbstractPositionRule> ruleCache = new ConcurrentHashMap<>(16, 1);

    /**
     * 获取规则
     *
     * @param ruleType
     * @param ruleValue
     * @param compareSymbol
     * @param forceFlag
     * @return
     */
    public static AbstractPositionRule getRule(String ruleType, String ruleValue, String compareSymbol, String forceFlag) {
        String key = ruleType + "_" + ruleValue + "_" + compareSymbol + "_" + forceFlag;
        return ruleCache.computeIfAbsent(key, k -> createRule(ruleType, ruleValue, compareSymbol, forceFlag));
    }


    /**
     * 创建规则
     *
     * @param ruleType
     * @param ruleValue
     * @param compareSymbol
     * @param forceFlag
     * @return
     */
    public static AbstractPositionRule createRule(String ruleType, String ruleValue, String compareSymbol, String forceFlag) {
        AbstractPositionRule rule;
        //年龄规则
        if (RuleTypeEnum.AGE_RULE.getType().equalsIgnoreCase(ruleType)) {
            rule = AgeRule.builder().ruleValue(ruleValue).forceFlag(forceFlag).compareSymbol(compareSymbol).build();
            //出生日期
        } else if (RuleTypeEnum.DATE_OF_BIRTH_RULE.getType().equalsIgnoreCase(ruleType)) {
            rule = DateOfBirthRule.builder().ruleValue(ruleValue).forceFlag(forceFlag).compareSymbol(compareSymbol).build();
            //参加工作日期
        } else if (RuleTypeEnum.DATE_OF_EMPLOYMENT_RULE.getType().equalsIgnoreCase(ruleType)) {
            rule = EmployeeSubgroupRule.builder().ruleValue(ruleValue).forceFlag(forceFlag).compareSymbol(compareSymbol).build();
            //学历形式(在职/全日制)
        } else if (RuleTypeEnum.EDU_BACKGROUND_FORM_RULE.getType().equalsIgnoreCase(ruleType)) {
            rule = EduBackgroundFormRule.builder().ruleValue(ruleValue).forceFlag(forceFlag).compareSymbol(compareSymbol).build();
            //性别规则
        } else if (RuleTypeEnum.SEX_RULE.getType().equalsIgnoreCase(ruleType)) {
            rule = SexRule.builder().ruleValue(ruleValue).forceFlag(forceFlag).compareSymbol(compareSymbol).build();
        } else {
            log.error("【{}】参数非法!", ruleType);
            throw new ServiceException(InternalErrorCode.PARAM_ERROR);
        }
        return rule;
    }
}

InternalRuleServiceImplTest 规则的单元测试类

@Slf4j
@SpringBootTest
@ActiveProfiles("local")
public class InternalRuleServiceImplTest {

    @Resource
    private InternalPositionRuleService ruleService;

    @Test
    public void matchTest() {
        //岗位要求符合:本科+35岁以下 或 本科+P7以上
        //规则组1: 本科+35岁以下
        RuleGroup ruleGroup1 = this.mockRuleGroup1();
        //规则组2:本科+P7以上
        RuleGroup ruleGroup2 = this.mockRuleGroup2();
        List<RuleGroup> ruleGroups = Arrays.asList(ruleGroup1, ruleGroup2);
        RuleSet ruleSet = new RuleSet();
        ruleSet.setRuleGroup(ruleGroups);
        Employee emp = getEmployeeByEmpCode("123456");
        System.err.println("规则匹配,单测,入参 规则信息=====>" + JSON.toJSONString(ruleSet));
        System.err.println("规则匹配,单测,入参 员工信息=====>" + JSON.toJSONString(emp));
        boolean result = PositionRuleContext.builder().ruleSet(ruleSet).build().executeRule(emp);
        System.err.println("规则匹配,单测,匹配结果=====>" + result);
        System.err.println("规则匹配,单测,告警提示=====>" + emp.getWarnMsg());
    }

    private RuleGroup mockRuleGroup1() {
        RuleGroup ruleGroup = new RuleGroup();
        List<RuleWrapper> wrappers = new ArrayList<>();
        RuleWrapper wrapper1 = new RuleWrapper();
        wrapper1.setForceFlag("0");
        wrapper1.setRuleValue("35");
        wrapper1.setRuleType(RuleTypeEnum.AGE_RULE.getType());
        wrapper1.setCompareSymbol(SymbolConstant.LE);
        wrappers.add(wrapper1);

        RuleWrapper wrapper2 = new RuleWrapper();
        wrapper2.setForceFlag("1");
        wrapper2.setRuleValue("本科");
        wrapper2.setRuleType(RuleTypeEnum.EDU_BACKGROUND_RULE.getType());
        wrapper2.setCompareSymbol(SymbolConstant.GE);
        wrappers.add(wrapper2);

        ruleGroup.setRules(wrappers);
        return ruleGroup;
    }

    private RuleGroup mockRuleGroup2() {
        RuleGroup ruleGroup = new RuleGroup();
        List<RuleWrapper> wrappers = new ArrayList<>();

        RuleWrapper wrapper1 = new RuleWrapper();
        wrapper1.setForceFlag("1");
        wrapper1.setRuleValue("本科");
        wrapper1.setRuleType(RuleTypeEnum.EDU_BACKGROUND_RULE.getType());
        wrapper1.setCompareSymbol(SymbolConstant.GE);
        wrappers.add(wrapper1);

        RuleWrapper wrapper2 = new RuleWrapper();
        wrapper2.setForceFlag("1");
        wrapper2.setRuleValue("7");
        wrapper2.setRuleType(RuleTypeEnum.POST_LEVEL_RULE.getType());
        wrapper2.setCompareSymbol(SymbolConstant.GE);
        wrappers.add(wrapper2);

        ruleGroup.setRules(wrappers);
        return ruleGroup;
    }


    /**
     * mock员工数据
     *
     * @param employeeCode
     * @return
     */
    private Employee getEmployeeByEmpCode(String employeeCode) {
        List<String> list = Arrays.asList("系统架构师证书");
        return Employee.builder()
                .name("张三")
                //党员
                .political(5)
                //不是重点院校
                .topSchool(0)
                //本科
                .edu(5)
                //40岁
                .age(39)
                //男
                .sex(1)
                //系统架构师证书
                .certificates(list)
                //岗位6级
                .level(8)
                .build();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值