From 92cc10cfd8e649eb4eab2fe78f06446304cbaebd Mon Sep 17 00:00:00 2001 From: icanci Date: Thu, 17 Nov 2022 09:15:15 +0800 Subject: [PATCH] SDK --- .../rec/admin/biz/mapper/StrategyMapper.java | 2 +- .../convertor/RuleModeEnumConverter.java | 13 + .../dal/mongodb/dateobject/StrategyDO.java | 14 + .../admin/web/mapper/StrategyWebMapper.java | 2 +- .../icanci/rec/admin/web/model/Strategy.java | 14 + .../common/aggregation/model/StrategyDTO.java | 14 + .../icanci/rec/common/enums/RuleModeEnum.java | 35 +++ .../rec/common/model/config/StrategyVO.java | 12 + .../cn/icanci/rec/common/utils/DateUtils.java | 147 +++++++++++ .../engine/sdk/RecEngineSDKAutoConfig.java | 61 +++-- .../sdk/actuator/RuleEngineResponse.java | 41 +++ .../sdk/condition/AbstractCondition.java | 48 ++++ .../rec/engine/sdk/condition/Condition.java | 21 ++ .../engine/sdk/condition/ConditionBean.java | 18 ++ .../sdk/condition/ConditionSupport.java | 44 ++++ .../rec/engine/sdk/condition/EQCondition.java | 45 ++++ .../rec/engine/sdk/condition/GTCondition.java | 22 ++ .../sdk/exception/ValidatorException.java | 29 ++ .../sdk/extensions/RecExtensionLoader.java | 2 - .../rec/engine/sdk/rule/EngineExecutor.java | 247 +++++++++++++++++- .../engine/sdk/rule/ErrorMessageFormat.java | 30 +++ .../rule/pool/ScriptExecutorPoolHolder.java | 39 +++ 22 files changed, 865 insertions(+), 35 deletions(-) create mode 100644 rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/convertor/RuleModeEnumConverter.java create mode 100644 rec-common/src/main/java/cn/icanci/rec/common/enums/RuleModeEnum.java create mode 100644 rec-common/src/main/java/cn/icanci/rec/common/utils/DateUtils.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/AbstractCondition.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/Condition.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionBean.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionSupport.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/EQCondition.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/GTCondition.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/exception/ValidatorException.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/ErrorMessageFormat.java create mode 100644 rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/pool/ScriptExecutorPoolHolder.java diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/StrategyMapper.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/StrategyMapper.java index 57d4fcd..8566238 100644 --- a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/StrategyMapper.java +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/StrategyMapper.java @@ -12,7 +12,7 @@ import org.mapstruct.NullValueMappingStrategy; * @since 1.0 Created in 2022/11/11 17:49 */ @Mapper(componentModel = "spring", uses = { DataSourceTypeEnumConverter.class, RuleTypeEnumConverter.class, OperatorEnumConverter.class, InterruptEnumConverter.class, - ResultTypeEnumConverter.class }, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) + ResultTypeEnumConverter.class, RuleModeEnumConverter.class }, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) public interface StrategyMapper extends BaseMapper { StrategyVO.RuleListInfo do2vo(StrategyVO.RuleListInfo ruleListInfo); diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/convertor/RuleModeEnumConverter.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/convertor/RuleModeEnumConverter.java new file mode 100644 index 0000000..4ce72fd --- /dev/null +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/convertor/RuleModeEnumConverter.java @@ -0,0 +1,13 @@ +package cn.icanci.rec.admin.biz.mapper.convertor; + +import cn.icanci.rec.common.enums.RuleModeEnum; + +import org.springframework.stereotype.Component; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 21:54 + */ +@Component +public class RuleModeEnumConverter extends AbstractBaseConverter { +} diff --git a/rec-admin/rec-admin-dal/src/main/java/cn/icanci/rec/admin/dal/mongodb/dateobject/StrategyDO.java b/rec-admin/rec-admin-dal/src/main/java/cn/icanci/rec/admin/dal/mongodb/dateobject/StrategyDO.java index e31ccb4..f8cdd92 100644 --- a/rec-admin/rec-admin-dal/src/main/java/cn/icanci/rec/admin/dal/mongodb/dateobject/StrategyDO.java +++ b/rec-admin/rec-admin-dal/src/main/java/cn/icanci/rec/admin/dal/mongodb/dateobject/StrategyDO.java @@ -37,6 +37,12 @@ public class StrategyDO extends BaseDO { * @see RuleTypeEnum#name() */ private String ruleType; + /** + * 规则模式 默认为simple + * + * @see RuleModeEnum#name() + */ + private String ruleMode; /** * 规则配置类型为List时候的规则数据 */ @@ -94,6 +100,14 @@ public class StrategyDO extends BaseDO { this.ruleType = ruleType; } + public String getRuleMode() { + return ruleMode; + } + + public void setRuleMode(String ruleMode) { + this.ruleMode = ruleMode; + } + public RuleListInfo getRuleListInfo() { return ruleListInfo; } diff --git a/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/mapper/StrategyWebMapper.java b/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/mapper/StrategyWebMapper.java index 9d96c26..939dd2f 100644 --- a/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/mapper/StrategyWebMapper.java +++ b/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/mapper/StrategyWebMapper.java @@ -12,7 +12,7 @@ import org.mapstruct.NullValueMappingStrategy; * @since 1.0 Created in 2022/11/11 17:49 */ @Mapper(componentModel = "spring", uses = { DataSourceTypeEnumConverter.class, RuleTypeEnumConverter.class, OperatorEnumConverter.class, InterruptEnumConverter.class, - ResultTypeEnumConverter.class }, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) + ResultTypeEnumConverter.class, RuleModeEnumConverter.class }, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) public interface StrategyWebMapper extends BaseWebMapper { StrategyVO.RuleListInfo web2vo(StrategyVO.RuleListInfo ruleListInfo); diff --git a/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/model/Strategy.java b/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/model/Strategy.java index 4d4d3b9..d48c69b 100644 --- a/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/model/Strategy.java +++ b/rec-admin/rec-admin-web/src/main/java/cn/icanci/rec/admin/web/model/Strategy.java @@ -38,6 +38,12 @@ public class Strategy extends Base { * @see RuleTypeEnum#name() */ private String ruleType; + /** + * 规则模式 默认为simple + * + * @see RuleModeEnum#name() + */ + private String ruleMode; /** * 规则配置类型为List时候的规则数据 */ @@ -95,6 +101,14 @@ public class Strategy extends Base { this.ruleType = ruleType; } + public String getRuleMode() { + return ruleMode; + } + + public void setRuleMode(String ruleMode) { + this.ruleMode = ruleMode; + } + public RuleListInfo getRuleListInfo() { return ruleListInfo; } diff --git a/rec-common/src/main/java/cn/icanci/rec/common/aggregation/model/StrategyDTO.java b/rec-common/src/main/java/cn/icanci/rec/common/aggregation/model/StrategyDTO.java index aa20ca2..6dd284e 100644 --- a/rec-common/src/main/java/cn/icanci/rec/common/aggregation/model/StrategyDTO.java +++ b/rec-common/src/main/java/cn/icanci/rec/common/aggregation/model/StrategyDTO.java @@ -39,6 +39,12 @@ public class StrategyDTO extends BaseDTO { * @see RuleTypeEnum#name() */ private String ruleType; + /** + * 规则模式 默认为simple + * + * @see RuleModeEnum#name() + */ + private String ruleMode; /** * 规则配置类型为List时候的规则数据 */ @@ -96,6 +102,14 @@ public class StrategyDTO extends BaseDTO { this.ruleType = ruleType; } + public String getRuleMode() { + return ruleMode; + } + + public void setRuleMode(String ruleMode) { + this.ruleMode = ruleMode; + } + public RuleListInfo getRuleListInfo() { return ruleListInfo; } diff --git a/rec-common/src/main/java/cn/icanci/rec/common/enums/RuleModeEnum.java b/rec-common/src/main/java/cn/icanci/rec/common/enums/RuleModeEnum.java new file mode 100644 index 0000000..8f61b12 --- /dev/null +++ b/rec-common/src/main/java/cn/icanci/rec/common/enums/RuleModeEnum.java @@ -0,0 +1,35 @@ +package cn.icanci.rec.common.enums; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 21:50 + */ +public enum RuleModeEnum { + /** + * SIMPLE + */ + SIMPLE("SIMPLE", "简单"), + /** + * COMPLEX + */ + COMPLEX("COMPLEX", "复杂"), + + ; + + private final String code; + private final String desc; + + RuleModeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public String getCode() { + return code; + } + + public String getDesc() { + return desc; + } + +} diff --git a/rec-common/src/main/java/cn/icanci/rec/common/model/config/StrategyVO.java b/rec-common/src/main/java/cn/icanci/rec/common/model/config/StrategyVO.java index 4e6cccf..f195df7 100644 --- a/rec-common/src/main/java/cn/icanci/rec/common/model/config/StrategyVO.java +++ b/rec-common/src/main/java/cn/icanci/rec/common/model/config/StrategyVO.java @@ -35,6 +35,10 @@ public class StrategyVO extends BaseVO { * 规则配置类型(默认为List) */ private RuleTypeEnum ruleType; + /** + * 规则模式 默认为simple + */ + private RuleModeEnum ruleMode; /** * 规则配置类型为List时候的规则数据 */ @@ -92,6 +96,14 @@ public class StrategyVO extends BaseVO { this.ruleType = ruleType; } + public RuleModeEnum getRuleMode() { + return ruleMode; + } + + public void setRuleMode(RuleModeEnum ruleMode) { + this.ruleMode = ruleMode; + } + public RuleListInfo getRuleListInfo() { return ruleListInfo; } diff --git a/rec-common/src/main/java/cn/icanci/rec/common/utils/DateUtils.java b/rec-common/src/main/java/cn/icanci/rec/common/utils/DateUtils.java new file mode 100644 index 0000000..69ef64b --- /dev/null +++ b/rec-common/src/main/java/cn/icanci/rec/common/utils/DateUtils.java @@ -0,0 +1,147 @@ +package cn.icanci.rec.common.utils; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; + +/** + * 时间工具类 + * 解决问题:因日期转换时会频繁创建format且format存在线程安全问题,故此处采用线程复用机制,复用format + * + * @author icanci + * @since 1.0 Created in 2022/11/16 23:06 + */ +public final class DateUtils { + /** YYYY */ + public final static String YYYY = "yyyy"; + + /** MM_DD */ + public final static String MM_DD = "MM-dd"; + + /** HH_MM_SS */ + public final static String HH_MM = "HH:mm"; + + /** HH_MM_SS */ + public final static String HH_MM_SS = "HH:mm:ss"; + + /** YYYY_MM */ + public static final String YYYY_MM = "yyyy-MM"; + + /** YYYY_MM_DD */ + public static final String YYYY_MM_DD = "yyyy-MM-dd"; + + /** YYYY_MM_DD_HH */ + public final static String YYYY_MM_DD_HH = "yyyy-MM-dd HH"; + + /** YYYY_MM_DD_HH_MM */ + public final static String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm"; + + /** YYYY_MM_DD_HH_MM_SS */ + public final static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + /** 本地线程日期格式 */ + private final static ThreadLocal> THREAD_LOCAL_FORMATTERS = ThreadLocal.withInitial(() -> { + Map map = new HashMap<>(16); + map.put(YYYY, new SimpleDateFormat(YYYY)); + map.put(YYYY_MM, new SimpleDateFormat(YYYY_MM)); + map.put(MM_DD, new SimpleDateFormat(MM_DD)); + map.put(HH_MM, new SimpleDateFormat(HH_MM)); + map.put(HH_MM_SS, new SimpleDateFormat(HH_MM_SS)); + map.put(YYYY_MM_DD, new SimpleDateFormat(YYYY_MM_DD)); + map.put(YYYY_MM_DD_HH, new SimpleDateFormat(YYYY_MM_DD_HH)); + map.put(YYYY_MM_DD_HH_MM, new SimpleDateFormat(YYYY_MM_DD_HH_MM)); + map.put(YYYY_MM_DD_HH_MM_SS, new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS)); + return map; + }); + + /** + * YYYY 转换日期 + * + * @param dateString the time + * @param format format + * @return date date + */ + public static Date parse(String dateString, String format) { + if (StringUtils.isBlank(dateString)) { + return null; + } + try { + return getFormat(format).parse(dateString); + } catch (ParseException e) { + return null; + } + } + + /** + * 格式化日期 + * + * @param date the time + * @param format format + * @return date date + */ + public static String format(Date date, String format) { + if (StringUtils.isBlank(format)) { + return null; + } + try { + return getFormat(format).format(date); + } catch (Exception e) { + return null; + } + } + + /** + * 获取格式化配置 + * + * @param format 格式化 + * @return 配置 + */ + private static SimpleDateFormat getFormat(String format) { + return THREAD_LOCAL_FORMATTERS.get().get(format); + } + + /** + * 获取差异天数 + * + * @param one date + * @param two date + * @return 差异天数 + */ + public static long getDiffDays(Date one, Date two) { + long minutes = getDiffMinutes(one, two); + return BigDecimal.valueOf(Math.ceil(minutes / (60 * 24 * 1.0))).longValue(); + } + + /** + * 获取差异分钟数 + * + * @param one date + * @param two date + * @return 差异分钟数 + */ + public static long getDiffMinutes(Date one, Date two) { + Calendar sysDate = new GregorianCalendar(); + sysDate.setTime(one); + Calendar failDate = new GregorianCalendar(); + failDate.setTime(two); + return (sysDate.getTimeInMillis() - failDate.getTimeInMillis()) / (60 * 1000); + } + + /** + * 取得两个日期间隔秒数(日期1-日期2) + * + * @param one 日期1 + * @param two 日期2 + * @return 间隔秒数 + */ + public static long getDiffSeconds(Date one, Date two) { + Calendar sysDate = new GregorianCalendar(); + sysDate.setTime(one); + Calendar failDate = new GregorianCalendar(); + failDate.setTime(two); + return (sysDate.getTimeInMillis() - failDate.getTimeInMillis()) / 1000; + } +} \ No newline at end of file diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/RecEngineSDKAutoConfig.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/RecEngineSDKAutoConfig.java index fef45de..425eebf 100644 --- a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/RecEngineSDKAutoConfig.java +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/RecEngineSDKAutoConfig.java @@ -4,16 +4,17 @@ import cn.icanci.rec.engine.script.RecScriptEngine; import cn.icanci.rec.engine.script.RecScriptEngineManager; import cn.icanci.rec.engine.sdk.actuator.RecRuleEngineActuator; import cn.icanci.rec.engine.sdk.actuator.impl.RecRuleEngineActuatorImpl; +import cn.icanci.rec.engine.sdk.condition.ConditionSupport; import cn.icanci.rec.engine.sdk.extensions.RecExtensionLoader; import cn.icanci.rec.engine.sdk.rule.EngineExecutor; import cn.icanci.rec.engine.sdk.rule.EngineRepositoryLoader; -import cn.icanci.rec.engine.sdk.rule.RuleAggregationCluster; import cn.icanci.rec.engine.sdk.rule.impl.EngineRepositoryLoaderImpl; +import cn.icanci.rec.engine.sdk.rule.pool.ScriptExecutorPoolHolder; import cn.icanci.rec.engine.sdk.rule.repository.EngineRepositoryHolder; import cn.icanci.rec.engine.sdk.spi.*; import org.springframework.beans.BeansException; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; @@ -25,8 +26,8 @@ import org.springframework.context.annotation.Configuration; */ @Configuration -@ConditionalOnClass(RuleAggregationCluster.class) -public class RecEngineSDKAutoConfig implements ApplicationContextAware { +//@ConditionalOnClass(RuleAggregationCluster.class) +public class RecEngineSDKAutoConfig implements ApplicationContextAware, CommandLineRunner { /** * Spring 上下文 */ @@ -67,25 +68,25 @@ public class RecEngineSDKAutoConfig implements ApplicationContextAware { return RecScriptEngineManager.getRecScriptEngine(); } -// /** -// * 执行引擎 暂不使用 -// * -// * @return 返回脚本执行引擎 -// */ -// @Bean("ruleAggregationCluster") -// public RuleAggregationCluster ruleAggregationCluster(AggregationRepositoryHolder aggregationRepositoryHolder, RecScriptEngine engine) { -// return new RuleAggregationClusterImpl(aggregationRepositoryHolder, engine); -// } -// -// /** -// * 执行引擎 暂不使用 -// * -// * @return 返回脚本执行引擎 -// */ -// @Bean("aggregationRepositoryHolder") -// public AggregationRepositoryHolder aggregationRepositoryHolder() { -// return new AggregationRepositoryHolder(); -// } + // /** + // * 执行引擎 暂不使用 + // * + // * @return 返回脚本执行引擎 + // */ + // @Bean("ruleAggregationCluster") + // public RuleAggregationCluster ruleAggregationCluster(AggregationRepositoryHolder aggregationRepositoryHolder, RecScriptEngine engine) { + // return new RuleAggregationClusterImpl(aggregationRepositoryHolder, engine); + // } + // + // /** + // * 执行引擎 暂不使用 + // * + // * @return 返回脚本执行引擎 + // */ + // @Bean("aggregationRepositoryHolder") + // public AggregationRepositoryHolder aggregationRepositoryHolder() { + // return new AggregationRepositoryHolder(); + // } /** * 执行引擎 @@ -169,4 +170,18 @@ public class RecEngineSDKAutoConfig implements ApplicationContextAware { return RecExtensionLoader.getExtensionLoader(StrategySPI.class).getExtension(); } + /** + * scriptExecutorPoolHolder + * + * @return scriptExecutorPoolHolder + */ + @Bean("scriptExecutorPoolHolder") + public ScriptExecutorPoolHolder scriptExecutorPoolHolder() { + return ScriptExecutorPoolHolder.getInstance(); + } + + @Override + public void run(String... args) throws Exception { + // no op + } } diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/actuator/RuleEngineResponse.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/actuator/RuleEngineResponse.java index 9815b4d..f60790a 100644 --- a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/actuator/RuleEngineResponse.java +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/actuator/RuleEngineResponse.java @@ -10,7 +10,48 @@ import java.io.Serializable; */ public class RuleEngineResponse implements Serializable { private static final long serialVersionUID = -4658078915045778725L; + /** 是否执行成功 */ private boolean success; + /** 失败原因 */ private String errorMessage; + /** 执行结果 */ + private Object result; + public static RuleEngineResponse fail(String errorMessage) { + RuleEngineResponse response = new RuleEngineResponse(); + response.setSuccess(false); + response.setErrorMessage(errorMessage); + return response; + } + + public static RuleEngineResponse success(Object result) { + RuleEngineResponse response = new RuleEngineResponse(); + response.setSuccess(true); + response.setResult(result); + return response; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } } diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/AbstractCondition.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/AbstractCondition.java new file mode 100644 index 0000000..fff5909 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/AbstractCondition.java @@ -0,0 +1,48 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.aggregation.model.BaseDataDTO; +import cn.icanci.rec.common.enums.ResultTypeEnum; +import cn.icanci.rec.common.enums.ScriptTypeEnum; +import cn.icanci.rec.engine.script.RecScriptEngine; +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; +import cn.icanci.rec.engine.script.enums.ResultTypeMapEnum; +import cn.icanci.rec.engine.sdk.rule.repository.EngineRepositoryHolder; + +import javax.annotation.Resource; +import javax.script.Bindings; +import javax.script.CompiledScript; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 22:48 + */ +public abstract class AbstractCondition implements Condition { + /** 推送仓储 */ + @Resource + protected EngineRepositoryHolder engineRepositoryHolder; + @Resource + private RecScriptEngine recScriptEngine; + + /** + * 获取左值 + * + * @param bindings bindings + * @param baseData baseData + * @return 返回左值 + */ + public String getValue(Bindings bindings, BaseDataDTO baseData) { + try { + CompiledScript compiledScript = baseData.getCompiledScript(); + ResultTypeEnum resultType = ResultTypeEnum.valueOf(baseData.getResultType()); + Class classByResultType = ResultTypeMapEnum.getClassByResultType(resultType); + if (compiledScript == null) { + RecScriptEngineContext eval = recScriptEngine.eval(ScriptTypeEnum.valueOf(baseData.getScriptType()), bindings, baseData.getScriptContent(), classByResultType); + return eval.getRealRetVal().toString(); + } else { + return compiledScript.eval(bindings).toString(); + } + } catch (Throwable e) { + return null; + } + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/Condition.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/Condition.java new file mode 100644 index 0000000..f18ae48 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/Condition.java @@ -0,0 +1,21 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.aggregation.model.StrategyDTO; + +import javax.script.Bindings; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 22:03 + */ +public interface Condition { + /** + * 是否匹配 + * + * @param bindings 请求参数 + * @param singleCondition 条件 + * @param domainCode 域 + * @return 是否匹配 + */ + boolean match(Bindings bindings, StrategyDTO.SingleCondition singleCondition, String domainCode); +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionBean.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionBean.java new file mode 100644 index 0000000..e07eeda --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionBean.java @@ -0,0 +1,18 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.enums.OperatorEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 22:34 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConditionBean { + OperatorEnum value(); +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionSupport.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionSupport.java new file mode 100644 index 0000000..11feea5 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/ConditionSupport.java @@ -0,0 +1,44 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.enums.OperatorEnum; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Service; + +import com.google.common.collect.Maps; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 22:05 + */ +@Service +public class ConditionSupport implements ApplicationContextAware { + /** 执行存储单元 */ + private static final Map REPOSITORY = Maps.newHashMap(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map beansOfTypeMap = applicationContext.getBeansOfType(Condition.class); + Collection conditions = beansOfTypeMap.values(); + for (Condition condition : conditions) { + ConditionBean conditionBean = AopUtils.getTargetClass(condition).getAnnotation(ConditionBean.class); + REPOSITORY.put(conditionBean.value(), condition); + } + } + + /** + * 获取Condition + * + * @param operator operator + * @return 返回Condition + */ + public Condition getCondition(OperatorEnum operator) { + return REPOSITORY.get(operator); + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/EQCondition.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/EQCondition.java new file mode 100644 index 0000000..bcb88ce --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/EQCondition.java @@ -0,0 +1,45 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.aggregation.model.BaseDataDTO; +import cn.icanci.rec.common.aggregation.model.StrategyDTO; +import cn.icanci.rec.common.enums.DataTypeEnum; +import cn.icanci.rec.common.enums.OperatorEnum; +import cn.icanci.rec.common.utils.DateUtils; + +import java.math.BigDecimal; + +import javax.script.Bindings; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 22:35 + */ +@Component +@ConditionBean(OperatorEnum.EQ) +public class EQCondition extends AbstractCondition { + + @Override + public boolean match(Bindings bindings, StrategyDTO.SingleCondition singleCondition, String domainCode) { + BaseDataDTO baseData = engineRepositoryHolder.getBaseData(domainCode, singleCondition.getLeftValue()); + String leftValue = getValue(bindings, baseData); + String rightValue = singleCondition.getRightValue(); + DataTypeEnum dataType = DataTypeEnum.valueOf(baseData.getDataType()); + switch (dataType) { + case BOOLEAN: + return StringUtils.equalsIgnoreCase(leftValue, rightValue); + case DATE: + return DateUtils.parse(leftValue, DateUtils.YYYY_MM_DD_HH_MM_SS).compareTo(DateUtils.parse(rightValue, DateUtils.YYYY_MM_DD_HH_MM_SS)) == 0; + case NUMBER: + return new BigDecimal(leftValue).compareTo(new BigDecimal(rightValue)) == 0; + case STRING: + case METADATA: + return StringUtils.equals(leftValue, rightValue); + default: + // no op + } + return false; + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/GTCondition.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/GTCondition.java new file mode 100644 index 0000000..96a9668 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/condition/GTCondition.java @@ -0,0 +1,22 @@ +package cn.icanci.rec.engine.sdk.condition; + +import cn.icanci.rec.common.aggregation.model.StrategyDTO; +import cn.icanci.rec.common.enums.OperatorEnum; + +import javax.script.Bindings; + +import org.springframework.stereotype.Component; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 23:08 + */ +@Component +@ConditionBean(OperatorEnum.GT) +public class GTCondition extends AbstractCondition { + + @Override + public boolean match(Bindings bindings, StrategyDTO.SingleCondition singleCondition, String domainCode) { + return false; + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/exception/ValidatorException.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/exception/ValidatorException.java new file mode 100644 index 0000000..0d33810 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/exception/ValidatorException.java @@ -0,0 +1,29 @@ +package cn.icanci.rec.engine.sdk.exception; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/16 19:55 + */ +public class ValidatorException extends RuntimeException { + private static final long serialVersionUID = -512855499786214321L; + + public ValidatorException() { + super(); + } + + public ValidatorException(String message) { + super(message); + } + + public ValidatorException(String message, Throwable cause) { + super(message, cause); + } + + public ValidatorException(Throwable cause) { + super(cause); + } + + protected ValidatorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/extensions/RecExtensionLoader.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/extensions/RecExtensionLoader.java index 8af0096..e2afa1f 100644 --- a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/extensions/RecExtensionLoader.java +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/extensions/RecExtensionLoader.java @@ -52,7 +52,6 @@ public class RecExtensionLoader { * @return 返回目标实现 */ public T getExtension() { - // 加载到一个第三方实现 Class clazz = loadExtensionFile(); if (clazz == null) { return null; @@ -70,7 +69,6 @@ public class RecExtensionLoader { * @return Class 返回目标第三方实现的{@link Class}对象 */ private Class loadExtensionFile() { - // 想要获取谁的实现类 String fileName = RecExtensionLoader.SERVICE_DIRECTORY + type.getName(); try { Enumeration urls; diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/EngineExecutor.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/EngineExecutor.java index ad40512..92b97a8 100644 --- a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/EngineExecutor.java +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/EngineExecutor.java @@ -1,11 +1,30 @@ package cn.icanci.rec.engine.sdk.rule; +import cn.icanci.rec.common.aggregation.model.DataSourceDTO; +import cn.icanci.rec.common.aggregation.model.DomainDTO; import cn.icanci.rec.common.aggregation.model.StrategyDTO; +import cn.icanci.rec.common.enums.*; +import cn.icanci.rec.common.utils.FastJsonUtils; +import cn.icanci.rec.engine.script.RecScriptEngine; +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; +import cn.icanci.rec.engine.script.wrapper.HttpResponseWrapper; import cn.icanci.rec.engine.sdk.actuator.RuleEngineRequest; import cn.icanci.rec.engine.sdk.actuator.RuleEngineResponse; +import cn.icanci.rec.engine.sdk.exception.ValidatorException; +import cn.icanci.rec.engine.sdk.rule.pool.ScriptExecutorPoolHolder; import cn.icanci.rec.engine.sdk.rule.repository.EngineRepositoryHolder; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + import javax.annotation.Resource; +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; +import javax.script.SimpleBindings; + +import org.apache.commons.lang3.StringUtils; /** * @author icanci @@ -14,7 +33,13 @@ import javax.annotation.Resource; public final class EngineExecutor { @Resource - private EngineRepositoryHolder holder; + private EngineRepositoryHolder holder; + + @Resource + private RecScriptEngine recScriptEngine; + + @Resource + private ScriptExecutorPoolHolder scriptExecutorPoolHolder; /** * 执行入口 @@ -23,24 +48,230 @@ public final class EngineExecutor { * @return 返回执行结果 */ public RuleEngineResponse execute(RuleEngineRequest request) { - // 1.数据基本校验 - validator(request); + try { + // 1.数据基本校验 + validator(request); + + // 2.获取策略 + String domainCode = request.getDomainCode(); + String sceneCode = request.getSceneCode(); + + DomainDTO domain = holder.getDomain(domainCode); + if (domain == null) { + return RuleEngineResponse.fail(ErrorMessageFormat.domainNotFound(domainCode)); + } + StrategyDTO strategy = holder.getStrategy(domainCode, sceneCode); + + if (strategy == null) { + return RuleEngineResponse.fail(ErrorMessageFormat.domainAndSceneNotFound(domainCode, sceneCode)); + } + // 3.聚合数据 + Bindings bindings = mergeDataSource(request, domain, strategy); + + // 4.执行 + RuleTypeEnum ruleType = RuleTypeEnum.valueOf(strategy.getRuleType()); + Object executorResult = null; + RuleModeEnum ruleMode = RuleModeEnum.valueOf(strategy.getRuleMode()); + switch (ruleType) { + case LIST: + executorResult = ruleListExecutor(strategy.getRuleListInfo(), ruleMode, bindings); + break; + case TREE: + executorResult = ruleTreeExecutor(strategy.getRuleTreeInfo(), ruleMode, bindings); + break; + default: + // no op + } + // 5.返回执行结果 + return RuleEngineResponse.success(executorResult); + } catch (Throwable e) { + return RuleEngineResponse.fail(e.getMessage()); + } + } + + /** + * List 执行 + * + * @param ruleListInfo ruleListInfo + * @param ruleMode ruleMode + * @param bindings bindings + * @return 返回执行结果 + */ + private Object ruleListExecutor(StrategyDTO.RuleListInfo ruleListInfo, RuleModeEnum ruleMode, Bindings bindings) { + // List 配置模式 + + // [condition1 ---------] ----------| + // |sc1.1----- | + // & | + // |sc1.2----- | + // [condition2 ---------] | + // |sc2.1----- | + // & | + // |sc2.2----- | + // & | + // |sc2.3----- | + + // condition1 和 condition2 是或的关系 + // condition1 中 sc1.1 和 sc1.2 是且的关系 + // condition2 中 sc2.1、sc2.2、sc2.3 是且的关系 + + // 判断执行模式,简单还是复杂 + List conditions = ruleListInfo.getConditions(); + for (StrategyDTO.Condition condition : conditions) { + // 组内 + List group = condition.getGroup(); + for (StrategyDTO.SingleCondition sc : group) { + String name = sc.getName(); + + } + } + return null; + } - // 2.获取策略 - StrategyDTO strategy = holder.getStrategy(request.getDomainCode(), request.getSceneCode()); - // 3.聚合数据 + /** + * Tree 执行 + * + * @param ruleTreeInfo ruleTreeInfo + * @param ruleMode ruleMode + * @param mergeMap mergeMap + * @return 返回执行结果 + */ + private Object ruleTreeExecutor(StrategyDTO.RuleTreeInfo ruleTreeInfo, RuleModeEnum ruleMode, Map mergeMap) { + List conditions = ruleTreeInfo.getConditions(); + for (StrategyDTO.Condition condition : conditions) { + List group = condition.getGroup(); - // 4.执行 - // 5.返回执行结果 + } return null; } + /** + * 聚合数据 + * + * @param request request + * @param domain domain + * @param strategy strategy + * @return 返回聚合的数据 + * @throws ScriptException 脚本执行异常 + */ + private Bindings mergeDataSource(RuleEngineRequest request, DomainDTO domain, StrategyDTO strategy) throws ScriptException, ExecutionException, InterruptedException, + TimeoutException { + Map parameters = request.getParameters(); + + SimpleBindings bindings = new SimpleBindings(); + if (parameters != null) { + bindings.putAll(parameters); + } + String dataSourceType = strategy.getDataSourceType(); + String dataSourceUuid = strategy.getDataSourceUuid(); + if (StringUtils.isNotBlank(dataSourceType) && StringUtils.isNotBlank(dataSourceUuid)) { + DataSourceDTO dataSource = holder.getDataSource(domain.getDomainCode(), dataSourceUuid); + DataSourceTypeEnum dataSourceTypeEnum = DataSourceTypeEnum.valueOf(dataSource.getDataSourceType()); + switch (dataSourceTypeEnum) { + case SCRIPT: + DataSourceDTO.ScriptInfo scriptInfo = dataSource.getScriptInfo(); + CompiledScript compiledScript = scriptInfo.getCompiledScript(); + if (compiledScript != null) { + Object eval = compiledScript.eval(); + bindings.putAll(mapperMap(eval, domain, strategy, dataSource)); + } else { + // 线程池 + FutureTask> task = new FutureTask<>(new ScriptExecutor(scriptInfo, recScriptEngine)); + scriptExecutorPoolHolder.submit(task); + // 等待执行结果 + RecScriptEngineContext context = task.get(scriptInfo.getTimeout(), TimeUnit.SECONDS); + if (context.isSuccess()) { + bindings.putAll(mapperMap(context.getRealRetVal(), domain, strategy, dataSource)); + } else { + throw new IllegalArgumentException(ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), + dataSource.getDataSourceName(), context.getThrowable().getMessage())); + } + } + break; + case HTTP: + DataSourceDTO.HttpInfo httpInfo = dataSource.getHttpInfo(); + HttpResponseWrapper wrapper = recScriptEngine.httpEval(HttpRequestTypeEnum.valueOf(httpInfo.getHttpRequestType()), httpInfo.getReqUrl(), httpInfo.getReqParam(), + httpInfo.getTimeout()); + if (wrapper.isSuccess()) { + String response = wrapper.getResponse(); + if (FastJsonUtils.isJson(response)) { + bindings.putAll(wrapper.getResponseForMap()); + } else { + throw new IllegalArgumentException( + ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "HTTP执行结果不是Json")); + } + } else { + throw new IllegalArgumentException(ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), + dataSource.getDataSourceName(), wrapper.getException().getMessage())); + + } + break; + default: + // no op + } + } + return bindings; + } + + /** + * mapperMap + * + * @param eval eval + * @param domain domain + * @param strategy strategy + * @param dataSource dataSource + * @return 返回Map + */ + private Map mapperMap(Object eval, DomainDTO domain, StrategyDTO strategy, DataSourceDTO dataSource) { + if (eval instanceof Map) { + return (Map) eval; + } else if (eval instanceof String) { + String str = String.valueOf(eval); + boolean isJson = FastJsonUtils.isJson(str); + if (!isJson) { + throw new IllegalArgumentException( + ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "脚本执行结果不是Json")); + } else { + return FastJsonUtils.fromJSONString(str, Map.class); + } + } else { + throw new IllegalArgumentException( + ErrorMessageFormat.dataSourceExecutorFail(domain.getDomainName(), strategy.getStrategyName(), dataSource.getDataSourceName(), "脚本执行结果不是Map")); + } + } + /** * 数据基本校验 * * @param request request */ private void validator(RuleEngineRequest request) { + if (request == null) { + throw new ValidatorException("request is Null!"); + } + if (StringUtils.isBlank(request.getDomainCode())) { + throw new ValidatorException("domainCode is empty!"); + } + if (StringUtils.isBlank(request.getSceneCode())) { + throw new ValidatorException("sceneCode is empty!"); + } + } + + /** 执行器 */ + private static class ScriptExecutor implements Callable> { + /** scriptInfo */ + private final DataSourceDTO.ScriptInfo scriptInfo; + /** recScriptEngine执行引擎 */ + private final RecScriptEngine recScriptEngine; + + public ScriptExecutor(DataSourceDTO.ScriptInfo scriptInfo, RecScriptEngine recScriptEngine) { + this.scriptInfo = scriptInfo; + this.recScriptEngine = recScriptEngine; + } + @Override + public RecScriptEngineContext call() throws Exception { + return recScriptEngine.eval(ScriptTypeEnum.valueOf(scriptInfo.getScriptType()), scriptInfo.getScriptContent()); + } } } diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/ErrorMessageFormat.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/ErrorMessageFormat.java new file mode 100644 index 0000000..97e5ff3 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/ErrorMessageFormat.java @@ -0,0 +1,30 @@ +package cn.icanci.rec.engine.sdk.rule; + +/** + * 错误信息格式化 + * + * @author icanci + * @since 1.0 Created in 2022/11/16 19:49 + */ +public class ErrorMessageFormat { + + /** 根据 [域: %s][场景: %s] 查询策略不存在,不执行处理 */ + private static final String DOMAIN_AND_SCENE_NOT_FOUND_FORMAT = "根据 [域: %s][场景: %s] 查询策略不存在,不执行处理"; + + /** 根据 [域: %s] 查询域不存在,不执行处理 */ + private static final String DOMAIN_FOUND_FORMAT = "根据 [域: %s] 查询域不存在,不执行处理"; + /** 策略[%s::%s] 数据源[%s] 脚本执行失败:%s */ + private static final String DATA_SOURCE_EXECUTOR_FAIL_FORMAT = "策略[%s::%s] 数据源[%s] 脚本执行失败:%s"; + + public static String domainAndSceneNotFound(String domainCode, String sceneCode) { + return String.format(DOMAIN_AND_SCENE_NOT_FOUND_FORMAT, domainCode, sceneCode); + } + + public static String domainNotFound(String domainCode) { + return String.format(DOMAIN_FOUND_FORMAT, domainCode); + } + + public static String dataSourceExecutorFail(String domainName, String strategyName, String dataSourceName, String message) { + return String.format(DATA_SOURCE_EXECUTOR_FAIL_FORMAT, domainName, strategyName, dataSourceName, message); + } +} diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/pool/ScriptExecutorPoolHolder.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/pool/ScriptExecutorPoolHolder.java new file mode 100644 index 0000000..ed30f36 --- /dev/null +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/pool/ScriptExecutorPoolHolder.java @@ -0,0 +1,39 @@ +package cn.icanci.rec.engine.sdk.rule.pool; + +import cn.icanci.rec.engine.script.context.RecScriptEngineContext; + +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 脚本执行线程池处理器 + * + * @author icanci + * @since 1.0 Created in 2022/11/16 21:06 + */ +public class ScriptExecutorPoolHolder { + + private static final ScriptExecutorPoolHolder instance = new ScriptExecutorPoolHolder(); + + public static ScriptExecutorPoolHolder getInstance() { + return instance; + } + + private ThreadPoolExecutor registryOrRemoveThreadPool = null; + + private ScriptExecutorPoolHolder() { + registryOrRemoveThreadPool = new ThreadPoolExecutor(2, 10, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), + r -> new Thread(r, "ScriptExecutorPoolHolderPool-" + r.hashCode()), (r, executor) -> r.run()); + } + + /** + * 提交任务 + * + * @param task task + */ + public void submit(FutureTask> task) { + registryOrRemoveThreadPool.submit(task); + } +} -- Gitee