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;
+ }
+ }
+
+}