From c391f8d3229879779fa0d06cd517b9f078a8c36d Mon Sep 17 00:00:00 2001 From: xueye Date: Sun, 4 Jul 2021 22:50:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notify-core/pom.xml | 23 +++ .../cicdi/notify/util/ExpressionUtils.java | 99 +++++++++++ .../com/cicdi/notify/util/TemplateParser.java | 162 ++++++++++++++++++ .../script/engine/DynamicScriptEngine.java | 41 +++++ .../util/script/engine/ExecuteResult.java | 39 +++++ .../script/engine/ListenerSupportEngine.java | 20 +++ .../util/script/engine/ScriptContext.java | 20 +++ .../script/engine/spel/SpelParserEngine.java | 88 ++++++++++ 8 files changed, 492 insertions(+) create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/ExpressionUtils.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/TemplateParser.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/script/engine/DynamicScriptEngine.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/script/engine/ExecuteResult.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/script/engine/ListenerSupportEngine.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/script/engine/ScriptContext.java create mode 100644 notify-core/src/main/java/com/cicdi/notify/util/script/engine/spel/SpelParserEngine.java diff --git a/notify-core/pom.xml b/notify-core/pom.xml index a50c80b..6a7f5aa 100644 --- a/notify-core/pom.xml +++ b/notify-core/pom.xml @@ -16,4 +16,27 @@ 8 + + + + commons-beanutils + commons-beanutils + 1.9.4 + + + + commons-codec + commons-codec + 1.14 + + + + org.springframework + spring-expression + 5.2.15.RELEASE + compile + + + + \ No newline at end of file diff --git a/notify-core/src/main/java/com/cicdi/notify/util/ExpressionUtils.java b/notify-core/src/main/java/com/cicdi/notify/util/ExpressionUtils.java new file mode 100644 index 0000000..531d41f --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/ExpressionUtils.java @@ -0,0 +1,99 @@ +package com.cicdi.notify.util; + +import com.cicdi.notify.util.script.engine.DynamicScriptEngine; +import com.cicdi.notify.util.script.engine.spel.SpelParserEngine; +import org.apache.commons.beanutils.BeanUtilsBean2; +import org.apache.commons.codec.digest.DigestUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * 表达式工具,用户解析表达式为字符串 + */ +public class ExpressionUtils { + //表达式提取正则 ${.+?} + private static final Pattern PATTERN = Pattern.compile("(?<=\\$\\{)(.+?)(?=})"); + + /** + * 获取默认的表达式变量 + * + * @return 变量集合 + */ + public static Map getDefaultVar() { + return new HashMap<>(); + } + + /** + * 获取默认的表达式变量并将制定的变量合并在一起 + * + * @param var 要合并的变量集合 + * @return 变量集合 + */ + public static Map getDefaultVar(Map var) { + Map vars = getDefaultVar(); + vars.putAll(var); + return vars; + } + + /** + * 使用默认的变量解析表达式 + * + * @param expression 表达式字符串 + * @return 解析结果 + * @throws Exception 解析错误 + * @see ExpressionUtils#analytical(String, Map) + */ + public static String analytical(String expression) throws Exception { + return analytical(expression, new HashMap<>()); + } + + /** + * 解析表达式,表达式使用{@link ExpressionUtils#PATTERN}进行提取
+ * 如调用 analytical("http://${3+2}/test",var,"spel")
+ * + * @param expression 表达式字符串 + * @param context 变量 + * @return 解析结果 + */ + public static String analytical(String expression, Map context) { + if (!expression.contains("${")) { + return expression; + } + + return TemplateParser.parse(expression, key -> { + if (key == null || "".equals(key)) { + return ""; + } + + // SpEL解析 + DynamicScriptEngine engine = new SpelParserEngine(); + String id = DigestUtils.md5Hex(key); + try { + if (!engine.compiled(id)) { + engine.compile(id, key); + } + if (engine.execute(id, context).isSuccess()) { + return String.valueOf(engine.execute(id, context).getIfSuccess()); + } + } catch (Exception e) { + return ""; + } + + // 表达式替换 + if (!key.contains("#")) { + try { + Object fast = BeanUtilsBean2.getInstance().getPropertyUtils().getProperty(context, key); + if (fast != null) { + return fast.toString(); + } + } catch (Exception ignore) { + return ""; + } + } + return ""; + }); + } + +} diff --git a/notify-core/src/main/java/com/cicdi/notify/util/TemplateParser.java b/notify-core/src/main/java/com/cicdi/notify/util/TemplateParser.java new file mode 100644 index 0000000..04fd5ac --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/TemplateParser.java @@ -0,0 +1,162 @@ +package com.cicdi.notify.util; + +import org.apache.commons.beanutils.BeanUtilsBean; + +import java.util.Arrays; +import java.util.function.Function; + +/** + * @author xueye + */ +public class TemplateParser { + private static final char[] DEFAULT_PREPARE_START_SYMBOL = "${".toCharArray(); + + private static final char[] DEFAULT_PREPARE_END_SYMBOL = "}".toCharArray(); + + private char[] prepareStartSymbol = DEFAULT_PREPARE_START_SYMBOL; + + private char[] prepareEndSymbol = DEFAULT_PREPARE_END_SYMBOL; + + private String template; + + private Object parameter; + + public char[] getPrepareStartSymbol() { + return prepareStartSymbol; + } + + public void setPrepareStartSymbol(char[] prepareStartSymbol) { + this.prepareStartSymbol = prepareStartSymbol; + } + + public char[] getPrepareEndSymbol() { + return prepareEndSymbol; + } + + public void setPrepareEndSymbol(char[] prepareEndSymbol) { + this.prepareEndSymbol = prepareEndSymbol; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public Object getParameter() { + return parameter; + } + + public void setParameter(Object parameter) { + this.parameter = parameter; + } + + private char[] templateArray; + + private int pos; + + private char symbol; + + private char[] newArr; + + private int len = 0; + + private byte prepareFlag = 0; + + public void setParsed(char[] chars, int end) { + for (int i = 0; i < end; i++) { + char aChar = chars[i]; + if (newArr.length <= len) { + newArr = Arrays.copyOf(newArr, len + templateArray.length); + } + newArr[len++] = aChar; + } + } + + public void setParsed(char... chars) { + setParsed(chars, chars.length); + } + + private void init() { + templateArray = template.toCharArray(); + pos = 0; + newArr = new char[templateArray.length * 2]; + } + + private boolean isPreparing() { + return prepareFlag > 0; + } + + private boolean isPrepare() { + if (prepareStartSymbol[prepareFlag] == symbol) { + prepareFlag++; + } + if (prepareFlag >= prepareStartSymbol.length) { + prepareFlag = 0; + return true; + } + return false; + } + + private boolean isPrepareEnd() { + for (char c : prepareEndSymbol) { + if (c == symbol) { + return true; + } + } + return false; + } + + private boolean next() { + symbol = templateArray[pos++]; + return pos < templateArray.length; + } + + public String parse(Function propertyMapping) { + init(); + boolean inPrepare = false; + + char[] expression = new char[128]; + int expressionPos = 0; + + while (next()) { + if (isPrepare()) { + inPrepare = true; + } else if (inPrepare && isPrepareEnd()) { + inPrepare = false; + setParsed(propertyMapping.apply(new String(expression, 0, expressionPos)).toCharArray()); + expressionPos = 0; + } else if (inPrepare) { + expression[expressionPos++] = symbol; + } else if (!isPreparing()) { + setParsed(symbol); + } + } + + if (isPrepareEnd() && expressionPos > 0) { + setParsed(propertyMapping.apply(new String(expression, 0, expressionPos)).toCharArray()); + } else { + setParsed(symbol); + } + return new String(newArr, 0, len); + } + + + public static String parse(String template, Object parameter) { + return parse(template, var -> { + try { + return BeanUtilsBean.getInstance().getProperty(parameter, var); + } catch (Exception ignored) { + } + return ""; + }); + } + + public static String parse(String template, Function parameterGetter) { + TemplateParser parser = new TemplateParser(); + parser.template = template; + return parser.parse(parameterGetter); + } +} \ No newline at end of file diff --git a/notify-core/src/main/java/com/cicdi/notify/util/script/engine/DynamicScriptEngine.java b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/DynamicScriptEngine.java new file mode 100644 index 0000000..a82b7d0 --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/DynamicScriptEngine.java @@ -0,0 +1,41 @@ +package com.cicdi.notify.util.script.engine; + +import java.util.Map; + +public interface DynamicScriptEngine { + + /** + * 引擎初始化 + * + * @param contents 初始化内容 + * @throws Exception 异常 + */ + void init(String... contents) throws Exception; + + /** + * 编译脚本 + * + * @param id 脚本id + * @param code 脚本内容 + * @return 编译是否成功 + * @throws Exception 异常欣喜 + */ + boolean compile(String id, String code) throws Exception; + + ScriptContext getContext(String id); + + boolean compiled(String id); + + boolean remove(String id); + + /** + * 执行编译好的脚本 + * + * @param id 编译后的id + * @param param 执行参数 + * @return 执行结果 + */ + ExecuteResult execute(String id, Map param); + + void addGlobalVariable(Map vars); +} diff --git a/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ExecuteResult.java b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ExecuteResult.java new file mode 100644 index 0000000..6dd2d9a --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ExecuteResult.java @@ -0,0 +1,39 @@ +package com.cicdi.notify.util.script.engine; + +import javax.script.ScriptException; + +public class ExecuteResult { + + private boolean success; + + private Object result; + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setResult(Object result) { + this.result = result; + } + + @Override + public String toString() { + return String.valueOf(this.get()); + } + + public Object get() { + return result; + } + + public Object getIfSuccess() throws Exception { + if (!success) { + throw new ScriptException(""); + } + return result; + } + +} diff --git a/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ListenerSupportEngine.java b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ListenerSupportEngine.java new file mode 100644 index 0000000..0ca0747 --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ListenerSupportEngine.java @@ -0,0 +1,20 @@ +package com.cicdi.notify.util.script.engine; + +import java.util.HashMap; +import java.util.Map; + +public abstract class ListenerSupportEngine implements DynamicScriptEngine { + + private Map globalVariable; + + protected Map getGlobalVariable() { + if (null == globalVariable) return new HashMap<>(); + return new HashMap<>(globalVariable); + } + + @Override + public void addGlobalVariable(Map vars) { + globalVariable = vars; + } + +} diff --git a/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ScriptContext.java b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ScriptContext.java new file mode 100644 index 0000000..a20fa6f --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/ScriptContext.java @@ -0,0 +1,20 @@ +package com.cicdi.notify.util.script.engine; + +public class ScriptContext { + private final String id; + + private final String md5; + + public ScriptContext(String id, String md5) { + this.id = id; + this.md5 = md5; + } + + public String getMd5() { + return md5; + } + + public String getId() { + return id; + } +} diff --git a/notify-core/src/main/java/com/cicdi/notify/util/script/engine/spel/SpelParserEngine.java b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/spel/SpelParserEngine.java new file mode 100644 index 0000000..84a85c3 --- /dev/null +++ b/notify-core/src/main/java/com/cicdi/notify/util/script/engine/spel/SpelParserEngine.java @@ -0,0 +1,88 @@ +package com.cicdi.notify.util.script.engine.spel; + +import com.cicdi.notify.util.script.engine.ExecuteResult; +import com.cicdi.notify.util.script.engine.ListenerSupportEngine; +import com.cicdi.notify.util.script.engine.ScriptContext; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SpelParserEngine extends ListenerSupportEngine { + + protected final Map scriptContextCache = new ConcurrentHashMap<>(); + + protected final ExpressionParser expressionParser = new SpelExpressionParser(); + + @Override + public boolean compiled(String id) { + return scriptContextCache.containsKey(id); + } + + @Override + public void init(String... contents) { + } + + @Override + public boolean remove(String id) { + return scriptContextCache.remove(id) != null; + } + + @Override + public ScriptContext getContext(String id) { + return scriptContextCache.get(id); + } + + @Override + public boolean compile(String id, String code) { + scriptContextCache.put(id, new SpelScriptContext(id, DigestUtils.md5Hex(code), expressionParser.parseExpression(code))); + return false; + } + + @Override + public ExecuteResult execute(String id, Map param) { + ExecuteResult result = new ExecuteResult(); + SpelScriptContext scriptContext = scriptContextCache.get(id); + try { + if (scriptContext != null) { + param = new HashMap<>(param); + param.putAll(getGlobalVariable()); + + StandardEvaluationContext context = new StandardEvaluationContext(param); + context.setVariable("u", new Date()); + for (Map.Entry entry : param.entrySet()) { + context.setVariable(entry.getKey(), entry.getValue()); + } + Expression script = scriptContext.getScript(); + Object obj = script.getValue(context); + result.setSuccess(true); + result.setResult(obj); + } else { + result.setSuccess(false); + result.setResult(null); + } + } catch (Exception ignored) { + } + return result; + } + + static class SpelScriptContext extends ScriptContext { + private final Expression script; + + public SpelScriptContext(String id, String md5, Expression script) { + super(id, md5); + this.script = script; + } + + public Expression getScript() { + return script; + } + } + +}