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 57d4fcdc947202d9b845bbfc59fc4202cf485637..8566238289f0ebffde332ece92ec900ed6d29e95 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 0000000000000000000000000000000000000000..4ce72fd95aa262e2d696d2cbede9dc57c4170628 --- /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 e31ccb4e00a7c022192419cf3d1cd0b0213c3d07..f8cdd920915f5318de721c1491ef28114c4c9ce1 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 9d96c2656aefe1a81ff48f80d2cefbed4bda4dcb..939dd2f96c64b95ccae9b49b8c0118eb2a78af87 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 4d4d3b946a1b9116f271c9b48dfe717ce33cd7fd..d48c69bdac6aa7a905d46ecbd9858d4fe7011233 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 aa20ca21609e1d43fb379d6bc0d3f0e2c3e11c7f..6dd284ec218487d8f1e1107bdab64a041db48840 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 0000000000000000000000000000000000000000..8f61b12d1f8947a06a8b80f5c8547eba77e13255 --- /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 4e6cccfc4475d4de2ea892bc6f0931b2cb17c67f..f195df7901f00ceb4128f5c2d3a160c017897134 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 0000000000000000000000000000000000000000..69ef64ba943b41c207200175bac7cb1a2d75ef21 --- /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 fef45dec64c0f149c35d8364f6d73b11d402440f..425eebf5d3b6e5d4d867eecc8a300892cbe5e7d6 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 9815b4d949caea504ec3c413ad6176a8ed564299..f60790a3b140129022c17eb0bda0751135e97a97 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 0000000000000000000000000000000000000000..fff590991e98971c13ad0079461e9ab2a5205bfa --- /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 0000000000000000000000000000000000000000..f18ae484af8b5fd03ebed13161b2ec10c8575795 --- /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 0000000000000000000000000000000000000000..e07eedaeffce69b7dda84ffa46391103bcff18ba --- /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 0000000000000000000000000000000000000000..11feea51cf137d782f2dde4c15fd698f635e9d8f --- /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 0000000000000000000000000000000000000000..bcb88ceab656ceae5acb9e9283af990960f0f6c3 --- /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 0000000000000000000000000000000000000000..96a966851ab81f94ff97af6927d448183847cebb --- /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 0000000000000000000000000000000000000000..0d33810cc8c2c1d092f4a253b36c97a1866b0645 --- /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 8af0096e7fcff9e86c149ed8422c814d527be77c..e2afa1f7f8ec201f9a326be8c6600f238247ceb6 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 ad40512c2baf7e1396fbeacc434ee88351ab2d69..92b97a83dfc29cb5f04e6b3449013ae7270d1c60 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 0000000000000000000000000000000000000000..97e5ff38cebcff193d71bd10ccd70d61315821b8 --- /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 0000000000000000000000000000000000000000..ed30f363dee22c5c6d4b520945c5080fa2ead9a2 --- /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); + } +}