一、业务背景传统优惠券规则有多痛苦电商系统优惠券场景规则五花八门、迭代极快满减门槛订单金额≥99 元可用、≥199 元可用用户限制仅新用户 / 会员等级≥3 级可用、黑名单用户禁用时间限制仅限活动期、下单时间在券有效期内商品限制优惠券限定类目订单商品需匹配类目叠加 / 库存限制优惠券剩余使用次数大于 0、不可与其他券叠加传统硬编码方案痛点业务规则写死在if/else、枚举、业务代码中新增优惠券规则、修改门槛必须改代码、重启服务、灰度发布规则耦合业务代码代码臃肿大量重复判断逻辑运营配置优惠券、临时调整活动规则依赖研发排期效率极低规则版本难追溯线上 bug 修复成本高最优解Spring EL 表达式实现动态规则无需 Drools、QLExpress 重型规则引擎基于 Spring 原生 Spring EL 表达式支持同时传入User、Coupon、Order多个业务对象规则存入数据库运行时动态解析判断优惠券是否可用改规则只改数据库服务无需重启、无需改代码轻量、零依赖、适配 SpringBoot 项目。二、Spring EL 多对象模式核心优势优惠券场景Spring Expression LanguageSpring 表达式语言Spring 原生内置无需引入第三方依赖原生支持 Spring 全家桶无额外 jar 包、无版本冲突支持同时传入User/Coupon/Order多个业务对象直接对象.属性取值表达式语义贴合业务支持数值比较、逻辑运算、集合匹配、时间区间、空安全运算符规则持久化 MySQL支持热更新、动态生效性能足够支撑优惠券高并发下单校验缓存表达式后性能大幅提升语法简单运营可快速编写基础规则三、优惠券规则数据库表设计优惠券主表简化设计sqlCREATE TABLE coupon (id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 优惠券ID,coupon_name VARCHAR(64) NOT NULL COMMENT 优惠券名称,coupon_type TINYINT NOT NULL COMMENT 1满减券 2折扣券,rule_expression VARCHAR(1024) NOT NULL COMMENT SpEL可用校验表达式返回布尔值支持user/coupon/order多对象属性,discount_amount DECIMAL(10,2) COMMENT 优惠金额,valid_start_time DATETIME NOT NULL COMMENT 券生效时间,valid_end_time DATETIME NOT NULL COMMENT 券失效时间,status TINYINT DEFAULT 1 COMMENT 状态 1正常 0下架,rule_desc VARCHAR(512) COMMENT 规则中文说明便于运营查看,create_time DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT优惠券表;多对象场景示例规则表达式直接存入数据库spel-- 示例1订单满199、会员3级以上、非黑名单、下单在券有效期、商品匹配类目order.totalAmount 199 and user.level 3 and !user.isBlack and order.createTime between coupon.validStartTime and coupon.validEndTime and coupon.category in order.goodsCategories-- 示例2新用户专享满99券券剩余次数0order.totalAmount 99 and user.isNewUser and coupon.maxUseCount 0-- 示例3空安全写法防止对象null空指针报错user?.level 2 and order?.totalAmount 50四、核心业务实体多对象入参载体1. User.java 用户对象javaimport lombok.Data;Datapublic class User {private Long userId;// 会员等级 1普通 2银卡 3金卡private Integer level;// 是否黑名单用户private Boolean isBlack;// 是否新用户private Boolean isNewUser;}2. Coupon.java 优惠券对象javaimport lombok.Data;import java.time.LocalDateTime;Datapublic class Coupon {private Long couponId;// 优惠券限定商品类目private String category;// 券生效时间private LocalDateTime validStartTime;// 券失效时间private LocalDateTime validEndTime;// 最大可使用次数private Integer maxUseCount;}3. Order.java 订单对象javaimport lombok.Data;import java.math.BigDecimal;import java.time.LocalDateTime;import java.util.List;Datapublic class Order {// 订单实付总金额private BigDecimal totalAmount;// 订单内全部商品类目集合private ListString goodsCategories;// 下单时间private LocalDateTime createTime;}五、Spring EL 全局配置类javaimport org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;/*** Spring EL 规则引擎全局配置*/Configurationpublic class SpelConfig {/*** 全局表达式解析器单例复用*/Beanpublic ExpressionParser expressionParser() {return new SpelExpressionParser();}}六、通用多对象优惠券规则校验工具类支持同时传入User、Coupon、Order三个对象绑定独立变量名内置异常捕获、空安全兼容可直接用于下单校验、领券资格校验。javaimport lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.stereotype.Component;/*** 基于Spring EL 多对象模式优惠券规则校验工具类* 支持同时传入 user / coupon / order 三个业务对象表达式直接读取对象属性*/Slf4jComponentpublic class CouponSpelRuleUtil {Autowiredprivate ExpressionParser expressionParser;/*** 多对象入参统一校验方法* param ruleExpression 数据库存储SpEL规则表达式* param user 当前操作用户对象* param coupon 当前待校验优惠券对象* param order 当前下单订单对象* return true优惠券可用 false不满足规则不可用*/public boolean checkCouponAvailable(String ruleExpression, User user, Coupon coupon, Order order) {// 无自定义规则直接放行if (ruleExpression null || ruleExpression.trim().isEmpty()) {return true;}try {// 初始化表达式上下文StandardEvaluationContext evalContext new StandardEvaluationContext();// 绑定多个业务对象指定变量名表达式中直接使用evalContext.setVariable(user, user);evalContext.setVariable(coupon, coupon);evalContext.setVariable(order, order);// 解析表达式并执行判断Expression expression expressionParser.parseExpression(ruleExpression);Boolean result expression.getValue(evalContext, Boolean.class);// 空结果默认判定不可用return Boolean.TRUE.equals(result);} catch (Exception e) {log.error(优惠券SpEL规则解析校验失败表达式{}异常信息{}, ruleExpression, e.getMessage(), e);// 语法错误、对象为空、属性不存在等异常统一返回不可用return false;}}}七、业务服务层调用实战1. 优惠券业务 Servicejavaimport lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;Slf4jServiceRequiredArgsConstructorpublic class CouponService {private final CouponMapper couponMapper;private final CouponSpelRuleUtil couponSpelRuleUtil;/*** 下单时校验优惠券是否可使用* param couponId 待使用优惠券ID* param userId 当前用户ID* param orderId 订单ID* return 校验结果提示文案*/public ResultVO checkCouponUse(Long couponId, Long userId, Long orderId) {// 1. 查询优惠券基础数据Coupon coupon couponMapper.selectById(couponId);if (coupon null || coupon.getStatus() 0) {return ResultVO.fail(优惠券不存在或已下架);}// 2. 查询用户、订单完整业务对象User user userMapper.selectById(userId);Order order orderMapper.selectById(orderId);if (user null || order null) {return ResultVO.fail(用户或订单信息异常);}// 3. Spring EL多对象动态校验规则boolean available couponSpelRuleUtil.checkCouponAvailable(coupon.getRuleExpression(), user, coupon, order);if (available) {return ResultVO.success(优惠券校验通过可正常使用);}return ResultVO.fail(不满足优惠券使用条件无法使用);}}2. 单元测试验证多对象属性判断javaimport org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.math.BigDecimal;import java.time.LocalDateTime;import java.util.List;SpringBootTestpublic class CouponSpelTest {Autowiredprivate CouponSpelRuleUtil couponSpelRuleUtil;Testvoid testMultiObjectRuleCheck() {// 1. 组装用户对象User user new User();user.setLevel(3);user.setIsBlack(false);user.setIsNewUser(false);// 2. 组装优惠券对象Coupon coupon new Coupon();coupon.setCategory(FOOD);coupon.setMaxUseCount(10);coupon.setValidStartTime(LocalDateTime.of(2026, 6, 1, 0, 0));coupon.setValidEndTime(LocalDateTime.of(2026, 6, 30, 23, 59));// 3. 组装订单对象Order order new Order();order.setTotalAmount(new BigDecimal(259));order.setGoodsCategories(List.of(FOOD, DRINK));order.setCreateTime(LocalDateTime.of(2026, 6, 10, 14, 30));// 4. 数据库存储的SpEL表达式String ruleExpr order.totalAmount 199 and user.level 3 and !user.isBlack and order.createTime between coupon.validStartTime and coupon.validEndTime and coupon.category in order.goodsCategories;boolean pass couponSpelRuleUtil.checkCouponAvailable(ruleExpr, user, coupon, order);System.out.println(优惠券是否可用 pass);// 输出 true校验通过}}八、多对象模式常用 SpEL 语法合集1. 对象基础属性比较spel// 订单金额门槛order.totalAmount 99// 用户会员等级限制user.level 2// 黑名单拦截!user.isBlack// 优惠券剩余可用次数coupon.maxUseCount 02. 空安全运算符避免空指针spel// user为null时直接返回false不会抛出NPEuser?.level 3order?.totalAmount 503. 集合包含判断类目匹配spel// 优惠券限定类目在订单商品类目列表中coupon.category in order.goodsCategories4. 时间区间判断spel// 下单时间落在优惠券有效期内order.createTime between coupon.validStartTime and coupon.validEndTime5. 复杂复合规则spelorder.totalAmount 299and user.level 2and !user.isBlackand coupon.maxUseCount 0and order.createTime between coupon.validStartTime and coupon.validEndTimeand coupon.category in order.goodsCategories九、方案优缺点分析✅ 优点业务语义清晰直接传入User/Coupon/Order领域对象表达式对象.属性贴合业务可读性远高于单一体封装参数轻量化无依赖Spring 原生能力无需引入第三方规则引擎规则完全解耦规则存储数据库运营修改规则无需改代码、重启服务扩展性强后续新增Shop、Member等对象仅需新增setVariable绑定工具类无需大幅改造适配分层架构符合项目原有 DO/VO 分层无需额外封装统一上下文 DTO❌ 缺点 生产优化方案表达式存在执行安全风险风险恶意表达式可调用对象setter、反射方法篡改数据优化自定义SpelParserConfiguration限制表达式仅允许读取 getter屏蔽修改类方法、反射、静态类执行重复解析表达式损耗性能优化增加本地缓存key 为表达式字符串缓存解析后的Expression对象避免重复解析字符串复杂长表达式可读性差优化数据库增加rule_desc字段存储中文规则描述后台配置页面同时展示表达式 说明十、高阶生产优化补充1. 表达式本地缓存高并发下单优化使用Caffeine缓存解析完成的Expression大幅降低下单接口 CPU 消耗避免重复解析字符串表达式。2. 自定义安全解析器线上必备重写 SpEL 解析器禁止表达式调用set、delete、反射、系统静态方法仅开放属性读取能力防止注入攻击。3. 支持工具类静态方法拓展可绑定自定义工具静态类在表达式中调用工具方法拓展复杂判断能力spel// 表达式调用自定义工具类判断是否叠加券T(com.market.util.CouponUtil).canStackCoupon(coupon.couponId) and order.totalAmount 99十一、总结电商优惠券、营销活动这类规则多变的业务摒弃硬编码if-else分支采用 Spring EL 动态规则是中小型项目最优落地方案。多对象入参模式相比单一上下文 DTO更贴合领域分层设计表达式语义直观新增业务参数无需修改统一上下文实体维护成本更低。规则持久化数据库实现热更新运营自主配置活动规则完全解放研发人力。该方案可无缝复用至红包、满减活动、会员权益、积分兑换等全部营销场景。