规则引擎与商业CRM的完美邂逅:将智能决策融入商业扩展

一、背景介绍

商业CRM系统的商机模块业务复杂、场景繁多、规则调整频繁,商机流转效率一定程度决定了销售开单的效率。如何高效配合产品侧完成业务规则调整,商机流转经历了硬编码到半配置化的优化升级,过程中遇到了一些问题,也总结了一些经验,今天来和大家掰开揉碎了讲一讲这其中遇到的问题和解决方案。

1.1 什么是CRM

先看一下CRM的官方定义: CRM (Customer Relationship Management):客户关系管理,是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理效率,向客户提供创新式的个性化的客户交互和服务的过程。

其最终目标是:吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场

可以这么简单的理解,CRM的核心价值就是:将潜在客户更高效的转化为客户,将客户更高效的转化为长期客户

介绍完什么是CRM,那CRM系统就很容易理解了,用于支撑企业进行客户关系管理的系统,就可以称作CRM系统。这个定义比较宽泛,每个公司对CRM中功能边界也不完全一样,大家初步理解这个概念就行。比如转转商业CRM系统,主要包含:商机管理、客户管理、销售/运营人员管理、业绩管理、效率监控等功能模块。

1.2 商机业务介绍

要想把潜在客户变成客户,就需要销售人员进行跟进,对潜在客户进行产品的售卖,用户购买产品后,变成客户。这个潜在客户,我们称作"商机",也就是一次成单的机会,有的CRM系统中,也称为"线索"。 上图是潜在用户到客户简单的流转图

从商机生成到最终成单,销售跟进过程中,涉及一些概念,比如商机池(公海池、私海池)、商机状态/来源/类型/等级、商机的流转等。

商机池 :各种商机的集合。
公海池 :该集合里的商机当前不归属于任何销售,所有销售均可以看到公海里的商机。
私海池 :绑定了特定销售的商机集合,比如销售A的商机私海池,只有销售A可以看到和跟进,其他销售不可见、不可跟进。
商机流转 :商机在跟进过程中,被不同的销售跟进,状态发生不同的变化。
流转规则:流转过程中会有各种各样的业务规则限制,比如销售最多可以认领100条商机、负责手机类目的销售不能认领电脑类目的商机、销售刚放弃的商机不能立马重新认领、单位时间内不进行电话沟通的商机将流出销售私海等等。

二、商机流转遇到的问题

2.1 商机流转业务特点

这里举一个例子来说明商机的流转,业务背景:商机对应用户主营类目为手机,销售A、B负责手机类目,销售C负责电脑类目,销售D也负责手机类目。 这是一个简单的流程,实际流程比这个复杂。从这个简易的流程介绍中,可以窥见部分商机流转模块的业务特点,总结起来有三点:

  • 状态的多样性

  • 状态间转换场景繁多

  • 流转规则复杂多变

2.2 商机流转业务痛点

之前商业CRM中关于流转的处理逻辑,好多都是硬编码,举个销售认领某条商机的例子:

java 复制代码
//状态校验
if(checkClueStatus(param)){
	return "状态不合法";
}
//绑定人校验
if(checkClueBindUser(param)){
	return "上一个绑定人不可以为···";
}
//私海容量校验
if(checkPrivateClueCount(param)){
	return "私海库已满,无法操作··";
}
//类目校验
if(checkClueCate(param)){
	return "类目不匹配,无法操作··";
}
//任务是否完成校验
if(checkClueTaskFinished(param)){
	return "任务未完成,无法操作··";
}  ······
bind(param);//绑定操作
log();//日志记录操作

从代码中可以看出,销售从公海进行认领一条自己觉得有价值的商机时,并不是直接就让该商机流入到该销售私海中,这个过程会有各种规则的业务校验。

2.2.1 痛点

  1. 硬编码实现的业务,维护成本大;
  2. 业务规则经常调整,难以应对变化,产研配合效率低;
  3. 业务规则调整需要配合修改代码,重新上线后生效。

2.2.2 诉求

  1. 规则抽取成可配置项,调整方便;
  2. 校验相关阈值,可随时动态调整,无需上线;
  3. 代码优雅度适当提高。

