每次業務規則變動都需要修改代碼、重新打包、上線部署?客户臨時想要調整折扣規則、風控策略或者計費邏輯,你就得加班加點改代碼?今天就來聊聊如何基於SpringBoot + QLExpress打造一個強大的動態規則引擎,讓業務規則變得靈活可控,再也不用擔心頻繁變更了! 原文鏈接

一、為什麼需要動態規則引擎?

在開始技術實現之前,我們先來理解為什麼動態規則引擎如此重要。

1.1 傳統業務規則的痛點

// 傳統業務規則的痛點示例
public class TraditionalBusinessRules {
    
    public void痛點() {
        System.out.println("=== 傳統業務規則的痛點 ===");
        System.out.println("1. 代碼硬編碼:規則寫死在代碼裏");
        System.out.println("2. 變更困難:每次修改都需要重新部署");
        System.out.println("3. 發佈風險:頻繁上線增加系統風險");
        System.out.println("4. 響應緩慢:無法快速響應業務需求");
        System.out.println("5. 維護成本高:多個版本難以維護");
    }
}

1.2 動態規則引擎的價值

// 動態規則引擎的價值
public class DynamicRuleEngineBenefits {
    
    public void benefits() {
        System.out.println("=== 動態規則引擎的價值 ===");
        System.out.println("1. 業務靈活:規則可動態調整");
        System.out.println("2. 快速響應:無需重新部署即可生效");
        System.out.println("3. 降低風險:減少代碼變更和上線頻率");
        System.out.println("4. 提升效率:業務人員可自助配置");
        System.out.println("5. 統一管理:集中管理所有業務規則");
    }
}

二、QLExpress簡介與優勢

QLExpress是阿里巴巴開源的一款輕量級表達式語言,非常適合用來實現動態規則引擎。

2.1 QLExpress特性

// QLExpress特性
public class QLExpressFeatures {
    
    public void features() {
        System.out.println("=== QLExpress特性 ===");
        System.out.println("1. 語法簡潔:類似Java語法,易於學習");
        System.out.println("2. 性能優秀:編譯後執行,速度快");
        System.out.println("3. 安全可控:沙箱機制,防止惡意代碼");
        System.out.println("4. 擴展性強:支持自定義函數和宏");
        System.out.println("5. 易於集成:與SpringBoot無縫集成");
    }
}

2.2 QLExpress與其他規則引擎對比

// QLExpress與其他規則引擎對比
public class RuleEngineComparison {
    
    public void comparison() {
        System.out.println("=== 規則引擎對比 ===");
        System.out.println("Drools:功能強大但複雜,學習成本高");
        System.out.println("QLExpress:輕量級,語法簡單,性能好");
        System.out.println("Aviator:表達式引擎,功能相對簡單");
        System.out.println("Groovy:功能強大但性能一般,安全性較差");
        System.out.println("推薦:中小型項目選QLExpress,大型複雜項目選Drools");
    }
}

三、SpringBoot集成QLExpress

3.1 依賴配置

<!-- pom.xml 添加依賴 -->
<dependencies>
    <!-- SpringBoot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- QLExpress -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>QLExpress</artifactId>
        <version>3.2.4</version>
    </dependency>
    
    <!-- Redis(用於規則緩存) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- 數據庫(用於規則存儲) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- 監控 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

3.2 QLExpress配置類

@Configuration
public class QLExpressConfig {
    
    @Bean
    public ExpressRunner expressRunner() {
        ExpressRunner runner = new ExpressRunner();
        
        // 註冊自定義函數
        try {
            runner.addFunction("contains", new ContainsFunction());
            runner.addFunction("startsWith", new StartsWithFunction());
            runner.addFunction("endsWith", new EndsWithFunction());
            runner.addFunction("formatDate", new FormatDateFunction());
        } catch (Exception e) {
            throw new RuntimeException("註冊QLExpress函數失敗", e);
        }
        
        return runner;
    }
    
    @Bean
    public DefaultContext<String, Object> expressContext() {
        return new DefaultContext<>();
    }
}

