简单的系统拖拽规则
1. 需求
公司想要一个规则控制功能,在功能执行过程中,可以动态的改变原来的执行逻辑,让业务人员可以进行简单的配置,并且实现拖拽功能,原有的drools规则引擎部分因为编译结果未知,执行出问题不太好定位问题,以及提示不能最快定为到底是什么规则触发引起的报错,代码部分也不能快速定为,所以对规则部分进行了一个二次开发,此次放弃使用规则引擎,直接全部自己手动实现,功能相对简单。
2. 实现思路
实现的步骤以及思路还是跟以前的drools规则实现差不多,主要分为规则模块以及非规则业务模块,最终简单的实现思路以及执行过程如下:
- 启动规则模块服务加载全部规则,将规则信息(入参,出参,描述等)加载到缓存中,提供规则详情调用具体实现方法
- 启动业务子模块,加载规则调用入口(既各个按钮等功能接口),将入口inVO参数以及入口信息描述等加入缓存中
- 业务人员进行规则链编辑,将需要加入规则的controller接口,配置上需要调用的规则链,同时校验controller的方法入参是否满足规则链中各个规则的入参信息
- 客户端调用规则链入口方法,首先执行切面方法,获取当前请求uri是否配置规则链,如果配置则依次循环调用规则模块规则详情方法,执行通过则进入controller对应的method方法,失败则进行失败信息提示即可,不进入controller方法
简单的实现思路图:
简单的逻辑调用图:
3. 页面设计
业务实现基本设置四个简单页面,大体有规则列表页面,规则编辑页面,规则链列表页面,规则链编辑页面,具体页面设计如下:
- 规则列表页面
- 规则详情页面:可以进行规则链
- 规则链列表页面:进行规则链的分页展示
- 规则链页面:可以进行规则链的编辑
- 规则链编辑提示页面:这部分会提示规则链的规则是否合法,不合法的话提示具体哪些规则哪些字段不合法
- 产品规则列表展示:此页面进行产品规则的分页以及展示,点击新增可以新增产品规则链,操作与普通规则链相同
4. 具体实现
实现部分主要包含客户端以及规则服务端,客户端提供规则接口入口信息,服务启动将接口信息上传至缓存中供规则模块规则链管理,服务端提供规则管理,规则链管理以及规则执行的具体实现。
4.1 客户端
具体逻辑为启动服务,扫描带有**@DragRule**的注解接口,将接口的对应的入参参数inVO,接口名称,描述信息等统一存入缓存,redis缓存使用结构为hash形式,我们使用的是微服务,服务启动执行需要枪锁执行。具体实现的主要代码如下所示
-
DragRule注解信息,此注解是接口扫描注解,只有标记此注解才会被扫描进入缓存信息中
package cn.git.common.annotation; import java.lang.annotation.*; /** * @description: 拖拽规则注解 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-20 09:38:40 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DragRule { /** * 描述信息 * @return */ String ruleDesc() default ""; }
-
DragRuleBeanDefinitionProcessor,此类在服务启动时候,获取全部规定目录下的所有有效bean的定义信息,并且封装成map
package cn.git.common.drag.processor; import cn.git.common.annotation.DragRule; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * @description: 获取拖拽规则注解定义类 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-29 04:33:16 */ @Slf4j @Component public class DragRuleBeanDefinitionProcessor implements BeanDefinitionRegistryPostProcessor { /** * controller类前缀 */ private static final String CONTROLLER_PREFIX = "cn.git"; /** * controller类类名后缀 */ private static final String CONTROLLER_SUFFIX = "Controller"; /** * 基础拖拽规则bean定义列表 * 扫描所有controller类,如果类中包含@DragRule注解的,就添加到beanDefinitionList中 */ public static List<BeanDefinition> BASE_DRAG_RULE_BEAN_DEFINITION = new ArrayList<>(); /** * Modify the application context's internal bean definition registry after its * standard initialization. All regular bean definitions will have been loaded, * but no beans will have been instantiated yet. This allows for adding further * bean definitions before the next post-processing phase kicks in. * * @param registry the bean definition registry used by the application context * @throws BeansException in case of errors */ @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String beanName : registry.getBeanDefinitionNames()) { // 判定类是否为cn.git路径下以及controller类名后缀 BeanDefinition definition = registry.getBeanDefinition(beanName); Class<?> clazz; try { clazz = Class.forName(definition.getBeanClassName()); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(DragRule.class) && !BASE_DRAG_RULE_BEAN_DEFINITION.contains(definition)) { BASE_DRAG_RULE_BEAN_DEFINITION.add(definition); } } } catch (Exception ignored) { // 加载失败,无需做处理 } } } /** * Modify the application context's internal bean factory after its standard * initialization. All bean definitions will have been loaded, but no beans * will have been instantiated yet. This allows for overriding or adding * properties even to eager-initializing beans. * * @param beanFactory the bean factory used by the application context * @throws BeansException in case of errors */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
-
CommonClientRuleUriInit ,服务初始化执行方法,主要进行bean定义信息解析,并且筛选出@DragRule标记类,存入缓存中
package cn.git.common.drag; import cn.git.common.annotation.DragRule; import cn.git.common.constant.CommonConstant; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.mapper.CommonDragRuleMapper; import cn.git.common.drag.processor.DragRuleBeanDefinitionProcessor; import cn.git.common.exception.ServiceException; import cn.git.common.lock.LockTypeEnum; import cn.git.common.lock.SimpleDistributeLock; import cn.git.common.util.CommonDragRuleUtil; import cn.git.redis.RedisUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.fastjson.JSONObject; import io.swagger.annotations.Api; import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** * @description: 拖拽规则规则链初始化 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-28 01:40:24 */ @Slf4j @Component @EnableScheduling public class CommonClientRuleUriInit implements ApplicationRunner { @Value("${spring.application.name}") private String applicationName; /** * 规则服务名称 */ private static final String RULE_SERVER_NAME = "rule-manage-server"; /** * 风控服务名称 */ private static final String RISK_SERVER_NAME = "risk-server"; @Autowired private CommonDragRuleMapper commonDragRuleMapper; @Autowired private RedisUtil redisUtil; @Autowired private CommonDragRuleUtil commonDragRuleUtil; @Autowired private SimpleDistributeLock simpleLock; /** * 拖拽规则规则链初始化 * 获取拖拽规则对应的入参参数包含值,方便后续保存规则的时候进行满足参数的校验 * @throws Exception */ @Override public void run(ApplicationArguments args) throws Exception { // 加锁,服务发版启动,只有一个服务可以执行 String lock = simpleLock.tryLock(LockTypeEnum.DRAG_RULE_URI_LOCK, applicationName); try { // 加锁成功并且服务为业务子模块服务则可以进行规则模块信息加载,否则不进行加载 if (StrUtil.isNotBlank(lock) && commonDragRuleUtil.checkIfLoadDragChain(applicationName)) { log.info("拖拽规则[{}]初始化规则链开始!", applicationName); // 获取定义好的拖拽注解实现方法类类型 List<BeanDefinition> beanDefinitionList = DragRuleBeanDefinitionProcessor.BASE_DRAG_RULE_BEAN_DEFINITION; // mapper中获取规则链信息,并且将list转换为map信息,其中key为规则链uri,value为规则链信息,并且过滤掉其他模块的规则链信息 String chainUriPrefix = applicationName.replace(StrUtil.DASHED, StrUtil.UNDERLINE).toUpperCase(); List<DragChainDTO> dragChainDTOList = commonDragRuleMapper.getDragChainList(); Map<String, DragChainDTO> dragChainMap = dragChainDTOList .stream() .filter(chainDTO -> { // 只获取当前服务自己的规则链信息 return chainDTO.getUri().startsWith(chainUriPrefix); }) .collect(Collectors.toMap( DragChainDTO::getUri, Function.identity(), (k1, k2) -> k2) ); // 获取beanDefinitionList中的全部规则链信息,封装成map结构,key为规则链uri Map<String, DragChainDTO> uriMap = new HashMap<>(CommonConstant.DEFAULT_MAP_SIZE_16); // 获取 DragRule.class注解对应的方法以及参数信息 for (BeanDefinition beanDefinition : beanDefinitionList) { // 获取类名 String className = beanDefinition.getBeanClassName(); // 获取类对象 Class<?> clazz = Class.forName(className); // 获取controller名称 String controllerName = clazz.getSimpleName(); // 获取请求头上面的RequestMapping路径信息 RequestMapping controllerRequestMapping = clazz.getAnnotation(RequestMapping.class); String uriPrefix = controllerRequestMapping.value()[0]; // 获取controller描述信息 String controllerDesc = null; if (clazz.isAnnotationPresent(Api.class)) { Api api = clazz.getAnnotation(Api.class); controllerDesc = api.tags()[0]; } // 循环获取DragRule注解的方法信息以及参数信息 for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(DragRule.class)) { // 获取方法的描述信息 String methodDesc = null; if (method.isAnnotationPresent(ApiOperation.class)) { ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); methodDesc = apiOperation.value(); } String methodName = method.getName(); // 获取方法请求uri后缀部分 String suffixUri = null; if (method.isAnnotationPresent(RequestMapping.class)) { // request请求方法uri suffixUri = method.getAnnotation(RequestMapping.class).value()[0]; } else if (method.isAnnotationPresent(GetMapping.class)) { // get请求方法uri suffixUri = method.getAnnotation(GetMapping.class).value()[0]; } else if (method.isAnnotationPresent(PostMapping.class)) { // post请求方法uri suffixUri = method.getAnnotation(PostMapping.class).value()[0]; } // 判断是否为path路径带有参数信息 if (suffixUri.contains(StrUtil.DELIM_START)) { suffixUri = suffixUri.substring(0, suffixUri.indexOf(StrUtil.DELIM_START) - 1); } // 整体全路径为 String allPathUri = uriPrefix.concat(suffixUri); // 获取参数信息 JSONObject paramJSON = new JSONObject(true); // 参数描述信息 List<DragChainDTO.DragRuleInVOParamsDesc> inVOParamsDescList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameters.length; i++) { // 当前参数 Parameter parameter = parameters[i]; // 当前参数类型 Class<?> parameterType = parameterTypes[i]; // 简单类型处理 if ("String".equals(parameterType.getSimpleName()) || "Integer".equals(parameterType.getSimpleName()) || "Long".equals(parameterType.getSimpleName()) || "Double".equals(parameterType.getSimpleName()) || "Float".equals(parameterType.getSimpleName()) || "Boolean".equals(parameterType.getSimpleName()) || "Date".equals(parameterType.getSimpleName())) { ApiParam annotation = parameter.getAnnotation(ApiParam.class); if (ObjectUtil.isNotNull(annotation)) { DragChainDTO.DragRuleInVOParamsDesc inVOParamsDesc = new DragChainDTO.DragRuleInVOParamsDesc(); inVOParamsDesc.setInParamDesc(annotation.value()); inVOParamsDesc.setInParamName(parameter.getName()); inVOParamsDesc.setInParamType(parameterType.getSimpleName()); inVOParamsDescList.add(inVOParamsDesc); } paramJSON.put(parameter.getName(), StrUtil.EMPTY); } else { // 复杂类类型 Field[] declaredFields = parameterType.getDeclaredFields(); for (Field field : declaredFields) { DragChainDTO.DragRuleInVOParamsDesc inVOParamsDesc = null; ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class); if (ObjectUtil.isNotNull(apiModelProperty)) { inVOParamsDesc = new DragChainDTO.DragRuleInVOParamsDesc(); inVOParamsDesc.setInParamDesc(apiModelProperty.value()); inVOParamsDesc.setInParamName(field.getName()); inVOParamsDesc.setInParamType(field.getType().getSimpleName()); inVOParamsDescList.add(inVOParamsDesc); } paramJSON.put(field.getName(), StrUtil.EMPTY); } } } // 设置最终路径信息,模块名:uri String finalUri = chainUriPrefix .toUpperCase() .concat(StrUtil.COLON) .concat(allPathUri); // 信息包含controller方法入参信息,规则链信息 DragChainDTO dragChainDTO = new DragChainDTO(); dragChainDTO.setMethodDesc(methodDesc); dragChainDTO.setMethodName(methodName); dragChainDTO.setControllerDesc(controllerDesc); dragChainDTO.setControllerName(controllerName); dragChainDTO.setMethodInParam(paramJSON.toJSONString()); dragChainDTO.setInVOParamsDescList(inVOParamsDescList); dragChainDTO.setUri(finalUri); dragChainDTO.setIfLoadDB(CommonDragRuleConstant.NUM_STR_0); dragChainDTO.setDragRuleChainId(controllerName.concat(StrUtil.DOT).concat(methodName)); dragChainDTO.setDragChainModule(applicationName.replace(StrUtil.DASHED, StrUtil.UNDERLINE).toUpperCase()); if (ObjectUtil.isNotNull(dragChainMap.get(finalUri))) { // 设置规则链信息 dragChainDTO.setIsDel(dragChainMap.get(finalUri).getIsDel()); dragChainDTO.setIfLoadDB(CommonDragRuleConstant.NUM_STR_1); dragChainDTO.setDragRuleChain(dragChainMap.get(finalUri).getDragRuleChain()); // 规则链校验 dragRule1,dragRule2,dragRule3联调校验入参,回参信息 commonDragRuleUtil.checkDragRuleWithParamLegal(dragChainDTO); } // 设置uriMap信息 uriMap.put(finalUri, dragChainDTO); // 设置规则链参数信息 redisUtil.hset(CommonDragRuleConstant.DRAG_RULE_CHAIN_FLAG, finalUri, JSONObject.toJSONString(dragChainDTO)); } } } // 进行规则链失效信息校验,表中有规则数据,但是beanDefinitionList中已经删除规则链入口,需要清理 todo: 暂时注释,生产环境可以开启 /** if (ObjectUtil.isNotEmpty(dragChainDTOList)) { dragChainDTOList.forEach(dragChainDBDTO -> { // 表存在数据,bean中不存在,进行删除操作,同时需要判定为自己模块,否则其他模块启动会删除本模块已经落库的规则链信息 String modelName = applicationName.replace(StrUtil.DASHED, StrUtil.UNDERLINE).toUpperCase(); if (ObjectUtil.isEmpty(uriMap.get(dragChainDBDTO.getUri())) && modelName.equalsIgnoreCase(dragChainDBDTO.getDragChainModule())) { // 删除规则链以及产品规则链数据 commonDragRuleMapper.deleteDragChainByUri(dragChainDBDTO.getUri()); commonDragRuleMapper.deleteDragProductChainByUri(dragChainDBDTO.getUri()); // 删除缓存数据 redisUtil.hdel(CommonDragRuleConstant.DRAG_RULE_CHAIN_FLAG, dragChainDBDTO.getUri()); } }); } **/ // 释放锁信息 simpleLock.releaseLock(LockTypeEnum.DRAG_RULE_URI_LOCK, applicationName, lock); } } catch (Exception e) { throw new ServiceException(e.getMessage()); } finally { // 释放锁信息 simpleLock.releaseLock(LockTypeEnum.DRAG_RULE_URI_LOCK, applicationName, lock); } } }
-
CommonDragRuleUtil ,client工具类,主要进行规则链的规则是否合法校验,如果不合法则服务停止启动并且进行提示
package cn.git.common.util; import cn.git.common.drag.CommonDragRuleConstant; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.dto.DragRuleDTO; import cn.git.common.exception.ServiceException; import cn.git.redis.RedisUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * @description: 拖拽规则通用工具类 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-03-04 09:21:01 */ @Component public class CommonDragRuleUtil { @Autowired private RedisUtil redisUtil; /** * 入参规则链校验,其中必须有规则调用切入点(既调用controller方法uri) * dragRuleChain规则链,多个规则使用逗号分割 * @param dragChainDTO 入参规则链信息 */ public void checkDragRuleWithParamLegal(DragChainDTO dragChainDTO) { // 参数信息校验 if (ObjectUtil.isNull(dragChainDTO) || StrUtil.isBlank(dragChainDTO.getUri()) || StrUtil.isBlank(dragChainDTO.getDragRuleChain()) || StrUtil.isBlank(dragChainDTO.getMethodInParam())) { throw new ServiceException(StrUtil.format("拖拽规则[{}],方法[{}]入参参数校验失败,请确认!", dragChainDTO.getControllerDesc(), dragChainDTO.getMethodDesc())); } // 分割出详细规则链 final String[] dragRuleChainArray = dragChainDTO.getDragRuleChain().split("\\|"); // 并行规则校验优化 for (String dragRuleChain : dragRuleChainArray) { // 开始参数校验,入参参数需要满足调用规则参数校验,并且入参参数+规则返回参数(传递参数)需要满足规则链下一个规则的入参 List<String> dragRuleChainList = Arrays.asList(dragRuleChain.split(StrUtil.COMMA).clone()) .stream() .map(String::trim) .collect(Collectors.toList()); // 只有一个规则,直接校验入参是否满足规则链第一个规则入参即可 String errorMessage; if (dragRuleChainList.size() == 1) { String dragRuleId = dragRuleChainList.get(0); DragRuleDTO dragRuleDTO = getDragRuleDTO(dragRuleId); // 分别获取规则参数以及规则链入参进行比较,如果入参不满足规则链第一个规则入参,直接抛出异常 JSONObject dragRuleInParam = JSONObject.parseObject(dragRuleDTO.getInputParam()); JSONObject dragChainInParam = JSONObject.parseObject(dragChainDTO.getMethodInParam()); // 参数空值校验 if (ObjectUtil.isEmpty(dragRuleInParam)) { errorMessage = StrUtil.format("拖拽规则规则链[{}]对应规则方法[{}]获取规则入参参数为空,规则id为[{}]请确认!", dragChainDTO.getControllerName(), dragChainDTO.getMethodName(), dragRuleDTO.getDragRuleId()); throw new ServiceException(errorMessage); } if (ObjectUtil.isEmpty(dragChainInParam)) { errorMessage = StrUtil.format("拖拽规则规则链[{}]切入点[{}]入参参数为空,请确认!", dragChainDTO.getControllerName(), dragChainDTO.getMethodName()); throw new ServiceException(errorMessage); } // 参数包含校验,比较参数,切入点入参必须包含规则链第一个规则入参,不满足直接抛出异常 Set<String> dragRuleInParamKeySet = dragRuleInParam.keySet(); Set<String> dragChainInParamKeySet = dragChainInParam.keySet(); Set<String> diffKeySet = dragRuleInParamKeySet.stream() .filter(element -> !dragChainInParamKeySet.contains(element)) .collect(Collectors.toSet()); if (ObjectUtil.isNotEmpty(diffKeySet)) { // Set集合数据转换为字符串,逗号分割 String dragRuleDiffKeyStr = diffKeySet.stream() .map(String::valueOf) .collect(Collectors.joining(StrUtil.COMMA)); errorMessage = StrUtil.format("规则链[{}],第[1]个规则[{}],其入参参数不满足要求,\n缺少入参信息为[{}],请确认!", dragRuleChain, dragRuleDTO.getDragRuleId(), dragRuleDiffKeyStr); throw new ServiceException(errorMessage); } } else { // 空值校验 JSONObject dragChainInParam = JSONObject.parseObject(dragChainDTO.getMethodInParam()); if (ObjectUtil.isEmpty(dragChainInParam)) { errorMessage = StrUtil.format("规则链[{}]控制层方法切入点[{}]入参参数为空,请确认!", dragRuleChain, dragChainDTO.getControllerName() .concat(StrUtil.DOT) .concat(dragChainDTO.getMethodName())); throw new ServiceException(errorMessage); } // 入参结果集,第一次入参参数为规则链切入点的入参参数,之后循环规则链规则,为规则链返回传递参数+切入点入参参数,再与下一个规则入参进行对比 JSONObject resultInParam = dragChainInParam.clone(); for (int i = 0; i < dragRuleChainList.size(); i++) { // 通过规则id,获取规则入参信息 String dragRuleId = dragRuleChainList.get(i); DragRuleDTO dragRuleDTO = getDragRuleDTO(dragRuleId); JSONObject dragRuleInParam = JSONObject.parseObject(dragRuleDTO.getInputParam()); // 进行参数校验,如果入参不满足规则链循环规则入参,直接抛出异常 Set<String> dragRuleInParamKeySet = dragRuleInParam.keySet(); Set<String> dragChainInParamKeySet = resultInParam.keySet(); Set<String> diffKeySet = dragRuleInParamKeySet.stream() .filter(element -> !dragChainInParamKeySet.contains(element)) .collect(Collectors.toSet()); if (ObjectUtil.isNotEmpty(diffKeySet)) { // Set集合数据转换为字符串,逗号分割 String dragRuleDiffKeyStr = diffKeySet.stream() .map(String::valueOf) .collect(Collectors.joining(StrUtil.COMMA)); errorMessage = StrUtil.format("规则链[{}],表第[{}]个规则[{}],入参参数不满足要求,\n缺少入参信息为[{}]", dragRuleChain, i + 1, dragRuleDTO.getDragRuleId(), dragRuleDiffKeyStr); throw new ServiceException(errorMessage); } // 设置规则返回参数作为传递参数设定到切入点入参参数中,作为下一入参参数校验集合,传递参数为字符串,多个参数以逗号分割 if (StrUtil.isNotBlank(dragRuleDTO.getCrossParam())) { String[] crossParams = dragRuleDTO.getCrossParam().split(StrUtil.COMMA); for (int j = 0; j < crossParams.length; j++) { resultInParam.put(crossParams[j], StrUtil.EMPTY); } } } } } } /** * 通过规则id获取规则信息 * @param dragRuleId */ public DragRuleDTO getDragRuleDTO(String dragRuleId) { Object dragRuleObj = redisUtil.hget(CommonDragRuleConstant.DRAG_RULES, dragRuleId); if (ObjectUtil.isNotNull(dragRuleObj)) { return JSONObject.parseObject(StrUtil.toString(dragRuleObj), DragRuleDTO.class); } throw new ServiceException(StrUtil.format("拖拽规则通过缓存获取规则Id[{}]不存在,请确认规则服务成功启动!", dragRuleId)); } /** * 检查当前服务是否需要进行加载规则链 * @param serverName 服务名称 * @return */ public boolean checkIfLoadDragChain(String serverName) { // 空值校验 if (StrUtil.isBlank(serverName)) { return false; } // 设定加载的模块名称 boolean ifLoad = serverName.equalsIgnoreCase("account-server") || serverName.equalsIgnoreCase("afterloan-server") || serverName.equalsIgnoreCase("collateral-server") || serverName.equalsIgnoreCase("credit-server") || serverName.equalsIgnoreCase("loan-server") || serverName.equalsIgnoreCase("management-server") || serverName.equalsIgnoreCase("rat-server") || serverName.equalsIgnoreCase("store-server") || serverName.equalsIgnoreCase("warning-server") || serverName.equalsIgnoreCase("product-server"); return ifLoad; } }
-
规则链mapper类,进行规则链的列表查询
package cn.git.common.drag.mapper; import cn.git.common.drag.dto.DragChainDTO; import org.apache.ibatis.annotations.Select; import java.util.List; /** * @description: 拖拽规则通用mapper * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-29 02:44:11 */ public interface CommonDragRuleMapper { /** * 获取拖拽规则链列表信息 * @return */ @Select("SELECT * FROM MANAGE.TB_SYS_DRAG_CHAIN") List<DragChainDTO> getDragChainList(); }
-
DTO参数类,拖拽规则链DTO以及拖拽规则详情DTO
package cn.git.common.drag.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * @description: 拖拽规则链表 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-20 03:32:00 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "拖拽规则链表信息", description = "拖拽规则链表信息") public class DragChainDTO { /** * 规则链id */ @ApiModelProperty("规则链id,由controllerName + methodName组合而成,eg: DragRuleTestController.test0001") private String dragRuleChainId; /** * 产品cd */ @ApiModelProperty("产品cd") private String productCd; /** * 产品规则链id */ @ApiModelProperty("产品规则链id") private String productChainId; /** * 切入点controller描述信息 */ @ApiModelProperty("切入点controller描述信息") private String controllerDesc; /** * 切入点controller名称 */ @ApiModelProperty("切入点controller名称") private String controllerName; /** * 规则链调用方法描述 */ @ApiModelProperty("规则链调用方法描述") private String methodDesc; /** * 规则链调用方法名称 */ @ApiModelProperty("规则链调用方法名称") private String methodName; /** * 方法入参参数 */ @ApiModelProperty("方法入参参数") private String methodInParam; /** * 作用方法地址,其实为controller对应method地址 * 具体格式为: server-name:uri */ @ApiModelProperty("作用方法地址,其实为controller对应method地址,server-name:uri") private String uri; /** * 规则链详情,多个规则按序逗号分割 */ @ApiModelProperty("规则链详情,多个规则按序逗号分割") private String dragRuleChain; /** * 产品规则链详情,多个规则按序逗号分割 */ @ApiModelProperty("规则链详情,多个规则按序逗号分割") private String dragProductRuleChain; /** * 删除标识 */ @ApiModelProperty("删除标识,非展示字段") private String isDel; /** * 模块信息 */ @ApiModelProperty("模块信息 eg: ACCOUNT_SERVER") private String dragChainModule; /** * 规则是否已经保存落库,如果没有落库,规则则展示灰色,如果落库,则展示正常颜色 */ @ApiModelProperty("规则链是否已经保存落库,如果没有落库,规则则展示灰色,如果落库,则展示正常颜色") private String ifLoadDB; /** * 拖拽规则inVO描述信息 */ @ApiModelProperty("拖拽规则inVO描述信息") private List<DragRuleInVOParamsDesc> inVOParamsDescList; /** * @description: 规则入参描述信息 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-03-05 10:43:51 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "规则入参描述信息", description = "规则入参描述信息") public static class DragRuleInVOParamsDesc { /** * 描述信息 */ @ApiModelProperty("描述信息,属性值中文含义") private String inParamDesc; /** * 参数名称 */ @ApiModelProperty("属性名称") private String inParamName; /** * 参数类型 */ @ApiModelProperty("参数类型") private String inParamType; } }
package cn.git.common.drag.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * @description: 拖拽规则详情DTO * @program: bank-credit-sy * @author: lixuchun * @create: 2024-03-01 02:07:06 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "拖拽规则详情信息", description = "拖拽规则详情信息") public class DragRuleDTO { /** * 规则id,eg: LOANRule0001 */ @ApiModelProperty("规则id,eg: LOANRule0001") private String dragRuleId; /** * 规则描述 */ @ApiModelProperty("规则描述") private String ruleDesc; /** * 传递参数,多个以逗号分割 */ @ApiModelProperty("传递参数,多个以逗号分割,同crossParamList") private String crossParam; /** * 传递参数list */ @ApiModelProperty("传递参数list") private List<String> crossParamList; /** * 拖拽规则方法入参 */ @ApiModelProperty("拖拽规则方法入参") private String inputParam; /** * 拖拽规则入参描述信息 */ @ApiModelProperty("拖拽规则入参描述信息") private List<DragRuleDTO.DragRuleParamsDesc> inputParamDescList; /** * 规则自定义入参描述信息 */ @ApiModelProperty("规则自定义入参描述信息") private List<DragRuleDTO.DragRuleParamsDesc> inputCustomParamDescList; /** * 拖拽规则方法出参 */ @ApiModelProperty("拖拽规则方法出参") private String outputParam; /** * 拖拽规则出参描述信息 */ @ApiModelProperty("拖拽规则出参描述信息") private List<DragRuleDTO.DragRuleParamsDesc> outputParamDescList; /** * 拖拽规则传递参数描述信息 */ @ApiModelProperty("拖拽规则传递参数描述信息") private List<DragRuleDTO.DragRuleParamsDesc> crossParamDescList; /** * 自定义参数比较规则信息,为jsonArray格式 */ @ApiModelProperty("自定义参数比较规则信息,多个用逗号隔开,同customOptionParamList") private String customOptionParam; /** * 删除标识 */ @ApiModelProperty("删除标识,非展示字段") private String isDel; /** * 规则是否已经保存落库,如果没有落库,规则则展示灰色,如果落库,则展示正常颜色 */ @ApiModelProperty("规则是否已经保存落库,如果没有落库,规则则展示灰色,如果落库,则展示正常颜色") private String ifLoadDB; /** * 自定义参数比较规则信息列表 */ @ApiModelProperty("自定义参数比较规则信息列表") private List<List<CustomOptionParam>> customOptionParamList; /** * @description: 自定义参数比较规则信息 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-03-05 11:26:44 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "自定义参数比较规则信息", description = "自定义参数比较规则信息") public static class CustomOptionParam { @ApiModelProperty("自定义操作类型: 0-操作标识符, 1-outVO参数信息, 2-自定义值") private String optionType; @ApiModelProperty("参数类型 eg: BigDecimal, Date, Long...") private String paramType; @ApiModelProperty("参数描述,eg: 合同金额,如果为操作标识,则设定 ''") private String paramDesc; @ApiModelProperty("参数名称,eg: contractAmt") private String paramName; @ApiModelProperty("码值codeTypeCd: eg: YesornoInd") private String sysCode; @ApiModelProperty("自定义值时候给送,其余不送") private String value; } /** * @description: 拖拽规则参数描述信息 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-03-05 10:43:51 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "拖拽规则参数描述信息", description = "拖拽规则参数描述信息") public static class DragRuleParamsDesc { /** * 描述信息 */ @ApiModelProperty("描述信息,属性值中文含义") private String paramDesc; /** * 参数名称 */ @ApiModelProperty("属性名称") private String paramName; /** * 参数类型 */ @ApiModelProperty("参数类型") private String paramType; /** * 是否规则自定义入参参数 * true是 false 否 */ @ApiModelProperty("是否规则自定义入参参数") private boolean ifCustomDefine; /** * 此字段码值信息 */ @ApiModelProperty("码值信息") private String sysCode; /** * 自定义入参参数值信息 */ @ApiModelProperty("自定义入参参数值信息") private String customDefineParamValue; } }
-
client客户端常量类
package cn.git.common.drag; /** * @description: 拖拽规则通用常量类 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-29 02:58:34 */ public class CommonDragRuleConstant { /** * 规则链标识 * 存储结构 redis hash hashKey customKey customValue * eg: DRAG_RULE_CHAIN server-name:uri -> {methodParams,controllerName,controllerDesc,methodDesc,methodName} */ public static final String DRAG_RULE_CHAIN_FLAG = "DRAG_RULE_CHAIN"; /** * 产品规则链标识 * 存储结构 redis hash hashKey customKey customValue * eg: DRAG_RULE_PRODUCT_CHAIN server-name:uri:productCd -> {uri,productCd,productDragChain} */ public static final String DRAG_RULE_PRODUCT_CHAIN_FLAG = "DRAG_RULE_PRODUCT_CHAIN"; /** * 产品编码标识 */ public static final String PRODUCT_CD = "productCd"; /** * 拖拽规则redis hash 结构 名称 */ public static final String DRAG_RULES = "DRAG_RULES"; /** * 规则链 */ public static final String DRAG_RULE_CHAIN = "dragRuleChain"; /** * 存入拖拽规则入参参数 */ public static final String DRAG_RULE_CHAIN_DTO = "dragChainDTO"; /** * 方法参数信息 */ public static final String METHOD_PARAMS = "methodParams"; /** * controller名称 */ public static final String CONTROLLER_NAME = "controllerName"; /** * controller描述 */ public static final String CONTROLLER_DESC = "controllerDesc"; /** * 方法描述 */ public static final String METHOD_DESC = "methodDesc"; /** * 方法名称 */ public static final String METHOD_NAME = "methodName"; /** * URI */ public static final String CONTROLLER_URI = "URI"; /** * 数字类型标识 */ public static final String NUM_STR_0 = "0"; public static final String NUM_STR_1 = "1"; /** * 下载文件类型XML */ public static final String APPLICATION_XML = "application/xml"; /** * 描述信息标识 */ public static final String CONTENT_DISPOSITION = "Content-Disposition"; }
-
DragRuleAspect 客户端切面类具体实现
封装GET,POST请求参数,将参数统一转换为JSON格式,发送到规则模块处理package cn.git.rules.aspect; import cn.git.common.constant.CacheManageConstant; import cn.git.common.constant.HeaderConstant; import cn.git.common.drag.CommonDragRuleConstant; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.exception.ServiceException; import cn.git.common.result.Result; import cn.git.common.result.ResultStatusEnum; import cn.git.redis.RedisUtil; import cn.git.rules.constant.RuleConstant; import cn.git.rules.dto.BaseAspectDTO; import cn.git.rules.feign.DragRuleFeignApi; import cn.git.rules.util.BaseAspectUtil; import cn.git.rules.util.RuleUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 通用规则调用切面方法 * @program: bank-credit-sy * @author: lixuchun * @create: 2022-04-01 */ @Slf4j @Component @Aspect @Order(value = RuleConstant.ASPECT_BEFORE_ORDER) public class DragRuleAspect { @Value("${spring.application.name}") private String applicationName; @Autowired private RedisUtil redisUtil; @Autowired private RuleUtil ruleUtil; @Autowired private ApplicationContext context; @Autowired private BaseAspectUtil baseAspectUtil; @Autowired private HttpServletRequest httpServletRequest; /** * 规则引擎提前通知切面 * @param joinPoint 切面参数 */ @Before("@annotation(cn.git.common.annotation.DragRule)") public void doBeforeRuleCheck(JoinPoint joinPoint) throws Throwable { // 通过postMapping或者getMapping标签获取请求uri,对应uri为不带参数对应uri ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 获取正确的请径地址求路信息 BaseAspectDTO baseAspectDTO = baseAspectUtil.getEffectiveURI(method, request); // 完整请求路径,包含传递参数信息 String uri = baseAspectDTO.getUri(); // 带参数url请求地址 String requestWithParamUri = baseAspectDTO.getRequestWithParamUri(); // 设置最终路径信息,模块名:uri String finalUri = applicationName .replace(StrUtil.DASHED, StrUtil.UNDERLINE) .toUpperCase() .concat(StrUtil.COLON) .concat(uri); // 获取全链路sign信息 String sign = null; String token = null; if (ObjectUtil.isNotNull(httpServletRequest)) { sign = httpServletRequest.getHeader(HeaderConstant.HEADER_SIGN); token = httpServletRequest.getHeader(HeaderConstant.HEADER_TOKEN); } // 如果redis中含有对应规则 if (redisUtil.hHasKey(CommonDragRuleConstant.DRAG_RULE_CHAIN_FLAG, finalUri)) { // 校验无论post或者get请求都必填 if (ObjectUtil.isEmpty(joinPoint.getArgs())) { throw new ServiceException("规则校验对应 controller 方法参数必须有请求参数!"); } // 获取缓存信息中的拖拽规则缓存链信息 DragChainDTO dragChainDTO = JSONObject.parseObject(redisUtil.hget(CommonDragRuleConstant.DRAG_RULE_CHAIN_FLAG, finalUri).toString(), DragChainDTO.class); String cacheChainStr = dragChainDTO.getDragRuleChain(); if (StrUtil.isNotBlank(cacheChainStr)) { // 获取请求参数并且转换位jsonObject格式 Object argsObject = null; if (joinPoint.getArgs().length > 1) { // 定义为请求url中带有参数,进行参数匹配,重新封装为key,value类型数据 List<String> uriParamList = baseAspectUtil.getPathParamConfig(requestWithParamUri); Map<String, Object> paramMap = new HashMap<>(RuleConstant.DEFAULT_MAP_SIZE); for (int i = 0; i < uriParamList.size(); i++) { paramMap.put(uriParamList.get(i), joinPoint.getArgs()[i]); } argsObject = paramMap; } else if (joinPoint.getArgs().length == 1) { argsObject = joinPoint.getArgs()[0]; // 如果为简单类型,必须设定为key value 对应json数据格式 if (argsObject instanceof String || argsObject instanceof BigDecimal || argsObject instanceof Integer || argsObject instanceof Date || argsObject instanceof Long || argsObject instanceof Double) { List<String> uriParamList = baseAspectUtil.getPathParamConfig(requestWithParamUri); Map<String, Object> paramMap = new HashMap<>(RuleConstant.NUM_1); paramMap.put(uriParamList.get(RuleConstant.NUM_0), joinPoint.getArgs()[0]); argsObject = paramMap; } } // argsObject 转换为json并且去除 null 值 JSONObject jsonParam = JSONObject.parseObject(JSONObject.toJSONString(argsObject)); // 获取产品信息校验,缓存的key为 uri + ":" + productCd,如果当前产品配置规则链,则使用产品配置规则链信息,否则使用默认规则链 String productCd = jsonParam.getString(CommonDragRuleConstant.PRODUCT_CD); // 从整理号的请求参数中获取产品cd,如果有则查看当前产品是否配置产品规则链 if (StrUtil.isNotBlank(productCd)) { String cacheKey = dragChainDTO.getUri().concat(StrUtil.COLON).concat(productCd); Object productRuleObj = redisUtil.hget(CommonDragRuleConstant.DRAG_RULE_PRODUCT_CHAIN_FLAG, cacheKey); if (ObjectUtil.isNotNull(productRuleObj)) { // 获取产品规则链 JSONObject productJson = JSONObject.parseObject(productRuleObj.toString()); String productChain = productJson.getString(CommonDragRuleConstant.DRAG_RULE_CHAIN); if (StrUtil.isNotBlank(productChain)) { // 替换默认规则链到产品规则链 dragChainDTO.setDragRuleChain(productChain); } } } jsonParam.put(CommonDragRuleConstant.DRAG_RULE_CHAIN_DTO, JSONObject.toJSONString(dragChainDTO)); // 错误前缀 String ruleErrorPrefix = "拖拽规则校验提示: "; // 调用规则模块,进行规则验证 DragRuleFeignApi dragRuleFeignApi = context.getBean(DragRuleFeignApi.class); // 设置X_sign log.info("拖拽规则DragRuleAspect获取x_sign:{},token信息为[{}]", sign, token); if (StrUtil.isNotBlank(sign)) { jsonParam.put("dragSign", sign); } if (StrUtil.isNotBlank(token)) { jsonParam.put("dragToken", token); } // 调用远程方法 Result rspRuleResult = dragRuleFeignApi.checkDragRule(jsonParam); if (ObjectUtil.isNotEmpty(rspRuleResult) && rspRuleResult.getCode() == ResultStatusEnum.ERROR.getCode()) { throw new ServiceException(ruleErrorPrefix.concat(rspRuleResult.getMsg())); } } } } }
-
公共dragFeign接口
package cn.git.rules.feign; import cn.git.common.result.Result; import com.alibaba.fastjson.JSONObject; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * @description: * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 02:20:31 */ @FeignClient(value = "rule-manage-server", fallbackFactory = RuleFeignApiFallCallBackFactory.class) @RequestMapping("/rule/feign") public interface DragRuleFeignApi { /** * 校验拖拽规则feign方法 * * @param dragRuleJsonParam 请求对象 * @return Result 校验结果 */ @PostMapping("/drag/rule") Result checkDragRule(@RequestBody JSONObject dragRuleJsonParam); }
4.2 拖拽规则服务端
服务端主要进行提供规则链规则的管理,以及子模块进行规则逻辑执行调用Feign接口的具体实现逻辑。服务端的代码pom为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>credit-rule-manage</artifactId>
<groupId>cn.git</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rule-manage-server</artifactId>
<description>规则引擎服务端</description>
<dependencies>
<dependency>
<groupId>cn.git</groupId>
<artifactId>business-common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</exclusion>
<exclusion>
<groupId>cn.git</groupId>
<artifactId>database-synchronize-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>credit-log-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>credit-swagger-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>rule-manage-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 规则引擎jar包 -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.6.0.Final</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>credit-redis-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>credit-discovery-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 监听nacos配置信息变更 -->
<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>dynamic-config-spring-boot-starter</artifactId>
<version>0.1.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>credit-monitor-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-log4j-2.x</artifactId>
<version>8.6.0</version>
</dependency>
<!-- 影像平台使用jar -->
<dependency>
<groupId>cn.git</groupId>
<artifactId>clientQuery</artifactId>
<version>v1.0.0</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>SunECMClient</artifactId>
<version>v3.1.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>sun-ws-all-v2.0</artifactId>
<version>v2.0</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>sun-ws-all-v3.1.5</artifactId>
<version>v3.1.5</version>
</dependency>
<!-- app影像上传使用 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>dom4j</artifactId>
<version>v1.6.1</version>
</dependency>
<dependency>
<groupId>cn.git</groupId>
<artifactId>converter-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--app影像上传使用 -->
<!-- 拖拽规则使用 -->
<dependency>
<groupId>org.camunda.bpm.model</groupId>
<artifactId>camunda-bpmn-model</artifactId>
<version>7.19.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- package -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其余对应具体的主要实现代码如下所示
4.2.1 规则链管理相关类
-
DragRuleController,拖拽规则管理controller,主要管理增删改查入口类
package cn.git.rules.controller; import cn.git.common.annotation.TableCode; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.dto.DragRuleDTO; import cn.git.common.exception.ServiceException; import cn.git.common.idempotent.ApiIdempotent; import cn.git.common.page.PageBean; import cn.git.common.result.Result; import cn.git.rules.drag.enums.DragRuleSequenceEnum; import cn.git.rules.dto.drag.DragRuleChainAllParamDTO; import cn.git.rules.mapstruct.DragRuleConvert; import cn.git.rules.service.drag.DragRuleService; import cn.git.rules.vo.drag.*; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * @description: 拖拽规则controller * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-23 04:46:26 */ @RestController @RequestMapping("/rule") @Api(tags = "拖拽规则服务controller", value = "拖拽规则服务controller") public class DragRuleController { @Autowired private DragRuleConvert dragRuleConvert; @Autowired private DragRuleService dragRuleService; /** * 添加拖拽规则 * @param dragRuleInVO * @return */ @ApiOperation(value = "添加拖拽规则", notes = "添加拖拽规则") @PostMapping(value = "/drag/add") public Result addDragRule( @ApiParam(name = "dragRuleAddInVO", value = "添加拖拽规则inVO", required = true) @Valid @RequestBody DragRuleInVO dragRuleInVO) { // 获取传递参数 DragRuleDTO dragRuleDTO = dragRuleConvert.dragRuleInVOToDTO(dragRuleInVO); // 进行自定义参数信息赋值 if (ObjectUtil.isNotEmpty(dragRuleInVO.getCustomOptionParamList())) { List<List<DragRuleDTO.CustomOptionParam>> dtoCustomParamList = new ArrayList<>(); dragRuleInVO.getCustomOptionParamList().forEach(inVOParamList -> { // 空值判定 if (ObjectUtil.isEmpty(inVOParamList)) { throw new ServiceException("保存自定义参数值为空,请正确设置自定义参数!"); } // 设置dto自定义参数列表信息 List<DragRuleDTO.CustomOptionParam> dtoParamList = new ArrayList<>(); inVOParamList.forEach(inVOParam -> { DragRuleDTO.CustomOptionParam dtoParam = dragRuleConvert.inVOCustomOptionParamToDTO(inVOParam); dtoParamList.add(dtoParam); }); dtoCustomParamList.add(dtoParamList); }); dragRuleDTO.setCustomOptionParamList(dtoCustomParamList); } dragRuleService.addDragRule(dragRuleDTO); return Result.ok("添加拖拽规则成功!", null); } /** * 查询单条拖拽规则 * @param id * @return */ @ApiOperation(value = "查询单条拖拽规则", notes = "查询单条拖拽规则") @GetMapping(value = "/drag/{id}") public Result<DragRuleOutVO> findDragRule(@PathVariable("id") String id) { // 获取单条拖拽规则,并且转换为outVO DragRuleDTO dragRuleDTO = dragRuleService.findById(id); DragRuleOutVO outVO = dragRuleConvert.dragRuleDTOToDragRuleOutVO(dragRuleDTO); // dto 自定义参数信息转换为 outVO自定义信息参数 if (ObjectUtil.isNotEmpty(dragRuleDTO.getCustomOptionParamList())) { List<List<DragRuleOutVO.CustomOptionParam>> outVOParamsList = new ArrayList<>(); dragRuleDTO.getCustomOptionParamList().forEach(dtoParamList -> { List<DragRuleOutVO.CustomOptionParam> outVOParamList = new ArrayList<>(); dtoParamList.forEach(dtoParam -> { DragRuleOutVO.CustomOptionParam outVOParam = dragRuleConvert.dtoCustomOptionParamToDTO(dtoParam); outVOParamList.add(outVOParam); }); outVOParamsList.add(outVOParamList); }); outVO.setCustomOptionParamList(outVOParamsList); } return Result.ok(outVO); } /** * 修改拖拽规则 * @param dragRuleInVO * @return */ @ApiOperation(value = "修改拖拽规则", notes = "修改拖拽规则") @PostMapping(value = "/drag/update") public Result updateDragRule( @ApiParam(name = "dragRuleInVO", value = "修改拖拽规则inVO", required = true) @Valid @RequestBody DragRuleInVO dragRuleInVO) { // 获取传递参数 DragRuleDTO dragRuleDTO = dragRuleConvert.dragRuleInVOToDTO(dragRuleInVO); // 进行自定义参数信息赋值 if (ObjectUtil.isNotEmpty(dragRuleInVO.getCustomOptionParamList())) { List<List<DragRuleDTO.CustomOptionParam>> dtoCustomParamList = new ArrayList<>(); dragRuleInVO.getCustomOptionParamList().forEach(inVOParamList -> { // 空值判定 if (ObjectUtil.isEmpty(inVOParamList)) { throw new ServiceException("修改自定义参数值为空,请正确设置自定义参数!"); } // 设置dto自定义参数列表信息 List<DragRuleDTO.CustomOptionParam> dtoParamList = new ArrayList<>(); inVOParamList.forEach(inVOParam -> { DragRuleDTO.CustomOptionParam dtoParam = dragRuleConvert.inVOCustomOptionParamToDTO(inVOParam); dtoParamList.add(dtoParam); }); dtoCustomParamList.add(dtoParamList); }); dragRuleDTO.setCustomOptionParamList(dtoCustomParamList); } dragRuleService.updateDragRule(dragRuleDTO); return Result.ok("修改成功!", null); } /** * 删除拖拽规则 * @param id * @return */ @ApiOperation(value = "删除拖拽规则", notes = "删除拖拽规则") @PostMapping(value = "/drag/delete/{id}") public Result deleteDragById( @ApiParam(name = "id", value = "删除拖拽规则id", required = true) @PathVariable("id") String id) { dragRuleService.deleteDragRule(id); return Result.ok("删除成功!", null); } /** * 优化并行规则链添加 * @param chainAddInVO * @return */ @ApiIdempotent @ApiOperation(value = "优化并行规则链添加", notes = "优化并行规则链添加") @PostMapping(value = "/drag/chain/add") public Result<String> addDragChain(@Valid DragGateChainAddInVO chainAddInVO) throws IOException { // 参数非空校验 if (ObjectUtil.isNotNull(chainAddInVO.getFile()) && chainAddInVO.getFile().isEmpty()) { throw new ServiceException(StrUtil.format("传入文件为空,请确认!")); } // 开始调用添加规则链信息 dragRuleService.addGatewayChain(chainAddInVO.getFile(), chainAddInVO.getUri(), chainAddInVO.getProductCd()); return Result.ok("添加拖拽规则链成功!", null); } /** * 修改规则链 * @param chainAddInVO 参数inVO * @return */ @ApiOperation(value = "修改规则链", notes = "修改规则链") @PostMapping(value = "/drag/chain/update") public Result updateGatewayDragChain(@Valid DragGateChainAddInVO chainAddInVO) throws IOException { // 参数非空校验 if (ObjectUtil.isNotNull(chainAddInVO.getFile()) && chainAddInVO.getFile().isEmpty()) { throw new ServiceException(StrUtil.format("传入文件为空,请确认!")); } // 获取传递参数 dragRuleService.updateGatewayChain(chainAddInVO.getFile(), chainAddInVO.getUri(), chainAddInVO.getProductCd()); return Result.ok("修改规则链成功!", null); } /** * 拖拽规则链删除 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "删除拖拽规则", notes = "删除拖拽规则") @PostMapping(value = "/drag/chain/delete") public Result deleteChainById( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) { dragRuleService.deleteChainByUri(dragRuleChainOpInVO.getUri()); return Result.ok("删除拖拽规则成功!", null); } /** * 拖拽产品规则链单个删除 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "拖拽产品规则链单个删除", notes = "拖拽产品规则链单个删除") @PostMapping(value = "/drag/product/chain/delete") public Result deleteProductChainByURIAndProductCd( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) { // 空值校验 if (StrUtil.isBlank(dragRuleChainOpInVO.getProductCd())) { throw new ServiceException("规则链对应产品cd不能为空!"); } dragRuleService.deleteProductChainByUriAndProductCd(dragRuleChainOpInVO.getUri(), dragRuleChainOpInVO.getProductCd()); return Result.ok("产品删除拖拽规则成功!", null); } /** * 拖拽产品规则链集合删除 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "拖拽产品规则链集合删除", notes = "拖拽产品规则链集合删除") @PostMapping(value = "/drag/product/chain/uri/delete") public Result deleteProductChainByURI( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) { // 空值校验 if (StrUtil.isNotBlank(dragRuleChainOpInVO.getProductCd())) { throw new ServiceException("拖拽产品规则链集合删除,产品cd必须为空!"); } dragRuleService.deleteProductChainByUri(dragRuleChainOpInVO.getUri()); return Result.ok("产品删除拖拽规则成功!", null); } /** * 规则列表获取 * @param dragRulePageInVO * @return */ @ApiOperation(value = "拖拽规则分页查询", notes = "拖拽规则分页查询") @PostMapping(value = "/drag/page") @TableCode(keyNameMapping = "ifLoadDB=YesornoInd") public Result<DragRulePageOutVO> dragPage( @ApiParam(name = "dragRuleInVO", value = "拖拽规则分页查询inVO", required = true) @Valid @RequestBody DragRulePageInVO dragRulePageInVO) { // 获取传递参数 DragRuleDTO dragRuleDTO = dragRuleConvert.dragRulePageInVOToDTO(dragRulePageInVO); PageBean<DragRuleDTO> pageBean = dragRuleService.getDragRulePage(dragRuleDTO); DragRulePageOutVO pageOutVO = new DragRulePageOutVO(); pageOutVO.setDragRuleDTOPageBean(pageBean); return Result.ok(pageOutVO); } /** * 单个规则链获取 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "单个规则链获取", notes = "单个规则链获取") @PostMapping(value = "/drag/chain") public Result<DragRuleChainOutVO> getDragRuleChainByUri( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) { // 获取单条规则链 DragChainDTO dragChainDTO = dragRuleService.getDragChain(dragRuleChainOpInVO.getUri(), dragRuleChainOpInVO.getProductCd()); DragRuleChainOutVO chainOutVO = dragRuleConvert.dragChainDTOtoOutVO(dragChainDTO); return Result.ok(chainOutVO); } /** * 下载文件 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "单个规则链文件获取", notes = "单个规则链文件获取") @PostMapping(value = "/drag/chain/file") public void downloadFile( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) throws IOException { // 获取文件 dragRuleService.downloadFile(dragRuleChainOpInVO.getUri(), dragRuleChainOpInVO.getProductCd()); } /** * 获取文件xml信息 * @param dragRuleChainOpInVO * @return */ @ApiOperation(value = "获取文件xml信息", notes = "获取文件xml信息") @PostMapping(value = "/drag/chain/xml") public Result<String> getXMLStr( @ApiParam(name = "dragRuleChainOpInVO", value = "拖拽规则链查询删除操作inVO", required = true) @Valid @RequestBody DragRuleChainOpInVO dragRuleChainOpInVO) throws IOException { // 获取文件 String xmlStr = dragRuleService.getXMLStr(dragRuleChainOpInVO.getUri(), dragRuleChainOpInVO.getProductCd()); return Result.ok(xmlStr); } /** * 添加产品拖拽规则链初始方法 * @param module 模块 * @param methodPath 请求路径 * @return */ @ApiOperation(value = "添加产品拖拽规则链初始方法", notes = "添加产品拖拽规则链初始方法") @PostMapping(value = "/drag/chain/product/add/init") public Result addDragProductChainInit( @ApiParam(name = "productCd", value = "产品cd", required = true) @RequestParam(value = "productCd") String productCd, @ApiParam(name = "module", value = "模块uri用 : 分割前缀 ge: ACCOUNT_SERVER", required = true) @RequestParam(value = "module") String module, @ApiParam(name = "methodPath", value = "uri除去模块后缀,将请求地址中的 '/' 更换为 '_',eg: _account_drag_test0002", required = true) @RequestParam(value = "methodPath") String methodPath) throws IOException { // 组成uri地址 String uri = module.concat(StrUtil.COLON).concat(methodPath.replaceAll(StrUtil.UNDERLINE, StrUtil.SLASH)); // 获取传递参数 dragRuleService.addProductChainInit(uri, productCd); return Result.ok("初始化校验通过!", null); } /** * 规则链列表获取 * @param dragRuleChainInVO * @return */ @ApiOperation(value = "规则链列表获取", notes = "规则链列表获取") @PostMapping(value = "/drag/chain/page") @TableCode(keyNameMapping = "ifLoadDB=YesornoInd") public Result<DragRuleChainPageOutVO> dragChainPage( @ApiParam(name = "DragRuleChainPageInVO", value = "规则链分页操作inVO", required = true) @Valid @RequestBody DragRuleChainPageInVO dragRuleChainInVO) { // 获取传递参数 DragChainDTO dragChainDTO = dragRuleConvert.dragRulePageInVOtoDTO(dragRuleChainInVO); PageBean<DragChainDTO> pageBean = dragRuleService.getDragChainPage(dragChainDTO); // 回参参数 DragRuleChainPageOutVO chainPageOutVO = new DragRuleChainPageOutVO(); chainPageOutVO.setPageBean(pageBean); return Result.ok(chainPageOutVO); } /** * 产品规则链列表获取 * @param pageInVO * @return */ @ApiOperation(value = "产品规则链列表获取", notes = "产品规则链列表获取") @PostMapping(value = "/drag/product/chain/page") public Result<DragRuleChainPageOutVO> dragProductChainPage( @ApiParam(name = "DragRuleProductPageInVO", value = "产品规则链列表获取", required = true) @Valid @RequestBody DragRuleProductPageInVO pageInVO) { // 产品cd非空校验 if (StrUtil.isBlank(pageInVO.getProductCd())) { throw new ServiceException("产品cd不能为空!"); } // 获取传递参数,转换为DTO DragChainDTO dragChainDTO = dragRuleConvert.dragRuleProductPageInVOtoDTO(pageInVO); // 开始获取分页列表细信息 PageBean<DragChainDTO> pageBean = dragRuleService.getDragProductChainPage(dragChainDTO); // 回参参数 DragRuleChainPageOutVO chainPageOutVO = new DragRuleChainPageOutVO(); chainPageOutVO.setPageBean(pageBean); return Result.ok(chainPageOutVO); } /** * 通过文件以及节点校验规则链是否合法 * @param dragChainFileCheckInVO * @return */ @ApiOperation(value = "通过文件以及节点校验规则链是否合法", notes = "通过文件以及节点校验规则链是否合法") @PostMapping(value = "/drag/chain/params/gateway") public Result<DragRuleChainAllParamDescOutVO> checkDragRuleChainByFile(@Valid DragChainFileCheckInVO dragChainFileCheckInVO) { // 参数非空校验 if (ObjectUtil.isNotNull(dragChainFileCheckInVO.getFile()) && dragChainFileCheckInVO.getFile().isEmpty()) { throw new ServiceException(StrUtil.format("传入文件为空,请确认!")); } // 参数转换 DragRuleChainAllParamDTO allParamDTO = new DragRuleChainAllParamDTO(); allParamDTO.setChooseDragRuleId(dragChainFileCheckInVO.getChooseDragRuleId()); allParamDTO.setFile(dragChainFileCheckInVO.getFile()); allParamDTO.setUri(dragChainFileCheckInVO.getUri()); allParamDTO.setFileNodeId(dragChainFileCheckInVO.getFileNodeId()); // 校验规则文件是否合法 allParamDTO = dragRuleService.checkDragRuleChainByFile(allParamDTO); DragRuleChainAllParamDescOutVO allParamDescOutVO = dragRuleConvert.dragRuleChainAllParamDescToOutVO(allParamDTO); return Result.ok(allParamDescOutVO); } /** * 获取规则链名称 * @param dragRuleSequenceEnum * @return */ @ApiOperation(value = "开发人员使用获取拖拽规则规则名称", notes = "开发人员使用获取拖拽规则规则名称") @GetMapping(value = "/drag/sequence/name") public Result<String> getDragSequenceName(@RequestParam DragRuleSequenceEnum dragRuleSequenceEnum) { // 通过枚举获取各模块规则信息 String dragRuleName = dragRuleService.getDragSequenceName(dragRuleSequenceEnum); return Result.ok(dragRuleName); } }
-
dragRuleService以及对应的serviceImpl实现类
package cn.git.rules.service.drag; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.dto.DragRuleDTO; import cn.git.common.page.PageBean; import cn.git.rules.drag.enums.DragRuleSequenceEnum; import cn.git.rules.dto.drag.DragRuleChainAllParamDTO; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; /** * @description: 拖拽规则service * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 11:55:33 */ public interface DragRuleService { /** * 添加拖拽规则方法 * @param dragRuleDTO */ void addDragRule(DragRuleDTO dragRuleDTO); /** * 通过id查询拖拽规则 * @param dragRuleId * @return */ DragRuleDTO findById(String dragRuleId); /** * 修改拖拽规则 * @param dragRuleDTO */ void updateDragRule(DragRuleDTO dragRuleDTO); /** * 删除拖拽规则 * @param id */ void deleteDragRule(String id); /** * 分页查询拖拽规则信息 * 参数有规则描述信息,模糊查询 * 参数有规则id信息,精确查询 * 查询结果由两部分拼接而成,注意:只有表数据中有数据才能进行规则链添加 * 1:通过缓存信息,可以获取规则入参,出参等信息 * 2:通过表数据,可以获取传递参数,自定义比较参数等 * * @param dragRuleDTO * @return */ PageBean<DragRuleDTO> getDragRulePage(DragRuleDTO dragRuleDTO); /** * 添加并行规则链信息 * @param file * @param uri * @param productCd */ void addGatewayChain(MultipartFile file, String uri, String productCd) throws IOException; /** * 添加产品链信息初始化校验 * @param uri uri * @param productCd 产品cd */ void addProductChainInit(String uri, String productCd) throws IOException; /** * 修改并行规则链信息 * @param file 文件 * @param uri uri */ void updateGatewayChain(MultipartFile file, String uri, String productCd) throws IOException; /** * 通过uri删除链信息 * @param uri * @return */ void deleteChainByUri(String uri); /** * 通过uri以及产品cd删除产品链信息 * @param uri * @return */ void deleteProductChainByUriAndProductCd(String uri, String productCd); /** * 通过uri删除多条产品链信息 * @param uri * @return */ void deleteProductChainByUri(String uri); /** * 通过uri查询链信息 * @param uri * @param productCd * @return */ DragChainDTO getDragChain(String uri, String productCd); /** * 获取规则文件信息 * @param uri * @throws IOException */ void downloadFile(String uri, String productCd) throws IOException; /** * 获取规则文件xml信息 * @param uri * @throws IOException */ String getXMLStr(String uri, String productCd) throws IOException; /** * 分页查询链信息 * @param dragChainDTO * @return */ PageBean<DragChainDTO> getDragChainPage(DragChainDTO dragChainDTO); /** * 分页查询产品链信息 * @param dragChainDTO * @return */ PageBean<DragChainDTO> getDragProductChainPage(DragChainDTO dragChainDTO); /** * 通过文件以及节点校验规则链是否合法 * @param allParamDTO * @return */ DragRuleChainAllParamDTO checkDragRuleChainByFile(DragRuleChainAllParamDTO allParamDTO); /** * 开发人员使用获取拖拽规则规则名称 * @param sequenceEnum * @return */ String getDragSequenceName(DragRuleSequenceEnum sequenceEnum); }
规则实现类代码过多,所以新增一个单独记录地址记录,DragRuleServiceImpl 详情请点击
-
规则注解类,标注此类的接口会被扫描到规则缓存中
package cn.git.rules.drag; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @description: 规则属性解释注解 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 08:44:44 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) public @interface DragRuleDesc { /** * 拖拽规则field作用注释 * @return */ String fieldDesc() default "属性字段描述"; /** * 字段码值 * @return */ String sysCode() default ""; /** * 是否自定义属性值 * @return */ boolean ifCustomDefine() default false; /** * 拖拽规则bean作用注释 * @return */ String beanDesc() default "拖拽规则类描述"; }
-
server端工具类
package cn.git.rules.drag; import cn.git.api.util.NewCoreProperties; import cn.git.common.drag.CommonDragRuleConstant; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.dto.DragRuleDTO; import cn.git.common.exception.ServiceException; import cn.git.common.lock.LockTypeEnum; import cn.git.common.lock.SimpleDistributeLock; import cn.git.common.util.LogUtil; import cn.git.redis.RedisUtil; import cn.git.rules.constant.RuleConstant; import cn.git.rules.entity.TbSysDragChain; import cn.git.rules.entity.TbSysDragRule; import cn.git.rules.entity.TbSysProductDragChain; import cn.git.rules.mapper.TbSysDragRuleMapper; import cn.git.rules.mapper.TbSysProductDragChainMapper; import cn.git.rules.mapstruct.DragRuleConvert; import cn.git.rules.util.DataSourceTypeEnum; import cn.git.rules.util.DatabaseUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.camunda.bpm.model.bpmn.instance.FlowNode; import org.camunda.bpm.model.bpmn.instance.SequenceFlow; import org.camunda.bpm.model.bpmn.instance.StartEvent; import org.springframework.aop.framework.Advised; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import javax.script.Compilable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.time.LocalDate; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.*; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @description: DragRule拖拽规则工具类 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-21 03:01:12 */ @Slf4j @Component public class DragRuleServerUtil { /** * 数组正则表达式 */ public final String ARRAY_REGEX = "^\\[(('([^']|\\\\')*')|[a-zA-Z0-9\\.]+)(,('([^']|\\\\')*')|[a-zA-Z0-9\\.]+)*\\]$"; @Autowired private DragRuleConvert dragRuleConvert; @Autowired private RedisUtil redisUtil; @Autowired private TbSysDragRuleMapper tbSysDragRuleMapper; @Autowired private TbSysProductDragChainMapper productDragChainMapper; @Autowired private ApplicationContext applicationContext; @Autowired private SimpleDistributeLock simpleDistributeLock; @Value("${spring.application.name}") private String applicationName; @Autowired private DatabaseUtil databaseUtil; @Autowired private HttpServletRequest httpServletRequest; /** * 初始化拖拽规则详情信息,设置规则信息DRAG_RULE供其他子模块获取使用 * 规则格式如下: hset DRAG_RULE DragRuleId RuleInfo */ public void initDragRule() throws Exception { // 获取锁信息 String lockResult = simpleDistributeLock.tryLock(LockTypeEnum.DRAG_RULE_INIT_LOCK, applicationName); try { if (StrUtil.isNotBlank(lockResult)) { // 扫描服务所有DragRule注解类信息,此处需要将获取bean由cglib转换为真实对象 Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(DragRuleDesc.class); beansWithAnnotation.replaceAll((key, value) -> { if (value instanceof Advised) { Advised advised = (Advised) value; Object target = null; try { target = advised.getTargetSource().getTarget(); } catch (Exception e) { e.printStackTrace(); } return target; } else { return value; } }); // 选择数据库 databaseUtil.chooseDB(DataSourceTypeEnum.MANAGE); // 获取全部拖拽规则信息 List<TbSysDragRule> dragRuleList = tbSysDragRuleMapper.selectList(null); // 切换默认数据源 databaseUtil.clearToDefault(); Map<String, TbSysDragRule> dragRuleMap = new HashMap<>(NewCoreProperties.FLAG_INT_16); // 如果规则不为空,则证明规则进行过设置,需要校验规则类是否有修改,规则是否存在校验 if (ObjectUtil.isNotEmpty(dragRuleList)) { // 获取注解类名称,判定数据库是否有配置,如果没有则抛出异常 for (TbSysDragRule dragRule : dragRuleList) { dragRuleMap.put(dragRule.getDragRuleId(), dragRule); // 获取规则id eg: TESTDragRule0001 String dragRuleId = dragRule.getDragRuleId(); // 查看beansWithAnnotation中是否有当前处理规则,如果没有则抛出异常信息 if (!beansWithAnnotation.containsKey(dragRuleId.concat(RuleConstant.TRAN_SUFFIX))) { throw new ServiceException(StrUtil .format("数据库表[MANAGE.TB_SYS_DRAG_RULE]中规则[{}]代码已经删除,确定规则是否无用,请手动删除此规则再启动!", dragRuleId)); } } } // 设置规则入参出参信息到规则缓存中 Map<String, Object> dragRuleDTOMap = new HashMap<>(NewCoreProperties.FLAG_INT_16); for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) { Class<?> dragRuleTranClass = entry.getValue().getClass(); // 获取process方法 Method[] methods = dragRuleTranClass.getMethods(); if (ObjectUtil.isEmpty(methods)) { log.error("规则[{}]没有设定方法,请正确设置方法!", dragRuleTranClass.getName()); continue; } // methods转换为List并且过滤出process方法 Method method = Arrays.stream(methods).filter(inMethod -> { // 进行参数校验,要求参数不能为Object类型 if (inMethod.getName().equals("process") && inMethod.getParameterTypes().length == 1) { // 校验参数不是Object类型 Class<?> parameterClass = inMethod.getParameterTypes()[0]; return parameterClass.getSimpleName().endsWith("DTO"); } return false; }).findFirst().orElse(null); if (ObjectUtil.isNull(method)) { log.error("规则[{}]没有设定process方法,请正确设置process方法!", dragRuleTranClass.getName()); continue; } // 获取方法参数信息 Class<?> parameterType = method.getParameterTypes()[0]; // 复杂类类型 Field[] declaredFields = parameterType.getDeclaredFields(); // 入参信息处理 JSONObject inputParamJSON = new JSONObject(); List<DragRuleDTO.DragRuleParamsDesc> inputParamDescList = new ArrayList<>(); // 入参自定义信息设置 List<DragRuleDTO.DragRuleParamsDesc> inputCustomParamList = new ArrayList<>(); for (Field field : declaredFields) { // 获取描述信息 DragRuleDesc dragRuleDesc = field.getAnnotation(DragRuleDesc.class); if (ObjectUtil.isNotNull(dragRuleDesc) && StrUtil.isNotBlank(dragRuleDesc.fieldDesc())) { DragRuleDTO.DragRuleParamsDesc fieldDesc = new DragRuleDTO.DragRuleParamsDesc(); fieldDesc.setParamDesc(dragRuleDesc.fieldDesc()); fieldDesc.setParamType(field.getType().getSimpleName()); fieldDesc.setParamName(field.getName()); fieldDesc.setSysCode(dragRuleDesc.sysCode()); fieldDesc.setIfCustomDefine(dragRuleDesc.ifCustomDefine()); // 判断是否为自定义参数 if (dragRuleDesc.ifCustomDefine()) { // 自定义参数类型校验 boolean ifParamTypeAllow = "Date".equals(field.getType().getSimpleName()) || "BigDecimal".equals(field.getType().getSimpleName()) || "Long".equals(field.getType().getSimpleName()) || "Integer".equals(field.getType().getSimpleName()) || "String".equals(field.getType().getSimpleName()); if (!ifParamTypeAllow) { throw new ServiceException(StrUtil.format("拖拽规则入参DTO[{}]自定义参数[{}]类型非法,合法参数类型为[Date,BigDecimal,Long,Integer,String],请确认!", parameterType.getSimpleName(), field.getName())); } inputCustomParamList.add(fieldDesc); } else { inputParamJSON.put(field.getName(), StrUtil.EMPTY); inputParamDescList.add(fieldDesc); } } else { throw new ServiceException(StrUtil.format("拖拽规则入参DTO[{}]对应属性[{}]未设置@DragRuleDesc的fieldDesc属性", parameterType.getSimpleName(), field.getName())); } } // 设置出参信息,首先获取出参内部类 String simpleClassName = parameterType.getSimpleName().replace("DTO", "OUTDTO"); String className = parameterType.getName(); JSONObject outputParamJSON = new JSONObject(); List<DragRuleDTO.DragRuleParamsDesc> outputParamDescList = new ArrayList<>(); try { Class<?> innerClass = Class.forName(className.concat("$").concat(simpleClassName)); outputParamJSON = new JSONObject(); for (Field field : innerClass.getDeclaredFields()) { DragRuleDesc dragRuleDesc = field.getAnnotation(DragRuleDesc.class); if (ObjectUtil.isNotNull(dragRuleDesc) && StrUtil.isNotBlank(dragRuleDesc.fieldDesc())) { DragRuleDTO.DragRuleParamsDesc fieldDesc = new DragRuleDTO.DragRuleParamsDesc(); fieldDesc.setParamDesc(dragRuleDesc.fieldDesc()); fieldDesc.setParamType(field.getType().getSimpleName()); fieldDesc.setParamName(field.getName()); fieldDesc.setSysCode(dragRuleDesc.sysCode()); outputParamDescList.add(fieldDesc); } else { throw new ServiceException(StrUtil.format("拖拽规则出参DTO[{}]对应属性[{}]未设置@DragRuleDesc的fieldDesc属性", innerClass.getSimpleName(), field.getName())); } outputParamJSON.put(field.getName(), StrUtil.EMPTY); } } catch (ClassNotFoundException e) { log.info("拖拽规则参数DTO[{}]未定义内部类出参信息,请确认!", method.getName()); throw new ServiceException(StrUtil.format("拖拽规则参数DTO[{}]未定义内部类出参信息,请确认!", method.getName())); } // 获取bean描述信息 String beanDesc = dragRuleTranClass.getAnnotation(DragRuleDesc.class).beanDesc(); if (StrUtil.isBlank(beanDesc)) { throw new ServiceException(StrUtil.format("规则类[{}]对应@DragRuleDesc注解beanDesc没有设置", dragRuleTranClass.getName())); } // 封装参数信息,入参出参从反射中获取,其余参数如果数据库查询有值则设定,如果没有则不设定 String ruleId = StrUtil.upperFirst(entry.getKey()).replace("Tran", StrUtil.EMPTY); TbSysDragRule dragRule = dragRuleMap.get(ruleId); DragRuleDTO dragRuleDTO; if (ObjectUtil.isNotNull(dragRule)) { // 转换拖拽规则 dragRuleDTO = dragRuleConvert.dragRuleToDragRuleDTO(dragRule); // 设置描述信息 dragRuleDTO.setRuleDesc(beanDesc); // 是否落库 dragRuleDTO.setIfLoadDB(RuleConstant.NUM_1_STR); // 设置crossParam列表以及自定义操作参数列表 if (StrUtil.isNotBlank(dragRule.getCrossParam())) { List<String> crossParamList = Arrays.asList(dragRule.getCrossParam().split(StrUtil.COMMA)); dragRuleDTO.setCrossParamList(crossParamList); dragRuleDTO.setCrossParam(dragRule.getCrossParam()); // 将outParamDescList转换为map,获取crossParamDesc描述信息 Map<String, DragRuleDTO.DragRuleParamsDesc> outParamDescMap = outputParamDescList.stream() .collect( Collectors.toMap(DragRuleDTO.DragRuleParamsDesc::getParamName, Function.identity()) ); List<DragRuleDTO.DragRuleParamsDesc> crossParamDescList = new ArrayList<>(); crossParamList.forEach(paramName -> { DragRuleDTO.DragRuleParamsDesc dragRuleParamsDesc = outParamDescMap.get(paramName); if (ObjectUtil.isNotNull(dragRuleParamsDesc)) { crossParamDescList.add(dragRuleParamsDesc); } }); dragRuleDTO.setCrossParamDescList(crossParamDescList); } // 设置自定义参数信息 if (StrUtil.isNotBlank(dragRule.getCustomOptionParam())) { // 将json类型转换为 List<List<DragRuleDTO.CustomOptionParam>> 类型 List customOptionParamsList = JSONObject.parseArray(dragRule.getCustomOptionParam(), List.class); List<List<DragRuleDTO.CustomOptionParam>> typeList = new ArrayList<>(); for (Object customParamList : customOptionParamsList) { List<JSONObject> jsonParamList = (List<JSONObject>) customParamList; List<DragRuleDTO.CustomOptionParam> innerParamList = jsonParamList.stream().map(jsonParam -> { return JSONObject.parseObject(jsonParam.toJSONString(), DragRuleDTO.CustomOptionParam.class); }).collect(Collectors.toList()); typeList.add(innerParamList); } dragRuleDTO.setCustomOptionParamList(typeList); } } else { // 新增dragRuleDTO,添加入参出参以及其他通用参数信息 dragRuleDTO = new DragRuleDTO(); dragRuleDTO.setDragRuleId(ruleId); dragRuleDTO.setRuleDesc(beanDesc); dragRuleDTO.setIfLoadDB(RuleConstant.NUM_0_STR); } // 设置入参出参信息 dragRuleDTO.setInputParamDescList(inputParamDescList); dragRuleDTO.setOutputParamDescList(outputParamDescList); // 规则自定义入参描述信息 dragRuleDTO.setInputCustomParamDescList(inputCustomParamList); dragRuleDTO.setInputParam(inputParamJSON.toJSONString()); dragRuleDTO.setOutputParam(outputParamJSON.toJSONString()); dragRuleDTOMap.put(ruleId, JSONObject.toJSONString(dragRuleDTO)); } if (ObjectUtil.isNotEmpty(dragRuleDTOMap)) { // 删除原有规则 redisUtil.del(RuleConstant.DRAG_RULES); // 重新加载 redisUtil.hmset(RuleConstant.DRAG_RULES, dragRuleDTOMap); } // 清除产品规则链缓存 redisUtil.del(CommonDragRuleConstant.DRAG_RULE_PRODUCT_CHAIN_FLAG); // 选择数据库 databaseUtil.chooseDB(DataSourceTypeEnum.MANAGE); // 开始初始化产品相关规则链信息到缓存中,缓存key为 uri + ":" + productCd格式 List<TbSysProductDragChain> productDragChainList = productDragChainMapper.selectList(null); databaseUtil.clearToDefault(); if (ObjectUtil.isNotEmpty(productDragChainList)) { Map<String, Object> productMap = productDragChainList.stream() .collect(Collectors.toMap( product -> product.getUri().concat(StrUtil.COLON).concat(product.getProductCd()), Function.identity() )); redisUtil.hmset(CommonDragRuleConstant.DRAG_RULE_PRODUCT_CHAIN_FLAG, productMap); } // 释放锁 simpleDistributeLock.releaseLock(LockTypeEnum.DRAG_RULE_INIT_LOCK, applicationName, lockResult); } else { log.error("当前服务拖拽规则引擎服务初始化加锁失败,未执行拖拽规则服务初始化!"); } } catch (Exception e) { e.printStackTrace(); throw new ServiceException(e.getMessage()); } finally { // 释放锁 simpleDistributeLock.releaseLock(LockTypeEnum.DRAG_RULE_INIT_LOCK, applicationName, lockResult); } } /** * 自定义检查自定义参数比较规则 * 规则格式 eg: [(, a, +, b, <, c , &&, c, ==, 2, ), ||, a, <, b, ||, loanType, in, [1,2,3,4] ] * * @param paramList 参数列表 * @param index 当前规则索引 */ public void checkCustomOptionParam(List<DragRuleDTO.CustomOptionParam> paramList, int index) { // 参数list转map信息 Map<String, DragRuleDTO.CustomOptionParam> paramMap = paramList.stream().collect( Collectors.toMap(DragRuleDTO.CustomOptionParam::getParamName, Function.identity(), (k1, k2) -> k2) ); // 自定义比较参数,至少包含一个outDTO参数校验 DragRuleDTO.CustomOptionParam atLeastOneOutDTOParam = paramList.stream().filter(param -> param.getOptionType().equals(RuleConstant.NUM_1_STR) ).findFirst().orElse(null); if (ObjectUtil.isNull(atLeastOneOutDTOParam)) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义参数比较规则至少包含一个出参参数,请确认规则信息", index)); } // 首先拼装参数信息,然后进行编译,观察编译是否通过 StringBuilder paramBuilder = new StringBuilder(); for (int i = 0; i < paramList.size(); i++) { DragRuleDTO.CustomOptionParam optionParam = paramList.get(i); // 操作标识符非法校验 if (RuleConstant.NUM_0_STR.equals(optionParam.getOptionType())) { boolean ifOptionValue = isLegalOperator(optionParam.getParamName()); if (!ifOptionValue) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义规则脚本操作符[{}]非法,请确认!", index, optionParam.getParamName())); } } // 操作标识符拼接名称eg : "(" if (RuleConstant.NUM_0_STR.equals(optionParam.getOptionType())) { paramBuilder.append(optionParam.getParamName()).append(StrUtil.SPACE); } else if (RuleConstant.NUM_2_STR.equals(optionParam.getOptionType())) { // 自定义值,拼接value值信息 eg: 2024-03-01 paramBuilder.append(optionParam.getValue()).append(StrUtil.SPACE); } else { // 其余为outDTO参数信息 paramBuilder.append(optionParam.getParamName()).append(StrUtil.SPACE); } } // 编译语句 String compareString = paramBuilder.toString(); // 进行in以及 !in 操作符,字段正确性校验,要求in以及!n 后面的字段必须为码值并且是数组格式[a,b,c,d] String[] splitSpace = paramBuilder.toString().split(StrUtil.SPACE); // 数组前端必须为操作符 in !in校验 Pattern pattern = Pattern.compile(ARRAY_REGEX); // 找到 in以及 !in 的索引位置,如果没有in,则判定数组类型数据 ,如果有数组类型数据,则前面必须为 in或者 !in 操作符 List<Integer> inParamIndexList = new ArrayList<>(); for (int paramIndex = 0; paramIndex < splitSpace.length; paramIndex++) { if ("in".equals(splitSpace[paramIndex]) || "!in".equals(splitSpace[paramIndex])) { inParamIndexList.add(paramIndex); // in !in 不能作为开头以及不能作为结尾校验 if (paramIndex == splitSpace.length - 1) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,in或者!in不能作为表达式结尾,请确认!", index, paramBuilder.toString())); } else if (paramIndex == 0) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,in或者!in不能作为头信息,请确认!", index)); } } // 数组逗号分割数据前面必须为操作标识符 in 或者 !in Matcher matcher = pattern.matcher(splitSpace[paramIndex]); if (matcher.matches()) { // 第一个参数不能为数组校验 if (paramIndex <= 1) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,数组类型参数不能作为首个参数,请确认!", index)); } // 前一个参数是否为 in !in校验 String paramBeforeArray = splitSpace[paramIndex - 1]; if (!"in".equals(paramBeforeArray) && !"!in".equals(paramBeforeArray) ) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,数组类型参数前面必须为in或者!in操作符,请确认!", index)); } } } // 遍历找到的索引位置,判断是否为数组格式 if (ObjectUtil.isNotEmpty(inParamIndexList)) { inParamIndexList.forEach(inParamIndex -> { String nextParam = splitSpace[inParamIndex + 1]; // 编译正则表达式 Matcher matcher = pattern.matcher(nextParam); if (!matcher.matches()) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,in或者!in后面的参数[{}]必须是数组格式,请确认!", index, nextParam)); } // in !in 前面不能是操作符 if (RuleConstant.NUM_0_STR.equals(splitSpace[inParamIndex - 1])) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,in或者!in前面不能是操作符,请确认!", index)); } }); // in !in 类型重编译执行语句 eg : a in [1,2,3,4] => [1,2,3,4].indexOf(a) for (Integer inParamIndex : inParamIndexList) { // [1,2,3,4] String arrayParam = splitSpace[inParamIndex + 1]; // a String paramBeforeIn = splitSpace[inParamIndex - 1]; // !in String operator = splitSpace[inParamIndex]; // 老参数信息 a in [1,2,3,4] String oldParam = paramBeforeIn.concat(StrUtil.SPACE).concat(operator).concat(StrUtil.SPACE).concat(arrayParam); // 优化后编译信息 String newParam = arrayParam .concat(".indexOf( ") .concat(paramBeforeIn) .concat(" )") .concat("!in".equals(operator) ? " < 0" : " >= 0"); compareString = compareString.replace(oldParam, newParam); } } // 进行param表达式编译校验,确保表达式正确 ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); Compilable compilableEngine = (Compilable) engine; try { compilableEngine.compile(compareString); } catch (ScriptException e) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义规则脚本表达式[{}]非法,请确认!", index, paramBuilder.toString())); } // 提取表达式信息 List<String> expressions = extractExpressions(paramBuilder.toString()); if (ObjectUtil.isEmpty(expressions)) { return; } // expressions中每一个表达式中,必须包含一个outDTO参数校验 expressions.forEach(expression -> { // 单个表达式信息 eg: conDate < 2025-05-03 List<String> checkParamNameList = CollUtil.newArrayList(expression.split(StrUtil.SPACE)); String checkOKParam = checkParamNameList.stream().filter(paramName -> { DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(paramName); return ObjectUtil.isNotNull(customOptionParam) && RuleConstant.NUM_1_STR.equals(customOptionParam.getOptionType()); }).findAny().orElse(null); if (StrUtil.isBlank(checkOKParam)) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义规则脚本表达式[{}]中未包含出参参数,请确认!", index, expression)); } }); // 遍历校验表达式信息 expressions.forEach(expression -> { // 确定表达式为什么类型表达式,一共分为三种,日期,数字以及字符串,我们获取首个outDTO参数,然后通过类型进行判定 List<String> checkParamNameList = CollUtil.newArrayList(expression.split(StrUtil.SPACE)); String checkOKParam = checkParamNameList.stream().filter(paramName -> { DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(paramName); return ObjectUtil.isNotNull(customOptionParam) && RuleConstant.NUM_1_STR.equals(customOptionParam.getOptionType()); }).findFirst().orElse(null); if (StrUtil.isBlank(checkOKParam)) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义规则脚本表达式[{}]中未包含出参参数,请确认!", index, expression)); } // 获取参数type,确定为什么类型操作表达式 String paramType = paramMap.get(checkOKParam).getParamType(); // 获取参数其中有日期类型参数,则证明为日期校验,进行参数校验,如果参数为自定义参数,则对表达式进行校验 if ("Date".equals(paramType)) { // 如果有其他类型数据,则必须为日期以及自定义日期格式类型 checkParamNameList.forEach(checkParam -> { // 获取单个自定义参数信息 DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(checkParam); if (ObjectUtil.isNull(customOptionParam)) { customOptionParam = paramList.stream().filter(param -> { if (checkParam.equals(param.getValue())) { return true; } return false; }).findFirst().orElse(null); } // 自定义类型参数,日期格式校验 if (RuleConstant.NUM_2_STR.equals(customOptionParam.getOptionType())) { String dateStr = customOptionParam.getValue(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); if (!"''".equals(dateStr) && StrUtil.isNotBlank(dateStr) && dateStr.length() > 10) { try { LocalDate.parse(dateStr, formatter); } catch (DateTimeParseException e) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义入参参数[{}]需要为日期格式[{}],当前值为[{}],请确认!", index, customOptionParam.getParamDesc(), "yyyy-MM-dd HH:mm:ss", customOptionParam.getValue())); } } else if (!"''".equals(dateStr) && StrUtil.isNotBlank(dateStr) && dateStr.length() < 10) { formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); try { LocalDate.parse(dateStr, formatter); } catch (DateTimeParseException e) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义入参参数[{}]需要为日期格式[{}],当前值为[{}],请确认!", index, customOptionParam.getValue(), "yyyy-MM-dd", customOptionParam.getValue())); } } } // 其他类型参数校验,类型为outDTO参数信息,并且不是Date类型 if (RuleConstant.NUM_1_STR.equals(customOptionParam.getOptionType()) && !"Date".equals(customOptionParam.getParamType())) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义日期类型规则参数[{}]需为日期类型,请确认!", index, customOptionParam.getParamDesc())); } }); } // 数字类型校验 boolean ifTypeNum = "BigDecimal".equals(paramType) || "Long".equals(paramType) || "Integer".equals(paramType) || "Double".equals(paramType); if (ifTypeNum) { // 校验类型必须为数字类型,并且自定义参数格式也进行校验 checkParamNameList.forEach(checkParam -> { // 获取单个自定义参数信息 DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(checkParam); if (ObjectUtil.isNull(customOptionParam)) { customOptionParam = paramList.stream().filter(param -> { if (checkParam.equals(param.getValue())) { return true; } return false; }).findFirst().orElse(null); } // 参数类型自定义,类型必须为数字类型校验 if (RuleConstant.NUM_2_STR.equals(customOptionParam.getOptionType())) { String numStr = customOptionParam.getValue(); if (!"''".equals(numStr) && !NumberUtil.isNumber(numStr)) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义数字类型规则参数[{}]需要为数字类型,当前值为[{}],请确认!", index, customOptionParam.getValue(), customOptionParam.getValue())); } } // outVO参数校验,其参数类型必须为数字类型 if (RuleConstant.NUM_1_STR.equals(customOptionParam.getOptionType())) { boolean ifNum = "BigDecimal".equals(customOptionParam.getParamType()) || "Long".equals(customOptionParam.getParamType()) || "Integer".equals(customOptionParam.getParamType()) || "Double".equals(customOptionParam.getParamType()); if (!ifNum) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义数字类型规则参数[{}]需为数字类型,请确认!", index, customOptionParam.getParamDesc())); } } }); } // 字符串类型校验 if ("String".equals(paramType)) { List<DragRuleDTO.CustomOptionParam> strParamList = checkParamNameList.stream().filter(checkParamName -> { DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(checkParamName); if (ObjectUtil.isNotNull(customOptionParam)) { return "String".equals(customOptionParam.getParamType()); } else { return false; } }).map( paramMap::get ).collect(Collectors.toList()); // 字符串校验,比较语句一般为 a == b ,a != b,其中参数不能为多个,outDTO中,字符串类型个数不能超过2个 if (strParamList.size() > 2) { throw new ServiceException("第[{}]个自定义规则,自定义字符串类型规则参数比较条件最多只能为2个,请确认!"); } // 字符串类型校验,操作符只能为括号以及等于,不等于,其余不可以 checkParamNameList.forEach(checkParam -> { DragRuleDTO.CustomOptionParam customOptionParam = paramMap.get(checkParam); if (ObjectUtil.isNull(customOptionParam)) { customOptionParam = paramList.stream().filter(param -> { if (checkParam.equals(param.getValue())) { return true; } return false; }).findFirst().orElse(null); } if (RuleConstant.NUM_1_STR.equals(customOptionParam.getOptionType()) && !"String".equals(customOptionParam.getParamType())) { throw new ServiceException(StrUtil.format("第[{}]个自定义规则,自定义字符串类型规则参数[{}]需为字符串类型,请确认!", index, customOptionParam.getParamDesc())); } }); } }); } /** * 是否合法操作符 * @param operator 操作符 */ public boolean isLegalOperator(String operator) { return ">=".equals(operator) || ">".equals(operator) || "<=".equals(operator) || "<".equals(operator) || "==".equals(operator) || "!=".equals(operator) || "&&".equals(operator) || ")".equals(operator) || "(".equals(operator) || "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator) || "!in".equals(operator) || "in".equals(operator) || "||".equals(operator); } /** * 提取表达式 * * @param script * @return */ public List<String> extractExpressions(String script) { // 匹配最内层的括号表达式 String[] expressionsArray = script.split("\\s*(?:(&&)|\\|\\|)\\s*"); List<String> expressionList = new ArrayList<>(); if (ObjectUtil.isNotEmpty(expressionsArray)) { expressionList = CollUtil.newArrayList(expressionsArray); // expressionList = CollUtil.newArrayList(expressionsArray).stream().map(exp -> { // String replaceExp = exp.replace("(", StrUtil.EMPTY).replace(")", StrUtil.EMPTY); // if (replaceExp.startsWith(StrUtil.SPACE)) { // replaceExp = replaceExp.replaceFirst(StrUtil.SPACE, StrUtil.EMPTY); // } // // 去除最后一个空格 // if (replaceExp.endsWith(StrUtil.SPACE)) { // replaceExp = replaceExp.substring(0, replaceExp.length() - 1); // } // // // 如果字符串包含多个连贯的空格,则统一替换为单个空格 // replaceExp = replaceExp.replaceAll("\\s+", StrUtil.SPACE); // // return replaceExp; // }).collect(Collectors.toList()); } return expressionList; } /** * 自定义检查拖拽规则选项比较 * * @param rspJsonObject 响应参数信息 * @param paramList 自定义参数信息 eg: [(, a, +, b, <, c , &&, c, ==, 2, ), ||, a, <, b ] * @param dragRuleId 规则名称 * @param typeCdValueKeyMap 码值信息map 以typeCd+"_"+codeCd[值] 为key CodeName 为Value * @return */ public void checkOptionCompare(JSONObject rspJsonObject, List<DragRuleDTO.CustomOptionParam> paramList, String dragRuleId, Map<String, String> typeCdValueKeyMap, String dragSign) { // 非空校验 if (ObjectUtil.isEmpty(paramList)) { return; } // 开始进行调用参数拼接操作 StringBuilder checkParamStr = new StringBuilder(); StringBuilder checkParamMessage = new StringBuilder(); // 循环单个参数信息,进行数据拼接 paramList.forEach(param -> { // 如果是操作标识符,则直接拼接即可 if (RuleConstant.NUM_0_STR.equals(param.getOptionType())) { checkParamStr.append(param.getParamName()).append(StrUtil.SPACE); if ("||".equals(param.getParamName())) { checkParamMessage.append("或者").append(StrUtil.SPACE); } else if ("&&".equals(param.getParamName())) { checkParamMessage.append("并且").append(StrUtil.SPACE); } else if ("==".equals(param.getParamName())) { checkParamMessage.append("等于").append(StrUtil.SPACE); } else if (">".equals(param.getParamName())) { checkParamMessage.append("大于").append(StrUtil.SPACE); } else if (">=".equals(param.getParamName())) { checkParamMessage.append("大于等于").append(StrUtil.SPACE); } else if ("<".equals(param.getParamName())) { checkParamMessage.append("小于").append(StrUtil.SPACE); } else if ("<=".equals(param.getParamName())) { checkParamMessage.append("小于等于").append(StrUtil.SPACE); } else if ("!=".equals(param.getParamName())) { checkParamMessage.append("不等于").append(StrUtil.SPACE); } else if ("in".equals(param.getParamName())) { checkParamMessage.append("属于").append(StrUtil.SPACE); } else if ("!in".equals(param.getParamName())) { checkParamMessage.append("不属于").append(StrUtil.SPACE); } else { checkParamMessage.append(param.getParamDesc()).append(StrUtil.SPACE); } } // 获取码值信息 String sysCodeName = ""; // outDTO参数,进行类型判定 if (RuleConstant.NUM_1_STR.equals(param.getOptionType())) { // 获取响应参数 Object rspObject = rspJsonObject.get(param.getParamName()); /** * 暂时注释空值校验 if (ObjectUtil.isNull(rspObject)) { throw new ServiceException(StrUtil.format("规则[{}],自定义规则响应参数[{}:{}]获取为空,规则校验失败!", dragRuleId, param.getParamDesc(), param.getParamName())); } **/ if (ObjectUtil.isNull(rspObject)) { rspObject = "''"; } boolean isNumberType = "BigDecimal".equals(param.getParamType()) || "Long".equals(param.getParamType()) || "Integer".equals(param.getParamType()) || "Double".equals(param.getParamType()); if (isNumberType) { // 通过rspJSON信息获取响应参数信息 String rspValue = StrUtil.toString(rspObject); if (StrUtil.isNotBlank(param.getSysCode())) { sysCodeName = typeCdValueKeyMap.get(param.getSysCode().concat(StrUtil.UNDERLINE).concat(rspValue)); } checkParamStr.append(rspValue).append(StrUtil.SPACE); checkParamMessage.append(param.getParamDesc()) .append("[") .append(StrUtil.isBlank(sysCodeName) ? ("''".equals(rspValue) ? "空" : rspValue) : sysCodeName) .append("]") .append(StrUtil.SPACE); } else if ("String".equals(param.getParamType())) { // 通过rspJSON信息获取响应参数信息 String rspValue = StrUtil.toString(rspObject); if (StrUtil.isNotBlank(param.getSysCode())) { sysCodeName = typeCdValueKeyMap.get(param.getSysCode().concat(StrUtil.UNDERLINE).concat(rspValue)); } // 字符串类型,则添加 '' 进行表示为字符串类型 checkParamStr.append("'").append(rspValue).append("'").append(StrUtil.SPACE); checkParamMessage.append(param.getParamDesc()) .append("[") .append(StrUtil.isBlank(sysCodeName) ? rspValue : sysCodeName) .append("]") .append(StrUtil.SPACE); } else if ("Date".equals(param.getParamType())) { // 通过rspJSON信息获取响应参数信息 long rspValue = rspJsonObject.getLongValue(param.getParamName()); // date类型,转换为yyyy-MM-dd类型 Date rspDate = DateUtil.date(rspValue); String formatDate = DateUtil.format(rspDate, DatePattern.NORM_DATE_PATTERN); // 日期类型则全部转换为数字类型,进行数字比较 checkParamStr.append(rspValue).append(StrUtil.SPACE); checkParamMessage.append(param.getParamDesc()).append("[").append(formatDate).append("]").append(StrUtil.SPACE); } } // 进行自定义参数类型字段拼接 if (RuleConstant.NUM_2_STR.equals(param.getOptionType())) { // 通过自定义值信息获取码值名称 String customValue = param.getValue(); // 如果是码值,则需要转换提示信息 if (ObjectUtil.isNotNull(param.getSysCode())) { if (StrUtil.isNotBlank(customValue) && StrUtil.isNotBlank(customValue)) { // 如果value值为 ['1', '2'] 这种形式,需要进行循环解析操作 if (customValue.startsWith("[") && customValue.endsWith("]") && customValue.length() > 2) { // value 修改为数组格式为 1 , 2 String[] codeCds = customValue .replace("[", "") .replace("]", "") .replace("'", "") .split(StrUtil.COMMA); // 拼接码值,拼接结果eg: 新增贷款,重组贷款,续贷贷款 for (int index = 0; index < codeCds.length; index++) { String codeCd = codeCds[index]; String codeCdName = typeCdValueKeyMap.get(param.getSysCode().concat(StrUtil.UNDERLINE).concat(codeCd)); sysCodeName = sysCodeName.concat(StrUtil.isBlank(codeCdName) ? codeCd : codeCdName); // 尾部加逗号 if (index != codeCds.length - 1) { sysCodeName = sysCodeName.concat(StrUtil.COMMA); } } } else { // 普通模式 sysCodeName = typeCdValueKeyMap.get(param.getSysCode().concat(StrUtil.UNDERLINE).concat(param.getValue())); } } } // 如果为自定义数字类型,则直接拼接即可 if (NumberUtil.isNumber(customValue)) { checkParamStr.append(customValue).append(StrUtil.SPACE); checkParamMessage.append(StrUtil.isNotBlank(sysCodeName) ? sysCodeName : customValue).append(StrUtil.SPACE); } else { // 不是数字类型,则为字符串类型,字符串类型则进行是否日期类型校验,如果为日期类型,则转换为数字类型比较 LocalDate localDate = null; if (!"''".equals(customValue) && customValue.split(StrUtil.DASHED, -1).length == 3) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); if ((StrUtil.isNotBlank(customValue) && customValue.length() <= 10)) { formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); } try { localDate = LocalDate.parse(customValue, formatter); } catch (DateTimeParseException ignored) { } // 如果是日期类型,则转换为数字类型进行比较,否则直接拼接即可 if (ObjectUtil.isNotNull(localDate)) { // 日期自定义格式,转换为数字类型 long timeMillis = localDate.atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli(); checkParamStr.append(timeMillis).append(StrUtil.SPACE); checkParamMessage.append(customValue).append(StrUtil.SPACE); } else { checkParamStr.append("'").append(customValue).append("'").append(StrUtil.SPACE); checkParamMessage.append(StrUtil.isBlank(sysCodeName) ? customValue : sysCodeName).append(StrUtil.SPACE); } } else { checkParamStr.append(customValue).append(StrUtil.SPACE); // 提示信息优化 customValue = "''".equals(customValue) ? "空" : customValue; checkParamMessage.append(StrUtil.isBlank(sysCodeName) ? customValue : sysCodeName).append(StrUtil.SPACE); } } } }); // 数字类型比较,创建ScriptEngine对象,使用eval表达式进行比较 ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName(RuleConstant.JAVA_SCRIPT); try { // 实际执行表达式语句 String executeStr = checkParamStr.toString(); // 进行['1','2','3']此类参数转义 eg: loanType in ['1','2','3'] => ['1','2','3'].indexOf(loanType) >= 0 if (executeStr.contains("['") && executeStr.contains("']")) { // 通过空格分割成数组,后续转义使用 String[] splitSpace = executeStr.split(StrUtil.SPACE); List<Integer> inParamIndexList = new ArrayList<>(); for (int paramIndex = 0; paramIndex < splitSpace.length; paramIndex++) { if ("in".equals(splitSpace[paramIndex]) || "!in".equals(splitSpace[paramIndex])) { inParamIndexList.add(paramIndex); } } // in !in 类型重编译执行语句 eg : a in [1,2,3,4] => [1,2,3,4].indexOf(a) for (Integer inParamIndex : inParamIndexList) { // [1,2,3,4] String arrayParam = splitSpace[inParamIndex + 1]; // a String paramBeforeIn = splitSpace[inParamIndex - 1]; // !in String operator = splitSpace[inParamIndex]; // 老参数信息 a in [1,2,3,4] String oldParam = paramBeforeIn.concat(StrUtil.SPACE).concat(operator).concat(StrUtil.SPACE).concat(arrayParam); // 优化后编译信息 String newParam = arrayParam .concat(".indexOf( ") .concat(paramBeforeIn) .concat(" )") .concat("!in".equals(operator) ? " < 0" : " >= 0"); executeStr = executeStr.replace(oldParam, newParam); } } // 开始执行eval函数解析表达式 log.info("X_sign[{}]执行规则脚本 => [{}]", dragSign, executeStr); log.info("执行规则展示信息 => [{}]", checkParamMessage); if (engine.eval(executeStr) == Boolean.FALSE) { String errorMessage = StrUtil.format("规则[{}]执行结果为\n[{}]\n,规则表达式校验失败,请确认!", dragRuleId, checkParamMessage.toString()); throw new ServiceException(errorMessage); } } catch (ScriptException e) { String errorMessage = LogUtil.getStackTraceInfo(e); log.error(errorMessage); e.printStackTrace(); throw new ServiceException("规则开始执行eval函数解析表达式异常,异常信息为 : ".concat(errorMessage)); } } /** * 获取缓存拖拽规则信息 * @param dragRuleId * @return */ public DragRuleDTO getCacheDragRule(String dragRuleId) { // 返回信息 DragRuleDTO dragRule = null; // 通过缓存获取拖拽信息 Object dragRuleJsonObject = redisUtil.hget(RuleConstant.DRAG_RULES, dragRuleId); if (ObjectUtil.isNotNull(dragRuleJsonObject)) { // 转换json为对象信息 dragRule = JSONObject.parseObject(StrUtil.toString(dragRuleJsonObject), DragRuleDTO.class); } return dragRule; } /** * 获取缓存拖拽规则信息列表 * @param dragRuleIdList * @return */ public List<DragRuleDTO> getCacheDragRuleList(List<Object> dragRuleIdList) { // 空值判定 if (ObjectUtil.isEmpty(dragRuleIdList)) { return null; } // 返回参数信息 List<DragRuleDTO> dragRuleDTOList = new ArrayList<>(); // 通过缓存获取拖拽信息 List<Object> dragListObject = redisUtil.hmget(RuleConstant.DRAG_RULES, dragRuleIdList); if (ObjectUtil.isNotNull(dragListObject)) { for (Object dragObject : dragListObject) { // 转换json为对象信息 DragRuleDTO dragRule = JSONObject.parseObject(StrUtil.toString(dragObject), DragRuleDTO.class); dragRuleDTOList.add(dragRule); } } return dragRuleDTOList; } /** * 检查拖拽规则更新信息,一个规则修改,需要检查所有引入当前规则的规则链是否参数都合法 * @param dragRuleChain 规则链 * @param dragRuleDTO 当前修改规则 */ public void checkDragRuleUpdateLegal(TbSysDragChain dragRuleChain, DragRuleDTO dragRuleDTO) { // 获取规则链数组,首先使用 | 进行分割 String[] dragRuleChainArray = dragRuleChain.getDragRuleChain().split("\\|"); // 循环进行规则信息校验 for (String dragRuleChainStr : dragRuleChainArray) { // 获取规则链,多个规则使用逗号分割,然后封装成list List<String> dragRuleChainList = Arrays.asList(dragRuleChainStr.split(StrUtil.COMMA).clone()) .stream() .map(String::trim) .collect(Collectors.toList()); // 通过uri获取规则链入参缓存信息,cacheDragChainDTO Object dragRuleChainObj = redisUtil.hget(CommonDragRuleConstant.DRAG_RULE_CHAIN_FLAG, dragRuleChain.getUri()); if (ObjectUtil.isNull(dragRuleChainObj)) { throw new ServiceException(StrUtil.format("通过规则链uri[{}]获取缓存规则链信息失败,请确认!", dragRuleChain.getUri())); } DragChainDTO cacheDragChainDTO = JSONObject.parseObject(StrUtil.toString(dragRuleChainObj), DragChainDTO.class); // 如果规则链长度为1,则只有一个规则既当前规则,无需校验,如果修改规则在最后一个,没有下一个规则使用传递参数,则也无需校验 if (dragRuleChainList.size() == 1 || dragRuleChainList.indexOf(dragRuleDTO.getDragRuleId()) == dragRuleChainList.size() - 1) { return; } // 开始规则链校验,既当前修改规则在规则链的中间位置执行,需要完整校验规则链参数返回值是否合法 String errorMessage = null; JSONObject dragChainInParam = JSONObject.parseObject(cacheDragChainDTO.getMethodInParam()); if (ObjectUtil.isEmpty(dragChainInParam)) { errorMessage = StrUtil.format("拖拽规则修改,校验拖拽规则规则链[{}]控制层方法切入点[{}]入参参数为空,请确认!", cacheDragChainDTO.getControllerName(), cacheDragChainDTO.getMethodName()); throw new ServiceException(errorMessage); } // 入参结果集,第一次入参参数为规则链切入点的入参参数,之后循环规则链规则,为规则链返回传递参数+切入点入参参数,再与下一个规则入参进行对比 JSONObject resultInParam = dragChainInParam.clone(); for (int i = 0; i < dragRuleChainList.size(); i++) { String dragRuleId = dragRuleChainList.get(i); DragRuleDTO checkDragRuleDTO = null; if (!dragRuleDTO.getDragRuleId().equals(dragRuleId)) { checkDragRuleDTO = getCacheDragRule(dragRuleId); } else { checkDragRuleDTO = dragRuleDTO; } JSONObject dragRuleInParam = JSONObject.parseObject(checkDragRuleDTO.getInputParam()); // 进行参数校验,如果入参不满足规则链循环规则入参,直接抛出异常 Set<String> dragRuleInParamKeySet = dragRuleInParam.keySet(); Set<String> dragChainInParamKeySet = resultInParam.keySet(); Set<String> diffKeySet = dragRuleInParamKeySet.stream() .filter(element -> !dragChainInParamKeySet.contains(element)) .collect(Collectors.toSet()); if (ObjectUtil.isNotEmpty(diffKeySet)) { // Set集合数据转换为字符串,逗号分割 String dragRuleDiffKeyStr = diffKeySet.stream() .map(String::valueOf) .collect(Collectors.joining(StrUtil.COMMA)); errorMessage = StrUtil.format("规则修改后,影响规则链[{}]第[{}]个规则[{}]入参参数缺少[{}],修改失败!", cacheDragChainDTO.getDragRuleChainId(), i + 1, checkDragRuleDTO.getDragRuleId(), dragRuleDiffKeyStr); throw new ServiceException(errorMessage); } // 设置规则返回参数作为传递参数设定到切入点入参参数中,作为下一入参参数校验集合,传递参数为字符串,多个参数以逗号分割 if (StrUtil.isNotBlank(checkDragRuleDTO.getCrossParam())) { String[] crossParams = checkDragRuleDTO.getCrossParam().split(StrUtil.COMMA); for (int j = 0; j < crossParams.length; j++) { resultInParam.put(crossParams[j], StrUtil.EMPTY); } } } } } /** * 通过规则id获取规则信息 * @param dragRuleId */ public DragRuleDTO getDragRuleDTO(String dragRuleId) { Object dragRuleObj = redisUtil.hget(CommonDragRuleConstant.DRAG_RULES, dragRuleId); if (ObjectUtil.isNotNull(dragRuleObj)) { return JSONObject.parseObject(StrUtil.toString(dragRuleObj), DragRuleDTO.class); } throw new ServiceException(StrUtil.format("拖拽规则通过缓存获取规则Id[{}]不存在,请确认规则服务成功启动!", dragRuleId)); } /** * 英文模块描述转换为中文描述 * @param modelEnDesc */ public String modelEnDescToCHDesc(String modelEnDesc) { // 空值校验 if (StrUtil.isBlank(modelEnDesc)) { return null; } // 将模块描述进行小写转换 String enDescLowerCase = modelEnDesc.toLowerCase(); if (enDescLowerCase.contains("account")) { return "客户模块"; } else if (enDescLowerCase.contains("collateral")) { return "押品模块"; } else if (enDescLowerCase.contains("risk")) { return "风险模块"; } else if (enDescLowerCase.contains("credit")) { return "评级授信模块"; } else if (enDescLowerCase.contains("loan")) { return "用信放款模块"; } else if (enDescLowerCase.contains("management")) { return "公共管理模块"; } else if (enDescLowerCase.contains("afterloan")) { return "贷后模块"; } else if (enDescLowerCase.contains("warning")) { return "风险预警模块"; } else if (enDescLowerCase.contains("query")) { return "综合查询模块"; } else if (enDescLowerCase.contains("foreign")) { return "外围服务模块"; } else { return null; } } /** * 校验规则链节点类型 * * @param nodeTypeName */ public Boolean checkProcessFileType(String nodeTypeName) { // 空值校验 if (StrUtil.isBlank(nodeTypeName)) { return false; } // 当前校验节点类型 task,start,end,sequence switch (nodeTypeName) { case "SequenceFlowImpl": case "TaskImpl": case "EndEventImpl": case "StartEventImpl": case "ExclusiveGatewayImpl" : return true; default: return false; } } /** * 获取所有路由 * @param startEvent 开始节点 */ public List<List<FlowNode>> findRoutes(StartEvent startEvent) { List<List<FlowNode>> allRoutes = new ArrayList<>(); findRoutesRecursive(startEvent, new ArrayList<>(), allRoutes); return allRoutes; } /** * 递归获取所有路由 * @param currentNode 当前节点 * @param currentPath 路径 * @param allRoutes 路由集合 */ public void findRoutesRecursive(FlowNode currentNode, List<FlowNode> currentPath, List<List<FlowNode>> allRoutes) { // 如果当前节点是任务节点,将其添加到路径中 if ("task".equals(currentNode.getElementType().getTypeName())) { currentPath.add(currentNode); } // 获取当前节点的所有出序列流 Object[] outgoingFlows = currentNode.getOutgoing().toArray(); // 如果没有出序列流,说明到达了一个终点 if (ObjectUtil.isEmpty(outgoingFlows)) { allRoutes.add(new ArrayList<>(currentPath)); } else { // 遍历所有出序列流 for (Object flowObj : outgoingFlows) { SequenceFlow flow = (SequenceFlow) flowObj; FlowNode nextNode = flow.getTarget(); findRoutesRecursive(nextNode, currentPath, allRoutes); } } // 回溯 currentPath.remove(currentNode); } }
-
规则具体类通用父类
package cn.git.rules.drag.tran.base; import cn.git.common.result.Result; import javax.validation.Valid; /** * @description: 拖拽规则执行方法基类 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 10:33:09 */ public interface BaseDragRuleTran<T> { /** * 决策执行方法 * @param requestParam 请求参数 * @return */ Result process(@Valid T requestParam); }
-
规则详情实例,注意规则详情名称命名要求必须此格式,controller中可以调用开发人员使用获取拖拽规则规则名称方法获取
package cn.git.rules.drag.tran.demo; import cn.git.common.result.Result; import cn.git.rules.drag.DragRuleDesc; import cn.git.rules.drag.dto.TES0001DTO; import cn.git.rules.drag.tran.BaseDragRuleTran; import cn.hutool.core.date.DateUtil; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.validation.Valid; import java.math.BigDecimal; /** * @description: * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 10:13:27 */ @Validated @Component @DragRuleDesc(beanDesc = "借据申请日期金额校验") public class TES0001Tran implements BaseDragRuleTran<TES0001DTO> { /** * 决策执行方法 * * @param requestParam 请求参数 * @return */ @Override public Result<TES0001DTO.TES0001OUTDTO> process(@Valid TES0001DTO requestParam) { // 响应参数 TES0001DTO.TES0001OUTDTO outDTO = new TES0001DTO.TES0001OUTDTO(); outDTO.setLoanType("2"); outDTO.setLoanShape("1"); System.out.println(requestParam.getDragSign()); outDTO.setBorrowAmt(new BigDecimal(500)); outDTO.setContractDate(DateUtil.parseDate("2020-11-5")); outDTO.setContractTotalAmt(BigDecimal.valueOf(50000)); outDTO.setCheckDragRule0001ResultDate(DateUtil.parseDate("2020-11-5")); return Result.ok(outDTO); } }
-
参数信息,注意参数名称命名要求必须此格式
package cn.git.rules.drag.dto; import cn.git.rules.drag.DragRuleDesc; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; import java.math.BigDecimal; import java.util.Date; /** * @description: 拖拽规则测试方法入参出参信息 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 10:31:37 */ @Data @NoArgsConstructor @AllArgsConstructor public class TES0001DTO extends DragBaseDTO { @NotBlank(message = "TESTDragRule0001DTO规则入参客户编号不能为空!") @DragRuleDesc(fieldDesc = "客户编号") private String customerNum; @NotBlank(message = "TESTDragRule0001DTO规则入参合同编号不能为空!") @DragRuleDesc(fieldDesc = "合同编号") private String contractNum; @DragRuleDesc(fieldDesc = "是否绿色企业", sysCode = "YesornoInd") private String ifGreen; @DragRuleDesc(fieldDesc = "贷款形式", sysCode = "LoanFormCd", ifCustomDefine = true) private String loanShape; @DragRuleDesc(fieldDesc = "贷款发放时间", ifCustomDefine = true) private Date contractStartDate; /** * @description: 出参信息 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 10:31:25 */ @Data @NoArgsConstructor @AllArgsConstructor public static class TES0001OUTDTO { @DragRuleDesc(fieldDesc = "合同金额") private BigDecimal contractTotalAmt; @DragRuleDesc(fieldDesc = "借据金额") private BigDecimal borrowAmt; @DragRuleDesc(fieldDesc = "合同日期") private Date contractDate; @DragRuleDesc(fieldDesc = "测试响应日期") private Date checkDragRule0001ResultDate; @DragRuleDesc(fieldDesc = "贷款类型", sysCode = "LoanTypeCd") private String loanType; @DragRuleDesc(fieldDesc = "贷款形式", sysCode = "LoanFormCd") private String loanShape; } }
-
通用的枚举类型
package cn.git.rules.drag.enums; import lombok.Getter; /** * @description: 拖拽规则链模块枚举 * @program: bank_credit_sy * @author: lixuchun * @create: 2024_03_07 10:59:00 */ @Getter public enum DragChainModulesEnum { /** * 子模块数据源信息 */ MANAGE("MANAGEMENT_SERVER", "通用管理模块"), ACCOUNT("ACCOUNT_SERVER", "客户信息模块"), LOAN("LOAN_SERVER", "用信放款模块"), WORKFLOW("WORKFLOW_SERVER", "流程模块"), COLLATERAL("COLLATERAL_SERVER", "押品模块"), AFTER_LOAN("AFTERLOAN_SERVER", "贷后模块"), QUERY("QUERY_SERVER", "综合查询模块"), CREDIT("CREDIT_SERVER", "评级授信模块"), RISK("RISK_SERVER", "风险模块"), WARNING("WARNING_SERVER", "预警模块"); /** * 拖拽规则链服务名称 */ @Getter private String dragServerName; /** * 服务描述 */ @Getter private String serverDesc; /** * 构造函数 * @param dragServerName * @param serverDesc */ DragChainModulesEnum(String dragServerName, String serverDesc) { this.dragServerName = dragServerName; this.serverDesc = serverDesc; } }
package cn.git.rules.drag.enums; import lombok.Getter; /** * @description: 拖拽规则序列信息生成枚举类型 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-06-03 */ @Getter public enum DragRuleSequenceEnum { /** * 序列模块分类描述信息 */ ACCOUNT("account-server", "ACC","客户模块"), AFTER("afterloan-server", "AFT", "贷后模块"), COLL("collateral-server", "COL", "押品模块"), CREDIT("credit-server", "CRE", "授信模块"), LOAN("loan-server", "LOA", "用信模块"), MANAGE("management-server", "MAN", "公共模块"), RAT("rat-server", "RAT", "新评级模块"), STORE("store-server", "STO", "储备模块"), WARNING("warning-server", "WAR", "预警模块"), PRODUCT("product-server", "PRO", "产品工厂模块") ; /** * 模块 */ private String module; /** * 模块别名 */ private String moduleAlias; /** * 描述 */ private String desc; DragRuleSequenceEnum(String module, String moduleAlias, String desc) { this.module = module; this.moduleAlias = moduleAlias; this.desc = desc; } }
4.2.2 规则链规则feign调用实现
- Feign接口调用impl实现类
可以实现常用规则以及并行规则调用,显著加速规则执行速度、package cn.git.rules.manage; import cn.git.cache.api.BaseCacheApi; import cn.git.common.drag.CommonDragRuleConstant; import cn.git.common.drag.dto.DragChainDTO; import cn.git.common.drag.dto.DragRuleDTO; import cn.git.common.exception.ServiceException; import cn.git.common.result.Result; import cn.git.common.util.DragRequestContext; import cn.git.common.util.LogUtil; import cn.git.rules.drag.DragRuleServerUtil; import cn.git.rules.drag.tran.BaseDragRuleTran; import cn.git.rules.feign.DragRuleFeignApi; import cn.git.rules.thread.DragRuleThreadPool; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.validation.ConstraintViolationException; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Collectors; /** * @description: * @program: bank-credit-sy * @author: lixuchun * @create: 2024-02-22 02:32:24 */ @Slf4j @RestController public class DragRuleFeignApiImpl implements DragRuleFeignApi { /** * 拖拽规则参数入参出参dto包名 */ private static final String DRAG_DTO_PACKAGE = "cn.git.rules.drag.dto."; /** * dto尾缀 */ public static final String SUFFIX_DTO = "DTO"; @Autowired private DragRuleServerUtil dragRuleServerUtil; @Autowired private ApplicationContext applicationContext; @Autowired private BaseCacheApi baseCacheApi; /** * 校验拖拽规则feign方法 * * @param dragRuleJsonParam 请求对象 * @return Result 校验结果 */ @Override public Result checkDragRule(JSONObject dragRuleJsonParam) { // 获取X_sign参数 String dragSign = dragRuleJsonParam.getString("dragSign"); String dragToken = dragRuleJsonParam.getString("dragToken"); // 获取规则链表 多个规则用逗号分割 String dragRuleDTOJSON = dragRuleJsonParam.getString(CommonDragRuleConstant.DRAG_RULE_CHAIN_DTO); DragChainDTO dragChainDTO = JSON.parseObject(dragRuleDTOJSON, DragChainDTO.class); // 获取规则链信息,使用竖线进行分割,规则链格式: dragRuleId1,dragRuleId2,dragRuleId3|dragRuleId4,dragRuleId5 String[] dragRuleChainArray = dragChainDTO.getDragRuleChain().split("\\|"); // 将竖线分割规则链再次使用逗号分割,获取所有规则id信息 List<Object> allDragRuleList = Arrays.stream(dragRuleChainArray).map( dragRuleChainStr -> dragRuleChainStr.split(StrUtil.COMMA) ).flatMap(Arrays::stream).collect(Collectors.toList()); // 设置入参json的克隆参数 JSONObject dragRuleJsonParamClone = dragRuleJsonParam.clone(); // 通过规则id,获取全部规则详情信息,并且设置map信息,方便后续方法使用 List<DragRuleDTO> allCacheDragRuleList = dragRuleServerUtil.getCacheDragRuleList(allDragRuleList); Map<String, DragRuleDTO> cacheDragRuleDTOMap = allCacheDragRuleList.stream().collect(Collectors.toMap( DragRuleDTO::getDragRuleId, Function.identity(), (k1, k2) -> k1) ); // 获取规则中码值列表 List<String> sysCodeTypeList = allCacheDragRuleList.stream() .flatMap(dragRuleList -> Optional.ofNullable(dragRuleList.getCustomOptionParamList()).orElse(Collections.emptyList()).stream() ) .flatMap(Collection::stream) .map(DragRuleDTO.CustomOptionParam::getSysCode) .filter( // 过滤掉 getSysCode 为 null 的情况 Objects::nonNull ) .filter( // 过滤掉空字符串 StrUtil::isNotBlank ) .collect(Collectors.toList()); // 获取全部带码值的参数码值名称信息 Map<String, String> typeCdValueKeyMap = baseCacheApi.getTypeCdValueKeyMap(sysCodeTypeList); // 如果规则拆分为1个,则直接执行单条规则,如果拆分出多个,则需要并行执行规则信息 if (dragRuleChainArray.length == 1) { return executeDragRule(dragRuleChainArray[0], cacheDragRuleDTOMap, typeCdValueKeyMap, dragRuleJsonParamClone, dragSign); } else { // 提取请求上下文信息 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); // 创建多个 CompletableFuture 任务 List<CompletableFuture<Result>> futures = CollUtil.newArrayList(dragRuleChainArray).stream() .map(dragRuleChain -> CompletableFuture.supplyAsync(() -> { try { // 在异步任务中恢复请求上下文,保证非web请求,请求request上下文信息不完整报错 if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes); } // 设置请求上下文信息,用于处理异步线程请求头丢失问题 DragRequestContext.DragTokenInfo dragTokenInfo = new DragRequestContext.DragTokenInfo(); dragTokenInfo.setSign(dragSign); dragTokenInfo.setToken(dragToken); DragRequestContext.setInfo(dragTokenInfo); // 设置请求上下文信息 return executeDragRule(dragRuleChain, cacheDragRuleDTOMap, typeCdValueKeyMap, dragRuleJsonParamClone, dragSign); } finally { // 清除请求上下文信息 DragRequestContext.clear(); // 清除请求上下文 if (requestAttributes != null) { RequestContextHolder.resetRequestAttributes(); } } }, DragRuleThreadPool.DRAG_RULE_THREAD_POOL) // 异常处理 .exceptionally(exception -> { String errorMessage = StrUtil.format("X_sign[{}],异常信息[{}]", dragSign, LogUtil.getStackTraceInfo(new Exception(exception))); log.error(errorMessage); // 返回封装异常信息 return Result.error(StrUtil.format("并行规则exceptionally异常处理,执行 X_sign为[{}]", dragSign)); })) .collect(Collectors.toList()); // 创建一个CompletableFuture,用于等待所有任务完成 CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); // 阻塞等待所有任务完成 try { allFutures.join(); } catch (Exception e) { e.printStackTrace(); String errorMessage = StrUtil.format("并行规则join异常处理,x_sign[{}],具体信息为[{}]", dragSign, LogUtil.getStackTraceInfo(e)); log.error(errorMessage); return Result.error(StrUtil.format("并行规则join异常处理,执行X_sign为[{}]", dragSign)); } // 获取所有任务的结果 List<Result> executeResultList = futures.stream() .map(CompletableFuture::join) // 过滤掉因异常返回的 .filter(Objects::nonNull) .collect(Collectors.toList()); // 结果分析,找到error规则则返回错误规则提示信息,没有错误则表示所有规则正常通过 return executeResultList.stream().filter(result -> { return result.getCode() != 1; }).findFirst().orElseGet(() -> Result.ok("拖拽规则规则验证通过")); } } /** * 执行规则 * * @param dragRuleChain 规则链 * @param cacheDragRuleDTOMap 规则缓存 * @param typeCdValueKeyMap 类型编码与值对应关系 * @param dragRuleJsonParamClone 规则入参 * @return */ public Result executeDragRule(String dragRuleChain, Map<String, DragRuleDTO> cacheDragRuleDTOMap, Map<String, String> typeCdValueKeyMap, JSONObject dragRuleJsonParamClone, String dragSign) { // 获取规则链中的规则id集合 List<String> dragRuleIdList = CollUtil.newArrayList(dragRuleChain.split(StrUtil.COMMA)); // 循环执行规则链的规则信息 for (String dragRuleId : dragRuleIdList) { DragRuleDTO dragRule = cacheDragRuleDTOMap.get(dragRuleId); String dragRuleName = dragRule.getDragRuleId(); // 通过规则名称获取规则信息,然后获取规则入参自定义参数,加入规则入参参数中 List<DragRuleDTO.DragRuleParamsDesc> inputCustomParamDescList = dragRule.getInputCustomParamDescList(); if (ObjectUtil.isNotEmpty(inputCustomParamDescList)) { // 过滤自未定义入参参数信息 List<DragRuleDTO.DragRuleParamsDesc> filterParamList = inputCustomParamDescList.stream().filter(inputCustomParam -> { return StrUtil.isNotBlank(inputCustomParam.getCustomDefineParamValue()); }).collect(Collectors.toList()); // 通过参数类型进行参数赋值 if (ObjectUtil.isNotEmpty(filterParamList)) { for (DragRuleDTO.DragRuleParamsDesc inputCustomParam : filterParamList) { // 日期类型,将日期参数由yyyy-MM-dd格式修改为 currentTimeMillis格式,类型设置为Long类型 if ("Date".equals(inputCustomParam.getParamType())) { Long dateTimeMillis = DateUtil.parse(inputCustomParam.getCustomDefineParamValue()).getTime(); dragRuleJsonParamClone.put(inputCustomParam.getParamName(), dateTimeMillis); } else if ("BigDecimal".equals(inputCustomParam.getParamType())) { // 数字类型 BigDecimal dragRuleJsonParamClone.put(inputCustomParam.getParamName(), Integer.valueOf(inputCustomParam.getCustomDefineParamValue())); } else if ("Long".equals(inputCustomParam.getParamType())) { // 数字类型 Long dragRuleJsonParamClone.put(inputCustomParam.getParamName(), Long.valueOf(inputCustomParam.getCustomDefineParamValue())); } else if ("Integer".equals(inputCustomParam.getParamType())) { // 数字类型 Integer dragRuleJsonParamClone.put(inputCustomParam.getParamName(), Integer.valueOf(inputCustomParam.getCustomDefineParamValue())); } else if ("String".equals(inputCustomParam.getParamType())) { dragRuleJsonParamClone.put(inputCustomParam.getParamName(), inputCustomParam.getCustomDefineParamValue()); } else { // 类型校验不同通过 return Result.error(StrUtil.format("规则[{}]自定义参数[{}]类型校验不通过,请确认是否正确!", dragRuleName, inputCustomParam.getParamName())); } } } } // 规则入参参数转换为dto对象 String fullClassName = DRAG_DTO_PACKAGE.concat(dragRuleName).concat(SUFFIX_DTO); Object reqObject; try { // 通过类名获取类类型,再获取参数实例信息 Class reqClazz = Class.forName(fullClassName); reqObject = JSONObject.parseObject(dragRuleJsonParamClone.toJSONString(), reqClazz); } catch (ClassNotFoundException e) { return Result.error(StrUtil .format("拖拽规则[{}]规则校验名称DTO[{}]解析失败,请确认是否正确!", dragRuleName, fullClassName)); } // 获取规则执行类 BaseDragRuleTran dragRuleTran; try { dragRuleTran = applicationContext.getBean(dragRuleName.concat("Tran"), BaseDragRuleTran.class); } catch (Exception e) { String errorMessage = StrUtil.format("拖拽规则[{}]获取规则执行类Tran失败,请确认参数是否正确!", dragRuleName); return Result.error(errorMessage); } // 开始执行规则 Result result = null; try { result = dragRuleTran.process(reqObject); } catch (ConstraintViolationException e) { e.printStackTrace(); String errorMessage = StrUtil .format("拖拽规则[{}]请求参数校验异常,异常信息为[{}]", dragRuleName, LogUtil.getStackTraceInfo(e)); log.error(errorMessage); return Result.error(e.getMessage()); } catch (ServiceException e) { e.printStackTrace(); // 抛出自定义异常信息 log.error("拖拽规则自定义异常信息[{}], x_sign为[{}]", e.getMessage(), dragSign); String errorMessage = StrUtil.format("拖拽规则[{}]请求执行异常,异常信息为[{}]", dragRuleName, e.getMessage()); return Result.error(errorMessage); } catch (Exception e) { e.printStackTrace(); // 其他异常信息 String errorMessage = LogUtil.getStackTraceInfo(e); String dragSignMessage = StrUtil .format("拖拽规则[{}]请求执行异常,x_sign[{}],异常信息为[{}]", dragRuleName, dragSign, errorMessage); log.error(dragSignMessage); return Result.error(dragSignMessage); } // 检查是否有传递参数,如果有则将传递参数设置为下一规则的入参 if (StrUtil.isNotBlank(dragRule.getCrossParam())) { String[] crossParams = dragRule.getCrossParam().split(StrUtil.COMMA); for (String crossParam : crossParams) { // 获取返回参数中传输数据信息 JSONObject rspJsonObject = JSON.parseObject(JSONObject.toJSONString(result.getData())); if (rspJsonObject.containsKey(crossParam)) { dragRuleJsonParamClone.put(crossParam, rspJsonObject.get(crossParam)); } else { return Result.error(StrUtil .format("拖拽规则[{}]从响应参数中获取传递参数获取为空,请确认[{}]传递参数!", dragRuleName, crossParam)); } } } // 自定义比较方法,执行自定义比较逻辑 List<List<DragRuleDTO.CustomOptionParam>> customOptionParamsList = dragRule.getCustomOptionParamList(); if (ObjectUtil.isNotEmpty(customOptionParamsList)) { // 获取返回参数中传输数据信息 JSONObject rspJsonObject = JSON.parseObject(JSONObject.toJSONString(result.getData())); if (ObjectUtil.isNull(rspJsonObject)) { return Result.error(StrUtil.format("拖拽规则[{}]")); } // 参数进行转化 for (List<DragRuleDTO.CustomOptionParam> paramList : customOptionParamsList) { // 进行自定义操作参数比对,可以比对日期与数值与字符串 try { dragRuleServerUtil.checkOptionCompare(rspJsonObject, paramList, dragRuleName, typeCdValueKeyMap, dragSign); } catch (ServiceException e) { return Result.error(e.getMessage()); } catch (Exception e) { e.printStackTrace(); String errorMessage = LogUtil.getStackTraceInfo(e); log.error(StrUtil.format("规则[{}]自定义操作参数比对异常,异常信息为[{}]", dragRuleName, errorMessage)); return Result.error(StrUtil.format("自定义拖拽规则执行异常,x_sign为[{}]", dragSign)); } } } } return Result.ok("拖拽规则规则验证通过"); } }
4.3 数据库表结构
主要包含三张表,分别为拖拽规则表,规则链表以及产品规则链表
--拖拽规则表
DROP TABLE TB_SYS_DRAG_RULE
CREATE TABLE TB_SYS_DRAG_RULE (
DRAG_RULE_ID varchar(32) NOT NULL,
RULE_DESC varchar(100),
CROSS_PARAM varchar(200),
CUSTOM_OPTION_PARAM varchar(3000),
ctime date DEFAULT sysdate,
mtime date DEFAULT sysdate,
is_del varchar(1) DEFAULT 0,
primary key(DRAG_RULE_ID)
)
COMMENT ON TABLE MANAGE.TB_SYS_DRAG_RULE IS '拖拽规则表';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.DRAG_RULE_ID IS '规则id,名称,同组件名称eg:LOANTest0001';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.RULE_DESC IS '规则描述';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.CROSS_PARAM IS '多个规则规则链传递值,多个以英文逗号分割';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.CUSTOM_OPTION_PARAM IS '自定义参数比较规则信息,多个用逗号隔开, 响应信息返回值 loanAmt<= 5000, eg:{"optionDesc": "描述信息", "customKey": "loanAmt","optionFlag": ">=","compareValue": 5000}';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.CTIME IS '创建时间';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.MTIME IS '修改时间';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_RULE.IS_DEL IS '是否删除标识';
--规则链表
DROP TABLE TB_SYS_DRAG_CHAIN
CREATE TABLE TB_SYS_DRAG_CHAIN (
DRAG_RULE_CHAIN_ID varchar(200),
CONTROLLER_DESC varchar(300),
CONTROLLER_NAME varchar(100),
METHOD_NAME varchar(100),
METHOD_DESC varchar(500),
METHOD_IN_PARAM varchar(3000),
URI varchar(500),
DRAG_RULE_CHAIN varchar(500),
CHAIN_FILE clob,
CHAIN_FILE_NAME varchar(50),
ctime date DEFAULT sysdate,
mtime date DEFAULT sysdate,
is_del varchar(1) DEFAULT 0,
primary key(DRAG_RULE_CHAIN_ID)
)
COMMENT ON TABLE MANAGE.TB_SYS_DRAG_CHAIN IS '拖拽规则链表';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.DRAG_RULE_CHAIN_ID IS '规则链id';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.CONTROLLER_DESC IS '切入点controller描述信息';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.CONTROLLER_NAME IS '切入点controller名称';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.METHOD_NAME IS '规则链调用方法名称';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.METHOD_DESC IS '规则链调用方法描述';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.METHOD_IN_PARAM IS '方法入参参数';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.URI IS '作用方法地址';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.DRAG_RULE_CHAIN IS '规则链详情,多个规则按序逗号分割';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.CHAIN_FILE IS '文件内容base64字符串信息';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.CHAIN_FILE_NAME IS '文件名称';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.CTIME IS '创建时间';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.MTIME IS '修改时间';
COMMENT ON COLUMN MANAGE.TB_SYS_DRAG_CHAIN.IS_DEL IS '是否删除';
--产品规则信息
DROP TABLE TB_SYS_PRODUCT_DRAG_CHAIN
CREATE TABLE TB_SYS_PRODUCT_DRAG_CHAIN (
PRODUCT_CHAIN_ID varchar(200),
PRODUCT_CD varchar(50),
URI varchar(500),
DRAG_RULE_CHAIN varchar(500),
CHAIN_FILE clob,
CHAIN_FILE_NAME varchar(50),
ctime date DEFAULT sysdate,
mtime date DEFAULT sysdate,
is_del varchar(1) DEFAULT 0,
primary key(PRODUCT_CHAIN_ID)
)
COMMENT ON TABLE MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN IS '拖拽规则作用产品定义信息表';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.PRODUCT_CHAIN_ID IS '主键id';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.PRODUCT_CD IS '产品cd';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.URI IS '产品作用点请求路径';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.DRAG_RULE_CHAIN IS '规则链,具体规则信息,多个使用逗号分割';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.CHAIN_FILE IS '规则文件';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.CHAIN_FILE_NAME IS '规则文件名称';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.CTIME IS '创建时间';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.MTIME IS '修改时间';
COMMENT ON COLUMN MANAGE.TB_SYS_PRODUCT_DRAG_CHAIN.IS_DEL IS '删除标识';
5. 管理页面展示
5.1 规则管理page页面介绍
此页面为规则分页查询,注意操作栏落库按钮代表此规则是否存入数据库,未存入的不能进行后续规则链操作,已经入库的可以进行修改以及清空
管理页面可以观察当前所有规则信息,需要使用管理员账号登录 310907 ,管理页面位置位于 系统管理 => 参数管理 => 规则管理
5.2 落库修改新增操作页面介绍
规则校验部分,主要是针对出参参数信息,可以对出参信息进行自定义校验,需要注意需要满足正常操作符规则
-
入参参数:入参参数即为规则自定义的入参参数信息,此例为 TES0001DTO 参数信息
-
自定义入参:自定义入参参数可以多选,将会逗号分割送入Tran规则类中,设定码值前端将反显码值信息
-
出参参数:出参参数即为规则自定义的出参参数信息,此例为 TES0001OUTDTO 出参参数信息
-
中转参数:中转参数,必须为OUTDTO中的参数,此值没有类型要求,会传递到下一规则中,供下一规则使用
注意:如果规则中的中转参数下一规则入参使用,那么在规则链固定好后,此中转参数不能随意删除,必须先解除规则链,再删除中转参数
-
自定义校验参数:此参数必须为OUTDTO中的数字类型参数,或者为日期类型参数。日期以及数字类型参数会有校验,如果为码值下拉则可以进行多选
设置常用比较格式如下所列:
表达式案例 解释 ( 合同金额 + 借据金额 ) / 2 < 合同金额 / 2000 复杂数字计算,包含参数比较,设置自定义参数值 合同金额 < 50000 简单数字类型比较 合同日期 < 合同到期日期 日期类型参数比较 合同日期 < 2023-05-03 日期类型自定义参数比较 (贷款类型 == ‘新增’ && 合同金额 > 500) || 借据金额 == ‘’ 与或运算,空值判定
5.3 规则链列表页面
此页面为规则调用入口列表页面,位于 系统管理 => 参数管理 => 规则链管理,管理了可以进行规则调用的入口接口列表页面,同样如果规则链的操作栏配置落库后,才能进行修改删除操作,未配置落库则不能进行操作
5.4 拖拽规则页面
此页面可以进行规则链详情配置,展示信息包含两部分
-
首先是规则链基本信息,包含id信息(controller.method)以及规则链描述信息,还有规则链入参信息
-
第二部分为规则拖拽部分,可以进行规则链拖拽选择,可以自己定义规则执行逻辑,双击规则可以进行规则转换
也可以设置并行规则,并行规则如下所示将会分成三个规则链,并且三个规则链(TES0001,TES0002|TES0003|TES0004)将会并发执行,最后全部通过则算校验通过,否则从多个结果中选择一个错误结果弹出。
-
双击后展示规则选择页面,主要可以观察当前规则链包含哪些规则信息,并且选择规则时候,会提示当前规则调用入口(controller.method)方法是否满足当前规则,如果不满足条件,会提示出为何不满足
-
在下面的规则列表中可以选择规则信息,点击规则id,可以进行规则信息查看,展示信息与规则管理页面相同
-
最终保存的规则链信息如下:规则调用会按照顺序执行 TESTDragRule0001 -> TESTDragRule0002 -> TESTDragRule0003 -> TESTDragRule0004
注意:上图的并行规则则最终保存效果则是
TES0001,TES0002|TES0003|TES0004
多个规则链用竖线分割,表示三个并行规则
5.5 产品规则
产品规则位于 系统管理 => 参数管理 => 业务品种管理 嵌套的tab页面中,代码部分与常有规则链一致,可以对固定产品进行规则配置,但是有以下前提
- 产品规则对应作用接口需要配置好默认的执行规则
- 产品规则接口中必须要有productCd参数,以便于规则判定时获取产品对应的规则链,然后执行对应的规则链,在调用过程中,如果接口inVO带有productCd参数,并且对应规则配置了响应的productCd产品对应规则链,则执行产品规则链,未匹配到productCd,则执行默认规则链
5.5.1 产品规则列表
- 按照标注按钮可以找到对应的产品规则链
- 点击查看按钮,进入到当前规则链详情信息,如果产品规则未配置,则可以进行产品规则新增,如果已经配置,则产品规则展示规则id,多个id逗号分割,并且可以进行修改以及删除操作
- 点击修改或者新增按钮,进入到规则链编辑页面,与之前规则链编辑页面一致,编辑保存会有规则是否合法校验,校验通过则可以进行规则链调用
产品规则同样可以设置并行规则,如下所示:
5.5.2 编辑规则页面
此页面展示当前规则链可以选择哪些规则进行设置,并且提示规则是否合法,如果不合法则显示具体哪个规则对应哪个字段不满足规则入参条件
6. 测试部分
规则链配置成功之后,直接在规则链入口对应的controller.method进行调用即可 DragRuleTestController.test0001
执行结果展示如下: