diff --git a/pom.xml b/pom.xml index e1922e895e925dd7954b6bed41ff484929121f25..867d32f441b1ff9f6a1f0ad10e06c5051623a4ad 100644 --- a/pom.xml +++ b/pom.xml @@ -66,8 +66,8 @@ 2.4.7.Final 2.5.11 - - + + 4.1.63.Final @@ -190,6 +190,11 @@ ${groovy.version} pom + + io.netty + netty-all + ${netty-all.version} + diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/config/StrategyVoDtoMapper.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/config/StrategyVoDtoMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..e377e44ea74178dce4e9759b26e322879677fd22 --- /dev/null +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/mapper/config/StrategyVoDtoMapper.java @@ -0,0 +1,28 @@ +package cn.icanci.rec.admin.biz.mapper.config; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +import cn.icanci.rec.admin.biz.mapper.convertor.*; +import cn.icanci.rec.common.aggregation.model.StrategyDTO; +import cn.icanci.rec.common.model.config.StrategyVO; + +/** + * @author icanci + * @since 1.0 Created in 2022/11/11 17:49 + */ +@Mapper(componentModel = "spring", uses = { DataSourceTypeEnumConverter.class, RuleTypeEnumConverter.class, OperatorEnumConverter.class, InterruptEnumConverter.class, + ResultTypeEnumConverter.class, RuleModeEnumConverter.class }, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) +public interface StrategyVoDtoMapper extends BaseMapper { + + StrategyDTO vo2dto(StrategyVO strategyVO); + + StrategyDTO.RuleListInfo vo2dto(StrategyVO.RuleListInfo ruleListInfo); + + StrategyDTO.RuleTreeInfo vo2dto(StrategyVO.RuleTreeInfo ruleTreeInfo); + + StrategyDTO.Condition vo2dto(StrategyVO.Condition condition); + + StrategyDTO.SingleCondition vo2dto(StrategyVO.SingleCondition singleCondition); + +} diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/model/StrategyDebugResult.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/model/StrategyDebugResult.java index aded7bcb2b126b19056b1b80895d651bee0f48a3..2b4391fa7dacdf80c1c349d03425f714b7ec315e 100644 --- a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/model/StrategyDebugResult.java +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/model/StrategyDebugResult.java @@ -2,10 +2,72 @@ package cn.icanci.rec.admin.biz.model; import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; + /** * @author icanci * @since 1.0 Created in 2022/11/18 22:35 */ public class StrategyDebugResult implements Serializable { private static final long serialVersionUID = -5412720512170304475L; + /** 是否执行成功 */ + public boolean success; + + /** 返回值是否是布尔类型 */ + public boolean retBoolean; + + /** 返回值Fastjson序列化值 */ + public Object retValue; + + /** 执行异常结果 */ + public String exceptionMessage; + + /** TODO 实际执行请求参数 */ + public String executorParam; + + public boolean isRetBoolean() { + if (retValue == null) { + return false; + } + if (retValue instanceof Boolean) { + return true; + } + if (retValue instanceof String) { + return StringUtils.equalsIgnoreCase(retValue.toString(), Boolean.TRUE.toString()) // + || StringUtils.equalsIgnoreCase(retValue.toString(), Boolean.FALSE.toString()); + } + return false; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public Object getRetValue() { + return retValue; + } + + public void setRetValue(Object retValue) { + this.retValue = retValue; + } + + public String getExceptionMessage() { + return exceptionMessage; + } + + public void setExceptionMessage(String exceptionMessage) { + this.exceptionMessage = exceptionMessage; + } + + public String getExecutorParam() { + return executorParam; + } + + public void setExecutorParam(String executorParam) { + this.executorParam = executorParam; + } } diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/StrategyService.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/StrategyService.java index d98e1b98fc7aea7324d35d878c670100f5726aa1..cd483de5f1114892b40f288095ef58604d36bae9 100644 --- a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/StrategyService.java +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/StrategyService.java @@ -23,7 +23,21 @@ public interface StrategyService extends BaseService { @Override PageList queryPage(StrategyVO strategyVO, int pageNum, int pageSize); + /** + * 根据domainCode和strategyName查询 + * + * @param domainCode domainCode + * @param strategyName strategyName + * @return 返回查询结果 + */ StrategyVO queryByStrategyName(String domainCode, String strategyName); + /** + * 对策略进行测试 + * + * @param strategy strategy + * @param scriptContentTest 测试参数,当数据源存在且为脚本的时候,此项有值 + * @return 返回测试结果 + */ StrategyDebugResult debug(StrategyVO strategy, String scriptContentTest); } diff --git a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/impl/StrategyServiceImpl.java b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/impl/StrategyServiceImpl.java index 50778cd80645df6ca52812871eea610cb068465d..1a7e8ad7fc578e5b694b27c7f724ba7f60090af6 100644 --- a/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/impl/StrategyServiceImpl.java +++ b/rec-admin/rec-admin-biz/src/main/java/cn/icanci/rec/admin/biz/service/impl/StrategyServiceImpl.java @@ -2,23 +2,44 @@ package cn.icanci.rec.admin.biz.service.impl; import cn.icanci.rec.admin.biz.event.log.LogEvent; import cn.icanci.rec.admin.biz.mapper.config.StrategyMapper; +import cn.icanci.rec.admin.biz.mapper.config.StrategyVoDtoMapper; import cn.icanci.rec.admin.biz.model.StrategyDebugResult; import cn.icanci.rec.admin.biz.service.StrategyService; +import cn.icanci.rec.admin.biz.service.WebApiService; import cn.icanci.rec.admin.dal.mongodb.common.PageList; import cn.icanci.rec.admin.dal.mongodb.daointerface.StrategyDAO; import cn.icanci.rec.admin.dal.mongodb.dateobject.StrategyDO; +import cn.icanci.rec.common.aggregation.model.*; +import cn.icanci.rec.common.enums.DataSourceTypeEnum; import cn.icanci.rec.common.enums.LogOperatorTypeEnum; import cn.icanci.rec.common.enums.ModuleTypeEnum; +import cn.icanci.rec.common.enums.ScriptTypeEnum; import cn.icanci.rec.common.model.config.StrategyVO; import cn.icanci.rec.common.utils.FastJsonUtils; +import cn.icanci.rec.engine.script.RecScriptEngine; +import cn.icanci.rec.engine.sdk.actuator.RuleEngineRequest; +import cn.icanci.rec.engine.sdk.actuator.RuleEngineResponse; +import cn.icanci.rec.engine.sdk.rule.EngineExecutor; +import cn.icanci.rec.engine.sdk.rule.repository.DomainSceneKey; +import cn.icanci.rec.engine.sdk.rule.repository.EngineRepositoryHolder; import cn.icanci.rec.spi.event.EventDispatcher; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.Resource; +import javax.script.CompiledScript; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + /** * @author icanci * @since 1.0 Created in 2022/11/12 10:38 @@ -26,11 +47,21 @@ import org.springframework.stereotype.Service; @Service public class StrategyServiceImpl implements StrategyService { @Resource - private StrategyDAO strategyDAO; + private StrategyDAO strategyDAO; + @Resource + private StrategyMapper strategyMapper; + @Resource + private StrategyVoDtoMapper strategyVoDtoMapper; + @Resource + private EventDispatcher eventDispatcher; + @Resource + private EngineRepositoryHolder holder; + @Resource + private EngineExecutor engineExecutor; @Resource - private StrategyMapper strategyMapper; + private RecScriptEngine recScriptEngine; @Resource - private EventDispatcher eventDispatcher; + private WebApiService webApiService; @Override public List queryAll() { @@ -67,7 +98,89 @@ public class StrategyServiceImpl implements StrategyService { @Override public StrategyDebugResult debug(StrategyVO strategy, String scriptContentTest) { + // 执行结果 + StrategyDebugResult result = new StrategyDebugResult(); + + String domainCode = strategy.getDomainCode(); + HashSet domainCodes = Sets.newHashSet(domainCode); + try { + // 刷新仓储 + + // 1.获取domain + DomainDTO domain = webApiService.loadDomainByDomains(domainCodes).iterator().next(); + // 2.获取基础数据 + List baseDatas = webApiService.loadBaseDataByDomains(domainCodes); + compileBaseDataScript(baseDatas); + Map refreshBaseDatas = baseDatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, baseData -> baseData)); + // 3.构建元数据信息 + List metadatas = webApiService.loadMetadataByDomains(domainCodes); + Map refreshMetadatas = metadatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, metadata -> metadata)); + // 4.构建数据源信息 + List dataSources = webApiService.loadDataSourceByDomains(domainCodes); + compileDataSourceScript(dataSources); + Map refreshDataSources = dataSources.stream().collect(Collectors.toMap(BaseDTO::getUuid, dataSource -> dataSource)); + // 5.构建策略信息 + StrategyDTO strategyDTO = strategyVoDtoMapper.vo2dto(strategy); + Map refreshStrategies = Lists.newArrayList(strategyDTO).stream() + .collect(Collectors.toMap(x -> new DomainSceneKey(x.getDomainCode(), x.getSceneCode()), s -> s)); + + // 6.全部构建成功之后,刷新缓存 + holder.refresh(domain, refreshBaseDatas, refreshMetadatas, refreshDataSources, refreshStrategies); + + // 7.构建执行参数 + RuleEngineRequest request = new RuleEngineRequest(); + request.setDomainCode(domainCode); + request.setSceneCode(strategy.getSceneCode()); + request.setParameters(StringUtils.isNotBlank(scriptContentTest) ? FastJsonUtils.fromJSONString(scriptContentTest, Map.class) : Maps.newHashMap()); + + // 8.执行 + RuleEngineResponse execute = engineExecutor.execute(request); - return null; + // 9.构建执行返回结果 + result.setSuccess(execute.isSuccess()); + result.setRetValue(execute.getResult()); + result.setExceptionMessage(execute.isSuccess() ? StringUtils.EMPTY : execute.getErrorMessage()); + } catch (Throwable e) { + result.setSuccess(false); + result.setExceptionMessage(e.getMessage()); + } finally { + // 10.清除仓储 + holder.clear(domainCodes); + } + return result; + } + + /** + * 编译执行脚本 + * + * @param baseDatas baseDatas + */ + private void compileBaseDataScript(List baseDatas) { + if (CollectionUtils.isEmpty(baseDatas)) { + return; + } + for (BaseDataDTO baseData : baseDatas) { + CompiledScript compiledScript = recScriptEngine.compile(ScriptTypeEnum.valueOf(baseData.getScriptType()), baseData.getScriptContent()); + baseData.setCompiledScript(compiledScript); + } } + + /** + * 编译执行脚本 + * + * @param dataSources dataSources + */ + private void compileDataSourceScript(List dataSources) { + if (CollectionUtils.isEmpty(dataSources)) { + return; + } + for (DataSourceDTO dataSource : dataSources) { + DataSourceTypeEnum sourceType = DataSourceTypeEnum.valueOf(dataSource.getDataSourceType()); + if (sourceType == DataSourceTypeEnum.SCRIPT) { + DataSourceDTO.ScriptInfo scriptInfo = dataSource.getScriptInfo(); + scriptInfo.setCompiledScript(recScriptEngine.compile(ScriptTypeEnum.valueOf(scriptInfo.getScriptType()), scriptInfo.getScriptContent())); + } + } + } + } diff --git a/rec-engine/rec-engine-sdk/pom.xml b/rec-engine/rec-engine-sdk/pom.xml index 663c0b10b0fe11c7a9812b21a86cf1d9e085ee44..7cf35adaa40ec3ae3e6710de57f27b1855de3fd9 100644 --- a/rec-engine/rec-engine-sdk/pom.xml +++ b/rec-engine/rec-engine-sdk/pom.xml @@ -60,5 +60,9 @@ com.google.guava guava + + io.netty + netty-all + \ No newline at end of file diff --git a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/repository/EngineRepositoryHolder.java b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/repository/EngineRepositoryHolder.java index 0122b49c2919476291df2d286aaa19bd3fbabf2d..686af9dfc6acb1b522d7b157ebba8bf6210742b8 100644 --- a/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/repository/EngineRepositoryHolder.java +++ b/rec-engine/rec-engine-sdk/src/main/java/cn/icanci/rec/engine/sdk/rule/repository/EngineRepositoryHolder.java @@ -43,7 +43,6 @@ public class EngineRepositoryHolder { * @param domainCodes domainCodes */ public void refresh(Set domainCodes) { - if (CollectionUtils.isEmpty(domainCodes)) { return; } @@ -92,7 +91,7 @@ public class EngineRepositoryHolder { // 3.5 构建策略信息 List strategies = engineRepositoryLoader.loadStrategy(domain); Map refreshStrategies = strategies.stream() - .collect(Collectors.toMap(x -> new DomainSceneKey(x.getDomainCode(), x.getSceneCode()), dataSource -> dataSource)); + .collect(Collectors.toMap(x -> new DomainSceneKey(x.getDomainCode(), x.getSceneCode()), strategy -> strategy)); // 4. 全部构建成功之后,再刷新缓存 repository.setDomainRepository(domainRepository); @@ -106,6 +105,58 @@ public class EngineRepositoryHolder { } + /** + * 本地缓存刷新,提供自行构建方法 + * + * @param domain domain + * @param refreshBaseDatas refreshBaseDatas + * @param refreshMetadatas refreshMetadatas + * @param refreshDataSources refreshDataSources + * @param refreshStrategies refreshStrategies + */ + public final void refresh(DomainDTO domain, Map refreshBaseDatas, Map refreshMetadatas, Map refreshDataSources, + Map refreshStrategies) { + List scenePairs = engineRepositoryLoader.loadScenePairs(domain.getDomainCode()); + if (CollectionUtils.isEmpty(scenePairs)) { + return; + } + EngineRepository repository = ENGINE_REPOSITORY_CONFIG.putIfAbsent(domain.getDomainCode(), new EngineRepository()); + repository.setDomainRepository(domain); + repository.getBaseDataRepository().putAll(refreshBaseDatas); + repository.getMetadataRepository().putAll(refreshMetadatas); + repository.getDataSourceRepository().putAll(refreshDataSources); + repository.getStrategyRepository().putAll(refreshStrategies); + } + + /** + * 清除缓存操作 + * + * @param domainCodes domainCodes + */ + public void clear(Set domainCodes) { + if (CollectionUtils.isEmpty(domainCodes)) { + return; + } + for (String domainCode : domainCodes) { + clear(domainCode); + } + } + + /** + * 仓储清除 + * + * @param domain domain + */ + private void clear(String domain) { + // 1.先获取场景,如果场景都没有,则不处理 + List scenePairs = engineRepositoryLoader.loadScenePairs(domain); + if (CollectionUtils.isEmpty(scenePairs)) { + return; + } + // 2.移除仓储 + ENGINE_REPOSITORY_CONFIG.remove(domain); + } + /** * 编译执行脚本 *