SpringBoot + QLExpress 规则分步调试器:复杂逻辑卡在哪?逐行输出执行轨迹!

相信很多小伙伴都有过这样的困扰:使用 QLExpress 编写复杂的业务规则时,当规则执行结果不符合预期,很难快速定位问题所在。特别是当规则包含多层嵌套、复杂表达式或依赖外部数据时,调试过程就像在黑盒中摸索,效率低下且容易出错。

那么,有没有一种方式能让我们像调试普通代码一样,逐行查看 QLExpress 规则的执行过程,了解每一步的变量值变化,从而快速定位问题?今天我就跟大家分享一套基于 SpringBoot 的 QLExpress 规则分步调试器方案。

为什么需要 QLExpress 规则分步调试器?

先来说说我们面临的挑战。在使用 QLExpress 进行规则引擎开发时,常见的问题包括:

// 复杂的业务规则示例
rule = "if (order.amount > 1000 && user.level >= 3) {
    if (user.vip) {
        discount = 0.8;
    } else {
        discount = 0.9;
    }
} else if (order.amount > 500) {
    discount = 0.95;
} else {
    discount = 1.0;
}
return discount;"

当这样的规则执行结果不符合预期时,我们很难直接看出问题出在哪里:

  1. 无法跟踪执行路径:不知道代码到底走了哪个分支
  2. 无法查看中间变量:不知道执行过程中变量值的变化
  3. 无法定位错误位置:不知道具体哪一行代码导致了问题
  4. 调试效率低下:只能通过修改规则、重新执行来反复测试

规则分步调试器的作用是:

  • 逐行跟踪规则执行过程
  • 实时查看变量值变化
  • 快速定位问题所在行
  • 提高规则开发和维护效率
  • 降低规则开发的学习成本

整体架构设计

我们的 QLExpress 规则分步调试器由以下几个组件构成:

  1. 调试器核心:实现规则的逐行执行和跟踪
  2. 执行轨迹收集器:收集执行过程中的详细信息
  3. 变量监控器:实时监控和记录变量值变化
  4. 调试信息展示:将调试信息以友好的方式展示
  5. SpringBoot 集成:与 SpringBoot 框架无缝集成

让我们看看如何在 SpringBoot 中实现这套调试系统:

1. 引入 QLExpress 依赖

首先在 pom.xml 中引入 QLExpress 依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>QLExpress</artifactId>
    <version>3.2.0</version>
</dependency>

2. 创建调试器核心类

实现 QLExpress 的调试器核心:

@Slf4j
public class QLExpressDebugger {
    
    private final ExpressRunner runner;
    private final List<DebugStep> debugSteps = new ArrayList<>();
    private final Map<String, Object> variableChanges = new LinkedHashMap<>();
    private final Map<String, Object> currentVariables = new HashMap<>();
    private boolean isDebugging = false;
    
    public QLExpressDebugger() {
        this.runner = new ExpressRunner();
        // 注册调试监听器
        registerDebugListener();
    }
    