3.3 自定義函數實現

// 自定義函數:字符串包含判斷
public class ContainsFunction implements InstructionSetFunction {
    
    @Override
    public OperateData callSelf(ExpressionExecutor[] list, 
                              ExpressPackage expressPackage, 
                              RunnerContext context,
                              ErrorInfo errorInfo) throws Exception {
        String source = (String) list[0].getObject(context);
        String target = (String) list[1].getObject(context);
        
        boolean result = source != null && source.contains(target);
        return new OperateData(result, Boolean.class);
    }
}

// 自定義函數:日期格式化
public class FormatDateFunction implements InstructionSetFunction {
    
    @Override
    public OperateData callSelf(ExpressionExecutor[] list, 
                              ExpressPackage expressPackage, 
                              RunnerContext context,
                              ErrorInfo errorInfo) throws Exception {
        Date date = (Date) list[0].getObject(context);
        String pattern = (String) list[1].getObject(context);
        
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        String result = sdf.format(date);
        return new OperateData(result, String.class);
    }
}

四、規則引擎核心實現

4.1 規則實體設計

@Entity
@Table(name = "rule_definitions")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RuleDefinition {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "rule_code", nullable = false, unique = true)
    private String ruleCode; // 規則編碼
    
    @Column(name = "rule_name", nullable = false)
    private String ruleName; // 規則名稱
    
    @Column(name = "rule_expression", nullable = false, columnDefinition = "text")
    private String ruleExpression; // 規則表達式
    
    @Column(name = "rule_description")
    private String ruleDescription; // 規則描述
    
    @Column(name = "rule_type", nullable = false)
    private String ruleType; // 規則類型(DECISION-決策規則,VALIDATION-校驗規則等)
    
    @Column(name = "enabled", nullable = false)
    private Boolean enabled = true; // 是否啓用
    
    @Column(name = "priority")
    private Integer priority = 0; // 優先級
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

4.2 規則存儲Repository

@Repository
public interface RuleDefinitionRepository extends JpaRepository<RuleDefinition, Long> {
    
    Optional<RuleDefinition> findByRuleCode(String ruleCode);
    
    List<RuleDefinition> findByEnabledTrueOrderByPriorityDesc();
    
    List<RuleDefinition> findByRuleTypeAndEnabledTrue(String ruleType);
}

4.3 規則引擎服務實現

@Service
@Slf4j
public class RuleEngineService {
    
    @Autowired
    private ExpressRunner expressRunner;
    
    @Autowired
    private RuleDefinitionRepository ruleRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String RULE_CACHE_PREFIX = "rule_engine:rule:";
    private static final long RULE_CACHE_TTL = 300; // 5分鐘緩存
    
    /**
     * 執行規則
     */
    public RuleExecutionResult executeRule(String ruleCode, Map<String, Object> context) {
        try {
            // 1. 獲取規則定義
            RuleDefinition rule = getRuleDefinition(ruleCode);
            if (rule == null) {
                return RuleExecutionResult.builder()
                        .success(false)
                        .errorCode("RULE_NOT_FOUND")
                        .errorMessage("規則不存在: " + ruleCode)
                        .build();
            }
            
            if (!rule.getEnabled()) {
                return RuleExecutionResult.builder()
                        .success(false)
                        .errorCode("RULE_DISABLED")
                        .errorMessage("規則已禁用: " + ruleCode)
                        .build();
            }
            
            // 2. 執行規則表達式
            Object result = executeExpression(rule.getRuleExpression(), context);
            
            // 3. 返回執行結果
            return RuleExecutionResult.builder()
                    .success(true)
                    .result(result)
                    .ruleCode(ruleCode)
                    .executedAt(LocalDateTime.now())
                    .build();
                    
        } catch (Exception e) {
            log.error("執行規則失敗: ruleCode={}", ruleCode, e);
            return RuleExecutionResult.builder()
                    .success(false)
                    .errorCode("EXECUTION_ERROR")
                    .errorMessage("規則執行異常: " + e.getMessage())
                    .build();
        }
    }
    
    /**
     * 批量執行規則
     */
    public List<RuleExecutionResult> executeRules(List<String> ruleCodes, Map<String, Object> context) {
        return ruleCodes.parallelStream()
                .map(ruleCode -> executeRule(ruleCode, context))
                .collect(Collectors.toList());
    }
    
    /**
     * 獲取規則定義(帶緩存)
     */
    private RuleDefinition getRuleDefinition(String ruleCode) {
        String cacheKey = RULE_CACHE_PREFIX + ruleCode;
        
        // 先從緩存獲取
        RuleDefinition cachedRule = (RuleDefinition) redisTemplate.opsForValue().get(cacheKey);
        if (cachedRule != null) {
            return cachedRule;
        }
        
        // 緩存未命中,從數據庫獲取
        Optional<RuleDefinition> ruleOpt = ruleRepository.findByRuleCode(ruleCode);
        if (ruleOpt.isPresent()) {
            RuleDefinition rule = ruleOpt.get();
            // 存入緩存
            redisTemplate.opsForValue().set(cacheKey, rule, RULE_CACHE_TTL, TimeUnit.SECONDS);
            return rule;
        }
        
        return null;
    }
    
    /**
     * 執行表達式
     */
    private Object executeExpression(String expression, Map<String, Object> context) throws Exception {
        // 創建執行上下文
        DefaultContext<String, Object> expressContext = new DefaultContext<>();
        expressContext.putAll(context);
        
        // 執行表達式
        Object result = expressRunner.execute(expression, expressContext, null, true, false);
        return result;
    }
    
    /**
     * 驗證規則語法
     */
    public RuleValidationResult validateRule(String expression) {
        try {
            expressRunner.checkSyntax(expression);
            return RuleValidationResult.builder()
                    .valid(true)
                    .message("規則語法正確")
                    .build();
        } catch (Exception e) {
            return RuleValidationResult.builder()
                    .valid(false)
                    .message("規則語法錯誤: " + e.getMessage())
                    .build();
        }
    }
    
    /**
     * 清除規則緩存
     */
    public void clearRuleCache(String ruleCode) {
        String cacheKey = RULE_CACHE_PREFIX + ruleCode;
        redisTemplate.delete(cacheKey);
    }
    
