Spring Boot 3.4 与 Easy Rules 引擎结合,实现灵活的优惠券管理系统

188 阅读5分钟

Spring Boot 3.4 与 Easy Rules 引擎结合,实现灵活的优惠券管理系统

随着电商行业的不断发展,优惠券和促销活动已成为商家吸引用户、提升销量的重要手段。然而,随着业务需求的复杂化,传统的优惠券管理系统往往存在规则管理难、灵活性差、扩展性弱等问题。为了应对这些挑战,越来越多的企业开始寻求将业务规则与业务逻辑解耦的解决方案,以便快速适应市场变化,并提升系统的可维护性。
在本篇文章中,我们将展示如何结合 Spring Boot 3.4 和 Easy Rules 引擎,构建一个灵活且易于扩展的优惠券管理系统。Easy Rules 是一个轻量级的规则引擎,支持以声明式的方式定义业务规则,帮助我们动态地加载和执行优惠策略。通过与 Spring Boot 和 MyBatis 框架的结合,我们能够轻松实现数据库驱动的规则加载和执行,从而减少硬编码、提升系统灵活性和可扩展性。

添加必要的依赖

首先,确保在 pom.xml 中添加以下依赖:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis Framework -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.1</version>
    </dependency>

    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Easy Rules Core -->
    <dependency>
        <groupId>org.jeasy</groupId>
        <artifactId>easy-rules-core</artifactId>
        <version>4.4.0</version>
    </dependency>

    <!-- Easy Rules Spring Support -->
    <dependency>
        <groupId>org.jeasy</groupId>
        <artifactId>easy-rules-support-spring</artifactId>
        <version>4.4.0</version>
    </dependency>

    <!-- Lombok (Optional for reducing boilerplate code) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- Test Dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

配置 MyBatis 和数据库连接

