优惠券使用规则引擎来计算优惠 (drools)

news/2024/11/20 9:28:57/

电商的促销花样越来越多,规则也也越来越复杂,因此,规则的频繁变更可能会带来频繁的版本开发上线,因此,业务希望能够快速上线,这就要求产品能够做到不修改代码快速上线。

平心而论,优惠券目前的几种玩法已经比较固定,通常都是通用券,折扣券,满减券,满赠券,即使不用规则引擎,大部分优惠券的设计都能够支撑业务侧的需求。对于业务侧的一些比较复杂的规则, 例如叠加规则,互斥规则,通常也是在优惠券可配置的一部分,在优惠券的价格计算中,已经实现了互斥,叠加。

其实,优惠券的计算逻辑非常复杂,尤其是可以使用多张优惠券的情况下,还要考虑不同级别的优惠券,在规则引擎中去实现,还是非常麻烦的。此外,由于 drools 的表达能力只能是 when-then 的方式, 并没有实现完整的编程语言的范式,因此,drools 脚本中很难实现复杂的业务逻辑。

我们还对 groovy 脚本进行了调研,在下一篇文章中,我们用 groovy 来实现优惠券的业务逻辑,在优惠券的场景中,能够做到比 drools 更加灵活。

规则引擎

规则引擎的核心包括两部分:

  1. 规则脚本;
  2. 规则脚本的编译,解释执行;

通常,规则脚本都是独立的语言实现,大部分规则引擎都是使用 java 的开源库 antlr 。antlr 是开源的语法解析器,规则脚本语法虽然简单,但也是一门独立的语言,因此,语法解析,词法解析是 必须要有的,此外,大部分规则引擎都可以做到和 JVM 相互调用,这部分的处理应该还是比较复杂的,有兴趣的可以研究下源代码。

drools 规则引擎

drools 规则引擎主要是应用于风控、反欺诈、智能营销、网点监控、智能核保、业务流自动化等场景中,核心是将业务的逻辑代码由 java 代码移到 drools 脚本,如果需要修改业务逻辑,只需要修改 drools 脚本,而不需要修改后台代码。

通常在实际中,我们把脚本保存在数据库中,大部分时候,不需要修改 drools 脚本。如果业务逻辑发生变化,可以通过修改 drools 脚本,然后 java 代码重新从数据库中 load 脚本,这样,就实现了

通过将业务逻辑代码与后台代码的分离,做到了可以随时修改

一个 drools 规则引擎的基本流程是:

rule rule001
when条件
then执行结果
end

drools 有几个重要的概念,分别是:

Facts

drools 中的 Facts,可以简单的理解为输入

Working memory

working memory 可以简单的理解为 drools 的运行环境

LHS RHS

LHS:条件部分,即 When

RHS:结果部分,即 then

与 java 的交互

drools 的强大之处在于,可以和 java 深度结合,引用 java 的代码,调用 java 的方法。规则在执行的过程中,经常需要与 java 交互,传递参数,判断条件,更新结果等。

KieServices, kieSession, KieContainer, KieFileSystem, KieModule

  • KieServices: kie 整体的入口,可以用来创建 Container,resource,fileSystem 等
  • KieContainer:KieContainer 就是一个 KieBase 的容器,通过 KieContainer 来获取具体的 KieSession
  • KieFileSystem:Kie 的虚拟文件系统,包括资源和组织结构,drools 脚本可以通过 KieFileSystem 来加载
  • KieModule:是一个包含了多个 kiebase 定义的容器。
  • KieRepository:是一个 KieModule 的仓库,包含了所有的 KieModule 描述,用一个 ReleaseId 做区分

概念很多,很难理解,我想这也是为什么很多人说 drools 很重的原因吧。

java 使用流程

其实,我们暂时不用关心这么多宏观的概念,先从 hello world 搞起。

通常,java 的使用流程是:

  1. 获取 kieSession;
  2. 将变量插入 kieSession 中
  3. 调用 kieSession.fireAllRules()

假设我们把 drools 脚本放在 resource/rules 目录下,获取 kieSession 的代码如下:

    private static Resource[] getRuleFiles() throws IOException {ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();return resourcePatternResolver.getResources("classpath*:rules/"  + "**/*.drl");}private static KieSession getSession() throws Exception {KieServices kieServices = KieServices.Factory.get();KieFileSystem kfs = kieServices.newKieFileSystem();for (Resource file : getRuleFiles()) {log.info("rule file: " + file.getFilename());try {kfs.write(ResourceFactory.newClassPathResource("rules/" + file.getFilename(), "UTF-8"));} catch (Exception e) {e.printStackTrace();}}KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();Results results = kieBuilder.getResults();if (results.hasMessages(Message.Level.ERROR)) {for (Message msg : results.getMessages()) {log.error("drools script error info : " + msg.getText());}throw new Exception("drools script error");}return kieServices.newKieContainer(KieServices.Factory.get().getRepository().getDefaultReleaseId()).newKieSession();}

执行 rule 的代码:

        KieSession kieSession = getSession();kieSession.insert(order);kieSession.insert(coupon);kieSession.insert(result);int hit = kieSession.fireAllRules(); // hit 是所有规则被命中的规则数

代码讲解

代码基于 spring boot,该项目仅为演示项目,因此,并未涉及数据库部分,在实际中可以细化这部分实现。

