Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

QLExpress 实践

QLExpress 实践

依赖

1
2
3
4
5
6

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.4</version>
</dependency>

模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@Data
public class ExprNode {

/**
* && / || / !
*/
String symbol;

/**
* and / or / not / var
*/
String type;

String identifier;

List<ExprNode> group;

public ExprNode() {
}

public ExprNode(ExprTypeEnum typeEnum, String identifier) {
if (typeEnum != null) {
this.symbol = typeEnum.getSymbol();
this.type = typeEnum.name();
}
this.identifier = identifier;
}

public ExprNode(String type, String identifier) {
this.type = type;
this.identifier = identifier;
}

public ExprNode(String type, String symbol, String identifier) {
this.symbol = symbol;
this.type = type;
this.identifier = identifier;
}

public ExprNode(ExprTypeEnum typeEnum, List<ExprNode> group) {
if (typeEnum != null) {
this.symbol = typeEnum.getSymbol();
this.type = typeEnum.name();
}
this.group = group;
}

@Override
public String toString() {
if (ExprTypeEnum.VAR.name().equalsIgnoreCase(this.type)) {
return this.identifier == null ? "" : this.identifier;
} else if (ExprTypeEnum.NOT.name().equalsIgnoreCase(this.type)) {
// 剔除无效的 not 表达式
if (this.group == null || this.group.isEmpty()) {
return "";
}
String inner = this.group.get(0).toString();
return "!(" + inner + ")";
} else if (ExprTypeEnum.AND.name().equalsIgnoreCase(this.type)
|| ExprTypeEnum.OR.name().equalsIgnoreCase(this.type)) {
// 剔除无效表达式
if (this.group == null || this.group.isEmpty()) {
return "";
}
String op = ExprTypeEnum.AND.name().equalsIgnoreCase(this.type) ? " && " : " || ";
String joined = this.group.stream()
.map(ExprNode::toString)
.collect(Collectors.joining(op));
return "(" + joined + ")";
} else if (ExprTypeEnum.FUNC.name().equalsIgnoreCase(this.type)) {
if (this.group == null || this.group.isEmpty()) {
return "";
}
String joined = this.group.stream()
.map(ExprNode::toString)
.collect(Collectors.joining(","));
return this.identifier + "(" + joined + ")";
} else if (ExprTypeEnum.NUM.name().equalsIgnoreCase(this.type)) {
if (this.identifier == null) {
return "";
}
return this.identifier;
} else if (ExprTypeEnum.STR.name().equalsIgnoreCase(this.type)) {
if (this.identifier == null) {
return "";
}
// 增加转义
return "\"" + this.identifier + "\"";
} else {
// 无法识别的表达式
return "";
}
}

public Set<String> extractVals() {
Set<String> variables = new HashSet<>();
if (ExprTypeEnum.VAR.name().equalsIgnoreCase(this.type) && this.identifier != null) {
variables.add(this.identifier);
} else if (this.group != null) {
for (ExprNode expr : this.group) {
variables.addAll(expr.extractVals());
}
}
return variables;
}

public String toReturnExpr() {
String expr = toString();
if (expr != null) {
return "return " + expr;
}
return expr;
}
}

类型枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum ExprTypeEnum {

AND("&&"), OR("||"), NOT("!"), VAR(""), FUNC(""), NUM(""), STR("");

private String symbol;

ExprTypeEnum(String symbol) {
this.symbol = symbol;
}

public String getSymbol() {
return symbol;
}
}

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Slf4j
public class ExprUtl {

private static final ExpressRunner expressRunner;

static {
expressRunner = new ExpressRunner(false, false);
expressRunner.getOperatorFactory().getOperator("<").setErrorInfo("$1<$2");
expressRunner.getOperatorFactory().getOperator(">").setErrorInfo("$1>$2");
expressRunner.getOperatorFactory().getOperator("<=").setErrorInfo("$1<=$2");
expressRunner.getOperatorFactory().getOperator(">=").setErrorInfo("$1>=$2");
expressRunner.getOperatorFactory().getOperator("!=").setErrorInfo("$1!=$2");
expressRunner.getOperatorFactory().getOperator("<>").setErrorInfo("$1<>$2");
expressRunner.getOperatorFactory().getOperator("==").setErrorInfo("$1==$2");
expressRunner.getOperatorFactory().getOperator("like").setErrorInfo("$1like$2");
expressRunner.addFunction("isEmpty", new OperatorIsEmpty("isEmpty"));
expressRunner.addFunction("contains", new OperatorContains("contains"));

}

public static Object run(String expression, Map<String, Object> params) {
try {
DefaultContext<String, Object> context = new DefaultContext<>();
context.putAll(params);
Object execute = expressRunner.execute(expression, context, null, true, false);
return execute;
} catch (Exception e) {
log.error("ExprUtl.run error, expression:{}, params:{}", expression, JSON.toJSONString(params), e);
}
throw new RuntimeException("ExprUtl.run error");
}

public static Boolean check(String expression, Map<String, Object> params) {
Object run = run(expression, params);
Boolean execute = (Boolean) run;
return execute;
}
}

