背景
因业务需求要做一个条件可视化组件, 这个组件我设计了草图(图1)需要支持逻辑运算(OR, AND), 关系运算(大于,小于,等于,包含,区间等).
规则树组件
简单调研了下蚂蚁的RuleTree, 当前内部已有的RuleView. 根据当前的需求已经现有工程的依赖, 还有部分定制功能来衡量选择.
组件 | 逻辑AND | 逻辑OR | 右值跟随左值类型变化 | 关系运算 | 功能完整 | antd4支持 | 定制难度 |
---|---|---|---|---|---|---|---|
RuleTree | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | 中等 |
RuleView | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | 简单 |
简单
从定制难度上来说RuleView简单容易上手, 而且内部有多个场景在使用, RuleTree很老都是使用的是antd3, 和现有的技术栈也不搭.
改造后的RuleView, 支持逻辑切换OR, AND
import React, { useEffect } from 'react';
import classnames from 'classnames';
const OPERATOR_AND = 'AND';
const OPERATOR_OR = 'OR';
const OPERATORS = [OPERATOR_AND, OPERATOR_OR];
export const RuleView = ({
value,
onChange,
children,
isSubTree,
}: {
value?: string;
// defaultValue: 'AND' | 'OR';
onChange?: (v: string) => void;
children: React.ReactNode;
isSubTree?: boolean;
}) => {
const [op, setOp] = React.useState(value || 'OR');
useEffect(() => {
if (value) {
setOp(value);
}
}, [value]);
const toggleOperator = () => {
const nextIndex = (OPERATORS.indexOf(op) + 1) % OPERATORS.length;
const newOp = OPERATORS[nextIndex];
// setOp(newOp);
onChange?.(newOp);
};
return (
<div className="flex items-center w-full">
<div
className={classnames(
'relative after:content-[""] after:block after:absolute after:w-7 after:top-1/2 after:bottom-0 after:border-t-2 after:left-full',
{ 'pl-7': isSubTree },
)}
>
<div
onDoubleClick={toggleOperator}
className={classnames(
'flex pl-2 pr-2 items-center rounded text-white h-8',
{
'bg-sky-400': op === OPERATOR_AND,
'bg-green-400': op === OPERATOR_OR,
},
)}
>
{op}
</div>
</div>
<div className="ml-7 flex">
<div>{children}</div>
</div>
</div>
);
};
export const RuleViewItem = ({
children,
isFirst,
isLast,
}: {
children: React.ReactNode;
isFirst?: boolean;
isLast?: boolean;
}) => {
return (
<div
className={classnames(
'relative before:block before:content-[""] before:absolute before:w-0.5 before:border-l-2',
{
'before:top-1/2 before:bottom-0': !isLast && isFirst,
'before:top-0 before:bottom-1/2': !isFirst && isLast,
'before:top-0 before:bottom-0': !isFirst && !isLast,
},
'after:block after:content-[""] after:absolute after:top-1/2 after:bottom-0 after:w-7 after:border-t-2',
)}
>
{children}
</div>
);
};
样式依赖Tailwind CSS
npm install tailwindcss postcss autoprefixer css-loader style-loader --save-dev
npx tailwindcss init -p
这样会得到一个tailwind.config.js, postcss.config.js文件
检查文件postcss.config.js内容:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
在全局index.css中添加
@tailwind base;
@tailwind components;
@tailwind utilities;
规则树的数据结构定义
export interface ConditionValue {
operation: string;
children?: ConditionValue[];
left?: DataValue;
right?: DataValue;
}
export interface DataValue {
kind?: string;
type?: string;
value?: any;
format?: string;
}
规则组件定义
const ConditionView = ({disabled}:{disabled?:boolean}) => {
return <div className="flex justify-center overflow-x-auto">
<Form.Item label="条件" name={['condition','operation']} noStyle>
<RuleView >
<Form.List
name={['condition','children']}
rules={[
{
validator: (r, v) => {
console.log('root condition >>>> ', r, v);
if (!v || !v?.length || v?.length === 0) {
return Promise.reject(new Error('条件组不能为空'));
}
const errors = v.map((child: ConditionValue) =>
validatorRuleTreeItem(child),
);
return Promise.all(errors)
.then(() => Promise.resolve()) // 如果所有子项验证通过
.catch(err => Promise.reject(err)); // 如果有任何一个子项验证失败
},
},
]}
>
{(fields, {add, remove}, {errors}) => (
<>
{fields.map((field, index) => {
return (<div key={field.key}>
<Form.Item label="节点" name={[field.name]} noStyle>
<ConditionNodeView
key={field.key}
field={field}
fields={fields}
add={add}
remove={remove}
index={index}
/>
</Form.Item>
</div>
);
})}
<RuleViewItem isLast>
<div className="h-10 relative flex items-center pl-7">
<Tooltip title="添加一组条件">
<Button
icon={<PlusOutlined />}
disabled={disabled}
onClick={() => add(initialFieldValue)}
/>
</Tooltip>
</div>
</RuleViewItem>
<Form.ErrorList errors={errors} />
</>
)}
</Form.List>
</RuleView>
</Form.Item>
</div>;
};
export const ConditionNodeView = ({
value,
onChange,
field,
fields,
add,
remove,
index,
disabled,
}: {
value?: ConditionValue;
onChange?: (v: ConditionValue) => void;
field: FormListFieldData;
fields: FormListFieldData[];
add: (defaultValue?: any, insertIndex?: number | undefined) => void;
remove: (index: number | number[]) => void;
index: number;
disabled?: boolean;
}) => {
// console.log('ConditionNodeView >>>> ', value);
if(value?.operation === 'OR' || value?.operation === 'AND') {
return (
<>
<RuleViewItem key={field.key} isFirst={index === 0}>
<Form.Item label="条件" name={[field.name, 'operation']} noStyle>
<RuleView isSubTree>
<Form.List name={[field.name, 'children']}>
{(subFields, { add: addSubField, remove: removeSubField }, {errors:subErrors}) =>
<>
{subFields.map((subField, subIndex) => {
return (<div key={subField.key}>
<Form.Item label="节点" name={[subField.name]} noStyle>
<ConditionNodeView
key={subField.key}
field={subField}
fields={subFields}
add={addSubField}
remove={removeSubField}
index={subIndex}
/>
</Form.Item>
</div>
);
})}
<RuleViewItem isLast>
<div className="h-10 relative flex items-center pl-7">
<Tooltip title="添加一组条件">
<Button
type="dashed"
icon={<PlusOutlined />}
disabled={disabled}
onClick={() => addSubField(initialFieldValue)}
/>
</Tooltip>
<Popconfirm
title="确认删除该组条件?"
disabled={fields?.length <= 1 || disabled}
onConfirm={() => { remove(field.name); }}
>
<Tooltip
title={
fields?.length !== 1 ? '删除该组条件' : undefined
}
>
<Button
type="dashed"
icon={
<DeleteOutlined
style={{
color: fields?.length <= 1 || disabled ? 'gray' : '#f50',
}}
/>
}
disabled={fields.length <= 1 || disabled}
/>
</Tooltip>
</Popconfirm>
<Divider type="vertical" />
<Tooltip title="添加一个条件">
<Button shape="circle"
type="dashed"
icon={<PlusOutlined />}
style={{
color: !disabled ? '#f50' : 'gray',
}}
onClick={() => !disabled && addSubField(initialSubFieldValue)}/>
</Tooltip>
</div>
</RuleViewItem>
<Form.ErrorList errors={subErrors} />
</>
}
</Form.List>
</RuleView>
</Form.Item>
</RuleViewItem>
</>
);
}
return (
<>
<RuleViewItem key={field.key} isFirst={index === 0}>
<div className="flex items-center ml-7 pb-1">
<div className="flex pl-3 pr-3 pt-3 bg-slate-100">
<Form.Item
key={field.key}
name={[field.name]}
validateFirst
rules={[
{ required: true },
{
validator: (r, v) => {
console.log('RuleViewItem condition >>>> ', r, v);
if (!v) {
return Promise.reject(new Error('条件不能为空'));
}
return validatorRuleTreeItem(v);
},
},
]}
>
<RuleTreeItem
disabled={disabled} />
</Form.Item>
<div className="mt-1.5 ml-3" data-desc="添加单个条件">
<CopyOutlined
style={{
color: !disabled ? '#f50' : 'gray',
}}
onClick={() => !disabled && add(value || initialSubFieldValue)}
/>
</div>
<div className="mt-1.5 ml-3" data-desc="删除单个条件">
<DeleteOutlined
style={{
color: fields.length !== 1 && !disabled ? '#f50' : 'gray',
}}
onClick={() => fields.length !== 1 && !disabled && remove(field.name)}
/>
</div>
</div>
</div>
</RuleViewItem>
</>
);
};
// 初始字段值
export const initialFieldValue = {
operation: 'AND',
children: [
{
operation: OPERATOR.EQ,
left: { kind: 'REF', type: 'STRING', operation: '' },
right: { kind: 'CONST', type: 'STRING', operation: '' },
},
],
};
export const initialSubFieldValue = {
operation: OPERATOR.EQ,
left: { kind: 'REF', type: 'STRING', operation: '' },
right: { kind: 'CONST', type: 'STRING', operation: '' },
}
规则树左右值选中组件
interface RuleTreeItemProps {
value?: ConditionValue;
onChange?: (v?: ConditionValue) => void;
disabled?: boolean;
mapping?: ModelMapping[];
}
export const RuleTreeItem: React.FC<RuleTreeItemProps> = ({
value,
onChange,
disabled = false,
}) => {
const modelMappingContext = useContext(ModelMappingContext);
return <>
<RuleTreeItemWarp value={value} onChange={onChange} mapping={modelMappingContext?.mapping} disabled={disabled}/>
</>;
}
// 自定义 组件
const RuleTreeItemWarp: React.FC<RuleTreeItemProps> = ({
value,
onChange,
disabled = false,
mapping,
}) => {
const handleLeftChange = useCallback(
(v?: string) => {
const map = mapping?.find(it => it.identifier === v);
const v1 = {
operation: value?.operation || '',
left: {
value: v,
type: map?.type,
kind: map?.kind,
format: map?.format,
},
right: {
value: null,
type: value?.right?.type,
kind: value?.right?.kind,
format: value?.right?.format,
},
};
onChange && onChange(v1);
},
[value],
);
const handleOperatorChange = useCallback(
(v?: string) => {
const v1 = {
operation: v || '',
left: value?.left,
right: {
value: null,
type: value?.right?.type,
kind: value?.right?.kind,
format: value?.right?.format,
},
};
onChange && onChange(v1);
},
[value],
);
const handleRightKindChange = useCallback(
(v?: string) => {
const v1 = {
operation: value?.operation || '',
left: value?.left,
right: {
value: null,
type: value?.right?.type,
kind: v,
format: value?.right?.format,
},
};
onChange && onChange(v1);
},
[value],
);
const handleRightSelectChange = useCallback(
(v?: string) => {
const map = mapping?.find(it => it.identifier === v);
const v1 = {
operation: value?.operation || '',
left: value?.left,
right: {
value: v,
type: map?.type,
kind: value?.right?.kind,
format: map?.format,
},
};
onChange && onChange(v1);
},
[value],
);
const handleRightChange = useCallback(
(v?: string) => {
const v1 = {
operation: value?.operation || '',
left: value?.left,
right: {
value: v,
type: value?.left?.type,
kind: value?.right?.kind,
format: value?.left?.format,
},
};
onChange && onChange(v1);
},
[value],
);
const mappingOptions = useMemo(() => {
if (!mapping) return [];
const ops =
(mapping
?.filter(it => {
if (it.invisible) {
return !it.invisible;
}
return true;
})
.map(it => ({
label: it.name,
value: it.identifier,
})) as Option[]) ?? [];
// console.log('mappingOptions', ops);
return ops;
}, [value]);
return (
<>
<Space>
<Select
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase()) ||
(option?.value ?? '').toString().toLowerCase().includes(input.toLowerCase())
}
placeholder="模型数据"
defaultValue={value?.left?.value}
options={mappingOptions}
onChange={handleLeftChange}
style={{ minWidth: '120px' }}
disabled={disabled}
/>
<Select
value={value?.operation}
style={{ minWidth: '120px' }}
onChange={handleOperatorChange}
options={
value?.left?.type === DATA_TYPE.BOOLEAN
? [OPERATOR.EQ].map(it => ({
label: OPERATOR_MAP.get(it)?.label ?? it,
value: it,
})) ?? []
: [
OPERATOR.EQ,
OPERATOR.NEQ,
OPERATOR.GT,
OPERATOR.GTE,
OPERATOR.LT,
OPERATOR.LTE,
OPERATOR.BETWEEN,
OPERATOR.RANGE,
OPERATOR.IN,
OPERATOR.NOT_IN,
].map(it => ({
label: OPERATOR_MAP.get(it)?.label ?? it,
value: it,
})) ?? []
}
/>
<Select
placeholder="种类"
value={value?.right?.kind || DATA_KIND.CONST}
options={[
{ label: '引用', value: DATA_KIND.REF },
{ label: '输入', value: DATA_KIND.CONST },
]}
onChange={handleRightKindChange}
style={{ width: '100px' }}
disabled={disabled}
/>
{DATA_KIND.REF === value?.right?.kind ? (
<Select
showSearch
filterOption={(input, option) =>
(option?.label ?? '').toLowerCase().includes(input.toLowerCase()) ||
(option?.value ?? '')
.toString()
.toLowerCase()
.includes(input.toLowerCase())
}
placeholder="模型数据"
defaultValue={value?.right?.value}
options={mappingOptions}
onChange={handleRightSelectChange}
style={{ minWidth: '120px' }}
disabled={disabled}
/>
) : (
<RuleTreeItemRight
value={value}
onChange={handleRightChange}
disabled={disabled}
/>
)}
</Space>
</>
);
};
const RuleTreeItemRight = ({
value,
onChange,
disabled,
}: {
disabled?: boolean;
value?: ConditionValue;
onChange?: (v?: any) => void;
}): React.ReactElement => {
if (
value?.left?.type === DATA_TYPE.DATE ||
value?.left?.type === DATA_TYPE.SECOND ||
value?.left?.type === DATA_TYPE.MILLIS
) {
if (
value?.operation === OPERATOR.RANGE ||
value?.operation === OPERATOR.BETWEEN
) {
return (
<DateTimeRangePicker
value={value?.right?.value}
onChange={v => {
onChange?.(v);
}}
/>
);
} else if (
value?.operation === OPERATOR.IN ||
value?.operation === OPERATOR.NOT_IN
) {
return (
<DateTimeMultiPicker
value={value?.right?.value}
onChange={v => {
onChange?.(v);
}}
/>
);
} else {
return (
<DateTimePicker
value={value?.right?.value}
onChange={v => {
onChange?.(v);
}}
/>
);
}
} else if (value?.left?.type === DATA_TYPE.NUMBER) {
if (
value?.operation === OPERATOR.RANGE ||
value?.operation === OPERATOR.BETWEEN
) {
return (
<NumberRangePicker
value={value?.right?.value}
onChange={v => {
onChange?.(v);
}}
/>
);
} else if (
value?.operation === OPERATOR.IN ||
value?.operation === OPERATOR.NOT_IN
) {
return (
<Select
mode="tags"
allowClear
style={{ minWidth: '280px' }}
placeholder="Please select"
defaultValue={value?.right?.value || []}
onChange={v => {
onChange?.(v);
}}
/>
);
} else {
return (
<InputNumber
style={{ minWidth: '240px' }}
defaultValue={value?.right?.value}
onChange={v => {
onChange?.(v);
}}
/>
);
}
} else if (value?.left?.type === DATA_TYPE.BOOLEAN) {
return (
<Select
allowClear
style={{ minWidth: '160px' }}
placeholder="Please select true or false"
defaultValue={value?.right?.value}
options={[
{ label: '真', value: true },
{ label: '假', value: false },
]}
onChange={v => {
onChange?.(v);
}}
/>
);
}
if (
value?.operation === OPERATOR.IN ||
value?.operation === OPERATOR.NOT_IN
) {
return (
<Select
mode="tags"
allowClear
style={{ minWidth: '240px' }}
placeholder="Please select"
defaultValue={value?.right?.value || []}
onChange={v => {
onChange?.(v);
}}
/>
);
}
return (
<Input
style={{ minWidth: '160px' }}
disabled={disabled}
defaultValue={value?.right?.value}
onChange={e => {
onChange?.(e.target.value);
}}
/>
);
};
export const validatorRuleTreeItem = (v: ConditionValue): Promise<any> => {
if (v.operation === 'OR' || v.operation === 'AND') {
if (!v.children || v.children.length === 0) {
return Promise.reject(new Error('条件不能为空'));
}
const errors = v.children.map(child => validatorRuleTreeItem(child));
return Promise.all(errors)
.then(() => Promise.resolve()) // 如果所有子项验证通过
.catch(err => Promise.reject(err)); // 如果有任何一个子项验证失败
} else {
if (!v.left || !v.right) {
return Promise.reject(new Error('条件不能为空'));
}
if (!v.left.value || v.left.value === '') {
return Promise.reject(new Error('条件左值不能为空'));
}
if (!v.right.value || v.right.value === '') {
if (DATA_TYPE.BOOLEAN === v.right?.type) {
if (v.right?.value === null || undefined === v.right?.value) {
console.log('条件右值不能为空:', v);
return Promise.reject(new Error(`条件右值不能为空`));
} else {
return Promise.resolve();
}
}
console.log('条件右值不能为空:', v);
return Promise.reject(new Error(`条件右值不能为空`));
}
}
return Promise.resolve();
};
规则树效果预览
规则树数据预览
{
"operation": "OR",
"children": [
{
"children": [
{
"left": {
"type": "NUMBER",
"value": "orderStatus",
"kind": "REF"
},
"operation": "EQ",
"right": {
"kind": "CONST",
"type": "NUMBER",
"value": 5300
}
},
{
"children": [
{
"children": [
{
"left": {
"value": "bizType",
"kind": "REF",
"type": "NUMBER"
},
"operation": "EQ",
"right": {
"kind": "CONST",
"type": "NUMBER",
"value": 24
}
},
{
"right": {
"kind": "CONST",
"type": "STRING",
"value": [
"24001",
"2486"
]
},
"left": {
"type": "STRING",
"value": "cpCode",
"kind": "REF"
},
"operation": "IN"
}
],
"operation": "AND"
},
{
"children": [
{
"left": {
"type": "NUMBER",
"value": "bizType",
"kind": "REF"
},
"operation": "EQ",
"right": {
"kind": "CONST",
"type": "NUMBER",
"value": 56
}
},
{
"left": {
"type": "STRING",
"value": "cpCode",
"kind": "REF"
},
"operation": "IN",
"right": {
"kind": "CONST",
"type": "STRING",
"value": [
"5644",
"5625"
]
}
}
],
"operation": "AND"
}
],
"operation": "OR"
},
{
"left": {
"format": "yyyy-MM-dd HH:mm:ss",
"kind": "REF",
"type": "MILLIS",
"value": "expireTime"
},
"operation": "BETWEEN",
"right": {
"type": "MILLIS",
"value": [
"2024-09-01 00:00:00",
"2024-10-31 00:00:00"
],
"format": "yyyy-MM-dd HH:mm:ss",
"kind": "CONST"
}
},
{
"left": {
"kind": "REF",
"type": "NUMBER",
"value": "orderStatus"
},
"operation": "NEQ",
"right": {
"kind": "REF",
"type": "NUMBER",
"value": "beforeOrderStatus"
}
}
],
"operation": "AND"
}
]
}
规则引擎
规则树转换
规则树需要将数据转换成, 扁平的逻辑表达式, 例如:
(cpCode in [5644, 5625] and bizType == 56) or (cpCode in [2433, 2401] and bizType == 24)
定义实体:
使用递归将规则树转换成逻辑表达式
public class ConditionValue {
private String operation;
private List<ConditionValue> children;
// 左右值, 比较类型, 非OR,AND时候有值
private DataValue left;
private DataValue right;
public String expression() {
if (Operation.isLogical(operation) || operation == null) {
return children.stream().map(it -> it.expression()).collect(Collectors.joining(Operation.ofSymbol(operation), " ( ", " ) "));
}
if(Operation.NOT_IN.name().equals(operation)) {
return " (!(" + left.getValue() + " in " + right.getValue() + ")) ";
} else if (Operation.IN.name().equals(operation)) {
return " (" + left.getValue() + " in " + right.getValue() + ") ";
} else if (Operation.RANGE.name().equals(operation)) {
return " (" + left.getValue() + " >= " + right.getValue() + "[0] && " + left.getValue() + " <= " + right.getValue() + "[1]" + ") ";
} else if (Operation.BETWEEN.name().equals(operation)) {
return " (" + left.getValue() + " > " + right.getValue() + "[0] && " + left.getValue() + " < " + right.getValue() + "[1]" + ") ";
}
return " (" + left.getValue() + " " + Operation.ofSymbol(operation) + " " + right.getValue() + ") ";
}
}
规则引擎设计
引擎使用antlr4定义一个简单脚本, 弱类型语法, 使用groovy、golang、javascript的一些常见语法组成, antlr4天然支持多语言, 表达式执行就天然支持多语言, 不受受限于语言.
a. 支持逻辑运行OR、AND
b. 支持弱类型关系比较自动类型转换, 100 == "100"
c. 支持in关系, uid in ["4042344", "512009"]
d. 使用antlr4的visitor模式实现
parser grammar 表达式 一部分定义:
expression
:
FUNC LPAREN formalParameterList? RPAREN functionBody # FunctionDeclExpression
| arrowFunctionParameters ARROW arrowFunctionBody # ArrowFunctionDeclExpression
| member=expression LBRACK min=expression? COLON max=expression? RBRACK # MemberSubsetExpression
| member=expression opt=(OPTIONAL_CHAINING | QUESTION)? LBRACK index=expression RBRACK # MemberIndexExpression
| member=expression opt=QUESTION? DOT method=identifier # MemberCallExpression
| expression argumentList # ArgumentsExpression
| expression op=(INC | DEC) # PostIncrementExpression //后置递增运算符
| op=(INC | DEC) expression # PreIncrementExpression //前置递增运算符
| op=(ADD | SUB) expression # UnaryExpression
| op=(TILDE | BANG) expression # NotExpression
| lhs=expression op=(MUL | DIV | MOD) rhs=expression # MultiplicativeExpression
| lhs=expression op=(ADD | SUB) rhs=expression # AdditiveExpression
| lhs=expression op=(LE | GE | GT | LT | EQUAL | NOTEQUAL | IN) rhs=expression # RelationalExpression
| lhs=expression op=(AND | OR) rhs=expression # LogicalExpression
| cond=expression '?' lhs=expression ':' rhs=expression # TernaryExpression
| lhs=expression '?:' rhs=expression # ElvisExpression
| <assoc = right> lhs=expression '=' rhs=expression # AssignmentExpression
| identifier # IdentifierExpression
| literal # LiteralExpression
| arrayLiteral # ArrayLiteralExpression
| objectLiteral # ObjectLiteralExpression
| LPAREN expression RPAREN # ParenthesizedExpression
;
关系运算实现
func (this *MainVisitor) VisitRelationalExpression(ctx *RelationalExpressionContext) interface{} {
logrus.Debugln("VisitRelationalExpression:", ctx.GetText())
left, right := this.Visit(ctx.GetLhs()), this.Visit(ctx.GetRhs())
//检查
left = ExpressionCallIfNecessary(left, []interface{}{})
right = ExpressionCallIfNecessary(right, []interface{}{})
logrus.Debugln("VisitRelationalExpression ctx.GetOp(): ", ctx.GetOp().GetText())
switch ctx.GetOp().GetTokenType() {
case FlyParserEQUAL:
return func_eq(left, right)
case FlyParserNOTEQUAL:
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) != cast.ToFloat64(right)
}
return cast.ToInt64(left) != cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) != cast.ToFloat64(right)
}
return left != right
case FlyParserGT:
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) > cast.ToFloat64(right)
}
return cast.ToInt64(left) > cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) > cast.ToFloat64(right)
}
panic("not support Relational left type")
case FlyParserGE:
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) >= cast.ToFloat64(right)
}
return cast.ToInt64(left) >= cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) >= cast.ToFloat64(right)
}
panic("not support Relational left type")
case FlyParserLT:
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) < cast.ToFloat64(right)
}
return cast.ToInt64(left) < cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) < cast.ToFloat64(right)
}
panic("not support Relational left type")
case FlyParserLE:
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) <= cast.ToFloat64(right)
}
return cast.ToInt64(left) <= cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) <= cast.ToFloat64(right)
}
panic("not support Relational left type")
case FlyParserIN:
if isNil(right) {
return false
}
list, ok := right.([]interface{})
if !ok {
panic("not support Relational right type")
}
for _, v := range list {
if func_eq(left, v) {
return true
}
}
return false
}
panic("not support Relational expression")
}
func func_eq(left interface{}, right interface{}) bool {
switch left.(type) {
case int64, int, int32, uint, uint32, uint64, int8, int16, uint16:
switch left.(type) {
case float64, float32:
return cast.ToFloat64(left) == cast.ToFloat64(right)
}
return cast.ToInt64(left) == cast.ToInt64(right)
case float64, float32:
return cast.ToFloat64(left) == cast.ToFloat64(right)
case string:
if rightStr, ok := right.(string); ok {
return left.(string) == rightStr
}
return left.(string) == cast.ToString(right)
}
return left == right
}
规则表达式执行测试
func TestExpression(t *testing.T) {
type tt struct {
expression string
value interface{}
recover bool
scope map[string]interface{}
}
caseList := [...]tt{
{
scope: map[string]interface{}{
"orderEnter": 1693875200,
"orderTime": 1693875200,
"orderStatus": 5300,
"uid": 40488888,
},
expression: "( ( (orderEnter >= orderTime)&&(orderStatus == 5300)&&(orderStatus in [5300,5400]) )||( (orderEnter < orderTime)&&( (orderEnter > orderTime) ) ) && uid in [\"40488888\", \"2075114\"] )",
value: true,
},
{
scope: map[string]interface{}{},
expression: "var x = 6;" +
"y,z := 2,4;" +
"x == y+z",
value: true,
},
{
scope: map[string]interface{}{},
expression: "var x = [6,7,8];" +
"y := \"7\";" +
"y in x",
value: true,
},
}
checkCase := func(t *testing.T, c tt) (ex error) {
defer func() {
if r := recover(); r != nil {
ex = fmt.Errorf("panic: %v", r)
}
}()
val := calc(c.expression, c.scope)
if val != nil && reflect.TypeOf(val).Kind() == reflect.Map {
str, err := jsoniter.MarshalToString(val)
fmt.Println("result str: ", str)
assert.Nil(t, err)
assert.JSONEq(t, c.value.(string), str)
return
}
assert.Equal(t, val, c.value)
return ex
}
for _, c := range caseList {
err := checkCase(t, c)
if c.recover {
fmt.Println(err)
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
}
}
func calc(expression string, scope map[string]interface{}) interface{} {
input := antlr.NewInputStream(expression)
lexer := NewFlyLexer(input)
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
parser := NewFlyParser(stream)
parser.BuildParseTrees = true
parser.RemoveErrorListeners()
parser.AddErrorListener(&VerboseListener{})
tree := parser.Program()
visitor := NewMainVisitor(nil, scope)
var result = visitor.Execute(tree)
return result
}