前言
使用规则引擎可以很方便的实现一些比力复杂的业务逻辑。
本文介绍的浅易版,是一个小的通用代码结构。
通过组装业务数据,创建执行模版,最终执行,获取到最终结果。
各人在复杂场景下,也可以选用Drools来实现。
Drools 是一个开源的业务规则管理系统,它提供了基于Java的规则引擎,用于实现复杂的变乱处理和业务逻辑。Drools 支持声明式编程,允许开发人员将业务逻辑从应用程序代码中分离出来,这使得业务逻辑的管理和变动更加机动和便捷。
正文
一、代码结构
二、焦点代码
maven依赖文件:
- <properties>
- <java.version>17</java.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <maven.compiler.source>17</maven.compiler.source>
- <maven.compiler.target>17</maven.compiler.target>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- </dependencies>
复制代码 2.1 上下文数据接口 ContextData.java
- package com.song.tools.ruleengine.core;
- /**
- * 上下文数据
- *
- * @author song tools
- */
- public interface ContextData {
- /**
- * 校验上下文数据
- */
- default void validProperty() {
- }
- }
复制代码 2.2 规则接口 Rule.java
- package com.song.tools.ruleengine.core;
- /**
- * 规则接口
- *
- * @author song tools
- */
- public interface Rule<ContextData extends com.song.tools.ruleengine.core.ContextData> {
- /**
- * 执行前
- *
- * @param contextData 上下文数据
- */
- default void before(ContextData contextData) {
- contextData.validProperty();
- }
- /**
- * 执行
- *
- * @param context 上下文
- */
- void execute(RuleEngineContext<ContextData> context);
- /**
- * 执行后
- *
- * @param contextData 上下文数据
- */
- default void after(ContextData contextData) {
- }
- }
复制代码 2.3 规则引擎接口 RuleEngine.java
- package com.song.tools.ruleengine.core;
- /**
- * 规则引擎
- *
- * @author song tools
- */
- public interface RuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> {
- /**
- * 注册规则
- *
- * @param ruleKey 规则key
- * @param rule 规则
- */
- void registerRule(String ruleKey, Rule<ContextData> rule);
- /**
- * 执行规则
- *
- * @param context 上下文
- */
- void executeRule(RuleEngineContext<ContextData> context) throws Exception;
- /**
- * 规则数量
- *
- * @return 规则数量
- */
- int ruleSize();
- }
复制代码 2.4 规则引擎上下文类 RuleEngineContext.java
- package com.song.tools.ruleengine.core;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- /**
- * 规则引擎上下文
- *
- * @author song tools
- */
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class RuleEngineContext<ContextData extends com.song.tools.ruleengine.core.ContextData> {
- /**
- * 上下文数据
- */
- private ContextData contextData;
- }
复制代码 2.5 规则引擎默认实现类 DefaultRuleEngine.java
- package com.song.tools.ruleengine.core;
- import java.util.LinkedHashMap;
- import java.util.Map;
- /**
- * 默认实现的规则引擎
- *
- * @author song tools
- */
- public class DefaultRuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> implements RuleEngine<ContextData> {
- private final Map<String, Rule<ContextData>> RULE_MAP;
- public DefaultRuleEngine() {
- RULE_MAP = new LinkedHashMap<>(16);
- }
- @Override
- public void registerRule(String ruleKey, Rule<ContextData> rule) {
- RULE_MAP.put(ruleKey, rule);
- }
- @Override
- public void executeRule(RuleEngineContext<ContextData> context) {
- for (Rule<ContextData> rule : RULE_MAP.values()) {
- rule.before(context.getContextData());
- rule.execute(context);
- rule.after(context.getContextData());
- }
- }
- @Override
- public int ruleSize() {
- return RULE_MAP.size();
- }
- }
复制代码 2.6 执行时出错监听器接口 ExecuteOnErrorListener.java
- package com.song.tools.ruleengine.core;
- /**
- * 执行时出错监听器
- *
- * @author song tools
- */
- public interface ExecuteOnErrorListener<ContextData extends com.song.tools.ruleengine.core.ContextData> {
- void onError(ContextData contextData, Exception e);
- }
复制代码 2.7 规则引擎执行模版类 RuleEngineExecuteTemplate.java
三、测试业务
假设现在必要机动的计算单个商品的代价折扣相关的内容。
3.1 新增折扣代价上下文实现类DiscountPriceContextData.java
- package com.song.tools.ruleengine;
- import com.song.tools.ruleengine.core.ContextData;
- import lombok.Data;
- import java.math.BigDecimal;
- import java.util.Objects;
- /**
- * 折扣价格上下文(对于单件商品,简单场景的计算)
- *
- * @author song tools
- */
- @Data
- public class DiscountPriceContextData implements ContextData {
- /**
- * 临时价格(存在多个计算规则时,使用它进行传递计算的中间值)
- */
- private BigDecimal calculatedTempPrice;
- /**
- * 原价
- */
- private BigDecimal originalPrice;
- /**
- * 折扣后价格
- */
- private BigDecimal discountedPrice;
- @Override
- public void validProperty() {
- Objects.requireNonNull(originalPrice, "原价不能为空");
- }
- }
复制代码 3.2 新增固定金额折扣规则实现类 FixedAmountDiscountRule.java
- package com.song.tools.ruleengine;
- import com.song.tools.ruleengine.core.Rule;
- import com.song.tools.ruleengine.core.RuleEngineContext;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- public class FixedAmountDiscountRule implements Rule<DiscountPriceContextData> {
- private final BigDecimal discountAmount;
- public FixedAmountDiscountRule(BigDecimal discountAmount) {
- this.discountAmount = discountAmount;
- }
- @Override
- public void execute(RuleEngineContext<DiscountPriceContextData> context) {
- DiscountPriceContextData contextData = context.getContextData();
- // 临时价格
- BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
- // 原始价格
- BigDecimal originalPrice = contextData.getOriginalPrice();
- if (calculatedTempPrice != null) {
- originalPrice = calculatedTempPrice;
- }
- // 进行精确减法运算,并确保结果不会小于0
- BigDecimal discountedPrice = (originalPrice.subtract(discountAmount).max(BigDecimal.ZERO)).setScale(2, RoundingMode.HALF_UP);
- // 设置折扣后的价格
- contextData.setDiscountedPrice(discountedPrice);
- contextData.setCalculatedTempPrice(discountedPrice);
- }
- @Override
- public void before(DiscountPriceContextData contextData) {
- Rule.super.before(contextData);
- }
- @Override
- public void after(DiscountPriceContextData contextData) {
- Rule.super.after(contextData);
- }
- }
复制代码 3.3 新增百分比折扣规则实现类 PercentageDiscountRule.java
- package com.song.tools.ruleengine;
- import com.song.tools.ruleengine.core.Rule;
- import com.song.tools.ruleengine.core.RuleEngineContext;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- public class PercentageDiscountRule implements Rule<DiscountPriceContextData> {
- private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
- private final BigDecimal discountPercentage;
- public PercentageDiscountRule(BigDecimal discountPercentage) {
- this.discountPercentage = discountPercentage;
- }
- @Override
- public void execute(RuleEngineContext<DiscountPriceContextData> context) {
- DiscountPriceContextData contextData = context.getContextData();
- // 临时价格
- BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
- // 原始价格
- BigDecimal originalPrice = contextData.getOriginalPrice();
- if (calculatedTempPrice != null) {
- originalPrice = calculatedTempPrice;
- }
- // 计算折扣率
- BigDecimal discountRate = discountPercentage.divide(ONE_HUNDRED, 2, RoundingMode.HALF_UP);
- // 计算折扣后的价格(四舍五入,保留2位小数)
- BigDecimal discountedPrice = originalPrice.multiply(discountRate).setScale(2, RoundingMode.HALF_UP);
- contextData.setDiscountedPrice(discountedPrice);
- contextData.setCalculatedTempPrice(discountedPrice);
- }
- @Override
- public void before(DiscountPriceContextData contextData) {
- Rule.super.before(contextData);
- }
- @Override
- public void after(DiscountPriceContextData contextData) {
- Rule.super.after(contextData);
- }
- }
复制代码 3.4 新增满减折扣规则实现类 FullReductionDiscountRule.java
- package com.song.tools.ruleengine;
- import com.song.tools.ruleengine.core.Rule;
- import com.song.tools.ruleengine.core.RuleEngineContext;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- /**
- * 满减折扣规则
- *
- * @author song tools
- */
- public class FullReductionDiscountRule implements Rule<DiscountPriceContextData> {
- /**
- * 满减金额,满xx金额数
- */
- private final BigDecimal fullReductionBoundAmount;
- /**
- * 满减折扣金额,减去xx金额数
- */
- private final BigDecimal fullReductionDiscountAmount;
- public FullReductionDiscountRule(BigDecimal fullReductionBoundAmount, BigDecimal fullReductionDiscountAmount) {
- this.fullReductionBoundAmount = fullReductionBoundAmount;
- this.fullReductionDiscountAmount = fullReductionDiscountAmount;
- }
- public void execute(RuleEngineContext<DiscountPriceContextData> context) {
- DiscountPriceContextData contextData = context.getContextData();
- // 临时价格
- BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
- // 原始价格
- BigDecimal originalPrice = contextData.getOriginalPrice();
- if (calculatedTempPrice != null) {
- originalPrice = calculatedTempPrice;
- }
- // 比较金额是否满足满减条件
- if (originalPrice.compareTo(fullReductionBoundAmount) >= 0) {
- BigDecimal discountedPrice = originalPrice.subtract(fullReductionDiscountAmount).setScale(2, RoundingMode.HALF_UP);
- contextData.setDiscountedPrice(discountedPrice);
- contextData.setCalculatedTempPrice(discountedPrice);
- }
- }
- }
复制代码 3.5 编写测试类 Test.java
编排差别的规则,不通的顺序,验证最终的执行结果。
- package com.song.tools.ruleengine;
- import com.song.tools.ruleengine.core.DefaultRuleEngine;
- import com.song.tools.ruleengine.core.RuleEngineContext;
- import com.song.tools.ruleengine.core.RuleEngineExecuteTemplate;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- /**
- * 测试规则执行
- *
- * @author song tools
- */
- public class Test {
- public static void main(String[] args) throws Exception {
- System.out.println("----------------test1----------------");
- test1();
- System.out.println("----------------test1----------------");
- System.out.println("----------------test2----------------");
- test2();
- System.out.println("----------------test2----------------");
- }
- private static void test1() {
- // 创建规则引擎
- DefaultRuleEngine<DiscountPriceContextData> ruleEngine = new DefaultRuleEngine<>();
- // 创建规则+注册规则
- ruleEngine.registerRule("percentageDiscountRule1", new PercentageDiscountRule(BigDecimal.valueOf(95)));
- ruleEngine.registerRule("fixedAmountDiscountRule1", new FixedAmountDiscountRule(BigDecimal.valueOf(12.34)));
- ruleEngine.registerRule("percentageDiscountRule2", new PercentageDiscountRule(BigDecimal.valueOf(80)));
- ruleEngine.registerRule("fixedAmountDiscountRule2", new FixedAmountDiscountRule(BigDecimal.valueOf(12.4)));
- // 创建上下文数据
- RuleEngineContext<DiscountPriceContextData> context = new RuleEngineContext<>();
- DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
- discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));
- context.setContextData(discountPriceContextData);
- // 执行规则引擎
- ruleEngine.executeRule(context);
- // 打印执行结果
- System.out.println(discountPriceContextData.getDiscountedPrice());
- System.out.println(BigDecimal.valueOf(100).multiply(BigDecimal.valueOf(0.95)).subtract(BigDecimal.valueOf(12.34)));
- System.out.println(BigDecimal.valueOf(82.66).multiply(BigDecimal.valueOf(0.8).setScale(2, RoundingMode.HALF_UP)).subtract(BigDecimal.valueOf(12.4)).setScale(2, RoundingMode.HALF_UP));
- }
- private static void test2() throws Exception {
- // 创建上下文数据
- DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
- discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));
- // 创建规则引擎执行模板
- RuleEngineExecuteTemplate<DiscountPriceContextData> ruleEngineExecuteTemplate = new RuleEngineExecuteTemplate<>();
- // 注册规则&执行规则
- ruleEngineExecuteTemplate.registerRule(new PercentageDiscountRule(BigDecimal.valueOf(95))) // 95折
- .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.34))) // 减12.34元
- .registerRule(new PercentageDiscountRule(BigDecimal.valueOf(80))) // 80折
- .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(50), BigDecimal.valueOf(10))) // 满50减10
- .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.4))) // 减12.4元
- .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(40), BigDecimal.valueOf(19.9))) // 满40减19.9元
- .execute(discountPriceContextData);
- // 打印最终结果
- System.out.println(discountPriceContextData.getDiscountedPrice());
- }
- }
复制代码 3.6 测试类运行结果
- ----------------test1----------------
- 53.73
- 82.66
- 53.73
- ----------------test1----------------
- ----------------test2----------------
- 23.83
- ----------------test2----------------
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |