最近在开发一个低代码平台时遇到了一个棘手的问题:如何让非技术人员也能灵活配置复杂的业务规则?传统方案要么限制太多(如固定下拉框选择),要么直接暴露代码编辑器(门槛太高)。经过多次迭代,我们最终采用Python的抽象语法树(AST)技术实现了自然语言式的条件表达式解析,让业务人员可以用接近日常语言的写法(如"订单金额>1000且VIP等级>=3")配置规则,系统自动转换为可执行的逻辑。
这种方案的核心优势在于:
当Python解释器执行代码时,会先将源代码转换为抽象语法树(Abstract Syntax Tree),这是一种分层级的树状结构表示。例如表达式"price > 100 and status == 'active'"会解析为:
code复制BoolOp(
op=And(),
values=[
Compare(
left=Name(id='price', ctx=Load()),
ops=[Gt()],
comparators=[Constant(value=100)]
),
Compare(
left=Name(id='status', ctx=Load()),
ops=[Eq()],
comparators=[Constant(value='active')]
)
]
)
Python内置的ast模块提供了完整的AST处理能力:
python复制import ast
expr = "price > 100 and status == 'active'"
tree = ast.parse(expr, mode='eval') # 解析为表达式节点
print(ast.dump(tree, indent=2)) # 打印AST结构
# 安全验证
safe_names = {'price', 'status'}
for node in ast.walk(tree):
if isinstance(node, ast.Name) and node.id not in safe_names:
raise ValueError(f"禁止访问变量 {node.id}")
关键提示:一定要用ast.literal_eval替代eval,或像上面这样严格检查节点类型和变量白名单
我们采用三层架构实现条件引擎:
mermaid复制graph TD
A[用户输入] --> B[语法高亮检查]
B --> C[AST解析]
C --> D[安全验证]
D --> E[JSON序列化存储]
E --> F[执行时反序列化]
F --> G[转换为Python字节码]
python复制class ConditionEngine:
def __init__(self, allowed_vars=None):
self.allowed_vars = allowed_vars or set()
def parse(self, expr):
try:
tree = ast.parse(expr, mode='eval')
self._validate(tree)
return {
'raw': expr,
'ast': self._serialize(tree.body)
}
except SyntaxError as e:
raise ValueError(f"语法错误: {e.msg}")
def _validate(self, node):
for sub_node in ast.walk(node):
if isinstance(sub_node, ast.Name):
if sub_node.id not in self.allowed_vars:
raise ValueError(f"禁止使用变量: {sub_node.id}")
elif isinstance(sub_node, ast.Call):
raise ValueError("函数调用被禁用")
def _serialize(self, node):
if isinstance(node, ast.Compare):
return {
'type': 'compare',
'left': self._serialize(node.left),
'ops': [self._serialize(op) for op in node.ops],
'comparators': [self._serialize(c) for c in node.comparators]
}
elif isinstance(node, ast.Name):
return {'type': 'var', 'id': node.id}
elif isinstance(node, ast.Constant):
return {'type': 'const', 'value': node.value}
elif isinstance(node, ast.Gt):
return {'type': 'op', 'op': '>'}
# 其他节点类型处理...
python复制import pickle
from functools import lru_cache
@lru_cache(maxsize=1000)
def get_compiled_expr(expr):
tree = ast.parse(expr, mode='eval')
validate_ast(tree)
return compile(tree, filename='<string>', mode='eval')
def evaluate(expr, context):
compiled = get_compiled_expr(expr)
return eval(compiled, {}, context)
| 风险类型 | 示例 | 防护措施 |
|---|---|---|
| 无限循环 | while True: pass | 禁止循环语句 |
| 危险函数调用 | import('os').system('rm -rf') | 禁止所有Call节点 |
| 属性访问 | a.class.base | 限制属性访问深度 |
| 内存耗尽 | 'a'*10**8 | 限制常量大小 |
python复制class AdvancedValidator(ast.NodeVisitor):
def __init__(self, max_attr_depth=2):
self.max_attr_depth = max_attr_depth
def visit_Call(self, node):
raise ValueError("函数调用被禁用")
def visit_Attribute(self, node):
if self._get_attr_depth(node) > self.max_attr_depth:
raise ValueError("属性访问层级过深")
self.generic_visit(node)
def _get_attr_depth(self, node):
depth = 0
while isinstance(node, ast.Attribute):
depth += 1
node = node.value
return depth
业务人员配置规则:
code复制用户等级 >= 2 且
(商品类别 in ['电子产品','家电'] 或
购物车总价 > 2000)
对应的AST处理流程:
设备运维人员配置:
code复制温度 > 75 且 持续分钟数 >= 5 或
电压波动率 > 0.15 且 不是测试设备
特殊处理:
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 提示"语法错误"但表达式看似正确 | 使用了保留关键字作为变量名 | 在解析前进行词法分析 |
| 复杂表达式执行缓慢 | 未启用AST缓存 | 使用lru_cache装饰器 |
| 返回意外None值 | 使用了and/or短路逻辑 | 明确提示用户使用完整条件 |
python复制def print_ast(expr):
import astpretty
tree = ast.parse(expr, mode='eval')
astpretty.pprint(tree.body)
# 示例输出:
# Compare(
# left=Name(id='temperature', ctx=Load()),
# ops=[Gt()],
# comparators=[Constant(value=30)]
# )
python复制def optimize_condition(ast_json):
# 实现类似以下优化:
# "a > 1 and a > 3" -> "a > 3"
# "status == 'active' or status == 'pending'" -> "status in ['active', 'pending']"
pass
这个方案在我们生产环境已稳定运行2年,日均处理超过50万次条件判断。最关键的收获是:AST虽然前期实现成本较高,但后期的灵活性和安全性回报巨大。对于需要平衡灵活性与安全性的场景,这绝对是个值得深入的技术方向。