自定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OperatorIsEmpty extends Operator {
public OperatorIsEmpty(String name) {
this.name = name;
}
@Override
public Object executeInner(Object[] list) throws Exception {
if (list.length != 1) {
throw new Exception("is_empty 操作符只接受一个参数");
}
Object operand = list[0];
if (operand == null) {
return true;
}
if (operand instanceof Collection) {
return ((Collection<?>) operand).isEmpty();
}
if (operand instanceof String) {
return ((String) operand).isEmpty();
}
if (operand.getClass().isArray()) {
return Array.getLength(operand) == 0;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class OperatorContains extends Operator {

public OperatorContains(String name) {
this.name = name;
}
@Override
public Object executeInner(Object[] list) throws Exception {
if (list.length != 2) {
throw new Exception("contains 操作符需要两个参数");
}
Object container = list[0]; // 容器对象
Object element = list[1]; // 要查找的元素

if (container == null) {
return false;
}
// 处理字符串类型
if (container instanceof String) {
if (element == null) return false;
return ((String) container).contains(element.toString());
}

// 处理 List 类型
if (container instanceof List) {
List<?> listContainer = (List<?>) container;
return listContainer.contains(element);
}
// 处理数组类型
if (container.getClass().isArray()) {
int len = Array.getLength(container);
for (int i = 0; i < len; i++) {
Object arrayElement = Array.get(container, i);
if (Objects.equals(arrayElement, element)) {
return true;
}
}
return false;
}
// 处理 Collection 类型
if (container instanceof Collection) {
Collection<?> collection = (Collection<?>) container;
return collection.contains(element);
}
return false;
}
}

ps. 因为QLExpress 不支持自定义单元操作符,所以使用的是更为灵活的 自定义函数
参考: https://deepwiki.com/search/qlexpress-isempty-test-public_c99e482d-83bb-403b-ae88-63ca80958cef

使用

测试表达式模型

1
{"expr": {"type": "AND", "group": [{"type": "NOT", "group": [{"type": "VAR", "identifier": "NewUser"}], "symbol": "!"}, {"type": "NOT", "group": [{"type": "VAR", "identifier": "Free7Advanced"}], "symbol": "!"}, {"type": "OR", "group": [{"type": "VAR", "identifier": "NoPayUser"}, {"type": "FUNC", "group": [{"type": "VAR", "identifier": "UserCinmooreLevel"}, {"type": "STR", "identifier": "general"}], "symbol": null, "identifier": "contains"}], "symbol": "||"}], "symbol": "&&"}}

使用实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
ThreadPoolExecutor realtimeTagRunner = new ThreadPoolExecutor(64, 200, 1, TimeUnit.HOURS,
new LinkedBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);

ThreadLocal<RealtimeTagCache> realtimeTagCacheThreadLocal = ThreadLocal.withInitial(() -> new RealtimeTagCache());

public boolean checkRealtimeRule(final Long userId, final String groupCode, final String ruleStr) {

GroupRealtimeRuleBO ruleBO = JSON.parseObject(ruleStr, GroupRealtimeRuleBO.class);
if (ruleBO == null || ruleBO.getExpr() == null) {
// 异常情况下先返回false
log.warn("groupCode:{}, check realtime rule failed. ruleStr:{}", groupCode, ruleStr);
return false;
}
ExprNode expr = ruleBO.getExpr();
// 提取实时标签
Set<String> tags = expr.extractVals();
if (tags == null || tags.isEmpty()) {
return false;
}
RealtimeTagCache realtimeTagCache = realtimeTagCacheThreadLocal.get();
Map<String, Object> params = new HashMap<>();
List<String> runTags = new ArrayList<>();
for (String tag : tags) {
Object ret = realtimeTagCache.get(userId, tag);
if (ret == null) {
// 没有缓存
runTags.add(tag);
continue;
}
params.put(tag, ret);
}

long start = System.currentTimeMillis();
if (CollectionUtils.isNotEmpty(runTags)) {
// 执行所有标签
Map<String, Future<Object>> runResultFutureMap = new HashMap<>();
for (String runTag : runTags) {
AbstractRealtimeTag realtimeTag = realtimeTagManager.get(runTag);
if (realtimeTag == null) {
//
log.warn("realtimeTag impl is null, tagname:{}", runTag);
continue;
}
Future<Object> submit = realtimeTagRunner.submit(() -> realtimeTag.run(userId, Collections.emptyMap()));
runResultFutureMap.put(runTag, submit);
}
// 获取结果
for (Map.Entry<String, Future<Object>> entry : runResultFutureMap.entrySet()) {
String tagName = entry.getKey();
Future<Object> future = entry.getValue();
try {
Object o = future.get(3, TimeUnit.SECONDS);
params.put(tagName, o);
// 线程缓存下
realtimeTagCache.put(userId, tagName, o);
} catch (Exception e) {
log.error("realtimeTagRunner error, tagName:{}, userId:{}", tagName, userId, e);
// 有任何异常输出,返回false
return false;
}
}
}
long end = System.currentTimeMillis();
String returnExpr = expr.toReturnExpr();
// 都跑完了,执行
Boolean check = ExprUtl.check(returnExpr, params);

if (loggerConfig.isLogEnable("realtime-rule-debug")) {
log.info("realtime rule check, userId:{}, group:{}, rule:{}, param:{}, result:{} , cost:{} ms",
userId, groupCode, expr.toString(), JSON.toJSONString(params), check, end - start);
}

return BooleanUtils.isTrue(check);
}