    /**
     * 清除所有規則緩存
     */
    public void clearAllRuleCache() {
        Set<String> keys = redisTemplate.keys(RULE_CACHE_PREFIX + "*");
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
}

4.4 規則執行結果封裝

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RuleExecutionResult {
    
    private boolean success;
    private String errorCode;
    private String errorMessage;
    private Object result;
    private String ruleCode;
    private LocalDateTime executedAt;
    private long executionTime; // 執行耗時(毫秒)
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RuleValidationResult {
    
    private boolean valid;
    private String message;
    private List<String> errors;
}

五、業務場景實戰

5.1 場景一:訂單折扣規則

// 訂單折扣規則示例
@RestController
@RequestMapping("/api/discount")
@Slf4j
public class DiscountRuleController {
    
    @Autowired
    private RuleEngineService ruleEngineService;
    
    /**
     * 計算訂單折扣
     */
    @PostMapping("/calculate")
    public ResponseEntity<DiscountResult> calculateDiscount(@RequestBody OrderRequest request) {
        try {
            // 構造規則執行上下文
            Map<String, Object> context = new HashMap<>();
            context.put("orderAmount", request.getOrderAmount());
            context.put("customerLevel", request.getCustomerLevel());
            context.put("productCategory", request.getProductCategory());
            context.put("orderTime", new Date());
            
            // 執行折扣規則
            RuleExecutionResult result = ruleEngineService.executeRule("DISCOUNT_RULE_001", context);
            
            if (result.isSuccess()) {
                Double discountRate = (Double) result.getResult();
                BigDecimal discountAmount = request.getOrderAmount().multiply(BigDecimal.valueOf(discountRate));
                BigDecimal finalAmount = request.getOrderAmount().subtract(discountAmount);
                
                DiscountResult discountResult = DiscountResult.builder()
                        .originalAmount(request.getOrderAmount())
                        .discountRate(discountRate)
                        .discountAmount(discountAmount)
                        .finalAmount(finalAmount)
                        .build();
                        
                return ResponseEntity.ok(discountResult);
            } else {
                return ResponseEntity.badRequest()
                        .body(DiscountResult.builder()
                                .errorMessage(result.getErrorMessage())
                                .build());
            }
        } catch (Exception e) {
            log.error("計算折扣失敗", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DiscountResult.builder()
                            .errorMessage("系統異常")
                            .build());
        }
    }
}

// 示例規則表達式(存儲在數據庫中)
/*
rule_code: DISCOUNT_RULE_001
rule_name: 訂單折扣計算規則
rule_expression: 
if (customerLevel == "VIP") {
    if (orderAmount > 1000) {
        return 0.2;  // VIP客户訂單金額大於1000元,享受2折
    } else {
        return 0.1;  // VIP客户其他情況,享受1折
    }
} else if (customerLevel == "GOLD") {
    if (orderAmount > 500) {
        return 0.15; // 金牌客户訂單金額大於500元,享受1.5折
    } else {
        return 0.05; // 金牌客户其他情況,享受0.5折
    }
} else {
    if (orderAmount > 2000) {
        return 0.1;  // 普通客户訂單金額大於2000元,享受1折
    } else {
        return 0.0;  // 普通客户其他情況,無折扣
    }
}
*/

5.2 場景二:風控校驗規則

// 風控校驗規則示例
@Service
@Slf4j
public class RiskControlService {
    
    @Autowired
    private RuleEngineService ruleEngineService;
    
    /**
     * 風控校驗
     */
    public RiskCheckResult checkRisk(TransactionRequest request) {
        try {
            // 構造風控上下文
            Map<String, Object> context = new HashMap<>();
            context.put("userId", request.getUserId());
            context.put("amount", request.getAmount());
            context.put("ipAddress", request.getIpAddress());
            context.put("deviceType", request.getDeviceType());
            context.put("transactionTime", new Date());
            context.put("userRiskScore", getUserRiskScore(request.getUserId()));
            
            // 執行風控規則
            RuleExecutionResult result = ruleEngineService.executeRule("RISK_CONTROL_RULE_001", context);
            
            if (result.isSuccess()) {
                Boolean allowTransaction = (Boolean) result.getResult();
                return RiskCheckResult.builder()
                        .allowed(allowTransaction)
                        .riskLevel(allowTransaction ? "LOW" : "HIGH")
                        .checkTime(LocalDateTime.now())
                        .build();
            } else {
                // 規則執行失敗,默認允許交易但記錄日誌
                log.warn("風控規則執行失敗: {}", result.getErrorMessage());
                return RiskCheckResult.builder()
                        .allowed(true)
                        .riskLevel("UNKNOWN")
                        .warningMessage("風控檢查異常")
                        .checkTime(LocalDateTime.now())
                        .build();
            }
        } catch (Exception e) {
            log.error("風控校驗異常", e);
            // 異常情況下默認允許交易
            return RiskCheckResult.builder()
                    .allowed(true)
                    .riskLevel("SYSTEM_ERROR")
                    .errorMessage("系統異常")
                    .checkTime(LocalDateTime.now())
                    .build();
        }
    }
    
    private double getUserRiskScore(Long userId) {
        // 獲取用户風險評分的邏輯
        return 0.5;
    }
}

// 示例風控規則表達式
/*
rule_code: RISK_CONTROL_RULE_001
rule_name: 交易風控校驗規則
rule_expression:
// 高風險IP地址檢查
def highRiskIps = ["192.168.1.100", "10.0.0.1"];
if (highRiskIps.contains(ipAddress)) {
    return false;  // 高風險IP,拒絕交易
}

// 大額交易檢查
if (amount > 10000 && userRiskScore < 0.7) {
    return false;  // 高額交易且用户風險評分較低,拒絕交易
}

// 異常設備檢查
if (deviceType == "emulator" || deviceType == "unknown") {
    return false;  // 模擬器或未知設備,拒絕交易
}

// 時間段檢查
def hour = formatDate(transactionTime, "HH");
if (hour >= "02" && hour <= "05") {
    // 凌晨2點到5點,增加額外檢查
    if (amount > 5000) {
        return false;  // 凌晨大額交易,拒絕
    }
}

return true;  // 通過風控檢查
*/

六、規則管理後台

6.1 規則管理Controller

@RestController
@RequestMapping("/api/rules")
@Slf4j
public class RuleManagementController {
    
    @Autowired
    private RuleEngineService ruleEngineService;
    
    @Autowired
    private RuleDefinitionRepository ruleRepository;
    
    /**
     * 查詢規則列表
     */
    @GetMapping
    public ResponseEntity<List<RuleDefinition>> listRules(
            @RequestParam(required = false) String ruleType,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        
        Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending());
        
        Page<RuleDefinition> rulePage;
        if (StringUtils.hasText(ruleType)) {
            rulePage = ruleRepository.findByRuleType(ruleType, pageable);
        } else {
            rulePage = ruleRepository.findAll(pageable);
        }
        
        return ResponseEntity.ok(rulePage.getContent());
    }
    
    /**
     * 獲取規則詳情
     */
    @GetMapping("/{id}")
    public ResponseEntity<RuleDefinition> getRule(@PathVariable Long id) {
        Optional<RuleDefinition> rule = ruleRepository.findById(id);
        return rule.map(ResponseEntity::ok)
                  .orElse(ResponseEntity.notFound().build());
    }
    
    /**
     * 創建規則
     */
    @PostMapping
    public ResponseEntity<RuleDefinition> createRule(@RequestBody RuleDefinition rule) {
        // 驗證規則語法
        RuleValidationResult validationResult = ruleEngineService.validateRule(rule.getRuleExpression());
        if (!validationResult.isValid()) {
            throw new IllegalArgumentException("規則語法錯誤: " + validationResult.getMessage());
        }
        
        rule.setCreatedAt(LocalDateTime.now());
        rule.setUpdatedAt(LocalDateTime.now());
        
        RuleDefinition savedRule = ruleRepository.save(rule);
        
        // 清除相關緩存
        ruleEngineService.clearRuleCache(rule.getRuleCode());
        
        return ResponseEntity.ok(savedRule);
    }
    
    /**
     * 更新規則
     */
    @PutMapping("/{id}")
    public ResponseEntity<RuleDefinition> updateRule(@PathVariable Long id, @RequestBody RuleDefinition rule) {
        Optional<RuleDefinition> existingRule = ruleRepository.findById(id);
        if (!existingRule.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        
        // 驗證規則語法
        RuleValidationResult validationResult = ruleEngineService.validateRule(rule.getRuleExpression());
        if (!validationResult.isValid()) {
            throw new IllegalArgumentException("規則語法錯誤: " + validationResult.getMessage());
        }
        
        RuleDefinition updatedRule = existingRule.get();
        updatedRule.setRuleName(rule.getRuleName());
        updatedRule.setRuleExpression(rule.getRuleExpression());
        updatedRule.setRuleDescription(rule.getRuleDescription());
        updatedRule.setRuleType(rule.getRuleType());
        updatedRule.setEnabled(rule.getEnabled());
        updatedRule.setPriority(rule.getPriority());
        updatedRule.setUpdatedAt(LocalDateTime.now());
        
        RuleDefinition savedRule = ruleRepository.save(updatedRule);
        
        // 清除相關緩存
        ruleEngineService.clearRuleCache(updatedRule.getRuleCode());
        
        return ResponseEntity.ok(savedRule);
    }
    
    /**
     * 刪除規則
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteRule(@PathVariable Long id) {
        Optional<RuleDefinition> rule = ruleRepository.findById(id);
        if (rule.isPresent()) {
            ruleRepository.deleteById(id);
            // 清除相關緩存
            ruleEngineService.clearRuleCache(rule.get().getRuleCode());
        }
        return ResponseEntity.ok().build();
    }
    
    /**
     * 測試規則
     */
    @PostMapping("/test")
    public ResponseEntity<RuleExecutionResult> testRule(@RequestBody TestRuleRequest request) {
        RuleExecutionResult result = ruleEngineService.executeRule(request.getRuleCode(), request.getContext());
        return ResponseEntity.ok(result);
    }
    
    /**
     * 驗證規則語法
     */
    @PostMapping("/validate")
    public ResponseEntity<RuleValidationResult> validateRule(@RequestBody ValidateRuleRequest request) {
        RuleValidationResult result = ruleEngineService.validateRule(request.getExpression());
        return ResponseEntity.ok(result);
    }
    
    /**
     * 清除所有規則緩存
     */
    @PostMapping("/cache/clear")
    public ResponseEntity<String> clearAllCache() {
        ruleEngineService.clearAllRuleCache();
        return ResponseEntity.ok("緩存清除成功");
    }
}

6.2 規則測試請求對象

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestRuleRequest {
    
    private String ruleCode;
    private Map<String, Object> context;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ValidateRuleRequest {
    
    private String expression;
}

七、性能優化與監控

7.1 規則執行監控

@Component
@Slf4j
public class RuleExecutionMetrics {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    private final Counter ruleExecutions;
    private final Counter ruleFailures;
    private final Timer ruleExecutionTimer;
    
    public RuleExecutionMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        this.ruleExecutions = Counter.builder("rule.engine.executions")
                .description("規則執行次數")
                .register(meterRegistry);
                
        this.ruleFailures = Counter.builder("rule.engine.failures")
                .description("規則執行失敗次數")
                .register(meterRegistry);
                
        this.ruleExecutionTimer = Timer.builder("rule.engine.execution.time")
                .description("規則執行耗時")
                .register(meterRegistry);
    }
    
    /**
     * 記錄規則執行
     */
    public void recordRuleExecution(String ruleCode, boolean success, long executionTime) {
        ruleExecutions.increment(Tag.of("rule_code", ruleCode), Tag.of("success", String.valueOf(success)));
        
        if (!success) {
            ruleFailures.increment(Tag.of("rule_code", ruleCode));
        }
        
        ruleExecutionTimer.record(executionTime, TimeUnit.MILLISECONDS);
    }
    
    /**
     * 獲取規則執行統計
     */
    public RuleExecutionStats getExecutionStats(String ruleCode) {
        // 通過Micrometer獲取統計數據
        return new RuleExecutionStats();
    }
}

7.2 規則緩存優化

@Service
public class OptimizedRuleCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 使用本地緩存 + Redis緩存的二級緩存策略
    private final Cache<String, RuleDefinition> localCache = 
        Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .build();
    
    /**
     * 獲取規則定義(二級緩存)
     */
    public RuleDefinition getRuleDefinition(String ruleCode) {
        // 1. 先查本地緩存
        RuleDefinition rule = localCache.getIfPresent(ruleCode);
        if (rule != null) {
            return rule;
        }
        
        // 2. 查Redis緩存
        String cacheKey = "rule_engine:rule:" + ruleCode;
        rule = (RuleDefinition) redisTemplate.opsForValue().get(cacheKey);
        if (rule != null) {
            localCache.put(ruleCode, rule);
            return rule;
        }
        
        // 3. 查數據庫並更新緩存
        // 實現邏輯...
        return null;
    }
}

八、安全與權限控制

8.1 規則編輯權限控制

@RestController
@RequestMapping("/api/rules")
@PreAuthorize("hasRole('ADMIN') or hasRole('RULE_MANAGER')")
public class SecureRuleManagementController {
    
