Browse Source

整合代碼

ZhangYanJie 2 months ago
parent
commit
54e447a420
22 changed files with 947 additions and 40 deletions
  1. 66 22
      src/main/java/com/sundata/internalevaluation/calc/model/CalcUnit.java
  2. 7 0
      src/main/java/com/sundata/internalevaluation/calc/model/finals/CalcStatus.java
  3. 2 0
      src/main/java/com/sundata/internalevaluation/calc/model/finals/CalcType.java
  4. 3 3
      src/main/java/com/sundata/internalevaluation/calc/model/interfaces/Calc.java
  5. 0 2
      src/main/java/com/sundata/internalevaluation/calc/running/CalcRunning.java
  6. 6 5
      src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcForkJoinWorkerThread.java
  7. 11 6
      src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcRecursiveTask.java
  8. 2 2
      src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcUnitThreadFactory.java
  9. 16 0
      src/main/java/com/sundata/internalevaluation/script/ScriptUtil.java
  10. 15 0
      src/main/java/com/sundata/internalevaluation/script/TemplateUtil.java
  11. 20 0
      src/main/java/com/sundata/internalevaluation/script/cache/ScriptLRUCache.java
  12. 19 0
      src/main/java/com/sundata/internalevaluation/script/cache/TemplateLRUCache.java
  13. 162 0
      src/main/java/com/sundata/internalevaluation/script/execute/ScriptExecute.java
  14. 161 0
      src/main/java/com/sundata/internalevaluation/script/execute/TemplateExecute.java
  15. 59 0
      src/main/java/com/sundata/internalevaluation/script/model/ScriptModel.java
  16. 79 0
      src/main/java/com/sundata/internalevaluation/script/model/ScriptRunningModel.java
  17. 62 0
      src/main/java/com/sundata/internalevaluation/script/model/TemplateModel.java
  18. 79 0
      src/main/java/com/sundata/internalevaluation/script/model/TemplateRunningModel.java
  19. 61 0
      src/main/java/com/sundata/internalevaluation/script/pool/GroovyScriptFactory.java
  20. 20 0
      src/main/java/com/sundata/internalevaluation/script/pool/GroovyScriptPool.java
  21. 46 0
      src/main/java/com/sundata/internalevaluation/script/pool/GroovyTemplateFactory.java
  22. 51 0
      src/main/java/com/sundata/internalevaluation/script/pool/GroovyTemplatePool.java

+ 66 - 22
src/main/java/com/sundata/internalevaluation/calc/model/CalcUnit.java

@@ -1,6 +1,8 @@
 package com.sundata.internalevaluation.calc.model;
 
-
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.TimeInterval;
+import com.sundata.internalevaluation.calc.model.finals.CalcStatus;
 import com.sundata.internalevaluation.calc.model.finals.CalcType;
 import com.sundata.internalevaluation.calc.model.interfaces.Calc;
 import org.slf4j.Logger;
