diff --git a/README.md b/README.md index 6858d7b..2eeb50e 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,5 @@ -# Business Rules Engine +# regular-expression-parsing-engine-use-cases - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://git.mesalab.cn/galaxy/platform/algorithm/business-rules-engine.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://git.mesalab.cn/galaxy/platform/algorithm/business-rules-engine/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +当你需要用AviatorScript、Janino去处理一些简单的过滤规则时,可参考此demo。若用例较为复杂建议查看官网文档。 +文档链接及性能测试结果查看: +https://docs.geedge.net/pages/viewpage.action?pageId=104762152 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cb79549 --- /dev/null +++ b/pom.xml @@ -0,0 +1,145 @@ + + + 4.0.0 + + org.example + business-rules-engine + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + + + + nexus + Team Nexus Repository + + https://maven.aliyun.com/repository/releases + + + + maven-ali + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + true + always + fail + + + + + + + 3.0.0 + 4.0.1 + 3.3.1 + 9.4.34.v20201102 + 3.1.2 + + + + + com.alibaba + fastjson + 2.0.26 + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + + org.codehaus.janino + janino + 3.0.11 + + + + com.googlecode.aviator + aviator + 5.2.6 + + + + + cglib + cglib + 3.3.0 + + + + + com.zdjizhi + galaxy + 1.0.8 + + + slf4j-log4j12 + org.slf4j + + + log4j-over-slf4j + org.slf4j + + + + + + com.zdjizhi + galaxy + 1.0.8 + + + slf4j-log4j12 + org.slf4j + + + log4j-over-slf4j + org.slf4j + + + + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + junit + junit + 4.13 + compile + + + + cn.hutool + hutool-all + 5.7.17 + + + + + + \ No newline at end of file diff --git a/src/main/java/com/zdjz/enginetest/People.java b/src/main/java/com/zdjz/enginetest/People.java new file mode 100644 index 0000000..9da2c4c --- /dev/null +++ b/src/main/java/com/zdjz/enginetest/People.java @@ -0,0 +1,38 @@ +package com.zdjz.enginetest; + +public class People { + public String name; + public Integer age; + public String ipAddress; + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public People(String name, Integer age, String ipAddress) { + this.name = name; + this.age = age; + this.ipAddress = ipAddress; + } +} diff --git a/src/main/java/com/zdjz/enginetest/aviatorscript/AviatorScriptEngine.java b/src/main/java/com/zdjz/enginetest/aviatorscript/AviatorScriptEngine.java new file mode 100644 index 0000000..bb58f01 --- /dev/null +++ b/src/main/java/com/zdjz/enginetest/aviatorscript/AviatorScriptEngine.java @@ -0,0 +1,162 @@ +package com.zdjz.enginetest.aviatorscript; + + +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.AviatorEvaluatorInstance; +import com.googlecode.aviator.Expression; +import com.googlecode.aviator.Options; +import com.zdjz.enginetest.People; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; + + +/** + * AviatorScript文档请参考:https://www.yuque.com/boyan-avfmj/aviatorscript/lvabnw + */ +public class AviatorScriptEngine { + + public static AviatorEvaluatorInstance getAviatorEvaluator() { + AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance(); + instance.setCachedExpressionByDefault(true); + instance.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); + instance.setFunctionMissing(null); + return instance; + } + + /** + * 传入变量求和 + */ + @Test + public void rule1() { + String expression = "a + b + c "; + Expression compiledExp = getAviatorEvaluator().compile(expression, true); + System.out.println(compiledExp.execute(compiledExp.newEnv("a", 1, "b", 2, "c", 3))); + } + + + /** + * 传入字符串进行空判断或值判断 + * nil + * 当我们想表示一个变量还没有赋值的时候,需要用到 nil 这个特殊类型,它和 java 中的 null 作用一样,表示当前变量还没有赋值。nil 最常见的场景就是用于判断变量是否为 null: + */ + @Test + public void rule2() { + String expression = "a == nil && b == 'Str'"; + Expression compiledExp = getAviatorEvaluator().compile(expression, true); + System.out.println(compiledExp.execute(compiledExp.newEnv("b", "Str"))); + } + + /** + * Aviator 有个方便用户使用变量的语法糖, 当你要访问变量a中的某个属性b, 那么你可以通过a.b访问到, 更进一步, a.b.c将访问变量a的b属性中的c属性值, 推广开来也就是说 Aviator 可以将变量声明为嵌套访问的形式。 + * TestAviator类符合JavaBean规范, 并且是 public的,例如:Map或POJO等。(具体测试时,属性为private也可以被访问到) + */ + @Test + public void rule3() { + AviatorEvaluatorInstance aviatorEvaluator = getAviatorEvaluator(); + + //当变量是POJO类时,且使用系统自带函数进行判断 + //当前支持的函数库列表:https://www.yuque.com/boyan-avfmj/aviatorscript/ashevw + //判断这个人的是否姓xiao且年龄大于18岁 + String expression1 = "string.startsWith(people.name,'xiao') && people.age > 18 "; + Expression compiledExp1 = getAviatorEvaluator().compile(expression1, true); + People people = new People("xiaoming", 19, "192.168.22.11"); + System.out.println(compiledExp1.execute(compiledExp1.newEnv("people", people))); + + //当变量为map且使用自定义函数进行判断 + //添加自定义函数,更多的自定义函数方式请参考:https://www.yuque.com/boyan-avfmj/aviatorscript/gpyg6p + //判断server_ip是否在192.168.22.1和192.168.22.199之间 + aviatorEvaluator.addFunction(new Ipv4StringToNum()); + String expression2 = "Ipv4StringToNum(map.server_ip) < Ipv4StringToNum('192.168.22.199') &&Ipv4StringToNum(map.server_ip) > Ipv4StringToNum('192.168.22.1') "; + Expression compiledExp2 = aviatorEvaluator.compile(expression2, true); + HashMap hashMap = new HashMap<>(); + hashMap.put("server_ip", "192.168.22.200"); + hashMap.put("client_ip", "192.168.22.99"); + System.out.println(compiledExp2.execute(compiledExp2.newEnv("map", hashMap))); + } + + + /** + * AviatorScript 支持多行表达式,表达式之间必须以分号 ; 隔开,支持换行。 + */ + @Test + public void rule4() { + AviatorEvaluatorInstance aviatorEvaluator = getAviatorEvaluator(); + + //使用let定义变量进行处理 + String expression1 = "let a = 1; let b = 2; c = a + b; "; + Expression compiledExp1 = aviatorEvaluator.compile(expression1, true); + System.out.println(compiledExp1.execute()); + + //整个脚本的返回结果默认是最后一个表达式的结果。但是这里需要注意的是,加上分号后,整个表达式的结果将固定为 nil,因此如果你执行上面的脚本,并打印结果,一定是 null,而不是 c 的值: + //如果你想返回表达式的值,而不是为 nil,最后一个表达式不加分号即可: + String expression2 = "let a = 1; let b = 2; c = a + b "; + Expression compiledExp2 = aviatorEvaluator.compile(expression2, true); + System.out.println(compiledExp2.execute()); + + //也可以用 return 语句来指定返回,return 也用于提前返回,结合条件语句可以做更复杂的逻辑判断。 + //注意, return 语句就必须加上分号才是完整的一条语句,否则将报语法错误。 + //使用条件语句进行判断,更多用法查看:https://www.yuque.com/boyan-avfmj/aviatorscript/mcoghv + String expression3 = "if (people.name == 'xiaomig') { return '这是小明';} return '这不是小明'; "; + Expression compiledExp3 = aviatorEvaluator.compile(expression3, true); + People people = new People("xiaoming", 19, "192.168.22.11"); + System.out.println(compiledExp3.execute(compiledExp3.newEnv("people", people))); + } + + + /** + * AviatorScript 支持 for 和 while 两种循环语句,和其他语言一样也支持return、break、continue、多层嵌套等。 + * 更多用法查看:https://www.yuque.com/boyan-avfmj/aviatorscript/vfqgqn + */ + @Test + public void rule5() { + AviatorEvaluatorInstance aviatorEvaluator = getAviatorEvaluator(); + + String expression1 = "for i in range(0, 10) {println(i)} "; + Expression compiledExp1 = aviatorEvaluator.compile(expression1, true); + System.out.println("for循环:"); + compiledExp1.execute(); + + String expression2 = "let sum = 1; while sum < 1000 { sum = sum + sum; } println(sum);"; + Expression compiledExp2 = aviatorEvaluator.compile(expression2, true); + System.out.println("while循环:"); + compiledExp2.execute(); + } + + + /** + * AviatorScript 完整支持了 java 的异常处理机制,只是做了一些简化, + * 更多用法查看:https://www.yuque.com/boyan-avfmj/aviatorscript/sikgal + * 1. throw 抛出了一个字符串,在 AviatorScript 中,可以 throw 任何东西,非异常的对象都将被转化包装为标准错误 com.googlecode.aviator.exception.StandardError 类的实例。 + * 2. catch(e) 没有指定异常的类型, AviatorScript 允许不指定异常类型,等价于 catch(Throwable e) 。 + * 3. pst(e) 用于打印异常堆栈,也就是 e.printStackTrace() 调用。 + * 4. AviatorScript 中同样支持 finally 语句,这跟 Java 保持一致。 + */ + @Test + public void rule6() { + AviatorEvaluatorInstance aviatorEvaluator = getAviatorEvaluator(); + String expression = "try { throw \"an exception\"; } catch(e) { pst(e); } finally { p(\"finally\");}"; + Expression compiledExp = aviatorEvaluator.compile(expression, true); + compiledExp.execute(); + } + + + /** + * AviatorScript 支持编译脚本,脚本语法与expression表达式相同 + * @throws IOException + */ + @Test + public void rule7() throws IOException { + //编译脚本 + //路径是文件系统的绝对路径或相对路径, + //相对路径的时候,必须项目的根目录开始的相对路径 + //classpath下的绝对或相对路径 + //脚本内容:let sum = 1; while sum < 1000 { sum = sum + sum; } println(sum); + Expression compiledExp = AviatorEvaluator.getInstance().compileScript("src/main/java/com/zdjz/enginetest/aviatorscript/script.av"); + compiledExp.execute(); + + } + + +} diff --git a/src/main/java/com/zdjz/enginetest/aviatorscript/Ipv4StringToNum.java b/src/main/java/com/zdjz/enginetest/aviatorscript/Ipv4StringToNum.java new file mode 100644 index 0000000..2329c8d --- /dev/null +++ b/src/main/java/com/zdjz/enginetest/aviatorscript/Ipv4StringToNum.java @@ -0,0 +1,27 @@ +package com.zdjz.enginetest.aviatorscript; + +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; +import com.googlecode.aviator.runtime.function.AbstractFunction; +import com.googlecode.aviator.runtime.function.FunctionUtils; +import com.googlecode.aviator.runtime.type.AviatorBigInt; +import com.googlecode.aviator.runtime.type.AviatorObject; +import com.zdjizhi.utils.IPUtil; + + +import java.util.Map; + +public class Ipv4StringToNum extends AbstractFunction { + private static final Log logger = LogFactory.get(); + @Override + public AviatorObject call(Map env, AviatorObject arg1) { + String ip = FunctionUtils.getStringValue(arg1, env); + + return new AviatorBigInt(IPUtil.getIpHostDesimal(ip)); + } + + @Override + public String getName() { + return "Ipv4StringToNum"; + } +} diff --git a/src/main/java/com/zdjz/enginetest/aviatorscript/script.av b/src/main/java/com/zdjz/enginetest/aviatorscript/script.av new file mode 100644 index 0000000..ea7e098 --- /dev/null +++ b/src/main/java/com/zdjz/enginetest/aviatorscript/script.av @@ -0,0 +1 @@ +let sum = 1; while sum < 1000 { sum = sum + sum; } println(sum); \ No newline at end of file diff --git a/src/main/java/com/zdjz/enginetest/janino/JaninoEngine.java b/src/main/java/com/zdjz/enginetest/janino/JaninoEngine.java new file mode 100644 index 0000000..f6c6ad9 --- /dev/null +++ b/src/main/java/com/zdjz/enginetest/janino/JaninoEngine.java @@ -0,0 +1,124 @@ +package com.zdjz.enginetest.janino; + + +import com.zdjz.enginetest.People; +import org.codehaus.commons.compiler.CompilerFactoryFactory; +import org.codehaus.commons.compiler.IExpressionEvaluator; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +/** + * Janino Expression表达式语法与Java一致。 + */ +public class JaninoEngine { + + public static IExpressionEvaluator getJaninoEvaluator() throws Exception { + return CompilerFactoryFactory.getDefaultCompilerFactory().newExpressionEvaluator(); + } + + + /** + * 传入变量求和 + */ + @Test + public void rule1() throws Exception { + IExpressionEvaluator janinoEvaluator = getJaninoEvaluator(); + //设置返回值类型,若不配置该项,则返回Object + janinoEvaluator.setExpressionType(int.class); + //设置参数名称及其类型 + janinoEvaluator.setParameters( + new String[]{"a", "b", "c"}, + new Class[]{Integer.class, Integer.class, Integer.class} + + ); + String expression = "a + b + c "; + janinoEvaluator.cook(expression); + + //给参数赋值,需要与上面定义的一一对应。 + Object[] arguments = new Object[3]; + arguments[0] = 1; + arguments[1] = 2; + arguments[2] = 3; + System.out.println(janinoEvaluator.evaluate(arguments)); + } + + + + /** + * 传入字符串进行空判断或值判断 + */ + @Test + public void rule2() throws Exception { + IExpressionEvaluator janinoEvaluator = getJaninoEvaluator(); + //设置返回值类型,若不配置该项,则返回Object + janinoEvaluator.setExpressionType(Boolean.class); + //设置参数名称及其类型 + janinoEvaluator.setParameters( + new String[]{"a","b"}, + new Class[]{String.class, String.class} + + ); + //判断变量a是否为空,且变量b是否为“Str” + //此方法不唯一,只要符合Java规范,也可以使用第三方工具类(使用import导入即可) + String expression = "import com.zdjizhi.utils.StringUtil; StringUtil.isBlank(a) && b.equals(\"Str\")"; + janinoEvaluator.cook(expression); + + //给参数赋值,需要与上面定义的一一对应。 + Object[] arguments = new Object[2]; + arguments[1] = "Str"; + System.out.println(janinoEvaluator.evaluate(arguments)); + } + + + /** + * Janino并不提供像Aviator那种的语法糖,不支持向a.b这样的方式访问变量a中的某个属性b,仅可以像Java语言那样通过get方法进行属性的访问 + * 若变量为JavaBean类型,且JavaBean的属性为public,则也可使用a.b的方式访问 + */ + @Test + public void rule3() throws Exception { + IExpressionEvaluator janinoEvaluator = getJaninoEvaluator(); + //设置返回值类型,若不配置该项,则返回Object + janinoEvaluator.setExpressionType(Boolean.class); + //设置参数名称及其类型 + janinoEvaluator.setParameters( + new String[]{"people"}, + new Class[]{People.class} + + ); + String expression = "people.age > 18"; + janinoEvaluator.cook(expression); + People people = new People("xiaoming", 19, "192.168.22.11"); + //给参数赋值,需要与上面定义的一一对应。 + Object[] arguments = new Object[1]; + arguments[0] = people; + System.out.println(janinoEvaluator.evaluate(arguments)); + } + + @Test + public void rule4() throws Exception { + IExpressionEvaluator janinoEvaluator = getJaninoEvaluator(); + //设置返回值类型,若不配置该项,则返回Object + janinoEvaluator.setExpressionType(Boolean.class); + //设置参数名称及其类型 + janinoEvaluator.setParameters( + new String[]{"map"}, + new Class[]{Map.class} + + ); + String expression = "import com.zdjizhi.utils.IPUtil; IPUtil.getIpHostDesimal(map.get(\"server_ip\").toString()) < IPUtil.getIpHostDesimal(\"192.168.22.199\") && IPUtil.getIpHostDesimal(map.get(\"server_ip\").toString()) > IPUtil.getIpHostDesimal(\"192.168.22.1\")"; + janinoEvaluator.cook(expression); + HashMap hashMap = new HashMap<>(); + hashMap.put("server_ip", "192.168.22.200"); + hashMap.put("client_ip", "192.168.22.99"); + + //给参数赋值,需要与上面定义的一一对应。 + Object[] arguments = new Object[1]; + arguments[0] = hashMap; + System.out.println(janinoEvaluator.evaluate(arguments)); + } + + + +}