代码已开源:https://github.com/guotie/drools

核心流程

优惠券的核心在于计算价格的接口,也就是常说的询价接口。

因此,我们写了这么几个 drools 脚本:

  • 折扣类优惠计算脚本
  • 满减类优惠计算脚本
  • 满赠类优惠计算脚本
  • 支付类优惠计算脚本
  • 其他

例如,折扣类优惠计算脚本的内容如下:

package com.mall.coupon.drools.rules;// 折扣型import com.mall.coupon.drools.model.Coupon
import com.mall.coupon.drools.model.Order
import com.mall.coupon.drools.model.OrderItem
import com.mall.coupon.drools.model.EnquiryResultglobal com.mall.coupon.drools.service.CouponBatchService couponBatchService
global com.mall.coupon.drools.service.UserCouponService userCouponService
//global com.mall.coupon.drools.service.UserCouponService userCouponService
global com.mall.coupon.drools.service.UserService userService// 折扣类优惠券// order 对整个订单打折
rule "rule-discount-order"
when$result: EnquiryResult()$order: Order()$coupon: Coupon(couponType == "5" && subCouponType == "1" &&(minBuyAmount == 0 || $order.totalAmount >= minBuyAmount))
thenSystem.out.println("命中 discount-order");$result.setTotalDiscount($order.getTotalAmount() * $coupon.getNominal() / 100);
end// sku 对特定的sku商品打折
rule "rule-discount-sku"
when$item: OrderItem()$result: EnquiryResult()$coupon: Coupon(couponType == "5" && subCouponType == "2" &&(minBuyAmount == 0 || $item.totalAmount >= minBuyAmount) &&couponBatchService.skuUsable($coupon.getCouponBatchCode(), $item.getProductSkuId()))
thenSystem.out.println("coupon code: " + $coupon.getCouponBatchCode());System.out.println("couponBatchService: " + couponBatchService);System.out.println("命中 discount-sku");$result.setTotalDiscount($item.getTotalAmount() * (100 - $coupon.getNominal()) / 100);
end

总结

如果我们需要新增一种优惠券,那么我们只需要新增该类型优惠券的 drools 脚本,测试无误后,让后台代码重新加载即可,也就实现了业务规则的快速部署。


http://www.ppmy.cn/news/75718.html

相关文章

计网笔记 01 概述 计算机网络体系结构、参考模型

文章目录 前言1、计网概述1.1 概念、组成、功能、分类1.1.1 概念1.1.2 计网组成1.1.2 计网分类 1.2 标准化工作及相关组织1.2.1 标准的分类 1.3 性能指标★★★1.3.1 速率相关性能指标1.3.2 时延相关指标 2、体系结构&参考模型★★★★★(对应王道视频7-10p 相当…

Redis7实战加面试题-基础篇(Redis10大数据类型)

说明 刚看完尚硅谷Redis零基础到进阶,最强redis7教程,阳哥亲自带练(附redis面试题) 趁热打铁,根据脑图和视频,对视频中的知识点进行总结,方便以后复习 Redis入门概述 Remote Dictionary Serv…

不敢妄谈K12教育

做为大学生的父亲:不敢妄谈孩子教育 大约10年前,写了一本教育书稿 找到一个出版社的编辑,被训了一通 打消了出书以及K12教育的念想 趣讲大白话:娘生九子,各有不同 【趣讲信息科技171期】 ****************************…

《微服务实战》 第十四章 RabbitMQ应用

前言 一般MQ用于系统解耦、削峰使用,常见于微服务、业务活动等场景。 1、RabbitMQ概念概念 RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。 1.1、生产者和消费者 Producer:生产者,就是投递消息的一方。消息一般可以包含2个部分:消息体和标签…

深入了解vector

vector 1. vector的介绍及使用1.1 vector的介绍1.2 vector的使用1.2.1 vector的定义((constructor)构造函数声明)1.2.2 vector iterator 的使用1.2.3 vector Capacity1.2.4 vector Modifiers1.2.4 vector 迭代器失效问题 2. vector模拟实现 1. vector的介…

leetcode 884. 两句话中的不常见单词

文章目录 题目描述解题思路法1 执行结果法1 leetcode 884. 两句话中的不常见单词 题目描述 两句话中的不常见单词 句子 是一串由空格分隔的单词。每个 单词 仅由小写字母组成。 如果某个单词在其中一个句子中恰好出现一次,在另一个句子中却 没有出现 &#xff0c…

验证码发明人的天才故事

验证码发明人的天才故事 Luis von Ahn,卡内基梅隆大学计算机科学副教授,天才计算机科学家和企业家 01 调皮捣蛋、不循规蹈矩的青少年时代 1978年8月19日,Luis von Ahn出生在危地马拉首都危地马拉城的一个中产阶级家庭。 他是德国和危地马拉…

MybatisPlus--基础入门!真滴方便

目录 一、简介 2.特性 二、入门 1.创建springboot 项目(点击查看如何创建 ) 注意&#xff1a;引入 MyBatis-Plus 之后请不要再次引入 MyBatis 以及 MyBatis-Spring&#xff0c;以避免因版本差异导致的问题 2.数据准备 3.配置application.yml 4.代码 BaseMapper<>…