带着我们的痛点和诉求,接下来看看可以找到什么样的解决方案。然后我们进入了调研阶段,发现对于这种策略比较密集的业务,规则引擎是一个可参考方向,然后就开始了解规则引擎相关细节,了解完后,发现和业务问题匹配度很高。接下来,先介绍一下规则引擎相关的基本知识。

三、什么是规则引擎

3.1 什么是规则

规则就是:条件 --> 推理 --> 结果。一个完整的规则是需要这三部分元素的,平时大家讨论规则,某些时候只是在讨论第一部分的条件,比如学校的行为规范,咱们国家的各种法律条文,可能关注的部分是需要满足什么条件(不能做什么什么),后面的推理和结果省去了。比如学生准则里的不辱骂同学,这是一个条件,那怎么算辱骂呢,这是推理过程,辱骂后会发生什么呢,会受到老师批评,这是结果。

那什么是推理引擎和规则引擎呢?

  • 推理引擎:如果由程序来处理推理过程,那么这个程序就叫做推理机/推理引擎。
  • 规则引擎:基于规则的推理机易于理解、易于获取、易于管理,被广泛采用。这种推理引擎被称为"规则引擎"。是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。

3.2 推理引擎原理

  • 模式匹配器决定选择执行哪个规则,何时执行规则;
  • 议程管理模式匹配器挑选出来的规则的执行次序;
  • 执行引擎负责执行规则和其他动作。

推理引擎通过决定哪些规则满足事实或目标,并授予规则优先级,满足事实或目标的规则被加入议程,具体过程:

  1. 将初始数据(fact)输入至工作内存(Working Memory)。
  2. 使用Pattern Matcher将规则库(Rules repository)的规则(rule)和数据(fact)比较。
  3. 如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。
  4. 解决冲突,将激活的规则按顺序放入Agenda
  5. 执行Agenda中的规则。
  6. 重复步骤2至5,直到执行完毕Agenda中的所有规则。

推理方式分为正向推理和反向推理 ::: block-1

  • 正向推理(演绎法):事实驱动,正向推理,直到无事实可用或者得出结论为止;
  • 反向推理(归纳法) :目标驱动,提出假设,寻找支持该假设的证据,如果能找到证明,结论正确,如果不能,换假设继续。 ::: 常见的模式匹配算法有Rete,LFA,TREAI,LEAPSRete 算法是目前效率最高的一个演绎法推理算法,许多规则引擎都是基于Rete算法来进行推理计算的。

3.3 Rete算法

3.3.1 Rete算法简介

  1. Rete在拉丁语中是"net",有网络的意思。Rete算法由Carnegie Mellon University的Dr Charles L. Forgy设计发明,是一个用来实现产生式规则系统(production/inference)的高效模式匹配算法。
  2. RETE算法可以分为两部分:规则编译(rule compilation)和运行时执行(runtime execution)。
    • 规则编译:是指根据规则集生成推理网络的过程。
    • 运行时执行:指将数据送入推理网络进行筛选的过程。
  3. 相关概念:
    • 事实(fact:对象之间及对象属性之间的关系
    • 规则(rule :是由条件和结论构成的推理语句,一般表示为if...Then。一个规则的if部分称为LHSleft-hand-side),then部分称为RHSright hand side)。
    • 模式(module:就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。

3.3.2 Rete算法原理

这是Rete算法的原理图,Rete算法涉及两种网络和6种不同作用的节点。

  • 网络节点Type NodeSelect NodeJoin NodeTeminal NodeAlpha MemoryBeta Memory
  • alpha网络 :过滤working memory,找出符合规则中每一个模式的集合,生成alpha memory(满足该模式的集合)。
  • Beta网络 :有两种类型的节点Beta MemoryJoin Node。前者主要存储Join完成后的集合。后者包含两个输入口,分别输入需要匹配的两个集合,由Join节点做合并工作传输给下一个节点。

3.3.2.1 Rete推理网络生成过程

  1. 创建root节点(根节点),推理网络的入口。
  2. 拿到规则1,从规则1中取出模式1(模式就是最小的原子条件,所以规则模式的关系是1:n)。
    • a) 检查模式1中的参数类型,如果是新类型,添加一个类型节点。
    • b) 检查模式1对应的Alpha节点是否存在,如果存在记录下节点的位置;如果没有,将模式1作为一个Alpha节点加入到网络中。同时根据Alpha节点建立Alpah内存表。
    • c) 重复b,直到处理完所有模式。
    • d) 组合Beta节点:Beta(2)左输入节点为Alpha(1),右输入节点为Alpha(2);Beta(i)左输入节点是Beta(i-1),右输入节点为Alpha(i),并将两个父节点的内存表内联成为自己的内存表。
    • e) 重复d,直到所有Beta节点处理完毕。
    • f) 将动作Then部分封装成最后节点做为Beta(n)。
  3. 重复2,直到所有规则处理完毕。

