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;"
当这样的规则执行结果不符合预期时,我们很难直接看出问题出在哪里:
- 无法跟踪执行路径:不知道代码到底走了哪个分支
- 无法查看中间变量:不知道执行过程中变量值的变化
- 无法定位错误位置:不知道具体哪一行代码导致了问题
- 调试效率低下:只能通过修改规则、重新执行来反复测试
规则分步调试器的作用是:
- 逐行跟踪规则执行过程
- 实时查看变量值变化
- 快速定位问题所在行
- 提高规则开发和维护效率
- 降低规则开发的学习成本
整体架构设计
我们的 QLExpress 规则分步调试器由以下几个组件构成:
- 调试器核心:实现规则的逐行执行和跟踪
- 执行轨迹收集器:收集执行过程中的详细信息
- 变量监控器:实时监控和记录变量值变化
- 调试信息展示:将调试信息以友好的方式展示
- 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. 断点调试
实现断点功能,支持在指定行设置断点:
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
公众号:服务端技术精选
评论