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 引擎动态地加载和执行优惠券规则。与传统的硬编码规则相比,这种方式具有以下优势:
- 灵活性业务规则能够独立于代码进行维护和修改,更新规则时无需重新部署整个应用。
- 可扩展性随着业务的发展,新增或修改优惠规则只需在数据库中更新相关记录,无需改动代码结构。
- 易于测试规则引擎的使用使得业务逻辑更加模块化,从而能够更方便地进行单元测试和集成测试。
- 提高可维护性通过统一的规则管理平台,能够方便地查看、修改和添加业务规则,减少了对业务逻辑的影响,降低了维护成本。
未来,我们可以进一步扩展这一系统,支持更多复杂的规则逻辑(例如多条件、复杂的优惠组合等),并结合更多的业务场景(如积分兑换、会员等级折扣等),使其在电商系统中发挥更大的作用。通过与其他微服务架构的结合,我们还可以构建一个更加全面的优惠券管理平台,为商家提供全面、个性化的营销解决方案。
通过这种方式,系统不仅变得更为灵活和强大,也更符合现代电商业务快速变化和灵活调整的需求。