SpringBoot + 自定义 DSL + Groovy 脚本:构建可拖拽的业务规则配置平台
业务规则配置的痛点
在我们的日常开发中,经常会遇到这样的场景:
- 产品经理:"这个活动规则要支持多种条件组合,用户可以根据需要自己配置"
- 运营人员:"我想要配置一个促销规则:购买满100元且是VIP用户,再加购任意商品就送优惠券"
- 技术人员:"又要改代码,这次需要重新发布..."
这种硬编码的方式不仅开发效率低,而且每次业务规则变化都需要技术人员参与,严重影响了业务响应速度。
解决方案思路
今天我们要解决的,就是如何构建一个可视化的业务规则配置平台,让业务人员也能自己配置复杂的业务规则。
核心思路是:
- 可视化配置:通过拖拽方式配置业务规则
- 自定义DSL:定义领域特定语言来表达业务逻辑
- Groovy脚本执行:运行时动态执行业务规则
- 规则验证:确保配置的规则语法正确、逻辑合理
技术选型
- SpringBoot:快速搭建应用
- Groovy:动态脚本执行
- Vue.js:前端可视化配置界面
- Jackson/Gson:JSON序列化
- JSR-223 Script Engine:脚本引擎集成
核心实现思路
1. 自定义DSL设计
首先,我们需要设计一套简单易懂的DSL来表达业务规则:
// 示例DSL
rule "VIP用户满额优惠" {
when
user.level == "VIP" && order.amount >= 100
then
discount = order.amount * 0.1
coupon = "COUPON_10_OFF"
}
2. 规则模型定义
定义规则的内部数据结构:
@Data
public class BusinessRule {
private String id;
private String name;
private String description;
private String dslScript; // DSL脚本内容
private List<RuleCondition> conditions; // 条件列表
private List<RuleAction> actions; // 动作列表
private boolean enabled; // 是否启用
private int priority; // 执行优先级
}
@Data
public class RuleCondition {
private String field; // 字段名
private String operator; // 操作符:==, !=, >, <, contains等
private Object value; // 比较值
private String logic; // 逻辑连接符:AND, OR
}
3. DSL解析器
创建DSL解析器来将可视化配置转换为可执行的脚本:
@Component
public class DSLParser {
public String parseToGroovy(BusinessRule rule) {
StringBuilder script = new StringBuilder();
// 构建条件部分
script.append("if (");
for (int i = 0; i < rule.getConditions().size(); i++) {
RuleCondition condition = rule.getConditions().get(i);
script.append(buildConditionExpression(condition));
if (i < rule.getConditions().size() - 1) {
script.append(" ").append(condition.getLogic()).append(" ");
}
}
script.append(") {\n");
// 构建动作部分
for (RuleAction action : rule.getActions()) {
script.append(" ").append(buildActionExpression(action)).append("\n");
}
script.append("}");
return script.toString();
}
private String buildConditionExpression(RuleCondition condition) {
String field = condition.getField();
String operator = condition.getOperator();
Object value = condition.getValue();
switch (operator) {
case "==":
return field + " == '" + value + "'";
case "!=":
return field + " != '" + value + "'";
case ">":
return field + " > " + value;
case "<":
return field + " < " + value;
case "contains":
return field + ".contains('" + value + "')";
default:
throw new IllegalArgumentException("Unsupported operator: " + operator);
}
}
private String buildActionExpression(RuleAction action) {
return action.getTarget() + " = " + action.getValue();
}
}
4. 规则执行引擎
创建规则执行引擎来运行Groovy脚本:
@Component
public class RuleEngine {
private final ScriptEngine groovyScriptEngine;
private final DSLParser dslParser;
public RuleEngine(DSLParser dslParser) {
this.dslParser = dslParser;
this.groovyScriptEngine = new ScriptEngineManager().getEngineByName("groovy");
}
public RuleExecutionResult executeRule(BusinessRule rule, Map<String, Object> context) {
try {
// 解析DSL为Groovy脚本
String groovyScript = dslParser.parseToGroovy(rule);
// 创建脚本执行上下文
Bindings bindings = groovyScriptEngine.createBindings();
bindings.putAll(context);
// 执行脚本
Object result = groovyScriptEngine.eval(groovyScript, bindings);
// 收集执行结果
RuleExecutionResult executionResult = new RuleExecutionResult();
executionResult.setSuccess(true);
executionResult.setRuleId(rule.getId());
executionResult.setRuleContext(new HashMap<>(bindings));
return executionResult;
} catch (Exception e) {
log.error("规则执行失败: {}", rule.getName(), e);
return RuleExecutionResult.failure(rule.getId(), e.getMessage());
}
}
public List<RuleExecutionResult> executeRules(List<BusinessRule> rules, Map<String, Object> context) {
// 按优先级排序执行规则
return rules.stream()
.sorted(Comparator.comparingInt(BusinessRule::getPriority))
.map(rule -> executeRule(rule, context))
.collect(Collectors.toList());
}
}
5. 规则配置服务
提供完整的规则管理功能:
@Service
@Transactional
public class RuleManagementService {
@Autowired
private RuleEngine ruleEngine;
@Autowired
private DSLParser dslParser;
/**
* 验证规则语法
*/
public ValidationResult validateRule(BusinessRule rule) {
try {
String groovyScript = dslParser.parseToGroovy(rule);
// 尝试编译脚本以验证语法
CompilerConfiguration config = new CompilerConfiguration();
config.setOptimizationOptions(Collections.singletonMap("indy", false));
GroovyShell shell = new GroovyShell(config);
shell.parse(groovyScript);
return ValidationResult.success();
} catch (Exception e) {
return ValidationResult.failure("规则语法错误: " + e.getMessage());
}
}
/**
* 测试规则执行
*/
public RuleTestResult testRule(BusinessRule rule, Map<String, Object> testData) {
return ruleEngine.executeRule(rule, testData);
}
/**
* 保存规则
*/
public void saveRule(BusinessRule rule) {
// 验证规则
ValidationResult validation = validateRule(rule);
if (!validation.isSuccess()) {
throw new BusinessException("规则验证失败: " + validation.getMessage());
}
// 保存到数据库
ruleRepository.save(rule);
}
/**
* 批量执行规则
*/
public List<RuleExecutionResult> executeRulesForContext(String contextType, Map<String, Object> context) {
// 根据上下文类型获取启用的规则
List<BusinessRule> activeRules = ruleRepository.findByContextTypeAndEnabledTrue(contextType);
return ruleEngine.executeRules(activeRules, context);
}
}
6. REST API接口
提供前后端交互的API:
@RestController
@RequestMapping("/api/rules")
public class RuleController {
@Autowired
private RuleManagementService ruleService;
/**
* 获取规则列表
*/
@GetMapping
public Result<List<BusinessRule>> getRules(
@RequestParam(required = false) String contextType,
@RequestParam(defaultValue = "true") Boolean enabled) {
List<BusinessRule> rules = ruleService.getRules(contextType, enabled);
return Result.success(rules);
}
/**
* 保存规则
*/
@PostMapping
public Result<String> saveRule(@RequestBody BusinessRule rule) {
ruleService.saveRule(rule);
return Result.success("规则保存成功");
}
/**
* 验证规则
*/
@PostMapping("/validate")
public Result<ValidationResult> validateRule(@RequestBody BusinessRule rule) {
ValidationResult result = ruleService.validateRule(rule);
return Result.success(result);
}
/**
* 测试规则
*/
@PostMapping("/test")
public Result<RuleTestResult> testRule(@RequestBody RuleTestRequest request) {
RuleTestResult result = ruleService.testRule(request.getRule(), request.getTestData());
return Result.success(result);
}
/**
* 执行规则
*/
@PostMapping("/execute")
public Result<List<RuleExecutionResult>> executeRules(@RequestBody RuleExecutionContext context) {
List<RuleExecutionResult> results = ruleService.executeRulesForContext(
context.getContextType(),
context.getContextData()
);
return Result.success(results);
}
}
前端可视化设计
前端主要包含以下几个组件:
- 规则画布:可拖拽的规则配置界面
- 条件配置面板:配置规则条件
- 动作配置面板:配置规则动作
- 预览面板:实时预览生成的DSL代码
- 测试面板:测试规则执行结果
前端可以通过拖拽组件来构建规则逻辑,然后实时生成相应的DSL脚本。
安全性考虑
在实际应用中,需要注意以下安全问题:
- 脚本沙箱:限制Groovy脚本的执行权限
- 输入验证:严格验证用户输入,防止恶意脚本注入
- 执行时间限制:设置脚本执行超时时间
- 资源限制:限制脚本使用的内存和CPU资源
优势分析
相比传统硬编码方式,这种方案的优势明显:
- 灵活性:业务人员可自主配置复杂规则,无需开发介入
- 可视化:拖拽式配置,降低使用门槛
- 可维护性:规则集中管理,便于维护和调试
- 扩展性:支持复杂的条件组合和业务逻辑
- 实时生效:规则修改后可立即生效,无需重启应用
注意事项
- 性能影响:动态脚本执行会有一定性能开销
- 调试困难:相比静态代码,动态脚本调试相对困难
- 安全性:需要严格的权限控制和安全验证
- 版本管理:需要对规则进行版本管理,支持回滚
总结
通过SpringBoot + 自定义DSL + Groovy脚本的技术组合,我们可以构建一个强大而灵活的业务规则配置平台。这不仅能大幅提升业务响应速度,还能解放开发人员,让他们专注于更有价值的技术工作。
在实际项目中,建议根据具体业务需求进行定制化开发,并充分考虑安全性、性能和可维护性等因素。
服务端技术精选,专注分享后端开发实战技术,助力你的技术成长!
标题:SpringBoot + 自定义 DSL + Groovy 脚本:构建可拖拽的业务规则配置平台
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/09/1767936645644.html
0 评论