动态标记黑科技SQL 表达式 切面编程实现灵活规则引擎本文将分享一种优雅的解决方案 —— 通过 SQL 表达式结合 Spring AOP 切面编程实现无需修改代码即可动态给订单配置标记规则的轻量级引擎。 需求背景从简单判断到灵活规则的进化想象这样一个场景某天老板提出需求当订单更新时需要根据字段值自动打上对应标记。最初的想法非常简单不过是几个 if-else 判断javaif (order.getProductId().equals(4000001)) { order.addMark(重要产品); } if (orderRate.getServerRate() 90) { orderRate.addMark(高评分服务); }但看到具体要打的密密麻麻的标记陷入了沉思业务要求支持多个多张表、多个字段的组合条件比如为创建人是某些用户的单子打上【合作伙伴】的标记评分为差评的打上【差】标记等等运营人员希望能在后台直接配置规则无需开发介入面对这种 规则频繁变更 的典型场景传统硬编码方式会导致代码臃肿标记的规则可能经常出现变动每次新增/修改规则都要修改核心逻辑维护困难条件组合复杂时难以理清逻辑写死的逻辑无法快速响应需求变化扩展性差无法支持运营人员自助配置标记的规则可能经常出现变动缺乏灵活性不同业务部门、不同客户可能需要不同的标记规则️ 解决方案SQL 表达式 Spring AOP 的黄金组合经过技术选型我们决定采用 SQL 表达式 切面编程 的方案核心优势在于规则动态化使用 SQL 语法作为规则表达式支持复杂条件组合非侵入式通过 Spring AOP 切面拦截订单更新操作不修改原有业务逻辑易维护性规则配置与代码实现分离运营人员可自助管理轻量级配置简单不依赖复杂的框架或中间件功能可快速迭代上线整体实现思路如下用户在前端输入 SQL 风格的规则表达式使用 JSqlParser 解析表达式为抽象语法树通过 Spring AOP 拦截订单、订单评分表的更新方法构建包含相关数据的上下文对象执行规则匹配符合条件则添加标记核心组件实现从设计到代码的完整落地1. RuleContext数据访问上下文容器首先需要创建一个数据访问上下文用于封装相关业务对象并提供统一的字段访问接口javapackage com.example.system.api.domain; import com.example.common.core.utils.sql.FieldAccessor; import com.example.system.api.domain.table.TableA; import com.example.system.api.domain.table.TableB; import java.lang.reflect.Field; /** * 规则执行上下文 - 封装业务数据并提供字段访问接口 */ public class RuleContext implements FieldAccessor { private TableA tableA; private TableB tableB; public RuleContext(TableA tableA, TableB tableB) { this.tableA tableA; this.tableB tableB; } Override public Object getFieldValue(String fieldName) { // 处理带TableA前缀的字段 if (fieldName.startsWith(tableA.)) { String subField fieldName.substring(tableA..length()); return getFieldFromObject(tableA, subField); } // 处理带TableB前缀的字段 else if (fieldName.startsWith(tableB.)) { String subField fieldName.substring(tableB..length()); return getFieldFromObject(tableB, subField); } // 无前缀字段默认从TableA获取 else if (!fieldName.contains(.)) { return getFieldFromObject(tableA, fieldName); } return null; } /** * 从对象中反射获取字段值 */ private Object getFieldFromObject(Object obj, String fieldName) { try { Field field obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (NoSuchFieldException e) { throw new IllegalArgumentException(未知字段: fieldName, e); } catch (IllegalAccessException e) { throw new RuntimeException(无法访问字段: fieldName, e); } } }这个上下文容器实现了FieldAccessor接口支持通过 表名.字段名 的方式访问数据例如tableA.productId - 访问 TableA 中的 productId 字段tableB.serverRate - 访问 TableB 中的 serverRate 字段2. JSqlParserUtilSQL 表达式解释器核心接下来是关键的 SQL 表达式解析与求值组件它负责将用户输入的 SQL 表达式转换为可执行的逻辑判断javapublic class JSqlParserUtil { /** * 计算SQL表达式的值 * param expression SQL条件表达式 * param fieldAccessor 字段访问器 * return 表达式计算结果 */ public static boolean evaluate(String expression, FieldAccessor fieldAccessor) { try { // 解析SQL条件表达式 Expression parsedExpression CCJSqlParserUtil.parseCondExpression(expression); // 计算表达式值 return evaluateExpression(parsedExpression, fieldAccessor); } catch (Exception e) { throw new RuntimeException(表达式解析失败: expression, e); } } /** * 递归计算表达式值 */ private static boolean evaluateExpression(Expression expression, FieldAccessor fieldAccessor) { // 处理括号嵌套 if (expression instanceof Parenthesis) { return evaluateExpression(((Parenthesis) expression).getExpression(), fieldAccessor); } // 处理AND逻辑 else if (expression instanceof AndExpression) { return evaluateExpression(((AndExpression) expression).getLeftExpression(), fieldAccessor) evaluateExpression(((AndExpression) expression).getRightExpression(), fieldAccessor); } // 处理OR逻辑 else if (expression instanceof OrExpression) { return evaluateExpression(((OrExpression) expression).getLeftExpression(), fieldAccessor) || evaluateExpression(((OrExpression) expression).getRightExpression(), fieldAccessor); } // 处理等于条件 else if (expression instanceof EqualsTo) { return compare((BinaryExpression) expression, fieldAccessor, Object::equals); } // 处理不等于条件 else if (expression instanceof NotEqualsTo) { return compare((BinaryExpression) expression, fieldAccessor, (a, b) - !a.equals(b)); } // 处理大于条件 else if (expression instanceof GreaterThan) { return compare((BinaryExpression) expression, fieldAccessor, (a, b) - compareValues(a, b) 0); } // 处理小于条件 else if (expression instanceof MinorThan) { return compare((BinaryExpression) expression, fieldAccessor, (a, b) - compareValues(a, b) 0); } // 处理大于等于条件 else if (expression instanceof GreaterThanEquals) { return compare((BinaryExpression) expression, fieldAccessor, (a, b) - compareValues(a, b) 0); } // 处理小于等于条件 else if (expression instanceof MinorThanEquals) { return compare((BinaryExpression) expression, fieldAccessor, (a, b) - compareValues(a, b) 0); } // 处理IN条件 else if (expression instanceof InExpression) { return handleInExpression((InExpression) expression, fieldAccessor); } else { throw new UnsupportedOperationException(不支持的表达式类型: expression); } } /** * 处理IN表达式 */ private static boolean handleInExpression(InExpression inExpression, FieldAccessor fieldAccessor) { Expression leftExpression inExpression.getLeftExpression(); ItemsList rightItemsList inExpression.getRightItemsList(); // 验证左侧必须是字段 if (!(leftExpression instanceof Column)) { throw new IllegalArgumentException(IN表达式的左侧必须是字段名); } Column column (Column) inExpression.getLeftExpression(); // 获取字段路径如tableA.productId String param getFieldPath(column); Object fieldValue fieldAccessor.getFieldValue(param); if (fieldValue null) { return false; } // 验证右侧必须是值列表 if (rightItemsList null) { throw new IllegalArgumentException(IN表达式的右侧必须是值列表); } // 处理表达式列表 if (rightItemsList instanceof ExpressionList) { ListExpression expressions ((ExpressionList) rightItemsList).getExpressions(); for (Expression expr : expressions) { Object value getRightValue(expr); if (value ! null value.equals(fieldValue)) { return true; } } } else { throw new IllegalArgumentException(不支持的值列表类型: rightItemsList.getClass()); } return false; } /** * 比较二元表达式 */ private static boolean compare(BinaryExpression binaryExpression, FieldAccessor fieldAccessor, BiPredicateObject, Object predicate) { try { Column column (Column) binaryExpression.getLeftExpression(); String param getFieldPath(column); Expression rightExpression binaryExpression.getRightExpression(); // 获取左侧字段值 Object leftValue fieldAccessor.getFieldValue(param); if (leftValue null) { return false; } // 获取右侧表达式值 Object rightValue getRightValue(rightExpression); // 类型转换处理如Integer转Long if (leftValue instanceof Integer) { leftValue Long.parseLong(leftValue.toString()); } return predicate.test(leftValue, rightValue); } catch (Exception e) { throw new IllegalArgumentException(表达式值比较失败: binaryExpression, e); } } /** * 获取字段完整路径表名.字段名 */ private static String getFieldPath(Column column) { if (column null) { throw new IllegalArgumentException(列对象不能为null); } String leftField column.getColumnName(); String tableName null; if (column.getTable() ! null) { tableName column.getTable().getName(); } return (tableName ! null !tableName.isEmpty()) ? tableName . leftField : leftField; } /** * 获取右侧表达式的值 */ private static Object getRightValue(Expression expression) { if (expression instanceof StringValue) { return ((StringValue) expression).getValue(); // 字符串值 } else if (expression instanceof LongValue) { return ((LongValue) expression).getValue(); // 整数值 } else if (expression instanceof DoubleValue) { return ((DoubleValue) expression).getValue(); // 浮点数值 } else { throw new IllegalArgumentException(不支持的右侧值类型: expression); } } /** * 统一值比较方法 */ private static int compareValues(Object a, Object b) { if (a instanceof Comparable b instanceof Comparable a.getClass().equals(b.getClass())) { SuppressWarnings(unchecked) ComparableObject comparableA (ComparableObject) a; return comparableA.compareTo(b); } else { throw new IllegalArgumentException(类型不匹配: 无法比较 a.getClass() 和 b.getClass()); } } }这个工具类实现了完整的 SQL 表达式解析与求值逻辑支持基础比较运算符、、、、、逻辑运算符AND、OR集合运算符IN括号嵌套优先级处理 实战应用从配置到执行的完整流程规则配置示例在实际应用中用户可以在前台页面配置如下规则表达式IN 表达式场景tableA.productId IN (4000001, 4000002)当 TableA 的 productId 属于指定集合时触发标记复合条件场景(tableA.productId 4000001 AND tableB.serverRate 90) OR tableA.status PAID满足 重要产品且高评分 或 已支付 条件时触发标记多表关联条件tableA.createTime 2025-06-28 AND tableB.score 85TableA 创建时间在 2025-06-28 后且 TableB 评分不低于 85 时触发标记切面编程实现通过 Spring AOP 拦截订单更新操作实现规则的自动触发javaimport org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; /** * 订单更新切面 - 自动应用标记规则 */ Aspect Component public class OrderMarkAspect { Resource private RuleService ruleService; Resource private OrderExtMapper orderExtMapper; /** * 拦截订单更新方法 */ Around(execution(* com.example.mapper.orderMapper.updateOrder(..))) public Object handleUpdate(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法参数 Object[] args joinPoint.getArgs(); Order order (Order) args[0]; // 获取关联的TableB数据 TableB tableB orderExtMapper.selectById(order.getExtId()); // 构建规则执行上下文 RuleContext context new RuleContext(order, tableB); // 加载所有标记规则 ListRule rules ruleService.loadRulesByType(mark); // 执行规则匹配 for (Rule rule : rules) { if (JSqlParserUtil.evaluate(rule.getExpression(), context)) { // 匹配规则则应用标记 applyMarkToEntity(order, rule.getMarkCode()); } } // 执行原始更新操作 return joinPoint.proceed(); } /** * 标记 */ private void applyMarkToEntity(Order order, String markCode) { // 实际项目中可能需要更新数据库或其他操作 order.addMark(markCode); // 记录标记日志... } }这个切面实现了非侵入式的规则触发机制当订单更新方法被调用时会自动加载相关业务数据TableA 和 TableB构建规则执行上下文匹配所有标记规则对符合条件的订单应用标记 测试验证规则引擎的可靠性保障为确保规则引擎的正确性部分测试用例如下测试表达式预期结果实际结果tableA.productId 4000001命中✅tableB.serverRate 90命中✅tableA.productId IN (4000001, 4000003)命中✅(tableA.status NEW AND tableB.score 70) OR tableA.type URGENT命中✅测试结果表明规则引擎能够准确解析并执行各种复杂条件表达式满足实际业务需求。 总结动态规则引擎的价值与扩展通过 SQL 表达式与 Spring AOP 的结合我们实现了一个轻量级但功能强大的动态规则引擎其核心价值在于 官方宣传版需求响应效率提升运营人员可直接在后台配置规则无需开发介入代码可维护性增强规则配置与业务逻辑分离避免条件判断代码膨胀系统扩展性提高支持无限扩展的复杂规则组合适应业务快速变化 程序员真实心声业务/产品提新需求时优雅甩锅 自己去前台页面写 SQL 呀运营同学改规则时边喝奶茶边提醒 改完记得测试别把weekday 周五写成周一不然全组陪你加班哦这种 代码少改、需求照办 的快乐就像周五下午五点半老板突然说今天不用加班下班—— 爽就完事了 原文链接https://juejin.cn/post/7520510822167183412