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

2023-12-16

一、背景介绍

商业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 , LEAPS Rete 算法是目前效率最高的一个演绎法推理算法,许多规则引擎都是基于 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部分称为 LHS left-hand-side ),then部分称为 RHS right hand side )。
    • 模式( module :就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。
3.3.2 Rete算法原理

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

  • 网络节点 Type Node Select Node Join Node Teminal Node Alpha Memory Beta Memory
  • alpha网络 :过滤 working memory ,找出符合规则中每一个模式的集合,生成 alpha memory (满足该模式的集合)。
  • Beta网络 :有两种类型的节点 Beta Memory Join 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算法的优点
  • 可共享 node memory ,提高效率;
  • memory 存储中间结果,空间换时间,避免重复计算;
  • Rete 匹配速度与规则数目无直接关系,因为 fact 只有满足本节点才会继续向下沿网络传递。
3.3.5 Rete算法的缺点

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

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

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

3.4 规则引擎优势

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

3.5 规则引擎使用场景

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

3.6 规则引擎的分类

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

  • 基于实现方式

  • 基于完成度和功能配置

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

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

通过各方面综合评估,重点放到了 Drools easyRule 两者,且 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 表达式语言支持

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

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. 对于集群中没有直接引用的代码,不要直接清理,有可能是在规则文件里有引用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Java 类查找工具

    任何经常使用 Java 的人都知道类路径中的多个 jar 会给我们带来多少麻烦 我正在寻找一个可以帮助我的工具 在 jar 中搜索类文件类路径 在 jar 中搜索类文件具体位置 也许要求太多 但请告诉我同一个类是否存在于多个罐子中 不用说它
  • C# 与 JAVA 接口实例

    我不知道该如何回答我的问题 它是关于Android可以实例化接口的 我正在尝试用 C 来做 现在我非常确定 Java 和 C 的规则是不能创建抽象和接口的实例 但我很想知道Android是如何做到这一点的 在 Android 中你可以这样做
  • string.split("(?!^)") 解释

    我正在尝试将字符串的字符拆分为字符串数组 我找到了解决方案here https stackoverflow com questions 5235401 split string into array of character strings
  • Google App Engine 数据存储写入:如何远程启用/禁用只读模式?

    在阅读备份时GAE 的数据存储 https developers google com appengine docs adminconsole datastoreadmin where 我们强烈建议您在备份或恢复期间将应用程序设置为只读模式
  • 我需要在 Java 9 中使用哪个模块才能使用 JPA?

    我正在使用一个需要 JPA 的项目测试 Java 9 javax persistence 类 当我添加module info java并声明我的模块 下的所有类javax persistece包变得不可用 我搜索了很多 但找不到在 Java
  • Java 7u51/7u55 带星号的清单变量

    我正在部署一个小程序 其中包含清单中的下一个变量 Manifest Version 2 0 Ant Version Apache Ant 1 8 2 Trusted Library true Permissions all permissi
  • 参考接口创建对象

    引用变量可以声明为类类型或接口类型 如果变量声明为接口类型 则它可以引用实现该接口的任何类的任何对象 根据上面的说法我做了一个理解上的代码 正如上面所说声明为接口类型 它可以引用实现该接口的任何类的任何对象 但在我的代码中显示display
  • grails 中的 log4j:如何登录文件?

    我的 grails config groovy 中有这个 log4j 配置 log4j error org codehaus groovy grails web servlet controllers org codehaus groovy
  • 加密 mongodb 中的密码字段

    我有以下代码 它插入userName and password进入数据库 但密码以纯文本格式存储 我的意思是 当我查看数据库时 我可以看到插入的密码 我想存储password in encrypted format MongoClient
  • 检查更新时 Maven 无限期挂起

    我正在使用 Maven 构建一个项目 我是新手 并且它挂起 mvn package INFO Scanning for projects INFO INFO Building Presentation Reports INFO task s
  • 如何在 PuTTY 中保存并运行 Java 文件?

    我是 AWS 亚马逊网络服务 的新手 所以这可能是一个基本问题 我在 AWS 上创建了一个 EC2 实例 我有一台 Windows 计算机 因此我使用 PUTTY 来连接 Linux 实例 连接到我的 EC2 实例后 我使用以下命令编写 J
  • 无法删除临时文件夹(有时)

    当我启动应用程序时 我创建一个临时文件夹 public static File createTempDir String name throws IOException File tempDir File createTempFile na
  • 在实现接口的类上强制使用单例模式

    我最好用一个例子来解释这个问题 我有一个接口模型可用于访问数据 模型可以有不同的实现 可以以各种格式表示数据 例如 XMl txt 格式等 Model不关心格式 可以说这样的一个实现是myxml模型 现在我想强迫myxml模型以及其他所有实
  • 为什么 CompletableFuture 的 thenAccept() 不在主线程上运行

    我在 CompletableFuture 的 SupplyAsync 中处理长时间运行的操作 并将结果放入 thenAccept 中 有时 thenAccept 在主线程上执行 但有时它在工作线程上运行 但我只想在主线程上运行 thenAc
  • AWS SQS Batch SendMessageBatchRequest 非常慢

    我的应用程序使用 SendMessageBatchRequest 将每个请求发布 10 条消息到 AWS SQS 每条消息的大小小于250字节 该应用程序预计每天发布约一百万条记录 但要实现这一目标 消息发布的速度非常慢 AmazonSQS
  • Java 中的连接路径

    In Python我可以连接两条路径os path join os path join foo bar gt foo bar 我正在尝试在 Java 中实现相同的目标 而不用担心是否OS is Unix Solaris or Windows
  • Java 执行器和长寿命线程

    我继承了一些使用 Executors newFixedThreadPool 4 的代码运行 4 个长寿命线程来完成应用程序的所有工作 这是推荐的吗 我读过Java 并发实践 https rads stackoverflow com amzn
  • 在 Spark MLlib 上使用 Java 中的 Breeze

    在尝试从Java使用MLlib时 使用微风矩阵运算的正确方法是什么 例如scala 中的乘法很简单 matrix vector 相应的功能在Java中是如何表达的 有一些方法 例如 colon times 可以通过正确的方式调用 breez
  • com.fasterxml.jackson.databind.JsonMappingException:无法反序列化 org.springframework.data.domain.Sort 的实例 START_ARRAY 令牌

    我的 objectMapper 正在获取类型的值Page
  • 为什么我们不能在函数式接口中重载抽象方法? (爪哇)

    所以我熟悉java中的函数式接口 以及它们与lambda表达式的使用 一个函数式接口只能包含一个抽象方法 当从 lambda 表达式使用这一孤独方法时 您不需要指定其名称 因为接口中只有一个抽象方法 编译器知道这就是您正在引用的方法 Exa

随机推荐

  • SpringBoot+线程池实现高频调用http接口并多线程解析json数据

    场景 Springboot FastJson实现解析第三方http接口json数据为实体类 时间格式化转换 字段包含中文 Springboot FastJson实现解析第三方http接口json数据为实体类 时间格式化转换 字段包含中文 C
  • 学籍服务平台省内转学批量自动申请

    学籍服务平台是指用于管理学生学籍信息的在线平台 包括学生的基本信息 学习成绩 奖惩记录等 在学籍服务平台上 学生可以进行选课 申请转学等操作 然而 目前的学籍服务平台存在一些问题 繁琐的操作流程 目前的学籍服务平台上 学生申请转学需要填写大
  • 用RPA轻松实现课程自动通知

    在教育领域 课程通知是一项重要的工作 但通常需要教师手动发送通知 记录学生反馈等繁琐的操作 这不仅耗费教师大量时间和精力 还容易出现遗漏或错误 为了提高效率和减轻教师的工作负担 可以使用八爪鱼rpa实现课程自动通知 八爪鱼rpa是一款强大的
  • mitm抓包实践---可用于投票、日常类任务运用

    文章目录 一 安装mitm 二 证书导入 三 抓包 三 后话补充 一 安装mitm 第一种方式 官网下载 https mitmproxy org downloads 第二种方式 py库安装 pip install mitmproxy 我是第
  • SVM原理理解

    目录 概念推导 共识 距离两个点集距离最大的分类直线的泛化能力更好 更能适应复杂数据 怎么能让margin最大 最大化margin公式 求解最大margin值 拉格朗日乘子法 为什么公式中出现求和符号 SVM模型 求解拉格朗日乘子 如何求解
  • 技术面试,如何谈薪资?

    众所周知 程序员是一个很容易出现薪资倒挂的职业 工作 3年比工作 5年薪资高的例子比比皆是 在 你手上有 offer吗 文章中 我们分析了如何巧妙地谈 offer 今天我们一起来分析如何谈薪资 顺利实现薪资倒挂 守住底线 不管是主动换工作还
  • 留给兼容安卓时间不多了!华为原生鸿蒙系统越来越近:跟iOS、安卓一样独立

    前言 据国内媒体报道称 余承东已经明确表态 华为明年将会推出鸿蒙原生应用与原生体验 HarmonyOS NEXT的产品 现在的情况就是 鸿蒙留给兼容安卓生态的时间越来越少了 而在之前已经有不少App厂商转入到他们的生态 并已经在开发相关的A
  • Docker仓库加密认证

    一 强制使用非加密访问仓库 insecure registry 实验环境 准备第二台虚拟机并配置docker服务及开启等 并把文件拷贝到第二台 记得配置好两台虚拟机仓库名的解析 配置步骤 1 配置文件使用非加密端口 vim etc dock
  • 鸿蒙开发入门:快速修复

    快速修复概述 快速修复是HarmonyOS系统提供给开发者的一种技术手段 支持开发者以远快于应用升级的方式对应用程序包进行缺陷修复 和全量应用升级软件版本相比 快速修复的主要优势在小 快和用户体验好 在较短的时间内不中断正在运行的应用的情况
  • Android神兵利器之协程和Lifecycle

    导语 一个安卓开发究竟要经历怎样的颠沛流离才终于能遇见Jetpack 遇见协程和Lifecycle 在Jetpack出现以前安卓应用架构这一块可以说非常混乱 早期没有官方架构组件 小公司可能都是mvc一把梭或者引入了简易版的mvp模式 而大
  • go-zero开发入门之网关往rpc服务传递数据1

    go zero 的网关往 rpc 服务传递数据时 可以使用 headers 但需要注意前缀规则 否则会发现数据传递不过去 或者对方取不到数据 go zero 的网关对服务的调用使用了第三方库 grpcurl 入口函数为 InvokeRPC
  • 30天精通Nodejs--第十三天:MySQL2

    目录 引言 MySQL2简介 使用说明 安装 连接到数据库 连接池 新增 查询 修改 删除
  • 鸿蒙开发入门:应用配置文件概述(一)

    应用配置文件概述 Stage模型 每个应用项目必须在项目的代码目录下加入配置文件 这些配置文件会向编译工具 操作系统和应用市场提供应用的基本信息 在基于Stage模型开发的应用项目代码下 都存在一个app json5及一个或多个module
  • 设置bat工作目录

    在执行bat脚本的时候 如果直接双击bat脚本 此时的工作路径一般为 C Users Administrator gt 很多时候需要将工作路径设置为bat脚本所在的目录 可以在bat脚本内设置当前工作路径为bat文件所在目录 cd d dp
  • 检查网络连通性的几种方法

    检查网络连通性的几种方法 检查网络连通性是确保计算机或设备能够与其他设备或互联网通信的重要步骤 以下是一些用于检查网络连通性的方法 Ping命令 在命令提示符 Windows 或终端 Linux macOS 中 使用ping命令 例如 在W
  • Java面试八股文及答案整理( 2023年 12月最新版,持续更新)

    一 Java 基础 1 JDK 和 JRE 有什么区别 JDK Java Development Kit 的简称 java 开发工具包 提供了 java 的开发环境和运行环境 JRE Java Runtime Environment 的简称
  • 深入理解 Android Activity 启动模式

    在 Android 应用开发中 Activity 是用户界面的核心组件 而 Activity 的启动模式则是决定应用界面如何在任务栈中交互 管理以及呈现的关键因素 正确的启动模式选择能够优化用户体验 提高应用性能 并确保应用在各种情景下都能
  • 各大厂为什么要适配鸿蒙?鸿蒙到底值不值得学

    各大厂为什么要适配鸿蒙 今天在脉脉上看到这个问题 有好多人在下面回复说什么不适配就不爱国之类的话 但是我们仔细想想 这些大厂资本家真的会被这些 不爱国 的舆论影响吗 这些大厂的公关花钱分分钟就能把舆论导向指向其他地方 不然为什么网上那么多黑
  • Centeos安装mysql

    安装mysql 检查MariaDB 因为这个会和MySQL有冲突 所以先检查一下是否有安装 查看mariadb rpm qa grep mariadb 卸载mariadb mariadb libs 5 5 44 2 el7 centos x
  • 规则引擎与商业CRM的完美邂逅:将智能决策融入商业扩展

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