電商大促活動時,營銷規則複雜多變,今天滿300減50,明天買2送1,後天又變成階梯式折扣?每次改規則都得改代碼、重新發布,簡直是開發人員的噩夢!今天就來聊聊如何用SpringBoot + Aviator表達式引擎,搭建一個靈活的營銷規則中心,讓運營同學也能輕鬆配置營銷規則,再也不用求着開發改代碼了!

一、營銷規則的痛點

1.1 傳統if-else的困境

在沒有規則引擎之前,營銷優惠計算通常是這樣寫的:

// 偽代碼:傳統的營銷優惠計算
public BigDecimal calculateDiscount(Order order) {
    if (order.getUserLevel().equals("VIP")) {
        if (order.getAmount().compareTo(new BigDecimal("1000")) > 0) {
            return order.getAmount().multiply(new BigDecimal("0.8")); // VIP用户滿1000打8折
        } else {
            return order.getAmount().multiply(new BigDecimal("0.9")); // VIP用户其他情況打9折
        }
    } else if (order.getUserLevel().equals("GOLD")) {
        if (order.getAmount().compareTo(new BigDecimal("500")) > 0) {
            return order.getAmount().multiply(new BigDecimal("0.85")); // 金牌用户滿500打8.5折
        } else {
            return order.getAmount().multiply(new BigDecimal("0.95")); // 金牌用户其他情況打9.5折
        }
    } else {
        if (order.getAmount().compareTo(new BigDecimal("1000")) > 0) {
            return order.getAmount().multiply(new BigDecimal("0.9")); // 普通用户滿1000打9折
        } else {
            return order.getAmount(); // 普通用户其他情況無折扣
        }
    }
}

這種寫法的問題顯而易見:

  • 代碼複雜:複雜的if-else嵌套,難以維護
  • 修改困難:每次改規則都要改代碼、重新發布
  • 擴展性差:新增規則類型需要修改核心代碼
  • 測試困難:各種規則組合需要大量測試用例

1.2 業務規則變化頻繁

電商行業的營銷活動變化極快:

  • 節日促銷:雙11、雙12、618等
  • 會員權益:不同等級用户享受不同優惠
  • 限時活動:秒殺、拼團、砍價等
  • 渠道差異:APP、小程序、H5不同渠道的差異化策略

面對這些變化,傳統的硬編碼方式顯然跟不上節奏。

二、Aviator表達式引擎的優勢

2.1 什麼是Aviator

Aviator是一個高性能、輕量級的Java表達式引擎,專門用於動態求值表達式。它的特點:

  • 高性能:通過編譯成Java字節碼執行,性能優異
  • 輕量級:依賴包僅450K,核心部分僅70K
  • 功能豐富:支持算術運算、邏輯運算、正則表達式等
  • 安全可靠:不支持賦值語句和外部函數調用,防止安全問題

2.2 Aviator vs 其他規則引擎

特性 Aviator Drools QLExpress Groovy
性能 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐
體積 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐
學習成本 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐
功能豐富度 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
適用場景 簡單計算 複雜業務規則 中等複雜度 通用腳本

推薦場景

  • Aviator:簡單到中等複雜度的計算場景
  • Drools:複雜業務規則,有圖形化編輯器需求
  • QLExpress:需要中文語法的業務場景

三、SpringBoot + Aviator 實戰

3.1 項目依賴配置

首先在pom.xml中添加Aviator依賴:

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.3.3</version>
</dependency>

3.2 核心代碼實現

創建Aviator規則引擎服務:

@Component
@Slf4j
public class AviatorRuleEngine {
    
    /**
     * 緩存編譯後的表達式,提高執行效率
     */
    private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
    
    /**
     * 執行規則表達式
     */
    public Object executeRule(String expression, Map<String, Object> env) {
        try {
            // 檢查緩存中是否存在編譯後的表達式
            Expression compiledExpression = expressionCache.get(expression);
            if (compiledExpression == null) {
                // 編譯表達式並緩存
                compiledExpression = AviatorEvaluator.compile(expression, true);
                expressionCache.put(expression, compiledExpression);
                log.debug("緩存表達式: {}", expression);
            }
            
            // 執行表達式
            Object result = compiledExpression.execute(env);
            log.debug("表達式執行結果: {} = {}", expression, result);
            return result;
        } catch (Exception e) {
            log.error("執行規則表達式失敗: {}", expression, e);
            throw new RuntimeException("規則執行失敗: " + e.getMessage(), e);
        }
    }
    
    /**
     * 驗證表達式語法
     */
    public boolean validateExpression(String expression) {
        try {
            AviatorEvaluator.compile(expression, true);
            return true;
        } catch (Exception e) {
            log.error("表達式語法錯誤: {}", expression, e);
            return false;
        }
    }
}

3.3 營銷規則服務實現

@Service
@Slf4j
public class RuleService {
    
    @Autowired
    private AviatorRuleEngine aviatorRuleEngine;
    
