ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
&emsp;&emsp;在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图。 &emsp;&emsp;因为一旦规则有所改变,那么就需要修改代码再发布代码,而在日常的开发中唯一不变的就是变化,修改规则是很常见的。 &emsp;&emsp;规则引擎的作用就是将决策逻辑从业务逻辑中抽离出来,使得两者可以独立于彼此,便于集中管理,减少硬编码的成本和风险,在不重启服务的情况下快速响应需求的变化。 &emsp;&emsp;规则本质上就是一个函数,包括n个输入(决策因子),一个输出(结果)和一段计算规则三部分。 ~~~ decision = rule(factor1, factor2, …, factorn) ~~~ &emsp;&emsp;计算规则包含LHS(Left Hand Side,条件分支逻辑)和RHS(Right Hand Side,执行逻辑)。换句话说就是如果XXX(规则),那么XXX(动作)。 &emsp;&emsp;LHS比较容易实现,就是判断条件的组合,包括常规表达式(算数运算、关系运算等),简单规则(数组索引等),业务定制规则(观看直播时间等)。 &emsp;&emsp;而RHS就比较复杂了,场景众多,可以是简单的算数运,也可以是单表查询,甚至是几张表的数据聚合,并且它的抽象程度直接会左右规则引擎的受众人员。 &emsp;&emsp;如果设计的规则引擎是给产品或运营,那么就不能加入过多的编程概念,给他们用的应该是比较傻瓜的那种。 &emsp;&emsp;如果是给开发人员用的,那么可以设计的更加自定义,并且还能添加编程语句进来。 &emsp;&emsp;增加了编程性,就降低了可用性;增加了可用性,就降低了扩展性。在权衡后,决定先封装已经出现的执行逻辑,做成可配置的。 &emsp;&emsp;例如有个活动规则,如果观看30分钟,那么赠送3天会员,其中30和3就是可配置的参数。这样就能保持一定程度的可扩展。 ## 一、界面 &emsp;&emsp;与产品沟通后,让她给出些规则,在看到她的文档后,大大超出我的预期。 &emsp;&emsp;她先分成了两个角色:主播和观众,然后根据这两种角色来设计动作,例如观众 - 观看直播 - 时长满XX。 :-: ![](https://img.kancloud.cn/39/4e/394ec91448441879e1eb1aeaf7f8ce97_506x484.png =300x) &emsp;&emsp;她还给出了统计粒度,分天、周、月和自定义,这也是我之前的盲点,奖励形式就是会员和兑换币。 &emsp;&emsp;经过她的拆解后,我界面的设计也明朗了。顺带便,将之前打榜活动的规则也移植到该配置系统中。 :-: ![](https://img.kancloud.cn/98/c0/98c0c2fdbcfb3a55200ccf82baae33c1_1612x1224.png =800x) &emsp;&emsp;在此界面中,规则和奖励都是可以多条的,运算符就是大于、小于、等于等。 &emsp;&emsp;规则关系就是与和或,由于不想实现太复杂,所以就降低了操作友好度,得手写关系。满足这层关系后,才会发放奖励或执行结果。 ## 二、Node.js &emsp;&emsp;核心逻辑就是运行规则,发放奖励,这些配置信息都存储在MongoDB中。 &emsp;&emsp;首先根据名称找到这条配置,然后先解析统计粒度,按日、周、月或自定义,时间库采用了[moment.js](https://momentjs.com/)。 ~~~ 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") }; } ~~~ &emsp;&emsp;然后是遍历规则,每条规则会对应不同的方法,未来扩展就是扩展这些规则方法,得到的结果再由运算符计算。 ~~~ 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]; } ~~~ &emsp;&emsp;接着将规则关系中的数字替换成那几个运算结果,得到嘴周的规则结果。 ~~~ 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);   //执行字符串代码 } ~~~ &emsp;&emsp;最后发放奖励,方法中包含Switch分支,未来就是完成这些分支中的逻辑。 ~~~ async giveRewards(params, type, value, project) { switch (type) { case "vip": //会员 break; case "gold": //兑换币 break; case "letter": //站内信 break; } } ~~~ &emsp;&emsp;完整的执行规则的逻辑如下所示。 ~~~ 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:构建强大且易用的规则引擎](https://tech.meituan.com/2017/06/09/maze-framework.html) [手把手搭建业务规则引擎 Rule Engine](http://www.waylon.one/monthly-skills/rule-engine/) [规则引擎基础知识](https://ld246.com/article/1627548055112) [URule Pro](http://www.bstek.com/resources/doc/4.0/) [规则引擎](https://juejin.cn/post/6989066814332354567) [从产品角度看物联网平台的规则引擎](http://www.woshipm.com/pd/4237660.html) [复杂风控场景下,如何打造一款高效的规则引擎](https://tech.meituan.com/2020/05/14/meituan-security-zeus.html) [动手撸一个规则引擎(二):方案解析](https://cloud.tencent.com/developer/article/1507807) ***** > 原文出处: [博客园-Node.js躬行记](https://www.cnblogs.com/strick/category/1688575.html) [知乎专栏-Node.js躬行记](https://zhuanlan.zhihu.com/pwnode) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。