    // 只有管理員或規則管理員才能訪問規則管理接口
    
    @PostMapping
    @PreAuthorize("hasAuthority('RULE_CREATE')")
    public ResponseEntity<RuleDefinition> createRule(@RequestBody RuleDefinition rule) {
        // 創建規則需要RULE_CREATE權限
        return ResponseEntity.ok(new RuleDefinition());
    }
    
    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('RULE_UPDATE')")
    public ResponseEntity<RuleDefinition> updateRule(@PathVariable Long id, @RequestBody RuleDefinition rule) {
        // 更新規則需要RULE_UPDATE權限
        return ResponseEntity.ok(new RuleDefinition());
    }
    
    @DeleteMapping("/{id}")
    @PreAuthorize("hasAuthority('RULE_DELETE')")
    public ResponseEntity<Void> deleteRule(@PathVariable Long id) {
        // 刪除規則需要RULE_DELETE權限
        return ResponseEntity.ok().build();
    }
}

8.2 規則表達式安全檢查

@Service
public class RuleSecurityValidator {
    
    private static final Set<String> FORBIDDEN_KEYWORDS = Set.of(
        "System", "Runtime", "Process", "File", "Thread", 
        "ClassLoader", "SecurityManager", "exit", "halt"
    );
    
    /**
     * 檢查規則表達式安全性
     */
    public boolean isExpressionSafe(String expression) {
        // 檢查是否包含禁用關鍵字
        for (String keyword : FORBIDDEN_KEYWORDS) {
            if (expression.contains(keyword)) {
                return false;
            }
        }
        
        // 檢查是否包含危險操作
        if (expression.contains("import ") || 
            expression.contains("new ") || 
            expression.contains("eval(")) {
            return false;
        }
        
        return true;
    }
}

九、最佳實踐總結

9.1 規則設計原則

public class RuleDesignPrinciples {
    