    private void registerDebugListener() {
        runner.addListener(new ExpressListener() {
            @Override
            public void beforeExecute(InstructionSetContext context, InstructionNode node) {
                if (!isDebugging) return;
                
                // 记录执行前的状态
                DebugStep step = new DebugStep();
                step.setStepType(DebugStepType.BEFORE_EXECUTE);
                step.setLineNumber(node.getLine());
                step.setCode(node.getSourceCode());
                step.setVariables(new HashMap<>(currentVariables));
                debugSteps.add(step);
            }
            
            @Override
            public void afterExecute(InstructionSetContext context, InstructionNode node, Object result) {
                if (!isDebugging) return;
                
                // 记录执行后的状态
                DebugStep step = new DebugStep();
                step.setStepType(DebugStepType.AFTER_EXECUTE);
                step.setLineNumber(node.getLine());
                step.setCode(node.getSourceCode());
                step.setResult(result);
                
                // 检查变量变化
                Map<String, Object> newVariables = collectVariables(context);
                Map<String, Object> changedVariables = new HashMap<>();
                
                for (Map.Entry<String, Object> entry : newVariables.entrySet()) {
                    String key = entry.getKey();
                    Object newValue = entry.getValue();
                    Object oldValue = currentVariables.get(key);
                    
                    if (!Objects.equals(oldValue, newValue)) {
                        changedVariables.put(key, newValue);
                        variableChanges.put(key, newValue);
                    }
                }
                
                step.setVariableChanges(changedVariables);
                step.setVariables(new HashMap<>(newVariables));
                debugSteps.add(step);
                
                // 更新当前变量状态
                currentVariables.clear();
                currentVariables.putAll(newVariables);
            }
            
            @Override
            public void onException(InstructionSetContext context, InstructionNode node, Exception e) {
                if (!isDebugging) return;
                
                // 记录异常信息
                DebugStep step = new DebugStep();
                step.setStepType(DebugStepType.EXCEPTION);
                step.setLineNumber(node.getLine());
                step.setCode(node.getSourceCode());
                step.setException(e.getMessage());
                debugSteps.add(step);
            }
        });
    }
    
    private Map<String, Object> collectVariables(InstructionSetContext context) {
        Map<String, Object> variables = new HashMap<>();
        // 收集所有变量
        for (String varName : context.getLocalVariableNames()) {
            try {
                variables.put(varName, context.getLocalVariable(varName));
            } catch (Exception e) {
                log.warn("获取变量 {} 失败: {}", varName, e.getMessage());
            }
        }
        return variables;
    }
    
    public DebugResult debug(String rule, Map<String, Object> context) {
        isDebugging = true;
        debugSteps.clear();
        variableChanges.clear();
        currentVariables.clear();
        
        try {
            // 执行规则
            Object result = runner.execute(rule, context, null, true, false);
            
            // 构建调试结果
            return DebugResult.builder()
                .success(true)
                .result(result)
                .debugSteps(debugSteps)
                .variableChanges(variableChanges)
                .build();
        } catch (Exception e) {
            log.error("规则执行失败: {}", e.getMessage());
            return DebugResult.builder()
                .success(false)
                .error(e.getMessage())
                .debugSteps(debugSteps)
                .build();
        } finally {
            isDebugging = false;
        }
    }
    
    @Data
    @Builder
    public static class DebugResult {
        private boolean success;
        private Object result;
        private String error;
        private List<DebugStep> debugSteps;
        private Map<String, Object> variableChanges;
    }
    
    @Data
    public static class DebugStep {
        private DebugStepType stepType;
        private int lineNumber;
        private String code;
        private Object result;
        private Map<String, Object> variables;
        private Map<String, Object> variableChanges;
        private String exception;
    }
    
    public enum DebugStepType {
        BEFORE_EXECUTE,
        AFTER_EXECUTE,
        EXCEPTION
    }
}

3. 创建调试服务

创建一个服务来提供调试功能:

@Service
@Slf4j
public class QLExpressDebugService {
    
    private final QLExpressDebugger debugger = new QLExpressDebugger();
    
    /**
     * 调试规则
     */
    public QLExpressDebugger.DebugResult debugRule(String rule, Map<String, Object> context) {
        log.info("开始调试规则,规则长度: {},上下文变量数: {}", rule.length(), context.size());
        return debugger.debug(rule, context);
    }
    
