商业CRM系统的商机模块业务复杂、场景繁多、规则调整频繁,商机流转效率一定程度决定了销售开单的效率。如何高效配合产品侧完成业务规则调整,商机流转经历了硬编码到半配置化的优化升级,过程中遇到了一些问题,也总结了一些经验,今天来和大家掰开揉碎了讲一讲这其中遇到的问题和解决方案。
先看一下CRM的官方定义:
CRM(Customer Relationship Management):客户关系管理,是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理效率,向客户提供创新式的个性化的客户交互和服务的过程。
其最终目标是:吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场。
可以这么简单的理解,CRM的核心价值就是:将潜在客户更高效的转化为客户,将客户更高效的转化为长期客户。
图片
介绍完什么是CRM,那CRM系统就很容易理解了,用于支撑企业进行客户关系管理的系统,就可以称作CRM系统。这个定义比较宽泛,每个公司对CRM中功能边界也不完全一样,大家初步理解这个概念就行。比如转转商业CRM系统,主要包含:商机管理、客户管理、销售/运营人员管理、业绩管理、效率监控等功能模块。
要想把潜在客户变成客户,就需要销售人员进行跟进,对潜在客户进行产品的售卖,用户购买产品后,变成客户。这个潜在客户,我们称作“商机”,也就是一次成单的机会,有的CRM系统中,也称为“线索”。
上图是潜在用户到客户简单的流转图
从商机生成到最终成单,销售跟进过程中,涉及一些概念,比如商机池(公海池、私海池)、商机状态/来源/类型/等级、商机的流转等。
商机池:各种商机的集合。公海池:该集合里的商机当前不归属于任何销售,所有销售均可以看到公海里的商机。私海池:绑定了特定销售的商机集合,比如销售A的商机私海池,只有销售A可以看到和跟进,其他销售不可见、不可跟进。商机流转:商机在跟进过程中,被不同的销售跟进,状态发生不同的变化。流转规则:流转过程中会有各种各样的业务规则限制,比如销售最多可以认领100条商机、负责手机类目的销售不能认领电脑类目的商机、销售刚放弃的商机不能立马重新认领、单位时间内不进行电话沟通的商机将流出销售私海等等。
这里举一个例子来说明商机的流转,业务背景:商机对应用户主营类目为手机,销售A、B负责手机类目,销售C负责电脑类目,销售D也负责手机类目。
图片
这是一个简单的流程,实际流程比这个复杂。从这个简易的流程介绍中,可以窥见部分商机流转模块的业务特点,总结起来有三点:
图片
图片
图片
之前商业CRM中关于流转的处理逻辑,好多都是硬编码,举个销售认领某条商机的例子:
//状态校验if(checkClueStatus(param)){ return “状态不合法”;}//绑定人校验if(checkClueBindUser(param)){ return “上一个绑定人不可以为···”;}//私海容量校验if(checkPrivateClueCount(param)){ return “私海库已满,无法操作··”;}//类目校验if(checkClueCate(param)){ return “类目不匹配,无法操作··”;}//任务是否完成校验if(checkClueTaskFinished(param)){ return “任务未完成,无法操作··”;} ······bind(param);//绑定操作log();//日志记录操作
从代码中可以看出,销售从公海进行认领一条自己觉得有价值的商机时,并不是直接就让该商机流入到该销售私海中,这个过程会有各种规则的业务校验。
带着我们的痛点和诉求,接下来看看可以找到什么样的解决方案。然后我们进入了调研阶段,发现对于这种策略比较密集的业务,规则引擎是一个可参考方向,然后就开始了解规则引擎相关细节,了解完后,发现和业务问题匹配度很高。接下来,先介绍一下规则引擎相关的基本知识。
规则就是:条件 --> 推理 --> 结果。一个完整的规则是需要这三部分元素的,平时大家讨论规则,某些时候只是在讨论第一部分的条件,比如学校的行为规范,咱们国家的各种法律条文,可能关注的部分是需要满足什么条件(不能做什么什么),后面的推理和结果省去了。比如学生准则里的不辱骂同学,这是一个条件,那怎么算辱骂呢,这是推理过程,辱骂后会发生什么呢,会受到老师批评,这是结果。
那什么是推理引擎和规则引擎呢?
图片
推理引擎通过决定哪些规则满足事实或目标,并授予规则优先级,满足事实或目标的规则被加入议程,具体过程:
推理方式分为正向推理和反向推理
常见的模式匹配算法有Rete,LFA,TREAI,LEAPS。Rete算法是目前效率最高的一个演绎法推理算法,许多规则引擎都是基于Rete算法来进行推理计算的。
图片
这是Rete算法的原理图,Rete算法涉及两种网络和6种不同作用的节点。
图片
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)。
图片
上面介绍的这些都偏理论化,可能有一点不太容易理解,没关系,接下来咱们用一个具体的例子,看看Rete算法到底是如何运行的。咱们依然以这个选拔篮球苗子的例子来说明。
图片
图片
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对象匹配了该规则,形成一个议程,加入到冲突区,执行该条件的结果部分:该学生是一个篮球苗子。
规则的条件与facts的数目如果过大,对应memory会指数级升高,极端情况下会耗尽系统资源。
看完这个匹配的过程,相信大家对规则引擎中常用的Rete算法有了一定的了解。回到规则引擎,那为啥用规则引擎呢,也就是它有何优势,以及有哪些适用的场景呢?
一般多用于规则较多且规则调整频繁的业务场景,比如:积分规则、计费系统、信用风险评估、监控告警、工作流系统。
网上有两种分类方式,这里我列举出来,供大家了解。
图片
了解了上面这些业务背景以及遇到的问题,也熟悉了规则引擎的理论知识,现在需要制定具体的解决方案了,我们怎么做的呢?市面有各种各样的规则引擎,先进行技术选型,这里列举下当前主流规则引擎优缺点。
图片
通过各方面综合评估,重点放到了Drools和easyRule两者,且easyRule最终胜出。
图片
确定了要使用easyRule就得知道easyRule如何使用的,先介绍下其相关概念和使用方法。
Easy Rules提供了3种CompositeRule的实现。
值越低优先级越高。要覆盖此行为,可重写compareTo()方法以提供自定义优先级策略。
支持自定义规则监听器、规则引擎监听器。
支持MVEL、SPEL表达式语言,可通过编程方式定义规则。
还是用筛选篮球苗子的例子
图片
public class Student { /** * 年级 */ private Integer grade; /** * 性别 */ private String gender; /** * 年龄 */ private Integer age; /** * 是否强壮 */ private Boolean isStrong; /** * 身高 */ private Integer height; /** * 是否一个好苗子 */ private Boolean isGoodSeed = true;}
//创建规则1-年级Rule rule1 = new MVELRule() .name("grade rule") .description("判断一个学生是否是一个篮球好苗子-年级") .priority(1) .when("student.getGrade() <= 3") .then("System.out.println(/"年级-不是好苗子/");") .then("student.setIsGoodSeed(false);");
//创建规则2-性别Rule rule2 = new MVELRuleFactory(new YamlRuleDefinitionReader()). createRule(new FileReader( ResourceUtils.getFile("classpath:gender-rule.yml")));
规则2需要的yml文件内容如下:
name: "gender rule"description: "判断一个学生是否是一个篮球好苗子-性别"priority: 2condition: "student.getGender().equals(/"girl/")"actions: - "System.out.println(/"性别-不是好苗子/");student.setIsGoodSeed(false);"
//创建规则3-年龄 Rule rule3 = new MVELRuleFactory(new JsonRuleDefinitionReader()). createRule(new FileReader( ResourceUtils.getFile("classpath:age-rule.json")));
//创建规则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();
@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);
//创建一个Student实例(Fact) Student student = new Student(3,"girl",9,true, 160,true); Facts facts = new Facts(); facts.put("student", student);
//注册规则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());
图片
熟悉了easyRule如何使用的,接下来看看我们如何在项目中落地的,我们分了几步:
初始化规则相关配置(首次初始化+定时更新);
提供对外public T fire(String ruleId, V v)通用的规则引擎api接口。
图片
这里我们将规则引擎的处理结果进行了返回,因为业务上很多场景需要,比如不符合规则时的提醒文案。
项目中配置规则引擎相关配置;
实例化RuleEngineTemplate类;
根据场景,组装上下文context;
调用ruleEngineTemplate.fire(ruleId,context)方法。
图片
引入后,我们的商机流转流程发生了如下变化:
图片
图片
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
[ { "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)" ] } ] }]
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; } ); }
图片
在easyRule引入商机流转业务过程中,从调研到选型再到最终落地,遇到了各种大大小小的问题,但最终的效果还是比较明显的,对团队的整体效率提升非常明显,这里有几点总结与建议与大家分享。
杨迎,转转商业后端开发工程师,目前负责商业B端相关业务系统开发(商机线索、客户运营、销售运营管理、广告发布等)。
本文链接:http://www.28at.com/showinfo-26-16530-0.html规则引擎与商业CRM的完美邂逅:将智能决策融入商业扩展
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 在Lombok的加持下,“小狗”.Equals(“老狗”) = True
下一篇: 听说你会架构设计?来,弄一个微信群聊系统