在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图。

  因为一旦规则有所改变,那么就需要修改代码再发布代码,而在日常的开发中唯一不变的就是变化,修改规则是很常见的。

  规则引擎的作用就是将决策逻辑从业务逻辑中抽离出来,使得两者可以独立于彼此,便于集中管理,减少硬编码的成本和风险,在不重启服务的情况下快速响应需求的变化。

  规则本质上就是一个函数,包括n个输入(决策因子),一个输出(结果)和一段计算规则三部分。

decision = rule(factor1, factor2, …, factorn)

  计算规则包含LHS(Left Hand Side,条件分支逻辑)和RHS(Right Hand Side,执行逻辑)。换句话说就是如果XXX(规则),那么XXX(动作)。

  LHS比较容易实现,就是判断条件的组合,包括常规表达式(算数运算、关系运算等),简单规则(数组索引等),业务定制规则(观看直播时间等)。

  而RHS就比较复杂了,场景众多,可以是简单的算数运,也可以是单表查询,甚至是几张表的数据聚合,并且它的抽象程度直接会左右规则引擎的受众人员。

  如果设计的规则引擎是给产品或运营,那么就不能加入过多的编程概念,给他们用的应该是比较傻瓜的那种。

  如果是给开发人员用的,那么可以设计的更加自定义,并且还能添加编程语句进来。

  增加了编程性,就降低了可用性;增加了可用性,就降低了扩展性。在权衡后,决定先封装已经出现的执行逻辑,做成可配置的。

  例如有个活动规则,如果观看30分钟,那么赠送3天会员,其中30和3就是可配置的参数。这样就能保持一定程度的可扩展。

一、界面

  与产品沟通后,让她给出些规则,在看到她的文档后,大大超出我的预期。

  她先分成了两个角色:主播和观众,然后根据这两种角色来设计动作,例如观众 - 观看直播 - 时长满XX。

  

  她还给出了统计粒度,分天、周、月和自定义,这也是我之前的盲点,奖励形式就是会员和兑换币。

  经过她的拆解后,我界面的设计也明朗了。顺带便,将之前打榜活动的规则也移植到该配置系统中。

  

  在此界面中,规则和奖励都是可以多条的,运算符就是大于、小于、等于等。

  规则关系就是与和或,由于不想实现太复杂,所以就降低了操作友好度,得手写关系。满足这层关系后,才会发放奖励或执行结果。

二、Node.js

  核心逻辑就是运行规则,发放奖励,这些配置信息都存储在MongoDB中。

  首先根据名称找到这条配置,然后先解析统计粒度,按日、周、月或自定义,时间库采用了moment.js

getInterval(type, start, end) {
const date = {};
switch (type) {
case 1: //每日
date.start = moment().startOf("day");
date.end = moment().endOf("day");
break;
case 2: //每周
date.start = moment().startOf("isoWeek");
date.end = moment().endOf("isoWeek");
break;
case 3: //每月
date.start = moment().startOf("month");
date.end = moment().endOf("month");
break;
default:
//自定义
date.start = moment(start);
date.end = moment(end);
break;
}
return {
start: date.start.format("YYYY-MM-DD HH:mm:ss"),
end: date.end.format("YYYY-MM-DD HH:mm:ss")
};
}

  然后是遍历规则,每条规则会对应不同的方法,未来扩展就是扩展这些规则方法,得到的结果再由运算符计算。

caculate(left, operator, right) {
const hash = {
lt: left < right,
lte: left <= right,
gt: left > right,
gte: left >= right,
equal: left == right,
notEqual: left != right,
allEqual: left === right,
notAllEqual: left !== right
};
return hash[operator];
}

  接着将规则关系中的数字替换成那几个运算结果,得到嘴周的规则结果。

let expression;     //规则表达式结果,可能是布尔值,也可能是其他类型的值
if (!row.relation) {
expression = operators[1];
} else {
// 将匹配的数字替换成规则结果值
expression = row.relation.replace(
/(\d+)/g,
function (match, p1, index, input) {
return operators[match];
}
);
expression = eval(expression);   //执行字符串代码
}

  最后发放奖励,方法中包含Switch分支,未来就是完成这些分支中的逻辑。

async giveRewards(params, type, value, project) {
switch (type) {
case "vip": //会员
break;
case "gold": //兑换币
break;
case "letter": //站内信
break;
}
}

  完整的执行规则的逻辑如下所示。