3.3.2.2 Rete匹配过程

  1. 导入需要处理的事实到facts集合中。
  2. 如果facts不为空,选择一个fact进行处理。否则停止匹配过程。
  3. 选择alpha网的第一个节点运行(建立网络的时候设定的),通过该节点则进入alpha网的下一个节点,直到进入alpha memory。否则跳转到下一条判断路径。
  4. 将alpha memory的结果加入到beta memory中,如果不为Terminal节点,则检测另一个输入集合中是否存在满足条件的事实,满足则执行join,进入到下一个beta memory重复执行3。若另一个输入集合无满足条件的事实,返回到2。如果该节点为Terminal节点,执行ACT并添加到facts中。

3.3.3 Rete算法实例

上面介绍的这些都偏理论化,可能有一点不太容易理解,没关系,接下来咱们用一个具体的例子,看看Rete算法到底是如何运行的。咱们依然以这个选拔篮球苗子的例子来说明。

  1. 首先看下对应的网络图
  2. Rete匹配过程
  3. 匹配过程详解
    • A节点 :拿StudentFact的年级数值进行年级匹配,如果年级符合条件,则把该StudentFact的引用记录到A节点的alpha内存区中,退出年级匹配。
    • B节点 :拿StudentFact的性别内容进行性别匹配,如果性别符合条件,则把该StudentFact的引用记录到B节点的alpha内存区中,然后找到B节点左引用的Beta节点,也就是C节点。
    • C节点 :C节点找到自己的左引用也就是A节点,看看A节点的alpha内存区中是否存放了StudentFact的引用,如果存放,说明年级和性别两个条件都符合,则在C节点的Beta内存区中存放StudentFact的引用,退出性别匹配。
    • D节点 :拿StudentFact的年龄数值进行年龄条件匹配,如果年龄符合条件,则把该StudentFact的引用记录到D节点的alpha的内存区中,然后找到D节点的左引用的Beta节点,也就是E节点。
    • E节点 :E节点找到自己的左引用也就是C节点,看看C节点的Beta内存区中是否存放了StudentFact的引用,如果存放,说明年级,性别,年龄三个条件符合,则在E节点的Beta内存区中存放StudentFact的引用,退出年龄匹配。
    • F节点 :拿StudentFact的身体数值进行身体条件匹配,如果身体条件符合,则把该StudentFact的引用记录到D节点的alpha的内存区中,然后找到F节点的左引用的Beta节点,也就是G节点。
    • G节点 :G节点找到自己的左引用也就是E节点,看看E节点的Beta内存区中是否存放了StudentFact的引用,如果存放,说明年级,性别,年龄,身体四个条件符合,则在G节点的Beta内存区中存放StudentFact的引用,退出身体匹配。
    • H节点 :拿StudentFact的身高数值进行身高条件匹配,如果身高条件符合,则把该StudentFact的引用记录到H节点的alpha的内存区中,然后找到H节点的左引用的Beta节点,也就是I节点。
    • I节点 :I节点找到自己的左引用也就是G节点,看看G节点的Beta内存区中是否存放了StudentFact的引用,如果存放了,说明年级,性别,年龄,身体,身高五个条件都符合,则在I节点的Beta内存区中存放StudentFact引用。同时说明该StudentFact对象匹配了该规则,形成一个议程,加入到冲突区,执行该条件的结果部分:该学生是一个篮球苗子。

3.3.4 Rete算法的优点

  • 可共享nodememory,提高效率;
  • memory存储中间结果,空间换时间,避免重复计算;
  • Rete 匹配速度与规则数目无直接关系,因为fact只有满足本节点才会继续向下沿网络传递。

3.3.5 Rete算法的缺点

规则的条件与facts的数目如果过大,对应memory会指数级升高,极端情况下会耗尽系统资源。

