背景
- 通过字符串匹配业务表每一条记录的部分字段,来对记录打上标签
要求
- 规则可以配置调整
- 规则支持复合运算、逻辑运算
开始
实现
- 将规则放到数据库中维护
- 从数据库中读出规则,将规则与参数作为输入到规则引擎进行计
- 规则引擎选用IKExpression
流程
依赖
<!-- IKExpression -->
<dependency>
<groupId>org.wltea.expression</groupId>
<artifactId>IKExpression</artifactId>
<version>2.1.2</version>
</dependency>
配置
-
IKExpression.cfg.xml:函数配置文件
<?xml version="1.0" encoding="UTF-8"?> <function-configuration> <!-- 自定义字符串匹配函数 --> <bean class="com.wf.dev.common.util.DevStringUtils"> <function name="CONTAINS" method="contains"> <parameter-type>java.lang.String</parameter-type> <parameter-type>java.lang.String</parameter-type> </function> </bean> <!-- 系统函数默认配置 --> <bean class="org.wltea.expression.function.SystemFunctions"> <function name="STARTSWITH" method="startsWith"> <parameter-type>java.lang.String</parameter-type> <parameter-type>java.lang.String</parameter-type> </function> <function name="ENDSWITH" method="endsWith"> <parameter-type>java.lang.String</parameter-type> <parameter-type>java.lang.String</parameter-type> </function> <function name="CALCDATE" method="calcDate"> <parameter-type>java.util.Date</parameter-type> <parameter-type>int</parameter-type> <parameter-type>int</parameter-type> <parameter-type>int</parameter-type> <parameter-type>int</parameter-type> <parameter-type>int</parameter-type> <parameter-type>int</parameter-type> </function> <function name="SYSDATE" method="sysDate"/> <function name="DAYEQUALS" method="dayEquals"> <parameter-type>java.util.Date</parameter-type> <parameter-type>java.util.Date</parameter-type> </function> </bean> <!-- 用户函数配置 ,请在这里定制您自己的函数--> <bean class="com.wf.dev.common.util.MyIkMethods"> <function name="POWER" method="power"> <parameter-type>double</parameter-type> <parameter-type>double</parameter-type> </function> <function name="MAX" method="max"> <parameter-type>java.util.List</parameter-type> </function> <function name="MIN" method="min"> <parameter-type>java.util.List</parameter-type> </function> <function name="AVERAGE" method="average"> <parameter-type>java.util.List</parameter-type> </function> <function name="SUM" method="sum"> <parameter-type>java.util.List</parameter-type> </function> </bean> </function-configuration>
KMP字符串匹配算法
-
空间复杂度O(m),m表示模式串的长度。
-
时间复杂度O(m+n),n表示子串长度。
package com.wf.dev.common.util; /** * @author wf * @date 2022年12月08日 15:36 * @description */ public class DevStringUtils { public boolean contains(String s1, String s2) { if (s1 != null && s2 != null) { return kmp(s1.toCharArray(), s1.length(), s2.toCharArray(), s2.length()) >= 0; } else { throw new NullPointerException("函数\"CONTAINS\"参数为空"); } } /** * a, b分别是主串和模式串;n, m分别是主串和模式串的长度。 */ public static int kmp(char[] a, int n, char[] b, int m) { int[] next = getNexts(b, m); int j = 0; for (int i = 0; i < n; ++i) { while (j > 0 && a[i] != b[j]) { // 一直找到a[i]和b[j] j = next[j - 1] + 1; } if (a[i] == b[j]) { ++j; } if (j == m) { // 找到匹配模式串的了 return i - m + 1; } } return -1; } private static int[] getNexts(char[] b, int m) { int[] next = new int[m]; next[0] = -1; int k = -1; for (int i = 1; i < m; ++i) { while (k != -1 && b[k + 1] != b[i]) { k = next[k]; } if (b[k + 1] == b[i]) { ++k; } next[i] = k; } return next; } }
代码
/**
如:【事件标题】字段中含有“暂不解决”“不处理”“没有时间”;
表达式:$CONTAINS(title, "暂不解决") || $CONTAINS(title, "不处理") || $CONTAINS(title, "没有时间")
*/
public static List<Variable> buildVariableWithMap(Map<String, Object> o) {
if (o == null) {
return Lists.newArrayList();
}
List<Variable> variables = Lists.newArrayList();
for (String key : o.keySet()) {
Object value = o.get(key);
if (value instanceof Number) {
variables.add(new Variable(key, BaseDataMeta.DataType.DATATYPE_DOUBLE, value));
} else {
if (value == null) {
value = StringUtils.EMPTY;
}
variables.add(Variable.createVariable(key, value));
}
}
return variables;
}
private boolean hit(String expression, List<Variable> variables) {
if (StringUtils.isBlank(expression) || CollectionUtils.isEmpty(variables)) {
return false;
}
try {
return (Boolean) ExpressionEvaluator.evaluate(expression, variables);
} catch (Exception e) {
log.error("异常数据", e);
}
return false;
}
private List<Map<String, Object>> handle(List<Map<String, Object>> details, List<RuleEntity> rules) {
for (Map<String, Object> eventDtl : details) {
List<Variable> variables = buildVariableWithMap(eventDtl);
for (RuleEntity ruleEntity : rules) {
if (hit(ruleEntity.getExpression(), variables)) {
// do something
break;
}
}
}
}
小结
- 自定义函数要求效率尽量高