@@ -12,17 +14,17 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
 public abstract class CalcUnit implements Calc, Serializable {
     private static final Logger log = LoggerFactory.getLogger(CalcUnit.class);
     @Serial
     private static final long serialVersionUID = 1L;
-
     /**
      * 计算单元编号
      */
     private final String calcCode;
-
     /**
      * 计算单元名称
      */
@@ -31,11 +33,18 @@ public abstract class CalcUnit implements Calc, Serializable {
      * 计算单元类型
      */
     private final CalcType calcType;
+
+    /**
+     * 计算状态
+     */
+    private CalcStatus status = CalcStatus.READY;
     /**
      * 初始化用属性对象节点
      */
     private final Map<String, Object> initContext;
 
+    private CompletableFuture<CalcResult<String, Object>> resultContextFuture;
+
     /**
      * 计算结果对象
      */
@@ -49,10 +58,10 @@ public abstract class CalcUnit implements Calc, Serializable {
     /**
      * 创建数据单元的绝对对象,对象必须包含如下参数
      *
-     * @param calcCode        计算对象编号
-     * @param calcName        计算对象名称
-     * @param calcType        计算类型
-     * @param initContext     计算单元初始化参数
+     * @param calcCode    计算对象编号
+     * @param calcName    计算对象名称
+     * @param calcType    计算类型
+     * @param initContext 计算单元初始化参数
      */
     public CalcUnit(String calcCode, String calcName, CalcType calcType, Map<String, Object> initContext) {
         this.calcCode = calcCode;
@@ -61,37 +70,61 @@ public abstract class CalcUnit implements Calc, Serializable {
         this.initContext = initContext;
     }
 
-    public void startCalc(String calculateInstanceNumber, Map<String, Object> context, Map<CalcUnit, CalcResult<String, Object>> sourceResults) {
 
-        if (isCalcFinished(this.calculateInstanceNumber)) { // 如果已经计算过则不再执行计算处理,仅需要通过 initResultContext 初始化 resultContext 对象
+    public CalcResult<String, Object> getResultContextFuture(String calculateInstanceNumber, Map<String, Object> context, Map<CalcUnit, CalcResult<String, Object>> sourceResults) {
+        try {
+            if (this.resultContextFuture != null) {
+                return this.resultContextFuture.get();
+            } else {
+                synchronized (this) {
+                    if (this.resultContextFuture == null) {
+                        this.resultContextFuture = CompletableFuture.supplyAsync(() -> {
+                                    this.startCalc(calculateInstanceNumber, context, sourceResults);
+                                    return this.resultContext;
+                                }
+                        );
+                    }
+                    return this.resultContextFuture.get();
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            log.error(e.getMessage(), e);
+            throw new CalcException(calculateInstanceNumber, "计算出现问题,线程级别,请检查处理过程。");
+        }
+    }
+
+
+    public void startCalc(String calculateInstanceNumber, Map<String, Object> context, Map<CalcUnit, CalcResult<String, Object>> sourceResults) {
+        setStatus(CalcStatus.RUNNING);
+        if (isCalcFinished(calculateInstanceNumber)) { // 如果已经计算过则不再执行计算处理,仅需要通过 initResultContext 初始化 resultContext 对象
+            log.info("当前计算节点为:[{}-{}-{}],曾经完成过计算,加载原始计算内容并结束", this.getCalcType(), this.getCalcCode(), this.getCalcName());
             initResultContext(calculateInstanceNumber);//计算过
-            getCalcUnitInstanceNumber();
+            setStatus(CalcStatus.FINISHED);
             return;
         }
-
+        TimeInterval interval = new TimeInterval();
         setCalculateInstanceNumber(calculateInstanceNumber);
 //        String calcUnitInstanceNumber = getCalcUnitInstanceNumber();
-        log.debug("计算对象为:{}", this);
-        log.debug("计算流水号为:{}",calculateInstanceNumber);
-        log.debug("计算参数为: {}", context);
-        log.debug("源头节点提供的结果为: {}", sourceResults);
+        log.info("当前计算节点为:[{}-{}-{}],计算流水号为:{}", this.getCalcType(), this.getCalcCode(), this.getCalcName(), calculateInstanceNumber);
+        interval.start();
         try {
-            log.debug("calcUnitInstanceNumber: {}", calculateInstanceNumber);
-            log.debug("startBeforeCalcUnit: {}", calculateInstanceNumber);
             this.beforeCalc(context);
-            final CalcResult<String, Object> thisResult = new CalcResult<>(calculateInstanceNumber);;
-            this.calc(thisResult,calculateInstanceNumber,context, sourceResults);
+            final CalcResult<String, Object> thisResult = new CalcResult<>(calculateInstanceNumber);
+            ;
+            this.calc(thisResult, calculateInstanceNumber, context, sourceResults);
 //            log.info("计算节点【{}-{}】完成,结果为【{}】",this.getCalcType(),this.getCalcCode(), thisResult);
-            if (thisResult.isEmpty()){
-                throw new CalcException(calculateInstanceNumber,"请注意,计算过程中必须将 thisResult 结果数据赋值,否则父节点无法正常计算,计算错误的节点为【"+this.toString()+"】");
+            if (thisResult.isEmpty()) {
+                throw new CalcException(calculateInstanceNumber, "请注意,计算过程中必须将 thisResult 结果数据赋值,否则父节点无法正常计算,计算错误的节点为【" + this.toString() + "】");
             }
             setResultContext(thisResult);// 计算完成需要设置结果
-            log.debug("startAfterCalcUnit: {}", calculateInstanceNumber);
             this.afterCalc(context);
         } catch (CalcException e) {
             this.calculateInstanceNumber = null;
             log.error("计算失败,恢复计算逻辑: {}", calculateInstanceNumber, e);
+            setStatus(CalcStatus.ERROR);
         }
+        setStatus(CalcStatus.FINISHED);
+        log.info("计算节点[{}-{}-{}]计算过程完成,耗时:{}", this.getCalcType(), this.getCalcCode(), this.getCalcName(), DateUtil.formatBetween(interval.interval()));
     }
 
     /**
@@ -105,12 +138,14 @@ public abstract class CalcUnit implements Calc, Serializable {
     /**
      * 初始化计算结果的方法,如果已经计算过,在实现过程中,应当在此方法中根据计算流水号重新初始化 resultContext 结果对象,为其他依赖对象做准备
      * 若明明计算过本单元但再次计算时没有初始化该对象,则计算依赖出现问题无法定位与处理
+     *
      * @param calculateInstanceNumber 计算流水号
      */
     public abstract void initResultContext(String calculateInstanceNumber);
 
     /**
      * 根据节点配置获取源节点;
+     *
      * @return 所有源头节点
      */
     public abstract List<CalcUnit> getSourceCalcUnits();
@@ -130,6 +165,7 @@ public abstract class CalcUnit implements Calc, Serializable {
     public CalcResult<String, Object> getResultContext() {
         return resultContext;
     }
+
     public void setResultContext(CalcResult<String, Object> resultContext) {
         this.resultContext = resultContext;
     }
@@ -182,4 +218,12 @@ public abstract class CalcUnit implements Calc, Serializable {
                 .add("resultContext=" + resultContext)
                 .toString();
     }
+
+    public CalcStatus getStatus() {
+        return status;
+    }
+
+    private void setStatus(CalcStatus status) {
+        this.status = status;
+    }
 }

+ 7 - 0
src/main/java/com/sundata/internalevaluation/calc/model/finals/CalcStatus.java

@@ -0,0 +1,7 @@
+package com.sundata.internalevaluation.calc.model.finals;
+
+public enum CalcStatus {
+    READY,RUNNING,FINISHED,ERROR
+    ;
+//    abstract void change();
+}

+ 2 - 0
src/main/java/com/sundata/internalevaluation/calc/model/finals/CalcType.java

@@ -8,6 +8,8 @@ public enum CalcType {
     ;
 
     private final String name ;
+    private String id;
+
     CalcType(String name){
         this.name = name;
     }

+ 3 - 3
src/main/java/com/sundata/internalevaluation/calc/model/interfaces/Calc.java

@@ -1,7 +1,5 @@
 package com.sundata.internalevaluation.calc.model.interfaces;
 
-
-
 import com.sundata.internalevaluation.calc.model.CalcResult;
 import com.sundata.internalevaluation.calc.model.CalcUnit;
 
@@ -22,6 +20,7 @@ public interface Calc {
     /**
      * 必须实现的主体计算内容
      *
+     * @param thisResult              本计算单元的结果
      * @param sourceResults           源头计算节点的结果
      * @param context                 节点计算参数清单
      * @param calculateInstanceNumber 计算流水号
@@ -34,6 +33,7 @@ public interface Calc {
      * @param sourceResults           源头计算节点的结果
      * @param context                 节点计算参数清单
      * @param calculateInstanceNumber 计算流水号
+     * @return
      */
-    void startCalc(String calculateInstanceNumber,final Map<String, Object> context,final Map<CalcUnit, CalcResult<String, Object>> sourceResults);
+    void startCalc(String calculateInstanceNumber, final Map<String, Object> context, final Map<CalcUnit, CalcResult<String, Object>> sourceResults);
 }

+ 0 - 2
src/main/java/com/sundata/internalevaluation/calc/running/CalcRunning.java

@@ -8,8 +8,6 @@ import com.sundata.internalevaluation.calc.running.threads.CalcRecursiveTask;
 import com.sundata.internalevaluation.calc.running.threads.CalcTaskResult;
 import com.sundata.internalevaluation.calc.running.threads.CalcUnitThreadFactory;
 import com.sundata.internalevaluation.calc.running.threads.fianl.CalcTaskResultType;
-
-
 import org.jgrapht.Graph;
 import org.jgrapht.graph.DefaultDirectedGraph;
 import org.jgrapht.graph.DefaultEdge;

+ 6 - 5
src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcForkJoinWorkerThread.java

@@ -6,6 +6,7 @@ import java.util.concurrent.ForkJoinWorkerThread;
 public class CalcForkJoinWorkerThread extends ForkJoinWorkerThread {
 
     private final String calculateInstanceNumber;
+
     /**
      * Creates a ForkJoinWorkerThread operating in the given pool.
      *
@@ -18,9 +19,9 @@ public class CalcForkJoinWorkerThread extends ForkJoinWorkerThread {
     }
 
     @Override
-        protected void onStart() {
-            super.onStart();
-            // 设置线程名称
-            setName("计算["+calculateInstanceNumber + "]-" + getPoolIndex());
-        }
+    protected void onStart() {
+        super.onStart();
+        // 设置线程名称
+        setName("计算[" + calculateInstanceNumber + "]"+"-"+getName());
+    }
 }

+ 11 - 6
src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcRecursiveTask.java

@@ -1,5 +1,7 @@
 package com.sundata.internalevaluation.calc.running.threads;
 
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.TimeInterval;
 import com.sundata.internalevaluation.calc.model.CalcResult;
 import com.sundata.internalevaluation.calc.model.CalcUnit;
 import org.jgrapht.Graph;
@@ -42,9 +44,11 @@ public class CalcRecursiveTask extends RecursiveTask<CalcResult<String, Object>>
      */
     @Override
     protected CalcResult<String, Object> compute() {
-        log.debug("开始计算");
+        TimeInterval interval = new TimeInterval();
+        interval.start();
+        log.info("计算任务开始[计算流水号:{},计算单元:{}-{}-{}]",calculateInstanceNumber, calcUnit.getCalcType(),calcUnit.getCalcCode(), calcUnit.getCalcName());
         if (results.containsKey(calcUnit)) {
-            log.debug("已经计算过,不再计算");
+            log.info("已经计算过,不再计算[计算流水号:{},计算单元:{}-{}-{}]",calculateInstanceNumber, calcUnit.getCalcType(),calcUnit.getCalcCode(), calcUnit.getCalcName());
             return results.get(calcUnit);
         }
         List<CalcUnit> predecessors = graph.incomingEdgesOf(calcUnit)
@@ -53,9 +57,10 @@ public class CalcRecursiveTask extends RecursiveTask<CalcResult<String, Object>>
                 .toList();
         // 如果有来源节点,则去计算来源节点并合并计算内容,如果没有来源节点,则处理
         if (!predecessors.isEmpty()) {
+            log.debug("存在子节点,处理子节点");
             childResults = new ConcurrentHashMap<>();
             List<CalcRecursiveTask> tasks = predecessors.stream()
-                    .map(predecessor -> new CalcRecursiveTask(calculateInstanceNumber, predecessor, content, graph,results,childResults))
+                    .map(predecessor -> new CalcRecursiveTask(calculateInstanceNumber, predecessor, content, graph, results, childResults))
                     .toList();
             for (int i = 0; i < predecessors.size(); i++) {
                 CalcRecursiveTask task = tasks.get(i);
@@ -63,10 +68,10 @@ public class CalcRecursiveTask extends RecursiveTask<CalcResult<String, Object>>
                 childResults.put(predecessors.get(i), task.invoke());
             }
         }
-        calcUnit.startCalc(calculateInstanceNumber, content,childResults);// 假设节点名称是一个整数
-        CalcResult<String, Object> result = calcUnit.getResultContext();
+        ;// 假设节点名称是一个整数
+        CalcResult<String, Object> result = calcUnit.getResultContextFuture(calculateInstanceNumber, content, childResults);
         results.put(calcUnit, result);
-        log.debug("计算完成");
+        log.info("计算完成,耗时:[{}][计算流水号:{},计算单元:{}-{}-{}]", DateUtil.formatBetween(interval.interval()),calculateInstanceNumber, calcUnit.getCalcType(),calcUnit.getCalcCode(), calcUnit.getCalcName());
         return result;
     }
 

+ 2 - 2
src/main/java/com/sundata/internalevaluation/calc/running/threads/CalcUnitThreadFactory.java

@@ -4,7 +4,7 @@ import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ForkJoinWorkerThread;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class CalcUnitThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory{
+public class CalcUnitThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
     private static AtomicInteger counter = new AtomicInteger();
 
 
@@ -31,6 +31,6 @@ public class CalcUnitThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadF
      */
     @Override
     public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
-        return new CalcForkJoinWorkerThread(pool,calculateInstanceNumber);
+        return new CalcForkJoinWorkerThread(pool, calculateInstanceNumber);
     }
 }

+ 16 - 0
src/main/java/com/sundata/internalevaluation/script/ScriptUtil.java

@@ -0,0 +1,16 @@
+package com.sundata.internalevaluation.script;
+
+import com.sundata.internalevaluation.script.execute.ScriptExecute;
+
+import java.util.Map;
+
+public class ScriptUtil {
+
+    public static void addScript(String scriptId,String scriptStr) {
+        ScriptExecute.addCache(scriptId, scriptStr);
+    }
+
+    public static Object executeScript(String scriptId, String scriptStr, Map<String,Object> params) {
+        return ScriptExecute.executeScript(scriptId, scriptStr, params);
+    }
+}

+ 15 - 0
src/main/java/com/sundata/internalevaluation/script/TemplateUtil.java

@@ -0,0 +1,15 @@
+package com.sundata.internalevaluation.script;
+
+
+import com.sundata.internalevaluation.script.execute.TemplateExecute;
+
+import java.util.Map;
+
+public class TemplateUtil {
+    public static void addTemplate(String templateId,String templateStr){
+        TemplateExecute.addCache(templateId, templateStr);
+    }
+    public static String execute(String templateId,String templateStr , Map<String,Object> data){
+        return TemplateExecute.executeScript(templateId, templateStr, data);
+    }
+}

+ 20 - 0
src/main/java/com/sundata/internalevaluation/script/cache/ScriptLRUCache.java

@@ -0,0 +1,20 @@
+package com.sundata.internalevaluation.script.cache;
+
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ScriptLRUCache<ScriptModel, ObjectPool> extends LinkedHashMap<ScriptModel, ObjectPool> {
+    private final int maxSize;
+
+    public ScriptLRUCache(int maxSize) {
+        super(maxSize, 0.75f, true); // 设置访问顺序
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<ScriptModel, ObjectPool> eldest) {
+        // 当元素数量超过最大大小时,移除最旧的元素
+        return size() > maxSize;
+    }
+}

+ 19 - 0
src/main/java/com/sundata/internalevaluation/script/cache/TemplateLRUCache.java

@@ -0,0 +1,19 @@
+package com.sundata.internalevaluation.script.cache;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class TemplateLRUCache<TemplateModel, ObjectPool> extends LinkedHashMap<TemplateModel, ObjectPool> {
+    private final int maxSize;
+
+    public TemplateLRUCache(int maxSize) {
+        super(maxSize, 0.75f, true); // 设置访问顺序
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(Map.Entry<TemplateModel, ObjectPool> eldest) {
+        // 当元素数量超过最大大小时,移除最旧的元素
+        return size() > maxSize;
+    }
+}

+ 162 - 0
src/main/java/com/sundata/internalevaluation/script/execute/ScriptExecute.java

@@ -0,0 +1,162 @@
+package com.sundata.internalevaluation.script.execute;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import groovy.lang.Script;
+import org.apache.commons.pool2.ObjectPool;
+import com.sundata.internalevaluation.script.cache.ScriptLRUCache;
+import com.sundata.internalevaluation.script.model.ScriptModel;
+import com.sundata.internalevaluation.script.pool.GroovyScriptPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ScriptExecute {
+    private static final Logger log = LoggerFactory.getLogger(ScriptExecute.class);
+    private static final ScriptLRUCache<ScriptModel, ObjectPool<Script>> cache = new ScriptLRUCache<>(200);
+
+    private static void addCache(ScriptModel model, ObjectPool<Script> pool) {
+        cache.put(model, pool);
+    }
+
+    public static void addCache(String scriptId, String scriptStr) {
+        ScriptModel model = new ScriptModel(scriptId, scriptStr);
+        ObjectPool<Script> pool = GroovyScriptPool.createGroovyShellPool(model);
+        addCache(model, pool);
+    }
+
+    /**
+     * 根据数据对象对缓存进行处理,使用一次就处理一次
+     *
+     * @param model 脚本对象
+     * @return 池对象
+     */
+    public static ObjectPool<Script> getPool(ScriptModel model) {
+        if (cache.containsKey(model)) {
+            return cache.get(model);
+        } else {
+            ObjectPool<Script> pool = GroovyScriptPool.createGroovyShellPool(model);
+            addCache(model, pool);
+            return cache.get(model);
+        }
+    }
+
+    /**
+     * 根据ID获取缓存中的数据内容,不建议直接使用,需要确保数据中存在才行
+     *
+     * @param scriptId 脚本ID
+     * @return 脚本池
+     */
+    public static ObjectPool<Script> getPoolById(String scriptId) {
+        AtomicReference<ObjectPool<Script>> pool = new AtomicReference<>();
+        cache.forEach((c, p) -> {
+            if (scriptId.equals(c.getScriptId())) {
+                pool.set(p);
+            }
+        });
+        if (pool.get() != null) {
+            return pool.get();
+        } else {
+            // 缓存级数据没有就没有,没有需要重新构建
+            return null;
+        }
+    }
+
+    /**
+     * 根据ID获取缓存中的数据内容,不建议直接使用,需要确保数据中存在才行
+     *
+     * @param scriptStr 脚本内容
+     * @return 脚本池
+     */
+    public static ObjectPool<Script> getPoolByScriptStr(String scriptStr) {
+        AtomicReference<ObjectPool<Script>> pool = new AtomicReference<>();
+        cache.forEach((c, p) -> {
+            if (scriptStr.equals(c.getScriptStr())) {
+                pool.set(p);
+            }
+        });
+        if (pool.get() != null) {
+            return pool.get();
+        } else {
+            // 缓存级数据没有就没有,没有需要重新构建
+            return null;
+        }
+    }
+
+    public static boolean hasCacheById(String scriptId) {
+        return getPoolById(scriptId) != null;
+    }
+    public static boolean hasCacheByScriptStr(String scriptStr) {
+        return getPoolByScriptStr(scriptStr) != null;
+    }
+
+    public static boolean hasCache(String scriptId, String scriptStr) {
+        return getPool(new ScriptModel(scriptId, scriptStr)) != null;
+    }
+
+    /**
+     * 计算主方法
+     *
+     * @param scriptId  计算id
+     * @param scriptStr 脚本类
+     * @param params 脚本参数
+     * @return 脚本计算结果
+     */
+    public static Object executeScript(String scriptId, String scriptStr, Map<String, Object> params) {
+        AtomicReference<ScriptModel> model = new AtomicReference<>();
+        if (StrUtil.isNotBlank(scriptId) && hasCacheById(scriptId)) {
+            String finalScriptId = scriptId;
+            cache.keySet().forEach((k) -> {
+                if (k.getScriptId().equals(finalScriptId)) {
+                    model.set(k);
+                }
+            });
+            return executeScript(model.get(), params);
+        }else if(StrUtil.isNotBlank(scriptStr) && hasCacheByScriptStr(scriptStr) ) {
+            cache.keySet().forEach((k) -> {
+                if (k.getScriptStr().equals(scriptStr)) {
+                    model.set(k);
+                }
+            });
+            return executeScript(model.get(), params);
+        } else if (hasCache(scriptId, scriptStr) && StrUtil.isNotBlank(scriptStr)) {
+            return executeScript(new ScriptModel(scriptId, scriptStr), params);
+        } else {
+            if (StrUtil.isBlank(scriptStr) && StrUtil.isBlank(scriptId)) {
+                throw new NullPointerException("脚本与id均为空,无法执行计算");
+            }
+            if (StrUtil.isBlank(scriptId)) {
+                scriptId = RandomUtil.randomString(10);
+            }
+            return executeScript(new ScriptModel(scriptId, scriptStr), params);
+        }
+
+
+    }
+
+    /**
+     * 计算的基础方法
+     *
+     * @param scriptModel 对象清单
+     * @param params      计算用脚本参数
+     * @return 计算结果
+     */
+    private static Object executeScript(ScriptModel scriptModel, Map<String, Object> params) {
+        if (scriptModel == null) {
+            throw new NullPointerException("scriptModel is null");
+        }
+        ObjectPool<Script> pool = getPool(scriptModel);
+        try {
+            Script script = pool.borrowObject();
+            if (params != null) {
+                params.forEach((k, v) -> script.getBinding().setVariable(k, v));
+            }
+            return script.run();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        throw new RuntimeException("计算失败,请检查实际执行情况与日志!");
+    }
+}

+ 161 - 0
src/main/java/com/sundata/internalevaluation/script/execute/TemplateExecute.java

@@ -0,0 +1,161 @@
+package com.sundata.internalevaluation.script.execute;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import groovy.text.Template;
+import org.apache.commons.pool2.ObjectPool;
+import com.sundata.internalevaluation.script.cache.ScriptLRUCache;
+import com.sundata.internalevaluation.script.model.TemplateModel;
+import com.sundata.internalevaluation.script.pool.GroovyTemplatePool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class TemplateExecute {
+    private static final Logger log = LoggerFactory.getLogger(TemplateExecute.class);
+    private static final ScriptLRUCache<TemplateModel, ObjectPool<Template>> cache = new ScriptLRUCache<>(200);
+
+    private static void addCache(TemplateModel model, ObjectPool<Template> pool) {
+        cache.put(model, pool);
+    }
+
+    public static void addCache(String templateId, String tempalteStr) {
+        TemplateModel model = new TemplateModel(templateId, tempalteStr);
+        ObjectPool<Template> pool = GroovyTemplatePool.templateFactoryObjectPool(model);
+        addCache(model, pool);
+    }
+
+    /**
+     * 根据数据对象对缓存进行处理,使用一次就处理一次
+     *
+     * @param model 脚本对象
+     * @return 池对象
+     */
+    public static ObjectPool<Template> getPool(TemplateModel model) {
+        if (cache.containsKey(model)) {
+            return cache.get(model);
+        } else {
+            ObjectPool<Template> pool = GroovyTemplatePool.templateFactoryObjectPool(model);
+            addCache(model, pool);
+            return cache.get(model);
+        }
+    }
+
+    /**
+     * 根据ID获取缓存中的数据内容,不建议直接使用,需要确保数据中存在才行
+     *
+     * @param templateId 脚本ID
+     * @return 脚本池
+     */
+    public static ObjectPool<Template> getPoolById(String templateId) {
+        AtomicReference<ObjectPool<Template>> pool = new AtomicReference<>();
+        cache.forEach((c, p) -> {
+            if (templateId.equals(c.getTemplateId())) {
+                pool.set(p);
+            }
+        });
+        if (pool.get() != null) {
+            return pool.get();
+        } else {
+            // 缓存级数据没有就没有,没有需要重新构建
+            return null;
+        }
+    }
+
+    /**
+     * 根据ID获取缓存中的数据内容,不建议直接使用,需要确保数据中存在才行
+     *
+     * @param templateStr 脚本内容
+     * @return 脚本池
+     */
+    public static ObjectPool<Template> getPoolByScriptStr(String templateStr) {
+        AtomicReference<ObjectPool<Template>> pool = new AtomicReference<>();
+        cache.forEach((c, p) -> {
+            if (templateStr.equals(c.getTemplateStr())) {
+                pool.set(p);
+            }
+        });
+        if (pool.get() != null) {
+            return pool.get();
+        } else {
+            // 缓存级数据没有就没有,没有需要重新构建
+            return null;
+        }
+    }
+
+    public static boolean hasCacheById(String scriptId) {
+        return getPoolById(scriptId) != null;
+    }
+
+    public static boolean hasCacheByScriptStr(String scriptStr) {
+        return getPoolByScriptStr(scriptStr) != null;
+    }
+
+    public static boolean hasCache(String scriptId, String scriptStr) {
+        return getPool(new TemplateModel(scriptId, scriptStr)) != null;
+    }
+
+    /**
+     * 计算主方法
+     *
+     * @param templateId  计算id
+     * @param templateStr 脚本类
+     * @param params      脚本参数
+     * @return 脚本计算结果
+     */
+    public static String executeScript(String templateId, String templateStr, Map<String, Object> params) {
+        AtomicReference<TemplateModel> model = new AtomicReference<>();
+        if (StrUtil.isNotBlank(templateId) && hasCacheById(templateId)) {
+            String finalTemplateId = templateId;
+            cache.keySet().forEach((k) -> {
+                if (k.getTemplateId().equals(finalTemplateId)) {
+                    model.set(k);
+                }
+            });
+            return executeScript(model.get(), params);
+        } else if (StrUtil.isNotBlank(templateStr) && hasCacheByScriptStr(templateStr)) {
+            cache.keySet().forEach((k) -> {
+                if (k.getTemplateStr().equals(templateStr)) {
+                    model.set(k);
+                }
+            });
+            return executeScript(model.get(), params);
+        } else if (hasCache(templateId, templateStr) && StrUtil.isNotBlank(templateStr)) {
+            return executeScript(new TemplateModel(templateId, templateStr), params);
+        } else {
+            if (StrUtil.isBlank(templateStr) && StrUtil.isBlank(templateId)) {
+                throw new NullPointerException("脚本与id均为空,无法执行计算");
+            }
+            if (StrUtil.isBlank(templateId)) {
+                templateId = RandomUtil.randomString(10);
+            }
+            return executeScript(new TemplateModel(templateId, templateStr), params);
+        }
+
+
+    }
+
+    /**
+     * 计算的基础方法
+     *
+     * @param scriptModel 对象清单
+     * @param params      计算用脚本参数
+     * @return 计算结果
+     */
+    private static String executeScript(TemplateModel scriptModel, Map<String, Object> params) {
+        if (scriptModel == null) {
+            throw new NullPointerException("scriptModel is null");
+        }
+        ObjectPool<Template> pool = getPool(scriptModel);
+        try {
+            Template template = pool.borrowObject();
+
+            return template.make(params).toString();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        throw new RuntimeException("计算失败,请检查实际执行情况与日志!");
+    }
+}

+ 59 - 0
src/main/java/com/sundata/internalevaluation/script/model/ScriptModel.java

@@ -0,0 +1,59 @@
+package com.sundata.internalevaluation.script.model;
+
+import cn.hutool.core.util.StrUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+public class ScriptModel {
+    private String scriptStr;
+    private String scriptId;
+    public ScriptModel(@Nullable String scriptId,@Nullable String scriptStr) {
+        if(StrUtil.isEmpty(scriptStr)) {
+            throw new IllegalArgumentException("脚本内容为空,我滴妈你想干啥!");
+        }
+        if(StrUtil.isEmpty(scriptId)) {
+            throw new IllegalArgumentException("脚本ID为空,我滴妈你想干啥!");
+        }
+        this.scriptStr = scriptStr;
+        this.scriptId = scriptId;
+    }
+    public String getScriptStr() {
+        return scriptStr;
+    }
+    public void setScriptStr(String scriptStr) {
+        this.scriptStr = scriptStr;
+    }
+    public String getScriptId() {
+        return scriptId;
+    }
+    public void setScriptId(String scriptId) {
+        this.scriptId = scriptId;
+    }
+
+    public ScriptModel(){}
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", ScriptModel.class.getSimpleName() + "[", "]")
+                .add("scriptStr='" + scriptStr + "'")
+                .add("scriptId='" + scriptId + "'")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ScriptModel scriptModel = (ScriptModel) o;
+        return Objects.equals(getScriptStr(), scriptModel.getScriptStr()) && Objects.equals(getScriptId(), scriptModel.getScriptId());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hashCode(getScriptStr());
+        result = 31 * result + Objects.hashCode(getScriptId());
+        return result;
+    }
+}

+ 79 - 0
src/main/java/com/sundata/internalevaluation/script/model/ScriptRunningModel.java

@@ -0,0 +1,79 @@
+package com.sundata.internalevaluation.script.model;
+
+import cn.hutool.core.util.StrUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+public class ScriptRunningModel {
+    private String runningId;
+    private ScriptModel scriptModel;
+    private Map<String,Object> variables;
+
+    public ScriptRunningModel(@Nullable String runningId,@Nullable ScriptModel scriptModel,@Nullable Map<String, Object> variables) {
+        if (StrUtil.isEmpty(runningId)) {
+            throw new IllegalArgumentException("runningId is null");
+        }
+        if (scriptModel == null) {
+            throw new IllegalArgumentException("scriptModel is null");
+        }
+        if (variables == null) {
+            throw new IllegalArgumentException("variables is null");
+        }
+        this.runningId = runningId;
+        this.scriptModel = scriptModel;
+        this.variables = variables;
+    }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", ScriptRunningModel.class.getSimpleName() + "[", "]")
+                .add("runningId='" + runningId + "'")
+                .add("scriptModel=" + scriptModel)
+                .add("variables=" + variables)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ScriptRunningModel that = (ScriptRunningModel) o;
+        return Objects.equals(getRunningId(), that.getRunningId()) && Objects.equals(getScript(), that.getScript()) && Objects.equals(getVariables(), that.getVariables());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hashCode(getRunningId());
+        result = 31 * result + Objects.hashCode(getScript());
+        result = 31 * result + Objects.hashCode(getVariables());
+        return result;
+    }
+
+    public String getRunningId() {
+        return runningId;
+    }
+
+    public void setRunningId(String runningId) {
+        this.runningId = runningId;
+    }
+
+    public ScriptModel getScript() {
+        return scriptModel;
+    }
+
+    public void setScript(ScriptModel scriptModel) {
+        this.scriptModel = scriptModel;
+    }
+
+    public Map<String, Object> getVariables() {
+        return variables;
+    }
+
+    public void setVariables(Map<String, Object> variables) {
+        this.variables = variables;
+    }
+
+}

+ 62 - 0
src/main/java/com/sundata/internalevaluation/script/model/TemplateModel.java

@@ -0,0 +1,62 @@
+package com.sundata.internalevaluation.script.model;
+
+import cn.hutool.core.util.StrUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+public class TemplateModel {
+    private String templateId;
+    private String templateStr;
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", TemplateModel.class.getSimpleName() + "[", "]")
+                .add("templateId='" + templateId + "'")
+                .add("templateStr='" + templateStr + "'")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TemplateModel templateModel = (TemplateModel) o;
+        return Objects.equals(getTemplateId(), templateModel.getTemplateId()) && Objects.equals(getTemplateStr(), templateModel.getTemplateStr());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hashCode(getTemplateId());
+        result = 31 * result + Objects.hashCode(getTemplateStr());
+        return result;
+    }
+
+    public String getTemplateId() {
+        return templateId;
+    }
+
+    public void setTemplateId(String templateId) {
+        this.templateId = templateId;
+    }
+
+    public String getTemplateStr() {
+        return templateStr;
+    }
+
+    public void setTemplateStr(String templateStr) {
+        this.templateStr = templateStr;
+    }
+
+    public TemplateModel(@Nullable String templateId,@Nullable String templateStr) {
+        if(StrUtil.isEmpty(templateStr)) {
+            throw new IllegalArgumentException("模板内容为空,我滴妈你想干啥!");
+        }
+        if(StrUtil.isEmpty(templateId)) {
+            throw new IllegalArgumentException("模板ID为空,我滴妈你想干啥!");
+        }
+        this.templateId = templateId;
+        this.templateStr = templateStr;
+    }
+}

+ 79 - 0
src/main/java/com/sundata/internalevaluation/script/model/TemplateRunningModel.java

@@ -0,0 +1,79 @@
+package com.sundata.internalevaluation.script.model;
+
+import cn.hutool.core.util.StrUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+public class TemplateRunningModel {
+    private String runningId;
+    private TemplateModel templateModel;
+    private Map<String,Object> variables;
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", TemplateRunningModel.class.getSimpleName() + "[", "]")
+                .add("runningId='" + runningId + "'")
+                .add("templateModel=" + templateModel)
+                .add("variables=" + variables)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+
+        TemplateRunningModel that = (TemplateRunningModel) o;
+        return Objects.equals(getRunningId(), that.getRunningId()) && Objects.equals(getTemplate(), that.getTemplate()) && Objects.equals(getVariables(), that.getVariables());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hashCode(getRunningId());
+        result = 31 * result + Objects.hashCode(getTemplate());
+        result = 31 * result + Objects.hashCode(getVariables());
+        return result;
+    }
+
+    public String getRunningId() {
+        return runningId;
+    }
+
+    public void setRunningId(String runningId) {
+        this.runningId = runningId;
+    }
+
+    public TemplateModel getTemplate() {
+        return templateModel;
+    }
+
+    public void setTemplate(TemplateModel templateModel) {
+        this.templateModel = templateModel;
+    }
+
+    public Map<String, Object> getVariables() {
+        return variables;
+    }
+
+    public void setVariables(Map<String, Object> variables) {
+        this.variables = variables;
+    }
+
+
+    public TemplateRunningModel(@Nullable String runningId, @Nullable TemplateModel templateModel,@Nullable Map<String, Object> variables) {
+        if (StrUtil.isEmpty(runningId)) {
+            throw new IllegalArgumentException("runningId is null");
+        }
+        if (templateModel == null) {
+            throw new IllegalArgumentException("templateModel is null");
+        }
+        if (variables == null) {
+            throw new IllegalArgumentException("variables is null");
+        }
+        this.runningId = runningId;
+        this.templateModel = templateModel;
+        this.variables = variables;
+    }
+}

+ 61 - 0
src/main/java/com/sundata/internalevaluation/script/pool/GroovyScriptFactory.java

@@ -0,0 +1,61 @@
+package com.sundata.internalevaluation.script.pool;
+
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.DestroyMode;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.PooledSoftReference;
+import com.sundata.internalevaluation.script.model.ScriptModel;
+
+import java.lang.ref.SoftReference;
+
+public class GroovyScriptFactory extends BasePooledObjectFactory<Script> {
+    private final GroovyShell groovyShell = new GroovyShell();
+    private final ScriptModel scriptModel;
+
+    public GroovyScriptFactory(ScriptModel scriptModel) {
+        this.scriptModel = scriptModel;
+    }
+    /**
+     * Creates an object instance, to be wrapped in a {@link PooledObject}.
+     * <p>This method <strong>must</strong> support concurrent, multi-threaded
+     * invocation.</p>
+     *
+     * @return an instance to be served by the pool, not null.
+     */
+    @Override
+    public Script create() {
+        return groovyShell.parse(scriptModel.getScriptStr());
+    }
+
+    /**
+     * Wraps the provided instance with an implementation of
+     * {@link PooledObject}.
+     *
+     * @param obj the instance to wrap, should not be null.
+     * @return The provided instance, wrapped by a {@link PooledObject}
+     */
+    @Override
+    public PooledObject<Script> wrap(Script obj) {
+        return new PooledSoftReference<>(new SoftReference<>(obj));
+    }
+
+    /**
+     * Destroys an instance no longer needed by the pool, using the provided
+     * DestroyMode.
+     *
+     * @param p           a {@code PooledObject} wrapping the instance to be destroyed
+     * @param destroyMode DestroyMode providing context to the factory
+     * @throws Exception should be avoided as it may be swallowed by
+     *                   the pool implementation.
+     * @see #validateObject
+     * @see #destroyObject(PooledObject)
+     * @see DestroyMode
+     * @since 2.9.0
+     */
+    @Override
+    public void destroyObject(PooledObject<Script> p, DestroyMode destroyMode) throws Exception {
+        super.destroyObject(p, destroyMode);
+    }
+}

+ 20 - 0
src/main/java/com/sundata/internalevaluation/script/pool/GroovyScriptPool.java

@@ -0,0 +1,20 @@
+package com.sundata.internalevaluation.script.pool;
+
+import groovy.lang.Script;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import com.sundata.internalevaluation.script.model.ScriptModel;
+
+public class GroovyScriptPool {
+    // 创建一个 GroovyShell 的池
+    public static GenericObjectPool<Script> createGroovyShellPool(ScriptModel scriptModel) {
+        GroovyScriptFactory factory = new GroovyScriptFactory(scriptModel);
+        GenericObjectPool<Script> pool = new GenericObjectPool<>(factory);
+        // 配置池的参数,例如最大空闲实例数、最小空闲实例数等
+        pool.setMaxIdle(10);
+        pool.setMinIdle(2);
+        pool.setMaxTotal(20);
+        pool.setTestOnBorrow(true);
+        pool.setTestOnReturn(true);
+        return pool;
+    }
+}

+ 46 - 0
src/main/java/com/sundata/internalevaluation/script/pool/GroovyTemplateFactory.java

@@ -0,0 +1,46 @@
+package com.sundata.internalevaluation.script.pool;
+
+import groovy.text.GStringTemplateEngine;
+import groovy.text.Template;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.PooledObjectFactory;
+import org.apache.commons.pool2.impl.PooledSoftReference;
+import com.sundata.internalevaluation.script.model.TemplateModel;
+
+import java.lang.ref.SoftReference;
+
+// 创建一个实现 PooledObjectFactory 的类,用于生成和管理 TemplateModel 对象
+public class GroovyTemplateFactory implements PooledObjectFactory<Template> {
+    private final GStringTemplateEngine engine = new GStringTemplateEngine();
+    private final TemplateModel template;
+
+    public GroovyTemplateFactory(TemplateModel template) {
+        this.template = template;
+    }
+
+    @Override
+    public PooledObject<Template> makeObject() throws Exception {
+        return new PooledSoftReference<>(new SoftReference<>(engine.createTemplate(template.getTemplateStr())));
+    }
+
+    @Override
+    public void destroyObject(PooledObject<Template> p) {
+//        p.deallocate()
+    }
+
+    @Override
+    public boolean validateObject(PooledObject<Template> p) {
+        // 简单地验证对象是否不为 null
+        return p.getObject() != null;
+    }
+
+    @Override
+    public void activateObject(PooledObject<Template> p) throws Exception {
+        // 可以在此处重新初始化模板对象如果需要
+    }
+
+    @Override
+    public void passivateObject(PooledObject<Template> p) throws Exception {
+        // 可以在此处清理模板对象的状态如果需要
+    }
+}

+ 51 - 0
src/main/java/com/sundata/internalevaluation/script/pool/GroovyTemplatePool.java

@@ -0,0 +1,51 @@
+package com.sundata.internalevaluation.script.pool;
+
+import groovy.text.Template;
+import org.apache.commons.pool2.ObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import com.sundata.internalevaluation.script.model.TemplateModel;
+
+import java.util.Map;
+
+public class GroovyTemplatePool {
+    // 创建一个 GroovyShell 的池
+    public static ObjectPool<Template> templateFactoryObjectPool(TemplateModel templateModel) {
+        GroovyTemplateFactory factory = new GroovyTemplateFactory(templateModel);
+        GenericObjectPool<Template> pool = new GenericObjectPool<>(factory);
+        // 配置池的参数,例如最大空闲实例数、最小空闲实例数等
+        pool.setMaxIdle(10);
+        pool.setMinIdle(2);
+        pool.setMaxTotal(20);
+        pool.setTestOnBorrow(true);
+        pool.setTestOnReturn(true);
+        return pool;
+    }
+
+    public static void main(String[] args) throws Exception {
+        // 定义模板文本
+        String templateText = "Hello, ${name}!";
+
+        // 创建 TemplateModel 对象的池
+        ObjectPool<Template> templatePool = templateFactoryObjectPool(new TemplateModel("1", templateText));
+
+        // 从池中借用一个 TemplateModel 对象
+        Template template = null;
+        try {
+            template = templatePool.borrowObject();
+
+            // 使用模板生成文本
+            String result = template.make(Map.of("name", "World")).toString();
+            System.out.println(result);  // 输出: Hello, World!
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            // 使用完后将 TemplateModel 对象归还到池中
+            if (template != null) {
+                templatePool.returnObject(template);
+            }
+
+            // 程序结束时,可以根据需要关闭池以释放资源
+            templatePool.close();
+        }
+    }
+}