目录
//参考com.ql.util.express.instruction.op.OperatorFactory
public OperatorFactory(boolean aIsPrecise){
this.isPrecise = aIsPrecise;
addOperator("new",new OperatorNew("new"));
addOperator("anonymousNewArray",new OperatorAnonymousNewArray("anonymousNewArray"));
addOperator("NewList",new OperatorAnonymousNewList("NewList"));
addOperator(":",new OperatorKeyValue(":"));
addOperator("NewMap",new OperatorAnonymousNewMap("NewMap"));
addOperator("def", new OperatorDef("def"));
addOperator("exportDef", new OperatorExportDef("exportDef"));
addOperator("!",new OperatorNot("!"));
addOperator("*", new OperatorMultiDiv("*"));
addOperator("/", new OperatorMultiDiv("/"));
addOperator("%", new OperatorMultiDiv("%"));
addOperator("mod", new OperatorMultiDiv("mod"));
addOperator("+",new OperatorAdd("+"));
addOperator("-",new OperatorReduce("-"));
addOperator("<",new OperatorEqualsLessMore("<"));
addOperator(">",new OperatorEqualsLessMore(">"));
addOperator("<=",new OperatorEqualsLessMore("<="));
addOperator(">=",new OperatorEqualsLessMore(">="));
addOperator("==",new OperatorEqualsLessMore("=="));
addOperator("!=",new OperatorEqualsLessMore("!="));
addOperator("<>",new OperatorEqualsLessMore("<>"));
addOperator("&&",new OperatorAnd("&&"));
addOperator("||",new OperatorOr("||"));
addOperator("nor",new OperatorNor("nor"));
addOperator("=",new OperatorEvaluate("="));
addOperator("exportAlias",new OperatorExportAlias("exportAlias"));
addOperator("alias",new OperatorAlias("alias"));
addOperator("break",new OperatorBreak("break"));
addOperator("continue",new OperatorContinue("continue"));
addOperator("return",new OperatorReturn("return"));
addOperator("ARRAY_CALL",new OperatorArray("ARRAY_CALL"));
addOperator("++",new OperatorDoubleAddReduce("++"));
addOperator("--",new OperatorDoubleAddReduce("--"));
addOperator("cast", new OperatorCast("cast"));
addOperator("macro",new OperatorMacro("macro"));
addOperator("function",new OperatorFunction("function"));
addOperator("in", new OperatorIn("in"));
addOperator("like", new OperatorLike("like"));
//bit operator
addOperator("&",new OperatorBit("&"));
addOperator("|",new OperatorBit("|"));
addOperator("^",new OperatorBit("^"));
addOperator("~",new OperatorBit("~"));
addOperator("<<",new OperatorBit("<<"));
addOperator(">>",new OperatorBit(">>"));
}
private final ExpressRunner expressRunner = new ExpressRunner();
基础用法: 返回true or false
@Test
public void mobilePrefixCheck() throws Exception {
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("mobilePrefix", "162");
String express = "mobilePrefix in (162,165,167,170,171)";
Object execute = expressRunner.execute(express, context, null, false, false);
System.out.println(execute);
}
进阶用法: 当执行器返回false时,额外赋值自定义错误信息
@Test
public void mobilePrefixCheck() throws Exception {
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("mobilePrefix", "162");
String express = "mobilePrefix in (162,165,167,170,171)";
//没有合适的设置errorInfo方法 ,无奈用重命名操作符方法来设置errorInfo;errorInfo只针对替换的操作符起作用
expressRunner.addOperatorWithAlias("等于", "==", "号码不满足期望");
List<String> errorInfo = new ArrayList<>();
Object execute = expressRunner.execute(express, context, errorInfo, false, false);
System.out.println(execute);
System.out.println(errorInfo.size() > 0 ? errorInfo.get(0) : null);
}
@Test
public void addressCheck() throws Exception {
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("city", "郑州");
context.put("address", "二七");
String express = "city in ('郑州','天庭') and address in ('二七','大上海','新郑')";
Object execute = expressRunner.execute(express, context, null, false, false);
System.out.println(execute);
}
@Test
public void addressCheck2() throws Exception {
DefaultContext<String, Object> context = new DefaultContext<>();
// 地址
context.put("country", "中国");
context.put("province", "河南");
context.put("city", "郑州");
context.put("address", "二七");
context.put("postcode", 1234);
// 手机号
context.put("mobile", "162");
// 表达式
String express = "" +
"country in ('中国','美国') " +
"and province in ('河南','浙江') " +
"and city in ('郑州','杭州') " +
"and address in ('二七','大上海','新郑') " +
"and postcode in ('1234')" +
"and (mobile in (162,165,167,170,171) 等于 false)";
expressRunner.addOperatorWithAlias("等于", "==", "号码或地址不满足期望");
List<String> errorInfo = new ArrayList<>();
Object execute = expressRunner.execute(express, context, errorInfo, false, false);
System.out.println(execute);
System.out.println(errorInfo.size() > 0 ? errorInfo.get(0) : null);
}
官方文档是对于自定义操作符是这样介绍的:
//定义一个继承自com.ql.util.express.Operator的操作符
public class JoinOperator extends Operator{
public Object executeInner(Object[] list) throws Exception {
Object opdata1 = list[0];
Object opdata2 = list[1];
if(opdata1 instanceof java.util.List){
((java.util.List)opdata1).add(opdata2);
return opdata1;
}else{
java.util.List result = new java.util.ArrayList();
result.add(opdata1);
result.add(opdata2);
return result;
}
}
}
//(1)addOperator
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
runner.addOperator("join",new JoinOperator());
Object r = runner.execute("1 join 2 join 3", context, null, false, false);
System.out.println(r);
//返回结果 [1, 2, 3]
//(2)replaceOperator
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
runner.replaceOperator("+",new JoinOperator());
Object r = runner.execute("1 + 2 + 3", context, null, false, false);
System.out.println(r);
//返回结果 [1, 2, 3]
//(3)addFunction
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
runner.addFunction("join",new JoinOperator());
Object r = runner.execute("join(1,2,3)", context, null, false, false);
System.out.println(r);
//返回结果 [1, 2, 3]
自己实现基于手机号截取后匹配in的规则:
这个规则是基于in操作符的二次改造,所有我们不用按官方的继承Operator,而是继承它的子类OperatorIn,构造方法可以设置截取字段的起始和结束(默认0,3)
import com.ql.util.express.instruction.op.OperatorIn;
/**
* 自定义ELPress操作符->手机号前缀操作符 (该方法可用于所有字符截取后in操作的场景)
* <p>
* ExpressRunner expressRunner = new ExpressRunner();
* <p>
* expressRunner.addOperator("prefix", new MobilePrefixInOperator(0, 3));
* <p>
* String express = "mobile prefix [162,165,167,170,171]" // []代替{}
* <p>
* <a href='' style='color:red;font-weight:900:'>
* 注意可以使用 mobile in (162,165,167) ,但无法使用data prefix (162,165,167),因为默认addOperator的是二元操作符o(╥﹏╥)o
* <p>
* {@link com.ql.util.express.instruction.InInstructionFactory}对语法树做了特殊处理
* </a>
*
* @author yangning
* @date 2021/4/16 2:10 下午
**/
public class MobilePrefixInOperator extends OperatorIn {
/**
* OperatorIn默认传参
* {@link com.ql.util.express.instruction.op.OperatorFactory}
*/
public static final String IN = "in";
/**
* 手机号加密头
*/
public static final String PREFIX_CRYPT = "crypt#";
private Integer beginIndex = 0;
private Integer endIndex = 3;
public MobilePrefixInOperator() {
super(IN);
}
public MobilePrefixInOperator(Integer beginIndex, Integer endIndex) {
super(IN);
this.beginIndex = beginIndex;
this.endIndex = endIndex;
}
public MobilePrefixInOperator(String aName) {
super(aName);
}
public MobilePrefixInOperator(String aAliasName, String aName, String aErrorInfo) {
super(aAliasName, aName, aErrorInfo);
}
@Override
public Object executeInner(Object[] list) throws Exception {
// 手机号判断明文还是密文->截取前缀->执行内置in关键字Operator
Object obj = list[0];
if ((obj instanceof Number || obj instanceof String)) {
String mobile = String.valueOf(obj);
String mobilePrefix;
if (mobile.startsWith(PREFIX_CRYPT)) {
mobilePrefix = CryptUtil.decrypt(mobile).substring(beginIndex, endIndex);
} else {
mobilePrefix = mobile.substring(beginIndex, endIndex);
}
list[0] = mobilePrefix;
}
return super.executeInner(list);
}
}
接下来我们试试自定义的操作符prefixIn,注意一个大坑,默认的in可以括号里加多个,但是自定义的操作符这样做就会报错,翻了下源码发现默认addOperator的是二元操作符o(╥﹏╥)o。
解决方法是用[]代替()
@Test
public void MobilePrefixOperatorCheck() throws Exception {
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("mobile", "17115847153");
// 注意可以使用 mobile in (162,165,167) ,但无法使用data prefix (162,165,167),因为默认addOperator的是二元操作符o(╥﹏╥)o
expressRunner.addOperator("prefix", new MobilePrefixInOperator(0, 3));
String express = "mobile prefix [162,165,167,170,171] 等于 false";
// 用等于替换==的唯一作用是false时往errorInfo添加自定义错误信息(官方api不支持直接设置错误信息)
expressRunner.addOperatorWithAlias("等于", "==", "号码不满足期望");
List<String> errorInfo = new ArrayList<>();
Object execute = expressRunner.execute(express, context, errorInfo, false, false);
System.out.println(execute);
System.out.println(errorInfo.size() > 0 ? errorInfo.get(0) : null);
}
首先,我们写一个自定义对象,实现我们想要的函数效果
import org.apache.commons.lang3.StringUtils;
/**
* QLExpress->自定义Contains函数
*
* @author yangning
* @date 2021/4/19 12:49 下午
**/
public class ContainsFunction {
public boolean contains(String str, String searchStr) {
return str.contains(searchStr);
}
public boolean contains(String str, String... searchStr) {
for (String s : searchStr) {
if (str.contains(s)) {
return true;
}
}
return false;
}
public boolean containsIgnoreCase(String str, String searchStr) {
return StringUtils.containsIgnoreCase(str, searchStr);
}
public boolean containsIgnoreCase(String str, String... searchStr) {
for (String s : searchStr) {
if (StringUtils.containsIgnoreCase(str, s)) {
return true;
}
}
return false;
}
}
然后,我们应用到QLExpress中
@Test
public void addressCheck() throws Exception {
ExpressRunner expressRunner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("city", "郑州");
context.put("address", "w我二七123订单");
expressRunner.addFunctionOfServiceMethod("contain", new ContainsFunction(), "contains",
new Class[]{String.class, String.class}, null);
expressRunner.addFunctionOfServiceMethod("contains", new ContainsFunction(), "contains",
new Class[]{String.class, String[].class}, null);
expressRunner.addFunctionOfServiceMethod("containsIgnoreCase", new ContainsFunction(), "containsIgnoreCase",
new Class[]{String.class, String.class}, null);
expressRunner.addFunctionOfServiceMethod("containsIgnoreCases", new ContainsFunction(), "containsIgnoreCase",
new Class[]{String.class, String[].class}, null);
String express = "city == '郑州' and contains (address,['二七','大上海'])";
String s = "w我二七123订单";
Object execute = expressRunner.execute(express, context, null, false, false);
System.out.println(execute);
}
规则引擎QLExpress是一个重量级的对象,我们实际中使用它应该设置成一个单例。
我们先模拟一次多线程执行导致的异常:
com.yn.risk.management.system.express.operator.MobilePrefixInOperator;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.OperateData;
import lombok.SneakyThrows;
/**
* @author yangning
* @date 2021/4/22 6:35 下午
**/
public class SynchronizedTest implements Runnable {
public int a;
public SynchronizedTest() {
}
public SynchronizedTest(int a) {
this.a = a;
}
@SneakyThrows
@Override
public void run() {
for (; ; ) {
ExpressRunner runner = ExpressRunnerFactory.getInstance();
Object execute;
String express = "score == '100" + a + "'";
DefaultContext<String, Object> context = new DefaultContext<>();
MobilePrefixInOperator prefixInOperator = new MobilePrefixInOperator(1, a);
// addCustomizeOperator(prefixInOperator, runner);
runner.replaceOperator("prefix", prefixInOperator);
context.put("score", "100" + a);
execute = runner.execute(express, context, null, true, false);
OperateData data = runner.getOperateDataCache().fetchOperateData(prefixInOperator, MobilePrefixInOperator.class);
if (!data.toString().equals("prefix")) {
System.out.println(data.toString());
System.out.println(execute);
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest synchronizedTest1 = new SynchronizedTest(1);
SynchronizedTest synchronizedTest2 = new SynchronizedTest(2);
SynchronizedTest synchronizedTest3 = new SynchronizedTest(3);
Thread t1;
Thread t2;
Thread t3;
t1 = new Thread(synchronizedTest1, "线程1");
t2 = new Thread(synchronizedTest2, "线程2");
t3 = new Thread(synchronizedTest3, "线程3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
public static synchronized void addCustomizeOperator(MobilePrefixInOperator prefixInOperator, ExpressRunner runner) {
// 手机号前缀规则校验
runner.replaceOperator("prefix", prefixInOperator);
}
}
输出
Exception in thread "线程2" Exception in thread "线程3" java.lang.RuntimeException: 重复定义操作符:prefix定义1:class com.yn.risk.management.system.express.operator.MobilePrefixInOperator 定义2:class com.yn.risk.management.system.express.operator.MobilePrefixInOperator
at com.ql.util.express.instruction.op.OperatorFactory.addOperator(OperatorFactory.java:71)
at com.ql.util.express.instruction.op.OperatorFactory.replaceOperator(OperatorFactory.java:79)
at com.ql.util.express.ExpressRunner.replaceOperator(ExpressRunner.java:480)
at com.yn.SynchronizedTest.run(SynchronizedTest.java:35)
at java.lang.Thread.run(Thread.java:748)
java.lang.RuntimeException: 重复定义操作符:prefix定义1:class com.yn.risk.management.system.express.operator.MobilePrefixInOperator 定义2:class com.yn.risk.management.system.express.operator.MobilePrefixInOperator
at com.ql.util.express.instruction.op.OperatorFactory.addOperator(OperatorFactory.java:71)
at com.ql.util.express.instruction.op.OperatorFactory.replaceOperator(OperatorFactory.java:79)
at com.ql.util.express.ExpressRunner.replaceOperator(ExpressRunner.java:480)
at com.yn.SynchronizedTest.run(SynchronizedTest.java:35)
at java.lang.Thread.run(Thread.java:748)
单例示范:
import com.ql.util.express.ExpressRunner;
import lombok.extern.slf4j.Slf4j;
/**
* 规则引擎ExpressRunner工厂,创建ExpressRunner单例
*
* @author yangning
* @date 2021/4/22 11:38 上午
**/
@Slf4j
public class ExpressRunnerFactory {
private volatile static ExpressRunner instance;
public static ExpressRunner getInstance() {
if (instance == null) {
synchronized (ExpressRunnerFactory.class) {
if (instance == null) {
ExpressRunner runner = new ExpressRunner();
initFunctionOfServiceMethods(runner);
instance = runner;
}
}
}
return instance;
}
/**
* 加载自定义的对象(例如Spring对象)方法转换为表达式
*
* @param runner 初始化语法分析和计算的入口类
*/
private static void initFunctionOfServiceMethods(ExpressRunner runner) {
try {
runner.addFunctionOfServiceMethod("contain", new ContainsFunction(), "contains",
new Class[]{String.class, String.class}, null);
runner.addFunctionOfServiceMethod("contains", new ContainsFunction(), "contains",
new Class[]{String.class, String[].class}, null);
runner.addFunctionOfServiceMethod("containsIgnoreCase", new ContainsFunction(), "containsIgnoreCase",
new Class[]{String.class, String.class}, null);
runner.addFunctionOfServiceMethod("containsIgnoreCases", new ContainsFunction(), "containsIgnoreCase",
new Class[]{String.class, String[].class}, null);
} catch (Exception e) {
log.error("规则引擎initFunctionOfServiceMethods()异常:", e);
throw ExFactory.throwSystem("规则引擎初始化异常!");
}
}
}
在注入自定义操作符和content的时候,需要注意线程安全问题:
public synchronized void injectCustomizeOperator(ExpressRunner runner, BaseContext baseContext) throws Exception {
// 手机号前缀规则校验
if (baseContext.getMobileContext() != null) {
Integer beginIndex = baseContext.getMobileContext().getBeginIndex();
Integer endIndex = baseContext.getMobileContext().getEndIndex();
OperatorBase prefix = runner.getOperatorFactory().getOperator("prefix");
if (prefix == null) {
runner.addOperator("prefix", new MobilePrefixInOperator(beginIndex, endIndex));
} else {
runner.replaceOperator("prefix", new MobilePrefixInOperator(beginIndex, endIndex));
}
}
}