|
@@ -0,0 +1,265 @@
|
|
|
+package com.sundata.product.rwa.calc.service;
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.date.TimeInterval;
|
|
|
+
|
|
|
+import com.sundata.product.rwa.calc.service.finals.CalcStatus;
|
|
|
+import com.sundata.product.rwa.calc.service.finals.CalcType;
|
|
|
+import com.sundata.product.rwa.calc.service.interfaces.Calc;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.io.Serial;
|
|
|
+import java.io.Serializable;
|
|
|
+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;
|
|
|
+ /**
|
|
|
+ * 计算单元名称
|
|
|
+ */
|
|
|
+ private final String calcName;
|
|
|
+ /**
|
|
|
+ * 计算单元类型
|
|
|
+ */
|
|
|
+ private final CalcType calcType;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算状态
|
|
|
+ */
|
|
|
+ private CalcStatus status = CalcStatus.READY;
|
|
|
+ /**
|
|
|
+ * 初始化用属性对象节点
|
|
|
+ */
|
|
|
+ private final Map<String, Object> initContext;
|
|
|
+
|
|
|
+ private CompletableFuture<CalcResult<String, Object>> resultContextFuture;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算结果对象
|
|
|
+ */
|
|
|
+ private CalcResult<String, Object> resultContext;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算流水号
|
|
|
+ */
|
|
|
+ private String calculateInstanceNumber = "";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建数据单元的绝对对象,对象必须包含如下参数
|
|
|
+ *
|
|
|
+ * @param calcCode 计算对象编号
|
|
|
+ * @param calcName 计算对象名称
|
|
|
+ * @param calcType 计算类型
|
|
|
+ * @param initContext 计算单元初始化参数
|
|
|
+ */
|
|
|
+ public CalcUnit(String calcCode, String calcName, CalcType calcType, Map<String, Object> initContext) {
|
|
|
+ this.calcCode = calcCode;
|
|
|
+ this.calcName = calcName;
|
|
|
+ this.calcType = calcType;
|
|
|
+ this.initContext = initContext;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取计算结果的异步方法。
|
|
|
+ * 如果结果上下文的Future已经存在,则直接获取结果;否则,创建一个新的Future并启动计算。
|
|
|
+ *
|
|
|
+ * @param calculateInstanceNumber 计算实例的编号
|
|
|
+ * @param context 计算上下文
|
|
|
+ * @param sourceResults 源计算结果
|
|
|
+ * @return 计算结果的Future
|
|
|
+ * @throws CalcException 如果计算过程中出现问题
|
|
|
+ */
|
|
|
+ public CalcResult<String, Object> getResultContextFuture(String calculateInstanceNumber, Map<String, Object> context, Map<CalcUnit, CalcResult<String, Object>> sourceResults) {
|
|
|
+ try {
|
|
|
+ // 如果结果上下文的Future已经存在,则直接获取结果
|
|
|
+ if (this.resultContextFuture != null) {
|
|
|
+ return this.resultContextFuture.get();
|
|
|
+ } else {
|
|
|
+ // 同步块,确保只有一个线程创建Future
|
|
|
+ synchronized (this) {
|
|
|
+ // 再次检查Future是否已经被其他线程创建
|
|
|
+ if (this.resultContextFuture == null) {
|
|
|
+ // 创建一个新的Future并启动计算
|
|
|
+ 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, "计算出现问题,线程级别,请检查处理过程。");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 开始计算的方法。
|
|
|
+ *
|
|
|
+ * @param calculateInstanceNumber 计算实例的编号
|
|
|
+ * @param context 计算上下文
|
|
|
+ * @param sourceResults 源计算结果
|
|
|
+ */
|
|
|
+ public void startCalc(String calculateInstanceNumber, Map<String, Object> context, Map<CalcUnit, CalcResult<String, Object>> sourceResults) {
|
|
|
+ // 设置计算状态为运行中
|
|
|
+ setStatus(CalcStatus.RUNNING);
|
|
|
+ // 如果已经计算过,则不再执行计算处理,仅需要通过 initResultContext 初始化 resultContext 对象
|
|
|
+ if (isCalcFinished(calculateInstanceNumber)) {
|
|
|
+ log.info("当前计算节点为:[{}-{}-{}],曾经完成过计算,加载原始计算内容并结束", this.getCalcType(), this.getCalcCode(), this.getCalcName());
|
|
|
+ initResultContext(calculateInstanceNumber);
|
|
|
+ setStatus(CalcStatus.FINISHED);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 创建一个时间间隔对象,用于计算耗时
|
|
|
+ TimeInterval interval = new TimeInterval();
|
|
|
+ // 设置计算实例的编号
|
|
|
+ setCalculateInstanceNumber(calculateInstanceNumber);
|
|
|
+// String calcUnitInstanceNumber = getCalcUnitInstanceNumber();
|
|
|
+ log.info("当前计算节点为:[{}-{}-{}],计算流水号为:{}", this.getCalcType(), this.getCalcCode(), this.getCalcName(), calculateInstanceNumber);
|
|
|
+ // 开始计时
|
|
|
+ interval.start();
|
|
|
+ try {
|
|
|
+ // 在计算之前执行的操作
|
|
|
+ this.beforeCalc(context);
|
|
|
+ // 创建一个新的计算结果对象
|
|
|
+ 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() + "】");
|
|
|
+ }
|
|
|
+ // 设置计算结果
|
|
|
+ setResultContext(thisResult);
|
|
|
+ // 在计算之后执行的操作
|
|
|
+ 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()));
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 判断是否已经计算过数据了
|
|
|
+ *
|
|
|
+ * @param calculateInstanceNumber 计算流水号
|
|
|
+ * @return 是否计算过 true 计算过 false 没有计算过
|
|
|
+ */
|
|
|
+ public abstract boolean isCalcFinished(String calculateInstanceNumber);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化计算结果的方法,如果已经计算过,在实现过程中,应当在此方法中根据计算流水号重新初始化 resultContext 结果对象,为其他依赖对象做准备
|
|
|
+ * 若明明计算过本单元但再次计算时没有初始化该对象,则计算依赖出现问题无法定位与处理
|
|
|
+ *
|
|
|
+ * @param calculateInstanceNumber 计算流水号
|
|
|
+ */
|
|
|
+ public abstract void initResultContext(String calculateInstanceNumber);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据节点配置获取源节点;
|
|
|
+ *
|
|
|
+ * @return 所有源头节点
|
|
|
+ */
|
|
|
+ public abstract List<CalcUnit> getSourceCalcUnits();
|
|
|
+
|
|
|
+ public String getCalcName() {
|
|
|
+ return calcName;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CalcType getCalcType() {
|
|
|
+ return calcType;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> getInitContext() {
|
|
|
+ return initContext;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CalcResult<String, Object> getResultContext() {
|
|
|
+ return resultContext;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setResultContext(CalcResult<String, Object> resultContext) {
|
|
|
+ this.resultContext = resultContext;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取计算单元的流水号
|
|
|
+ *
|
|
|
+ * @return 计算单元流水号 通常由 计算流水号 + 计算单元类型 + 计算单元名称组成
|
|
|
+ */
|
|
|
+ public String getCalcUnitInstanceNumber() {
|
|
|
+ return calculateInstanceNumber + "-" + calcType + "-" + calcName;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置计算流水号
|
|
|
+ *
|
|
|
+ * @param calcInstanceNumber 计算流水号
|
|
|
+ */
|
|
|
+ private void setCalculateInstanceNumber(String calcInstanceNumber) {
|
|
|
+ this.calculateInstanceNumber = calcInstanceNumber;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getCalcCode() {
|
|
|
+ return calcCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object o) {
|
|
|
+ if (o == null || getClass() != o.getClass()) return false;
|
|
|
+
|
|
|
+ CalcUnit unit = (CalcUnit) o;
|
|
|
+ return Objects.equals(getCalcCode(), unit.getCalcCode()) && getCalcType() == unit.getCalcType() && Objects.equals(calculateInstanceNumber, unit.calculateInstanceNumber);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ int result = Objects.hashCode(getCalcCode());
|
|
|
+ result = 31 * result + Objects.hashCode(getCalcType());
|
|
|
+ result = 31 * result + Objects.hashCode(calculateInstanceNumber);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return new StringJoiner(", ", CalcUnit.class.getSimpleName() + "[", "]")
|
|
|
+ .add("calculateInstanceNumber='" + calculateInstanceNumber + "'")
|
|
|
+ .add("calcCode='" + calcCode + "'")
|
|
|
+ .add("calcName='" + calcName + "'")
|
|
|
+ .add("calcType=" + calcType)
|
|
|
+ .add("resultContext=" + resultContext)
|
|
|
+ .toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public CalcStatus getStatus() {
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setStatus(CalcStatus status) {
|
|
|
+ this.status = status;
|
|
|
+ }
|
|
|
+}
|