3.3.6 Rete算法使用建议

  • 容易变化的规则尽量置后匹配,可以减少规则的变化带来规则库的变化。
  • 约束性较为通用或较强的模式尽量置前匹配,可以避免不必要的匹配。
  • 针对 Rete 算法内存开销大和facts增加删除影响效率的问题,技术上应该在 AlphaMemoryBetaMemory 中,只存储指向内存的指针,并对指针建里索引(可用 hash 表或者非平衡二叉树)。
  • Rete 算法 JoinNode 可以扩展为 AndJoinNodeOrJoinNode,两种节点可以再进行组合。

看完这个匹配的过程,相信大家对规则引擎中常用的Rete算法有了一定的了解。回到规则引擎,那为啥用规则引擎呢,也就是它有何优势,以及有哪些适用的场景呢?

3.4 规则引擎优势

  1. 高灵活性:规则保存在知识库中,规则变动可以轻易做出修改。
  2. 容易掌控:规则比过程代码更易于理解,因此可以有效地来弥补业务人员和开发人员之间的沟通问题。
  3. 降低复杂度:在程序中编写大量的判断条件,很可能是会造成一场噩梦。使用规则引擎却能够通过一致的表示形式,更好的处理日益复杂的业务逻辑。开发人员也可以更关注逻辑处理,而无需过多关注逻辑判断。
  4. 可重用性:规则集中管理,可提高业务的规则的可重用性。而且,传统的代码程序通常会添加不必要的变数,很难进行重复利用。
  5. 规则外部化:即有利于规则知识的复用,也可避免改变规则时带来的代码变更问题。

3.5 规则引擎使用场景

一般多用于规则较多且规则调整频繁的业务场景,比如:积分规则、计费系统、信用风险评估、监控告警、工作流系统。

3.6 规则引擎的分类

网上有两种分类方式,这里我列举出来,供大家了解。

  • 基于实现方式
  • 基于完成度和功能配置

四、商机流转问题解决方案

了解了上面这些业务背景以及遇到的问题,也熟悉了规则引擎的理论知识,现在需要制定具体的解决方案了,我们怎么做的呢?市面有各种各样的规则引擎,先进行技术选型,这里列举下当前主流规则引擎优缺点。 通过各方面综合评估,重点放到了DroolseasyRule两者,且easyRule最终胜出。

4.1 Drools和easyRule对比

确定了要使用easyRule就得知道easyRule如何使用的,先介绍下其相关概念和使用方法。

4.2 easyRule插件介绍

4.2.1 规则说明

  • name:规则命名空间中的唯一规则名称
  • description:规则的简要描述
  • priority:规则的优先级
  • facts:触发规则时的一组已知事实
  • conditions:在给定一些事实的情况下,为了应用该规则,需要满足的一组条件
  • actions:满足条件时要执行的一组操作(可能会添加/删除/修改事实)

4.2.2 规则定义方式

  • 通过在POJO上添加注解来声明
  • 通过RuleBuilder API编程

4.2.3 组合规则用法

Easy Rules提供了3种CompositeRule的实现。

  • UnitRuleGroup:要么应用所有规则,要么不应用任何规则。--要么全用要么不用
  • ActivationRuleGroup:激活规则组触发第一个适用规则并忽略组中的其他规则。--首个选用
  • ConditionalRuleGroup:条件规则组将具有最高优先级的规则作为条件,如果具有最高优先级的规则的计算结果为true,那么将触发其余的规则。--优先级最高说了算

4.2.4 自定义优先级

值越低优先级越高。要覆盖此行为,可重写compareTo()方法以提供自定义优先级策略。

4.2.5 引擎执行模式

  • skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。--一个成功,不管其他
  • skipOnFirstFailedRule:当一个规则失败时,跳过余下的规则。--一个失败,不管其他
  • skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则。--一个不符合,不管其他
  • rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。--优先级达标,不管其他

4.2.6 多种监听器可供选择

支持自定义规则监听器、规则引擎监听器。

4.2.7 表达式语言支持

支持MVELSPEL表达式语言,可通过编程方式定义规则。

4.2.8 规则中的错误处理

  • 对于条件求值过程中可能发生的任何运行时异常(丢失事实、表达式中输入错误等),引擎将记录一个警告,并认为条件求值为false。
  • 对于任何在执行操作时可能发生的运行时异常(丢失事实、表达式中输入错误等),该操作将不会执行,引擎将记录一个错误。

