开发者社区> 咖啡机(K.F.J)> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Node.js躬行记(15)——活动规则引擎

简介:   在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图。   因为一旦规则有所改变,那么就需要修改代码再发布代码,而在日常的开发中唯一不变的就是变化,修改规则是很常见的。   规则引擎的作用就是将决策逻辑从业务逻辑中抽离出来,使得两者可以独立于彼此,便于集中管理,减少硬编码的成本和风险,在不重启服务的情况下快速响应需求的变化。
+关注继续查看

  在日常的业务开发中,会包含许多的业务规则,一般就是用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。


1.png


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

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


2.png


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

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


二、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;
}



版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
浅析Easy Rules规则引擎
Easy Rules是一个简单而强大的Java规则引擎,提供以下功能: 轻量级框架和易于学习的API 基于POJO的开发与注解的编程模型 定义抽象的业务规则并轻松应用它们 支持从简单规则创建组合规则的能力 支持使用表达式语言(如MVEL和SpEL)定义规则的能力
0 0
Node.js躬行记(3)——命令行工具
  创建一个空目录,然后通过npm init命令初始化package.json文件,并按提示输入相关信息或直接回车使用默认信息,生成的内容如下所示。
0 0
Node.js躬行记(11)——E2E测试
  Cypress是为现代网络构建的前端测试工具,解决了开发人员和 QA 工程师在测试应用程序时面临的关键痛点。   在这个测试框架中包含了E2E测试、集成测试和单元测试(内嵌了Mocha),我们需要的是它的E2E测试的能力。   官网中包含详尽的API接口文档,以及多个视频教程、实例等,只要有耐心,看完文档,上手是不成问题的。   之所以要引入E2E测试,主要是为了保证主流程能够不出错,尤其是在后期做修修补补后,有一个可靠的方式告诉我们当前页面是正常的就行。
0 0
nvm版本控制以及node.js
nvm node.js版本控制工具        下载 nvm 包 地址:https://github.com/coreybutler/nvm-windows/releases  我们选择第一个:nvm-noinstall.
919 0
当Node.js遇见Docker
Node.js Best Practices - How to Become a Better Developer in 2017提到的几点,我们Fundebug深有同感: 使用ES6 使用Promise 使用LTS 使用Docker ... 想必大家都知道ES6,Promise以及LTS,那Docker是啥玩意啊?翻遍Node文档也没见踪迹啊! Paste_Image.png GitHub仓库: Fundebug/nodejs-docker 什么是Docker? Docker是最流行的的容器工具,没有之一。
1101 0
【java规则引擎】规则引擎RuleBase中利用观察者模式
(1)当RuleBase中有规则添加或删除,利用观察者模式实现,一旦有变动,规则引擎其他组件也做出相应的改变。(2)学习思想:当一个应用中涉及多个组件,为了实现易扩展,解耦思想。可以利用观察者模式实现。
671 0
Node.js Event-loop事件循环
node.js事件循环 Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。 Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。
1010 0
Node.js入门
一、Node.js简介  Node.js是一套用来编写高性能网络服务器的JavaScript工具包。初学者可能会误以为这是一个Javascript应用,事实上,Node.js采用C++语言编写而成,是一个Javascript的运行环境,这也是为称为.js的原因。
828 0
Node.js Web 开发框架大全《中间件篇》
  这篇文章与大家分享优秀的 Node.js 中间件模块。Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念。它的目标是帮助程序员构建高度可伸缩的应用程序,编写能够处理数万条同时连接到一个(只有一个)物理机的连接代码。
755 0
+关注
咖啡机(K.F.J)
每天进步一点点 研磨生活的香甜
文章
问答
文章排行榜
最热
最新
相关电子书
更多
沪江基于Node.js大规模应用实践
立即下载
基于 OpenResty 和 Node.js 的个推微服务实践
立即下载
大促背后的前端核心业务实践
立即下载