在 application.properties 文件中配置数据库连接和 MyBatis 的相关设置:

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/coupon_system?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis 配置
mybatis.type-aliases-package=com.icoderoad.couponsystemdb.model
mybatis.mapper-locations=classpath*:mapper/*.xml

定义业务实体类

接下来,我们需要为购物车、商品和优惠券规则定义一些实体类:

ShoppingCart.java

package com.icoderoad.couponsystemdb.model;
import lombok.Data;import java.util.ArrayList;import java.util.List;
@Datapublic
class ShoppingCart {
    private double totalPrice = 0;
    private final List<Item> items = new ArrayList<>();
    public void addItem(Item item) { 
        items.add(item); 
        totalPrice += item.getPrice();   
    }
    public void applyDiscount(double discount) { 
       totalPrice -= totalPrice * discount;    
    }
}

Item.java

package com.icoderoad.couponsystemdb.model;
import lombok.Data;
@Datapublic class Item {    private String name;    private String category;    private double price;
    public Item(String name, String category, double price) {        this.name = name;        this.category = category;        this.price = price;    }}

CouponRule.java

package com.icoderoad.couponsystemdb.model;
import lombok.Data;
@Datapublic class CouponRule {    private Long id;    private String ruleName;    private String description;    private String conditionExpression;    private String actionExpression;}

创建数据库表和初始化数据

创建一个数据库 coupon_system 并在其中创建 coupon_rule 表,同时插入一些初始的优惠券规则数据:

CREATE  DATABASE  coupon_system;

USE  coupon_system;

CREATETABLE  coupon_rule  (
      id  BIGINTAUTO_INCREMENTPRIMARYKEY,
      rule_name  VARCHAR(255)NOTNULL,
      description  TEXT,
      condition_expression  TEXTNOTNULL,
      action_expression  TEXTNOTNULL
);

INSERTINTO  coupon_rule  (rule_name,  description,  condition_expression,  action_expression)VALUES
('Total Price Discount Rule','如果总价超过1000,给予10%的折扣','cart.getTotalPrice() > 1000','cart.applyDiscount(0.10); System.out.println("Applied 10% discount for total price exceeding 1000");'),
('Category Discount Rule','购买电子产品类别时,提供额外5%的折扣','cart.getItems().stream().anyMatch(item -> item.getCategory().equals("Electronics"))','cart.applyDiscount(0.05); System.out.println("Applied 5% discount for purchasing electronics");');

定义动态规则

使用 Easy Rules 提供的 DynamicRule 类从数据库加载规则并执行:
DynamicCouponRule.java

package  com.icoderoad.couponsystemdb.rule;
import  org.jeasy.rules.annotation.Action;import  org.jeasy.rules.annotation.Condition;import  org.jeasy.rules.annotation.Fact;import  org.jeasy.rules.api.Facts;import  org.jeasy.rules.core.DynamicRule;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.stereotype.Component;
@Componentpublic  class  DynamicCouponRule  extends  DynamicRule  {
       @Autowired       private  CouponService couponService;
       @Condition       public  boolean  evaluate(Facts facts)  throws  Exception {             return  super.evaluate(facts);      }
       @Action       public  void  execute(Facts facts)  throws  Exception {             super.execute(facts);      }
       public  void  setRuleFromDatabase(Long ruleId)  {             CouponRule  couponRule  =  couponService.getCouponRuleById(ruleId);            setName(couponRule.getRuleName());            setDescription(couponRule.getDescription());            setConditionExpression(couponRule.getConditionExpression());            setActionExpression(couponRule.getActionExpression());      }}

配置规则引擎

配置 Easy Rules 引擎,并将规则注册到 Spring 上下文中:
EasyRulesConfig.java

package  com.icoderoad.couponsystemdb.config;
import  org.jeasy.rules.api.RulesEngine;import  org.jeasy.rules.core.DefaultRulesEngine;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;
@Configurationpublic  class  EasyRulesConfig  {
       @Bean       public  RulesEngine rulesEngine() {             return  new DefaultRulesEngine();      }}

创建 Service 层

我们为数据库操作创建一个服务层:
CouponService.java

package  com.icoderoad.couponsystemdb.service;
import  com.icoderoad.couponsystemdb.mapper.CouponRuleMapper;import  com.icoderoad.couponsystemdb.model.CouponRule;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.stereotype.Service;
@Servicepublic  class  CouponService  {
       @Autowired       private  CouponRuleMapper couponRuleMapper;
       public  CouponRule getCouponRuleById(Long  id) {             return  couponRuleMapper.selectById(id);      }}

创建 Mapper 接口

使用 MyBatis 创建映射器接口:
CouponRuleMapper.java

package  com.icoderoad.couponsystemdb.mapper;
import  com.icoderoad.couponsystemdb.model.CouponRule;import  org.apache.ibatis.annotations.Mapper;import  org.apache.ibatis.annotations.Select;
@Mapperpublic  interface  CouponRuleMapper  {
       @Select("SELECT * FROM coupon_rule WHERE id = #{id}")      CouponRule selectById(Long  id);}

编写主应用程序类

在应用启动时,我们使用规则引擎来处理购物车中的商品,并应用相应的优惠规则:
CouponSystemApplication.java

package  com.icoderoad.couponsystemdb;
import  com.icoderoad.couponsystemdb.model.ShoppingCart;import  com.icoderoad.couponsystemdb.model.Item;import  com.icoderoad.couponsystemdb.rule.DynamicCouponRule;import  org.jeasy.rules.api.Facts;import  org.jeasy.rules.api.Rules;import  org.jeasy.rules.api.RulesEngine;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.boot.CommandLineRunner;import  org.springframework.boot.SpringApplication;import  org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic  class  CouponSystemApplication  implements  CommandLineRunner  {
       @Autowired       private  RulesEngine rulesEngine;
       @Autowired       private  DynamicCouponRule dynamicCouponRule;
       @Autowired       private  CouponService couponService;
       public  static  void  main(String[] args)  {            SpringApplication.run(CouponSystemApplication.class, args);      }
       @Override       public  void  run(String... args)  throws  Exception {             ShoppingCart  cart  =  new  ShoppingCart();            cart.addItem(new  Item("Laptop",  "Electronics",  1200));            cart.addItem(new  Item("Book",  "Books",  30));
             Facts  facts  =  new  Facts();            facts.put("cart", cart);
             Rules  rules  =  new  Rules();            Long[] ruleIds = {1L,  2L};
             // Load rules from the database and add them to the rule engine            for (Long ruleId : ruleIds) {                  dynamicCouponRule.setRuleFromDatabase(ruleId);                  rules.register(dynamicCouponRule);            }
            // Execute the rules engine            rulesEngine.fire(rules, facts);            System.out.println("Final Price: " + cart.getTotalPrice());      }}

总结

通过本篇文章的实现,我们成功地将业务规则和系统逻辑进行了有效的解耦,并利用 Easy Rules 引擎动态地加载和执行优惠券规则。与传统的硬编码规则相比,这种方式具有以下优势:

  1. 灵活性业务规则能够独立于代码进行维护和修改,更新规则时无需重新部署整个应用。
  2. 可扩展性随着业务的发展,新增或修改优惠规则只需在数据库中更新相关记录,无需改动代码结构。
  3. 易于测试规则引擎的使用使得业务逻辑更加模块化,从而能够更方便地进行单元测试和集成测试。
  4. 提高可维护性通过统一的规则管理平台,能够方便地查看、修改和添加业务规则,减少了对业务逻辑的影响,降低了维护成本。

未来,我们可以进一步扩展这一系统,支持更多复杂的规则逻辑(例如多条件、复杂的优惠组合等),并结合更多的业务场景(如积分兑换、会员等级折扣等),使其在电商系统中发挥更大的作用。通过与其他微服务架构的结合,我们还可以构建一个更加全面的优惠券管理平台,为商家提供全面、个性化的营销解决方案。
通过这种方式,系统不仅变得更为灵活和强大,也更符合现代电商业务快速变化和灵活调整的需求。