    /**
     * 根據訂單金額和用户等級計算折扣
     */
    public Double calculateDiscount(Double orderAmount, String userLevel, String productCategory) {
        Map<String, Object> context = new HashMap<>();
        context.put("orderAmount", orderAmount);
        context.put("userLevel", userLevel);
        context.put("productCategory", productCategory);
        
        // 根據不同用户等級和訂單金額計算折扣
        String ruleExpression;
        if ("VIP".equals(userLevel)) {
            // VIP用户:訂單金額>1000打8折,否則打9折
            ruleExpression = "orderAmount > 1000 ? 0.8 : 0.9";
        } else if ("GOLD".equals(userLevel)) {
            // 金牌用户:訂單金額>500打8.5折,否則打9.5折
            ruleExpression = "orderAmount > 500 ? 0.85 : 0.95";
        } else {
            // 普通用户:訂單金額>1000打9折,否則無折扣
            ruleExpression = "orderAmount > 1000 ? 0.9 : 1.0";
        }
        
        Object result = aviatorRuleEngine.executeRule(ruleExpression, context);
        return Double.valueOf(result.toString());
    }
    
    /**
     * 計算滿減優惠
     */
    public Double calculateCouponDiscount(Double orderAmount, String couponType) {
        Map<String, Object> context = new HashMap<>();
        context.put("orderAmount", orderAmount);
        context.put("couponType", couponType);
        
        String ruleExpression;
        if ("MANYIJIAN".equals(couponType)) {
            // 滿減券:滿200減20,滿500減50
            ruleExpression = "orderAmount >= 500 ? 50 : (orderAmount >= 200 ? 20 : 0)";
        } else if ("ZHEKOU".equals(couponType)) {
            // 折扣券:滿100打9折
            ruleExpression = "orderAmount >= 100 ? orderAmount * 0.1 : 0";
        } else {
            ruleExpression = "0";
        }
        
        Object result = aviatorRuleEngine.executeRule(ruleExpression, context);
        return Double.valueOf(result.toString());
    }
}

3.4 API接口實現

@RestController
@RequestMapping("/api/marketing")
@Slf4j
public class MarketingRuleController {
    
    @Autowired
    private RuleService ruleService;
    
    /**
     * 計算訂單折扣
     */
    @PostMapping("/calculate-discount")
    public ResponseEntity<Map<String, Object>> calculateDiscount(@RequestBody Map<String, Object> request) {
        try {
            Double orderAmount = Double.valueOf(request.get("orderAmount").toString());
            String userLevel = request.get("userLevel").toString();
            String productCategory = request.get("productCategory").toString();
            
            Double discountRate = ruleService.calculateDiscount(orderAmount, userLevel, productCategory);
            
            Map<String, Object> result = new HashMap<>();
            result.put("originalAmount", orderAmount);
            result.put("discountRate", discountRate);
            result.put("discountedAmount", orderAmount * discountRate);
            result.put("savedAmount", orderAmount * (1 - discountRate));
            result.put("userLevel", userLevel);
            
            log.info("訂單折扣計算完成: {}", result);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("計算訂單折扣失敗", e);
            Map<String, Object> error = new HashMap<>();
            error.put("error", e.getMessage());
            return ResponseEntity.badRequest().body(error);
        }
    }
}

3.5 常用的營銷規則表達式

// 1. 滿減規則:滿200減20,滿500減50
String manjianRule = "orderAmount >= 500 ? 50 : (orderAmount >= 200 ? 20 : 0)";

// 2. 折扣規則:VIP用户打8折,金牌用户打8.5折
String zhekouRule = "userLevel == 'VIP' ? 0.8 : (userLevel == 'GOLD' ? 0.85 : 1.0)";

// 3. 階梯規則:消費金額越高折扣越大
String jietiRule = "orderAmount >= 1000 ? 0.7 : (orderAmount >= 500 ? 0.8 : (orderAmount >= 200 ? 0.9 : 1.0))";

// 4. 組合規則:多條件判斷
String complexRule = "(userLevel == 'VIP' and orderAmount >= 1000) ? 0.7 : " +
                    "(userLevel == 'GOLD' and orderAmount >= 500) ? 0.8 : 1.0";

// 5. 時間規則:特定時間段內有效
String timeRule = "orderTime >= '2023-11-11 00:00:00' and orderTime <= '2023-11-11 23:59:59' ? 0.5 : 1.0";

四、規則中心架構設計

4.1 整體架構

前端運營系統 ←→ 規則配置API ←→ 規則引擎服務 ←→ 緩存 ←→ 數據庫
                                        ↓
                                   業務服務調用

4.2 規則存儲設計

@Entity
@Table(name = "t_rule")
@Data
public class Rule {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 規則編碼
     */
    @Column(unique = true, nullable = false)
    private String ruleCode;
    
    /**
     * 規則名稱
     */
    private String ruleName;
    
    /**
     * 規則表達式
     */
    @Lob
    private String ruleExpression;
    