    /**
     * 格式化调试结果
     */
    public String formatDebugResult(QLExpressDebugger.DebugResult result) {
        if (!result.isSuccess()) {
            return "规则执行失败: " + result.getError();
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append("规则执行成功,结果: ").append(result.getResult()).append("\n\n");
        sb.append("执行轨迹:\n");
        
        for (QLExpressDebugger.DebugStep step : result.getDebugSteps()) {
            sb.append(String.format("[%s] 第 %d 行: %s\n", 
                step.getStepType(), step.getLineNumber(), step.getCode()));
            
            if (step.getVariableChanges() != null && !step.getVariableChanges().isEmpty()) {
                sb.append("  变量变化: ");
                step.getVariableChanges().forEach((k, v) -> {
                    sb.append(k).append("=")
                      .append(v != null ? v.toString() : "null").append(", ");
                });
                sb.delete(sb.length() - 2, sb.length()).append("\n");
            }
            
            if (step.getResult() != null) {
                sb.append("  执行结果: " + step.getResult() + "\n");
            }
            
            if (step.getException() != null) {
                sb.append("  异常: " + step.getException() + "\n");
            }
            
            sb.append("\n");
        }
        
        return sb.toString();
    }
    
    /**
     * 执行规则(不调试)
     */
    public Object executeRule(String rule, Map<String, Object> context) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        return runner.execute(rule, context, null, true, false);
    }
}

4. 创建 Controller

创建 Controller 来提供调试接口:

@RestController
@RequestMapping("/api/ql-express")
@Slf4j
public class QLExpressDebugController {
    
    @Autowired
    private QLExpressDebugService debugService;
    
    @PostMapping("/debug")
    public ResponseEntity<?> debug(@RequestBody DebugRequest request) {
        try {
            QLExpressDebugger.DebugResult result = debugService.debugRule(
                request.getRule(), request.getContext());
            
            if (result.isSuccess()) {
                return ResponseEntity.ok(DebugResponse.builder()
                    .success(true)
                    .result(result.getResult())
                    .debugSteps(result.getDebugSteps())
                    .variableChanges(result.getVariableChanges())
                    .build());
            } else {
                return ResponseEntity.badRequest().body(DebugResponse.builder()
                    .success(false)
                    .error(result.getError())
                    .debugSteps(result.getDebugSteps())
                    .build());
            }
        } catch (Exception e) {
            log.error("调试失败: {}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(DebugResponse.builder()
                    .success(false)
                    .error("调试服务异常: " + e.getMessage())
                    .build());
        }
    }
    
    @PostMapping("/execute")
    public ResponseEntity<?> execute(@RequestBody DebugRequest request) {
        try {
            Object result = debugService.executeRule(
                request.getRule(), request.getContext());
            return ResponseEntity.ok(ExecuteResponse.builder()
                .success(true)
                .result(result)
                .build());
        } catch (Exception e) {
            log.error("执行失败: {}", e.getMessage());
            return ResponseEntity.badRequest().body(ExecuteResponse.builder()
                .success(false)
                .error(e.getMessage())
                .build());
        }
    }
    
    @Data
    public static class DebugRequest {
        private String rule;
        private Map<String, Object> context;
    }
    
    @Data
    @Builder
    public static class DebugResponse {
        private boolean success;
        private Object result;
        private String error;
        private List<QLExpressDebugger.DebugStep> debugSteps;
        private Map<String, Object> variableChanges;
    }
    
    @Data
    @Builder
    public static class ExecuteResponse {
        private boolean success;
        private Object result;
        private String error;
    }
}

5. 配置文件

在 application.yml 中配置调试器选项:

ql-express:
  debug:
    enabled: true
    max-rule-length: 10000
    max-execution-time: 5000

spring:
  application:
    name: ql-express-debugger

logging:
  level:
    com.example.ql.express: DEBUG

server:
  port: 8080

6. 创建调试工具类

创建一个工具类来简化调试过程:

@Component
public class QLExpressDebugUtil {
    
    @Autowired
    private QLExpressDebugService debugService;
    
    /**
     * 快速调试规则
     */
    public String quickDebug(String rule, Map<String, Object> context) {
        QLExpressDebugger.DebugResult result = debugService.debugRule(rule, context);
        return debugService.formatDebugResult(result);
    }
    
    /**
     * 调试并打印结果
     */
    public void debugAndPrint(String rule, Map<String, Object> context) {
        String result = quickDebug(rule, context);
        System.out.println("\n=== QLExpress 调试结果 ===");
        System.out.println(result);
        System.out.println("========================\n");
    }
    
