電商大促活動時,營銷規則複雜多變,今天滿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 規則設計原則
- 單一職責:每個規則只負責一個業務邏輯
- 可測試性:規則表達式應該易於單元測試
- 可讀性:複雜的規則應該拆分成多個簡單規則
- 性能考慮:避免過於複雜的嵌套表達式
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的組合,我們可以輕鬆構建一個靈活的營銷規則引擎:
- 業務靈活性:運營人員可以動態配置營銷規則,無需開發介入
- 系統性能:Aviator高性能表達式引擎,滿足高併發場景
- 維護性:規則與代碼分離,降低維護成本
- 擴展性:支持複雜的業務規則表達式
這套方案特別適合電商、金融等營銷活動頻繁變化的業務場景。記住,技術的價值在於解決業務問題,選擇合適的工具才能事半功倍!
掌握了這套規則引擎方案,相信你再面對複雜的營銷規則時會更加從容不迫,讓運營同學也能輕鬆玩轉營銷活動!
本文由服務端技術精選原創,轉載請註明出處。關注我們,獲取更多後端技術乾貨!
源碼下載