4.3 easyRule使用样例

还是用筛选篮球苗子的例子

  1. 定义一个学生实体类
java 复制代码
public class Student {
    /**
     * 年级
     */
    private Integer grade;
    /**
     * 性别
     */
    private String gender;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 是否强壮
     */
    private Boolean isStrong;
    /**
     * 身高
     */
    private Integer height;
    /**
     * 是否一个好苗子
     */
    private Boolean isGoodSeed = true;

}
  1. 定义规则(有多种方式,我列举几种)
java 复制代码
 //创建规则1-年级
Rule rule1 = new MVELRule()
        .name("grade rule")
        .description("判断一个学生是否是一个篮球好苗子-年级")
        .priority(1)
        .when("student.getGrade() <= 3")
        .then("System.out.println(\"年级-不是好苗子\");")
        .then("student.setIsGoodSeed(false);");
java 复制代码
//创建规则2-性别
Rule rule2 = new MVELRuleFactory(new YamlRuleDefinitionReader()).
        createRule(new FileReader(
                ResourceUtils.getFile("classpath:gender-rule.yml")));

规则2需要的yml文件内容如下:

yml 复制代码
name: "gender rule"
description: "判断一个学生是否是一个篮球好苗子-性别"
priority: 2
condition: "student.getGender().equals(\"girl\")"
actions:
  - "System.out.println(\"性别-不是好苗子\");student.setIsGoodSeed(false);"
java 复制代码
//创建规则3-年龄
  Rule rule3 = new MVELRuleFactory(new JsonRuleDefinitionReader()).
          createRule(new FileReader(
                  ResourceUtils.getFile("classpath:age-rule.json")));
java 复制代码
//创建规则4-是否强壮
Condition condition = new MVELCondition("!student.getIsStrong()");
Action action = new Action() {
    @Override
    public void execute(Facts facts) throws Exception {
        Student student1 = (Student) facts.get("student");
        student1.setIsGoodSeed(false);
        System.out.println("强壮-不是好苗子");
    }
};
Rule rule4 = new RuleBuilder()
        .name("strong rule")
        .description("判断一个学生是否是一个篮球好苗子-是否强壮")
        .priority(4)
        .when(condition)
        .then(action).build();
java 复制代码
@Rule(name = "height rule" ,description = "判断一个学生是否是一个篮球好苗子-身高")
public class HeightRule {
    @Condition
    public boolean checkHeight(){ return student.getHeight() <= 170;}

    @Action
    public void action(){
        System.out.println("身高-不是好苗子");
        student.setIsGoodSeed(false);
    }
    private Student student;
    public HeightRule(Student student){
        this.student = student;
    }
}
//创建规则5-身高
HeightRule rule5 = new HeightRule(student);
  1. 创建实例(fact)
java 复制代码
 //创建一个Student实例(Fact)
  Student student = new Student(3,"girl",9,true, 160,true);
  Facts facts = new Facts();
  facts.put("student", student);
  1. 创建引擎,并执行规则
java 复制代码
//注册规则
Rules rules = new Rules();
rules.register(rule1);
rules.register(rule2);
//rules.register(rule3);
rules.register(rule4);
rules.register(rule5);

//创建规则执行引擎,并执行规则
RulesEngine rulesEngine = new DefaultRulesEngine();
System.out.println("开始判断是否是一个篮球苗子:" + JSON.toJSONString(student));
rulesEngine.fire(rules, facts);
System.out.println("是否为好苗子:" + student.getIsGoodSeed());
  1. 执行结果

4.4 商机流转如何接入easyRule

熟悉了easyRule如何使用的,接下来看看我们如何在项目中落地的,我们分了几步:

  1. easyRule工具包进行二次改装,使其执行规则后能有返回值,封装成jar包,将规则引擎抽取成通用能力。
    • 初始化规则相关配置(首次初始化+定时更新);
    • 提供对外public T fire(String ruleId, V v)通用的规则引擎api接口。 这里我们将规则引擎的处理结果进行了返回,因为业务上很多场景需要,比如不符合规则时的提醒文案。
  2. 将现有的流转规则进行整理提取,将各种判断条件拆解成单一的指标判断。
  3. 项目中引入easyRule工具。
    • 项目中配置规则引擎相关配置;
    • 实例化RuleEngineTemplate类;
    • 根据场景,组装上下文context
    • 调用ruleEngineTemplate.fire(ruleId,context)方法。