    /**
     * 验证规则语法
     */
    public boolean validateRule(String rule) {
        try {
            ExpressRunner runner = new ExpressRunner();
            runner.parseInstructionSet(rule);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

实际应用效果

通过这套方案,我们可以实现:

调试前

规则执行结果不符合预期,但不知道问题出在哪

调试后

=== QLExpress 调试结果 ===
规则执行成功,结果: 0.9

执行轨迹:
[BEFORE_EXECUTE] 第 1 行: if (order.amount > 1000 && user.level >= 3) {
[AFTER_EXECUTE] 第 1 行: if (order.amount > 1000 && user.level >= 3) {
  执行结果: false

[BEFORE_EXECUTE] 第 7 行: } else if (order.amount > 500) {
[AFTER_EXECUTE] 第 7 行: } else if (order.amount > 500) {
  执行结果: true

[BEFORE_EXECUTE] 第 8 行:     discount = 0.95;
[AFTER_EXECUTE] 第 8 行:     discount = 0.95;
  变量变化: discount=0.95

[BEFORE_EXECUTE] 第 10 行: } else {
[AFTER_EXECUTE] 第 10 行: } else {
  执行结果: false

[BEFORE_EXECUTE] 第 13 行: return discount;
[AFTER_EXECUTE] 第 13 行: return discount;
  执行结果: 0.95
========================

发现问题

  • 规则走到了 order.amount > 500 分支,设置了 discount = 0.95
  • 但最终返回的结果是 0.9,说明可能有其他代码修改了 discount 值

最佳实践建议

  1. 规则设计:将复杂规则拆分为多个小规则,便于调试和维护
  2. 变量命名:使用清晰、有意义的变量名,便于理解执行过程
  3. 调试策略:先使用小数据集调试,再处理完整数据
  4. 性能考虑:在生产环境中关闭调试功能,只在开发和测试环境使用
  5. 安全防护:对规则内容进行安全检查,防止恶意代码注入
  6. 版本控制:对规则进行版本管理,便于回滚和对比
  7. 文档记录:为复杂规则添加注释和文档,便于后续维护

高级功能扩展

1. 断点调试

实现断点功能,支持在指定行设置断点:

public void setBreakpoint(int lineNumber) {
    // 实现断点逻辑
}

public DebugResult debugWithBreakpoints(String rule, Map<String, Object> context, List<Integer> breakpoints) {
    // 带断点的调试逻辑
}

2. 变量监控

实现变量监控功能,当变量值达到特定条件时触发断点:

public void watchVariable(String variableName, Predicate<Object> condition) {
    // 实现变量监控逻辑
}

3. 规则性能分析

添加性能分析功能,分析规则执行的时间消耗:

public PerformanceAnalysis analyzePerformance(String rule, Map<String, Object> context) {
    // 性能分析逻辑
}

4. 远程调试

支持远程调试功能,便于在生产环境排查问题:

public void startRemoteDebugServer(int port) {
    // 启动远程调试服务
}

总结

通过 SpringBoot + QLExpress 的组合,我们可以构建一套强大的规则分步调试器。这套方案具有以下优点:

  • 逐行跟踪:清晰展示规则的执行过程
  • 实时监控:实时查看变量值的变化
  • 快速定位:迅速定位问题所在行
  • 易于集成:与 SpringBoot 框架无缝集成
  • 功能丰富:支持多种调试功能和扩展
  • 性能友好:只在调试模式下启用,不影响生产环境性能

在实际项目中,建议根据具体的业务需求和规则复杂度,合理使用调试器功能,提高规则开发和维护的效率。对于复杂的业务规则,分步调试器可以大大减少排查问题的时间,提高开发效率。

希望这篇文章能对你有所帮助,如果你觉得有用,欢迎关注"服务端技术精选",我会持续分享更多实用的技术干货。


标题:SpringBoot + QLExpress 规则分步调试器:复杂逻辑卡在哪?逐行输出执行轨迹!
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/05/04/1777105775732.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消