    public void principles() {
        System.out.println("=== 規則設計原則 ===");
        System.out.println("1. 簡潔性:規則表達式應儘量簡單明瞭");
        System.out.println("2. 可讀性:使用有意義的變量名和註釋");
        System.out.println("3. 可測試性:規則應易於測試和驗證");
        System.out.println("4. 可維護性:避免過於複雜的嵌套邏輯");
        System.out.println("5. 性能考慮:避免在規則中執行耗時操作");
    }
}

9.2 運維操作手冊

public class OperationsManual {
    
    public void manual() {
        System.out.println("=== 規則引擎運維手冊 ===");
        System.out.println("日常維護:");
        System.out.println("- 監控規則執行成功率和響應時間");
        System.out.println("- 定期清理過期的規則緩存");
        System.out.println("- 備份重要的規則定義");
        System.out.println("- 監控Redis緩存使用情況");
        
        System.out.println("\n應急處理:");
        System.out.println("- 規則執行異常:檢查規則語法和上下文");
        System.out.println("- 性能下降:分析慢規則並優化");
        System.out.println("- 緩存失效:手動清除緩存並重啓服務");
        System.out.println("- 數據庫連接異常:檢查數據庫連接池配置");
        
        System.out.println("\n版本升級:");
        System.out.println("- 規則語法變更時需要適配");
        System.out.println("- 數據庫表結構變更時需要遷移");
        System.out.println("- 緩存策略變更時需要清理舊緩存");
    }
}

結語

通過本文的介紹,相信你已經掌握瞭如何基於SpringBoot + QLExpress打造一個功能強大的動態規則引擎。這套方案不僅能夠滿足大多數業務場景的需求,還具有良好的擴展性和維護性。

關鍵要點總結:

  1. 選擇合適的規則引擎:QLExpress輕量級且易用
  2. 合理的架構設計:緩存+數據庫的存儲方案
  3. 完善的監控體系:性能指標和執行日誌
  4. 安全保障機制:權限控制和表達式安全檢查
  5. 運維友好性:管理後台和操作手冊

記住,規則引擎的價值在於讓業務變得更加靈活,但也要注意避免過度設計。在實際應用中,要根據業務複雜度和變更頻率來決定是否需要引入規則引擎。

如果你覺得這篇文章對你有幫助,歡迎分享給更多的朋友。在規則引擎的探索路上,我們一起成長!


關注「服務端技術精選」,獲取更多幹貨技術文章!