在日常的業務開發中,會包含許多的業務規則,一般就是用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](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")
};
}
~~~
  然后是遍歷規則,每條規則會對應不同的方法,未來擴展就是擴展這些規則方法,得到的結果再由運算符計算。
~~~
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:構建強大且易用的規則引擎](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),歡迎閱讀。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020