引入后,我们的商机流转流程发生了如下变化:

4.5 商机解绑流程举例

  1. 商机解绑流程
  2. 解绑对应的规则配置application.yml
yml 复制代码
spring:
  easy-rule:
    priority-threshold: 100
    skip-on-first-failed-rule: false
    skip-on-first-applied-rule: true
    skip-on-first-non-triggered-rule: false
    rules:
      - rule-id: "opportunity_unbind"
        rule-file-location: "opportunity_unbind" #规则配置文件
        rule-config-type: JSON
        rule-factory-type: SPEL

具体的规则配置json

json 复制代码
[
      {
        "name": "bind_check_cate",
        "description": "判断是否冻结-72小时",
        "condition": "@opportunityUnbindRuleBll.checkOpportunityNeedFreeze(#context.getOpportunityId(), n,m)",
        "priority": 4,
        "actions": [
          "@clueOpporBll.unbindOpportunity(#context,T(OpportunityStatusEnum).UNBIND, T(com.clue.enums.OpportunityMinorStatusEnum).UNBIND_FROZEN)"
        ]
      },
      {
        "name": "task_bind_out",
        "description": "任务商机流回公海",
        "condition": "#context.getOpportunityStatus() == T(com.enums.OpportunityStatusEnum).TASK && #context.getOperationTypeEnum() == T(com.OpportunityOperationTypeEnum).TASK_BACK_PUBLIC",
        "priority": 5,
        "actions": [
          "@clueBll.unbindOpportunity(#context,T(com.zhuanzhuan.biz.clue.enums.OpportunityStatusEnum).UNBIND, T(com.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
        ]
      },
      {
        "name": "unbind_operate",
        "description": "判断解绑后去向,现阶段全部回到公海",
        "condition": "true",
        "priority": 10,
        "actions": [
          "@clueOpportunityBll.unbindOpportunity(#context,T(com.OpportunityStatusEnum).UNBIND, T(com.enums.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
        ]
      }
    ]
  }
]
  1. 业务代码
java 复制代码
public Result<ParallelExecuteDTO> unbindOpportunity(UnbindOpportunityRequest request) {
  return parallelExecutor.parallelExecute(request.getOpportunityIds(), (Long opportunityId) -> {
            final Result<String> unbindResult = opportunityUnbindRuleBll.unbindOpportunity(opportunityId, request.getOperator(),
                  request.getReasonType(), request.getReasonDesc(), request.getOperationType());
            logger.info("method=unbindOpportunity, act=unbind, opportunityId={},unbindResult={}", opportunityId, unbindResult);
            return unbindResult;
            }
        );
    }

4.6 引入规则引擎前后效果对比

五、总结

easyRule引入商机流转业务过程中,从调研到选型再到最终落地,遇到了各种大大小小的问题,但最终的效果还是比较明显的,对团队的整体效率提升非常明显,这里有几点总结与建议与大家分享。

  1. 系统引入规则引擎,一定要场景符合,不能为了引入而引入;
  2. 业务规则转换为抽象的规则配置,可以多和业务人员交流,他们对于规则的理解可能更深刻;
  3. 选择规则引擎方案后,需要定好规则维护规范,后续执行按照规范维护;
  4. 对于集群中没有直接引用的代码,不要直接清理,有可能是在规则文件里有引用。

作者介绍

杨迎,转转商业后端开发工程师,目前负责商业B端相关业务系统开发(商机线索、客户运营、销售运营管理、广告发布等)。

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。`
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~`

相关推荐
李姆斯33 分钟前
复盘上瘾症:到底什么时候该“复盘”,什么时候不需要“复盘”
前端·后端·团队管理
javachen__44 分钟前
Spring Boot配置error日志发送至企业微信
spring boot·后端·企业微信
seabirdssss1 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续1 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0441 小时前
ReAct模式解读
java·ai
轮到我狗叫了2 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
OC溥哥9993 小时前
Flask论坛与个人中心页面开发教程完整详细版
后端·python·flask·html
Volunteer Technology3 小时前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~3 小时前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar
Mr.Entropy4 小时前
ecplise配置maven插件
java·maven