    /**
     * 規則描述
     */
    private String description;
    
    /**
     * 規則類型
     */
    private String ruleType;
    
    /**
     * 是否啓用
     */
    private Boolean enabled = true;
    
    /**
     * 版本號
     */
    private Integer version = 1;
    
    /**
     * 創建時間
     */
    private LocalDateTime createTime = LocalDateTime.now();
    
    /**
     * 更新時間
     */
    private LocalDateTime updateTime = LocalDateTime.now();
}

4.3 規則緩存策略

@Service
public class CachedRuleService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RuleRepository ruleRepository;
    
    private static final String RULE_CACHE_PREFIX = "rule:expression:";
    
    /**
     * 獲取規則表達式(帶緩存)
     */
    public String getRuleExpression(String ruleCode) {
        String cacheKey = RULE_CACHE_PREFIX + ruleCode;
        
        // 先從緩存獲取
        String expression = (String) redisTemplate.opsForValue().get(cacheKey);
        if (expression != null) {
            return expression;
        }
        
        // 緩存未命中,從數據庫獲取
        Rule rule = ruleRepository.findByRuleCodeAndEnabledTrue(ruleCode);
        if (rule != null) {
            expression = rule.getRuleExpression();
            // 存入緩存,設置過期時間
            redisTemplate.opsForValue().set(cacheKey, expression, 300, TimeUnit.SECONDS);
            return expression;
        }
        
        return null;
    }
    
    /**
     * 更新規則時清除緩存
     */
    public void updateRule(Rule rule) {
        ruleRepository.save(rule);
        
        // 清除緩存
        String cacheKey = RULE_CACHE_PREFIX + rule.getRuleCode();
        redisTemplate.delete(cacheKey);
    }
}

五、最佳實踐建議

5.1 規則設計原則

  1. 單一職責:每個規則只負責一個業務邏輯
  2. 可測試性:規則表達式應該易於單元測試
  3. 可讀性:複雜的規則應該拆分成多個簡單規則
  4. 性能考慮:避免過於複雜的嵌套表達式

5.2 安全性考慮

// 1. 表達式驗證
public boolean validateRuleExpression(String expression) {
    try {
        // 編譯表達式驗證語法
        Expression compiled = AviatorEvaluator.compile(expression, true);
        return true;
    } catch (Exception e) {
        log.error("表達式驗證失敗: {}", expression, e);
        return false;
    }
}

// 2. 沙箱執行(可選)
public Object executeRuleSafely(String expression, Map<String, Object> env) {
    // 設置執行超時時間,防止死循環
    Map<String, Object> envWithTimeout = new HashMap<>(env);
    envWithTimeout.put("max_execution_time", 1000); // 1秒超時
    
    return AviatorEvaluator.execute(expression, envWithTimeout);
}

5.3 監控和日誌

@Service
@Slf4j
public class MonitoredRuleService {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    public Object executeRuleWithMonitor(String ruleCode, String expression, Map<String, Object> env) {
        Timer.Sample sample = Timer.start(meterRegistry);
        
        try {
            Object result = aviatorRuleEngine.executeRule(expression, env);
            
            // 記錄成功指標
            sample.stop(Timer.builder("rule.execution.time")
                .tag("rule_code", ruleCode)
                .tag("result", "success")
                .register(meterRegistry));
                
            log.info("規則執行成功: ruleCode={}, result={}", ruleCode, result);
            return result;
        } catch (Exception e) {
            // 記錄失敗指標
            sample.stop(Timer.builder("rule.execution.time")
                .tag("rule_code", ruleCode)
                .tag("result", "error")
                .register(meterRegistry));
                
            log.error("規則執行失敗: ruleCode={}, expression={}", ruleCode, expression, e);
            throw e;
        }
    }
}

5.4 規則版本管理

// 規則版本管理
@Entity
@Table(name = "t_rule_version")
@Data
public class RuleVersion {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String ruleCode;
    private String ruleExpression;
    private Integer version;
    private String description;
    private LocalDateTime createTime;
    private String createdBy;
    
    // 是否為當前版本
    private Boolean current = false;
}

六、總結

通過SpringBoot + Aviator的組合,我們可以輕鬆構建一個靈活的營銷規則引擎:

  1. 業務靈活性:運營人員可以動態配置營銷規則,無需開發介入
  2. 系統性能:Aviator高性能表達式引擎,滿足高併發場景
  3. 維護性:規則與代碼分離,降低維護成本
  4. 擴展性:支持複雜的業務規則表達式

這套方案特別適合電商、金融等營銷活動頻繁變化的業務場景。記住,技術的價值在於解決業務問題,選擇合適的工具才能事半功倍!

掌握了這套規則引擎方案,相信你再面對複雜的營銷規則時會更加從容不迫,讓運營同學也能輕鬆玩轉營銷活動!


本文由服務端技術精選原創,轉載請註明出處。關注我們,獲取更多後端技術乾貨!

源碼下載