async runRule({ name, params }) {
const row = await this.models.WebRule.findOne({ name });
if (!row) return false;
const { project } = row;   //项目类型
const date = this.getInterval(row.statis_type, row.rule_start, row.rule_end);
const operators = {};     //运算符
// 遍历规则
for (let i = 0; i < row.rules.length; i++) {
const rule = row.rules[i];
// 得到方法值
const result = await this[rule.role[1]](params, date, project, rule.value);
// 计算规则值
operators[i + 1] = this.caculate(result, rule.operator, rule.value);
}
let expression;     //规则表达式结果,可能是布尔值,也可能是其他类型的值
if (!row.relation) {
expression = operators[1];
} else {
// 将匹配的数字替换成规则结果值
expression = row.relation.replace(
/(\d+)/g,
function (match, p1, index, input) {
return operators[match];
}
);
expression = eval(expression);   //执行字符串代码
}
if (!expression) {
return false;
}
// 发放奖励
for (const data of row.awards) {
await this.giveRewards(params, data.award[2], data.value, project);
}
return expression;
}

参考资料:

从0到1:构建强大且易用的规则引擎

手把手搭建业务规则引擎 Rule Engine

规则引擎基础知识

URule Pro

规则引擎

从产品角度看物联网平台的规则引擎

复杂风控场景下,如何打造一款高效的规则引擎

动手撸一个规则引擎(二):方案解析

Node.js躬行记(15)——活动规则引擎的更多相关文章

  1. Node.js躬行记(16)——活动配置化

    一直想将一些常规活动抽象化,制作成可配置的.原先的计划是做成拖拽的,那种可视化搭建,运营也能自己搭建页面. 这是一个美好的愿景,但是现实不允许我花太多精力去制作这样一个系统.经过权衡后,先设计成一个可 ...

  2. Node.js躬行记(4)——自建前端监控系统

    这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...

  3. Node.js躬行记(6)——自制短链系统

    短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉. 原始地址:https://github.com ...

  4. Node.js躬行记(14)——压力测试

    公司有个匿名聊天的常规H5界面,运营向做一次 50W 的推送,为了能配合她的计划,需要对该界面做一次压力测试. 一.JMeter 压测工具选择了JMeter,这是Apache的一个项目,它是用Java ...

  5. Node.js躬行记(23)——Worker threads

    Node.js 官方提供了 Cluster 和 Child process 创建子进程,通过 Worker threads 模块创建子线程.但前者无法共享内存,通信必须使用 JSON 格式,有一定的局 ...

  6. Node.js躬行记(1)——Buffer、流和EventEmitter

    一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...

  7. Node.js躬行记(2)——文件系统和网络

    一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...

  8. Node.js躬行记(19)——KOA源码分析(上)

    本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...

  9. Node.js躬行记(21)——花10分钟入门Node.js

    Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...

随机推荐

  1. 微擎框架中 uid、acid、uniacid 之间的关系

    首先,在创建应用的时候,会在表 uni_account 中插入一条应用数据,其中 default_acid = 0 ,返回值为该表的主键,作为 $uniacid . 然后,会在表 account 中插 ...

  2. html基础 表单相关属性综合实战案例 附有注释

    html结构代码: <form > <!-- form 为了让重置按钮生效--> <h1>青春不常在,抓紧谈恋爱</h1> <hr> 昵称: ...

  3. MySQL存储过程入门基础

    创建存储过程无参语法: delimiter // create procedure 函数名() begin 业务逻辑 end // call 函数名() 通过函数名调用存储过程 创建存储过程有参与法: ...

  4. 安装Apache-storm-0.9.1-incubating图解教程

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6596214331988247054/ 安装步骤 (1) 安装Zookeeper集群,可以参考前一篇文章,本文已安装 ...

  5. linux服务器之间传输文件的四种方式

    linux文件传输在内网渗透中至关重要,所以我在此总结一下几种Linux服务器之间传输文件的四种方式 1. scp [优点]简单方便,安全可靠:支持限速参数[缺点]不支持排除目录[用法]scp就是se ...

  6. 【Java】简单了解网络编程

    文章目录 网络编程 网络编程中有两个主要的问题 网络编程中的两个要素 通信要素一:IP和端口号 实例化InetAddress 两个常用方法 端口号 通信要素二:网络通信协议 实现TCP的网络编程 例子 ...

  7. 【刷题-LeetCode】207. Course Schedule

    Course Schedule There are a total of numCourses courses you have to take, labeled from 0 to numCours ...

  8. pytest文档1-环境搭建

    1.安装方法 pip install -U pytest 2.查看安装版本 pip show pytest pytest -version 3.快速开始 新建test开头py文件 打开test_sam ...

  9. Ansible + shell 实现部署fastdfs+nginx 实现图片服务器并提供动态缩放功能;

    因为公司阿里服务器变动几次,手动部署了好几次fastdfs+nginx,于是就想到了自动化部署,以下为脚本内容,由于只是想把着功能实现,并未有完完整的判断逻辑: 以下为ansible-playbook ...

  10. AOP-基本概念

    AOP(概念) 1,什么是AOP (1)面向切面(方面)编程 :利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. (2)通 ...