背景
在流程图中,时常会涉及到分支的判断,进入判断逻辑后,我们一般只会从分支的一个出口出来,然后往下继续执行,这在流程引擎中,称之为排他网关(Exclusive Gateway),排他网关用于在流程执行过程中做出决策,基于流程中的条件来选择一个唯一的路径继续执行。
下面我们以登录为例,在登录流程中,会依次执行下列流程:
- 账号定位:根据入参定位账号信息
- 密码检查:根据获取的账号信息,以及输入的密码,比对密码信息是否正确
- 状态检查:检查用户状态,判断是否被盗、冻结等
- 登录核身判断:
- 状态检查通过,颁发登录token
- 状态检查不通过,设置核身相关信息,如核身类型等
- 创建token:状态检查通过,则下发登录态token,若不通过,下发核身token
具体的流程图如下所示:
设计思路
原先的执行方式
- 根据流程id,解析流程配置文件,获取ActivitiFlow
- 遍历ActivitiFlow的所有节点,依次执行节点活动
修改后的执行方式
- 根据流程id,解析流程配置文件,获取ActivitiFlow
- 执行流程节点活动
- 对于ServiceTask,转为IServiceTask执行execute方法
- 对于排他网关,转为IExeclusiveGateway执行parseCredential方法
- 节点执行完毕后,根据sequenceFlow连线信息,获取下一个节点(对于网关,会加上凭证credential的判断,来决策下一个节点)
实现
首先,我们根据上述流程图,定义流程文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <activiti id="register_flow"> <!--节点定义--> <startEvent id="startEvent"/> <serviceTask id="userLocate" name="userLocate" description="账号定位" class="com.yang.business.activiti.login.UserLocateActivity"/> <serviceTask id="passwordCheck" name="passwordCheck" description="密码检查" class="com.yang.business.activiti.login.PasswordCheckActivity"/> <serviceTask id="userAccountStatusCheck" name="userAccountStatusCheck" description="账号状态检查" class="com.yang.business.activiti.login.UserAccountStatusCheckActivity"/> <exclusiveGateway id="needBuildIdentityAction" name="needBuildIdentityAction" description="是否需要转核身" class="com.yang.business.activiti.login.NeedTransfer2IdentityExclusiveGateway"/> <serviceTask id="buildIdentityAction" name="buildIdentityAction" description="核身行为构建" class="com.yang.business.activiti.login.BuildIdentityActionActivity"/> <serviceTask id="buildUserToken" name="buildUserToken" description="创建用户token" class="com.yang.business.activiti.common.BuildUserTokenActivity"/> <endEvent id="endEvent"/>
<!--节点执行顺序编排--> <sequenceFlow source="startEvent" target="userLocate"/> <sequenceFlow source="userLocate" target="passwordCheck"/> <sequenceFlow source="passwordCheck" target="userAccountStatusCheck"/> <sequenceFlow source="userAccountStatusCheck" target="needBuildIdentityAction"/> <sequenceFlow source="needBuildIdentityAction" target="buildIdentityAction" credential="identity"/> <sequenceFlow source="needBuildIdentityAction" target="buildUserToken" credential="login"/> <sequenceFlow source="buildIdentityAction" target="buildUserToken"/> <sequenceFlow source="buildUserToken" target="endEvent"/> </activiti>
|
对于节点与节点之间的联系,是通过sequenceFlow来实现的,而目前我们的sequenceFlow只有source和target这两个信息,前者表示来源,后者表示去处,此时的sequenceFlow是没有阻塞条件的,当出现一个source有多个target时,我们可以在sequenceFlow加上阻塞条件,用于根据上下文信息,决策需要走哪一条路径。修改后的sequenceFlow如下,我们新增一个crendential字段,用于表示当上下文中凭证信息为相关值时,才可以走这条连线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.yang.core.infrastructure.flow.activiti.model;
import lombok.Data;
import java.io.Serializable;
@Data public class SequenceFlowNode implements Serializable {
private String source;
private String target;
private String credential;
}
|
在上一节中,我们定义的activitiFlow流程模型结构如下,此时的activitiFlow对sequenceFlow不感知,因为之前涉及的时候,不存在一个来源对应多个去处的连线,所以获取节点顺序,依次执行即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.yang.core.infrastructure.flow.activiti.model;
import java.util.ArrayList; import java.util.List;
public class ActivitiFlow { private String id;
private List<FlowNode> flowNodeList = new ArrayList<>();
public void addFlowNode(FlowNode flowNode) { this.flowNodeList.add(flowNode); }
public List<FlowNode> getFlowNodeList() { return this.flowNodeList; }
public String getId() { return id; }
public void setId(String id) { this.id = id; } }
|
当前模型显然无法支持我们的排他网关,因为排他网关的入口是一个,但是出口可以是多个的,而List结构,只能支持一对一的顺序串联,所以我们对ActivitiFlow模型进行改造,使其能支持我们的一对多逻辑,同时,对模型进行充血,将一些决策逻辑收敛到模型中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.yang.core.infrastructure.flow.activiti.model;
import lombok.Data; import org.apache.commons.lang3.StringUtils;
import java.util.List; import java.util.Map;
@Data public class ActivitiFlow { private String id;
private FlowNode startNode;
private FlowNode endNode;
private List<SequenceFlowNode> sequenceFlowNodeList;
private Map<String, FlowNode> nodeId2FlowNodeMap;
public FlowNode chooseNextNode(FlowNode curNode, String credential) { String sourceNodeId = curNode.getId(); String targetNodeId = null; for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { if (!sequenceFlowNode.getSource().equals(sourceNodeId)) { continue; } if (StringUtils.isEmpty(sequenceFlowNode.getCredential())) { continue; } if (sequenceFlowNode.getCredential().equals(credential)) { targetNodeId = sequenceFlowNode.getTarget(); break; } } return nodeId2FlowNodeMap.get(targetNodeId); }
public FlowNode getNextNode(FlowNode curNode) { String sourceNodeId = curNode.getId(); String targetNodeId = null; for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { if (sequenceFlowNode.getSource().equals(sourceNodeId)) { targetNodeId = sequenceFlowNode.getTarget(); break; } } return nodeId2FlowNodeMap.get(targetNodeId); } }
|
因为ActivitiFlow的模型结构有所变更,因此,我们需要修改对应的解析器,以适应当前结构。首先修改XmlActivitiFileParser的parseSequenceFlowNode方法,增加对credential字段的解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /** * 解析流程执行顺序节点 * @param attributes * @return */ private SequenceFlowNode parseSequenceFlowNode(NamedNodeMap attributes) { Node sourceNode = attributes.getNamedItem("source"); Node targetNode = attributes.getNamedItem("target"); if (sourceNode != null && targetNode != null) { SequenceFlowNode sequenceFlowNode = new SequenceFlowNode(); sequenceFlowNode.setSource(sourceNode.getNodeValue()); sequenceFlowNode.setTarget(targetNode.getNodeValue());
Node credentialNode = attributes.getNamedItem("credential"); if (credentialNode != null) { sequenceFlowNode.setCredential(credentialNode.getNodeValue()); } return sequenceFlowNode; } return null; }
|
其次,修改AbstractActivitiParser,使其能正确解析ActivitiFlow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| package com.yang.core.infrastructure.flow.activiti.parser;
import com.yang.api.common.ErrorCode; import com.yang.api.common.exception.UicException; import com.yang.core.infrastructure.flow.activiti.enums.FlowNodeType; import com.yang.core.infrastructure.flow.activiti.model.ActivitiFlow; import com.yang.core.infrastructure.flow.activiti.model.FlowNode; import com.yang.core.infrastructure.flow.activiti.model.SequenceFlowNode; import com.yang.core.infrastructure.utils.SpringContextUtil;
import java.util.*; import java.util.function.Function; import java.util.stream.Collectors;
public abstract class AbstractActivitiFileParser implements IActivitiFileParser{
/** * 填充流程节点内容 * @param flowNode * @param flowNodeType */ protected void richFlowNode(FlowNode flowNode, FlowNodeType flowNodeType) { if (flowNodeType != FlowNodeType.SERVICE_TASK && flowNodeType != FlowNodeType.EXCLUSIVE_GATEWAY) { return; } try { // 填充target对象 Class<?> aClass = Class.forName(flowNode.getClassPath()); Object target = SpringContextUtil.getBeanByType(aClass); flowNode.setTarget(target); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
/** * 根据连线顺序,对流程节点进行排序,构建相应的流程flow * @param fileName * @param flowNodeList * @param sequenceFlowNodeList * @return */ protected ActivitiFlow buildActivitiFlow(String fileName, List<FlowNode> flowNodeList, List<SequenceFlowNode> sequenceFlowNodeList) { String startNodeId = getStartNodeId(sequenceFlowNodeList); String endNodeId = getEndNodeId(sequenceFlowNodeList); Map<String, FlowNode> nodeId2FlowNodeMap = flowNodeList.stream() .collect(Collectors.toMap(FlowNode::getId, Function.identity()));
ActivitiFlow activitiFlow = new ActivitiFlow(); activitiFlow.setId(fileName); activitiFlow.setStartNode(nodeId2FlowNodeMap.get(startNodeId)); activitiFlow.setEndNode(nodeId2FlowNodeMap.get(endNodeId)); activitiFlow.setNodeId2FlowNodeMap(nodeId2FlowNodeMap); activitiFlow.setSequenceFlowNodeList(sequenceFlowNodeList); return activitiFlow; }
/** * 获取结束节点id * @param sequenceFlowNodeList * @return */ private String getEndNodeId(List<SequenceFlowNode> sequenceFlowNodeList) { List<String> oneNodeIds = filterCountOneNodeId(sequenceFlowNodeList); Set<String> oneNodeIdSet = new HashSet<>(oneNodeIds); String endNodeId = null; for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { String target = sequenceFlowNode.getTarget(); if (oneNodeIdSet.contains(target)) { endNodeId = target; break; } } return endNodeId; }
/** * 获取开始节点id * @param sequenceFlowNodeList * @return */ private String getStartNodeId(List<SequenceFlowNode> sequenceFlowNodeList) { List<String> oneNodeIds = filterCountOneNodeId(sequenceFlowNodeList); Set<String> oneNodeIdSet = new HashSet<>(oneNodeIds); String startNodeId = null; for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { String source = sequenceFlowNode.getSource(); if (oneNodeIdSet.contains(source)) { startNodeId = source; break; } } return startNodeId; }
/** * 过滤计数次数只有1次的nodeId,只有startEvent和endEvent会计数1次,其他都是两次,如果是网关,那么次数会更多 * @param sequenceFlowNodeList * @return */ private List<String> filterCountOneNodeId(List<SequenceFlowNode> sequenceFlowNodeList) { Map<String, Integer> countNodeIdMap = new HashMap<>(); for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { Integer sourceCount = countNodeIdMap.getOrDefault(sequenceFlowNode.getSource(), 0); countNodeIdMap.put(sequenceFlowNode.getSource(), sourceCount + 1);
Integer targetCount = countNodeIdMap.getOrDefault(sequenceFlowNode.getTarget(), 0); countNodeIdMap.put(sequenceFlowNode.getTarget(), targetCount + 1); } List<String> countOneNodeIds = new ArrayList<>(); countNodeIdMap.forEach((nodeId, count) -> { if (count == 1) { countOneNodeIds.add(nodeId); } }); if (countOneNodeIds.size() > 2) { throw new UicException(ErrorCode.ACTIVITI_PARSER_ERROR); } return countOneNodeIds; }
}
|
最后,修改ActivitiManager类,对排他网关做定制处理,根据网关返回的凭证值,决策出下一个节点是哪一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| package com.yang.core.infrastructure.flow.activiti;
import com.yang.api.common.ErrorCode; import com.yang.api.common.exception.UicException; import com.yang.core.infrastructure.flow.activiti.enums.FlowNodeType; import com.yang.core.infrastructure.flow.activiti.function.IExclusiveGateway; import com.yang.core.infrastructure.flow.activiti.function.IServiceTask; import com.yang.core.infrastructure.flow.activiti.model.ActivitiFlow; import com.yang.core.infrastructure.flow.activiti.model.FlowNode; import com.yang.core.infrastructure.flow.activiti.parser.IActivitiFileParser; import com.yang.core.infrastructure.flow.activiti.request.ActivitiEngineRequest; import com.yang.core.infrastructure.flow.activiti.response.ActivitiEngineResponse; import com.yang.core.infrastructure.utils.SpringContextUtil;
import java.util.HashMap; import java.util.Map;
public class ActivitiFlowManager { private static Map<String, ActivitiFlow> id2ActivitiFlowCache = new HashMap<>();
public static ActivitiFlow getActivitiFlow(String flowName) { ActivitiFlow activitiFlow = id2ActivitiFlowCache.get(flowName); if (activitiFlow != null) { return activitiFlow; } IActivitiFileParser iActivitiFileParser = SpringContextUtil.getBeanByType(IActivitiFileParser.class); synchronized (flowName.intern()) { activitiFlow = id2ActivitiFlowCache.get(flowName); if (activitiFlow != null) { return activitiFlow; } activitiFlow = iActivitiFileParser.parseActivitiFlow(flowName); if (activitiFlow == null) { throw new UicException(ErrorCode.ACTIVITI_NOT_FOUND_ERROR); } id2ActivitiFlowCache.put(flowName, activitiFlow); } return activitiFlow; }
/** * 流程引擎执行方法 * @param flowName * @param activitiEngineRequest * @return * @param <Request> * @param <Response> */ public static <Request, Response> ActivitiEngineResponse<Response> startEngine(String flowName, ActivitiEngineRequest<Request> activitiEngineRequest) { ActivitiFlow activitiFlow = getActivitiFlow(flowName); ActivitiEngineResponse<Response> activitiEngineResponse = new ActivitiEngineResponse<>(); FlowNode curNode = activitiFlow.getStartNode(); while (curNode != null) { FlowNodeType flowNodeType = curNode.getFlowNodeType(); FlowNode nextFlow = null; switch (flowNodeType) { case START_EVENT: break; case END_EVENT: break; case SERVICE_TASK: executeServiceTask(activitiEngineRequest, activitiEngineResponse, curNode); break; case EXCLUSIVE_GATEWAY: nextFlow = executeExclusiveGateway(activitiEngineRequest, activitiEngineResponse, curNode, activitiFlow); break; } if (nextFlow != null) { curNode = nextFlow; } else { curNode = activitiFlow.getNextNode(curNode); } } return activitiEngineResponse; }
private static <Request, Response> FlowNode executeExclusiveGateway(ActivitiEngineRequest<Request> activitiEngineRequest, ActivitiEngineResponse<Response> activitiEngineResponse, FlowNode curNode, ActivitiFlow activitiFlow) { IExclusiveGateway iExclusiveGateway = (IExclusiveGateway) curNode.getTarget(); String credential = iExclusiveGateway.chooseNextNode(activitiEngineRequest, activitiEngineResponse); return activitiFlow.chooseNextNode(curNode, credential); }
private static <Request, Response> void executeServiceTask(ActivitiEngineRequest<Request> activitiEngineRequest, ActivitiEngineResponse<Response> activitiEngineResponse, FlowNode flowNode) { IServiceTask iServiceTask = (IServiceTask) flowNode.getTarget(); iServiceTask.execute(activitiEngineRequest, activitiEngineResponse); } }
|
这里的IExclusiveGateway类定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.yang.core.infrastructure.flow.activiti.function;
import com.yang.core.infrastructure.flow.activiti.request.ActivitiEngineRequest; import com.yang.core.infrastructure.flow.activiti.response.ActivitiEngineResponse;
public interface IExclusiveGateway extends IActivitiService { public static final String CREDENTIAL = "credential";
default void execute(ActivitiEngineRequest request, ActivitiEngineResponse response) { String credential = chooseNextNode(request, response); request.getContext().put(CREDENTIAL, credential); }
String chooseNextNode(ActivitiEngineRequest request, ActivitiEngineResponse response);
}
|
其具体实现示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.yang.business.activiti.login;
import com.yang.api.common.ErrorCode; import com.yang.api.common.exception.UicException; import com.yang.core.infrastructure.flow.activiti.function.IExclusiveGateway; import com.yang.core.infrastructure.flow.activiti.request.ActivitiEngineRequest; import com.yang.core.infrastructure.flow.activiti.response.ActivitiEngineResponse; import org.springframework.stereotype.Component;
@Component public class NeedTransfer2IdentityExclusiveGateway implements IExclusiveGateway { @Override public String chooseNextNode(ActivitiEngineRequest request, ActivitiEngineResponse response) { Object needIdentityObj = response.getContext().get("needIdentity"); if (needIdentityObj != null) { boolean needIdentity = (Boolean) needIdentityObj; if (needIdentity) { return "identity"; } return "login"; } throw new UicException(ErrorCode.ACTIVITI_EXECUTE_ERROR); } }
|
测试
这里准备两个账号,一个普通账号,一个被盗账号,被盗账号会进入登录转核身节点,在该节点中,会打印控制台信息——“登录转核身======”,依次登录普通账号和被盗账号,结果如下:
规则引擎决策
上述实现方式,有一个问题在于,我们的判断条件是一个隐式的判断条件,即sequenceFlow线条上指定的credential值和IExclusiveGateway网关产出的值相同时,可以进入该sequenceFlow线条指引的分支连线。这其实就是一个等于的判断,但是这种判断方式不够灵活,如果我们后续的需求是某个值大于、小于或不等于时,才能进行分支,那么显然现在这种方式是不能满足的,因此,我们需要引入规则引擎,通过执行规则引擎的方式,来进行决策。
整体的思路如下:
- 网关节点,根据业务流程需要进行判断,并设置流程上下文值
- 网关节点执行完毕后,根据sequenceFlow条件,和流程上下文信息,调用规则引擎,判断是否符合条件,符合条件则走到sequenceFlow对应分支
流程上下文参数简化
之前定义的ActivitiRequest和ActivitiResponse类的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Data public class ActivitiEngineRequest <T> implements Serializable {
/** * 场景code */ private String scenarioCode;
private T baseRequest;
private Map<String, Object> context = new HashMap<>(); }
@Data public class ActivitiEngineResponse <T> implements Serializable { /** * 场景code */ private String scenarioCode;
private T response;
private Map<String, Object> context = new HashMap<>(); }
|
这两个类的字段有些重复,包括场景code和流程上下文context字段,特别是流程上下文字段,当我们需要塞值到上下文中时,有时候不好判断要放request的上下文还是response的上下文,其实感觉这里放request和放response都可以,为了减少歧义,这里将这两个类进行聚合,收敛到一个类中,即ActivitiExecuteContext类,其内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.yang.core.infrastructure.flow.activiti.context;
import lombok.Data;
import java.io.Serializable; import java.util.HashMap; import java.util.Map;
@Data public class ActivitiExecuteContext<Request, Response> implements Serializable { /** * 场景code */ private String scenarioCode;
private Request baseRequest; private Response baseResponse;
private Map<String, Object> context = new HashMap<>(); }
|
通过ActivitiExecuteContext来替换原先的ActivitiEngineRequest和ActivitiEngineResponse, 所以要修改之前用到过那两个类的接口和实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package com.yang.core.infrastructure.flow.activiti.function;
import com.yang.core.infrastructure.flow.activiti.context.ActivitiExecuteContext;
public interface IActivitiService { void execute(ActivitiExecuteContext activitiExecuteContext); }
package com.yang.core.infrastructure.flow.activiti.function;
import com.yang.core.infrastructure.flow.activiti.context.ActivitiExecuteContext;
public interface IExclusiveGateway extends IActivitiService { default void execute(ActivitiExecuteContext activitiExecuteContext) { parseCredential(activitiExecuteContext); }
void parseCredential(ActivitiExecuteContext activitiExecuteContext);
}
package com.yang.core.infrastructure.flow.activiti.function;
import com.yang.core.infrastructure.flow.activiti.context.ActivitiExecuteContext; public interface IServiceTask<DomainRequest, DomainResponse> extends IActivitiService { default void execute(ActivitiExecuteContext activitiExecuteContext) { DomainRequest domainRequest = buildDomainRequest(activitiExecuteContext); if (domainRequest != null) { DomainResponse domainResponse = apply(domainRequest); attachment(domainResponse, activitiExecuteContext); return; } doElse(activitiExecuteContext); }
/** * 默认不实现,一般先转为domain Request,然后调用领域层方法,进行转化,doElse适用于目前领域层无法支持相关实现,顾需要在activity层进行适配的逻辑 */ default void doElse(ActivitiExecuteContext activitiExecuteContext) {}
/** * 入参转化 * @param activitiExecuteContext * @return */ DomainRequest buildDomainRequest(ActivitiExecuteContext activitiExecuteContext);
/** * 领域服务调用 * @param domainRequest * @return */ DomainResponse apply(DomainRequest domainRequest);
/** * 领域服务调用结果填充 * @param domainResponse * @param activitiExecuteContext */ void attachment(DomainResponse domainResponse, ActivitiExecuteContext activitiExecuteContext); }
|
引入规则引擎
市面上常见的开源规则引擎有easy-rules、 Drools、Aviator,这里使用Aviator来作为规则引擎进行决策,首先引入相关的依赖:
1 2 3 4 5
| <dependency> <groupId>com.googlecode.aviator</groupId> <artifactId>aviator</artifactId> <version>5.1.4</version> </dependency>
|
然后创建相关的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.yang.core.infrastructure.rule;
import com.googlecode.aviator.AviatorEvaluator;
import java.util.HashMap; import java.util.Map;
public class AviatorUtil { public static Object execute(String condition, Map<String, Object> env) { return AviatorEvaluator.execute(condition, env); }
public static boolean match(String condition, Map<String, Object> env) { return (Boolean) execute(condition, env); }
public static void main(String[] args) { Map<String, Object> env = new HashMap<>(); env.put("flow", "identity"); String condition = "flow == 'identity'"; System.out.println(match(condition, env)); } }
|
修改流程配置
原先的网关连线,通过credential来拦截是否符合条件,现在我们引入规则引擎后,可以在sequenceFlow上,通过表达式来进行拦截判断,首先修改sequenceFlow,添加condition条件字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.yang.core.infrastructure.flow.activiti.model;
import lombok.Data;
import java.io.Serializable;
@Data public class SequenceFlowNode implements Serializable {
private String source;
private String target;
private String condition;
}
|
然后修改流程配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <activiti id="register_flow"> <!--节点定义--> <startEvent id="startEvent"/> <serviceTask id="userLocate" name="userLocate" description="账号定位" class="com.yang.business.activiti.login.UserLocateActivity"/> <serviceTask id="passwordCheck" name="passwordCheck" description="密码检查" class="com.yang.business.activiti.login.PasswordCheckActivity"/> <serviceTask id="userAccountStatusCheck" name="userAccountStatusCheck" description="账号状态检查" class="com.yang.business.activiti.login.UserAccountStatusCheckActivity"/> <exclusiveGateway id="needBuildIdentityAction" name="needBuildIdentityAction" description="是否需要转核身" class="com.yang.business.activiti.login.NeedTransfer2IdentityExclusiveGateway"/> <serviceTask id="buildIdentityAction" name="buildIdentityAction" description="核身行为构建" class="com.yang.business.activiti.login.BuildIdentityActionActivity"/> <serviceTask id="buildUserToken" name="buildUserToken" description="创建用户token" class="com.yang.business.activiti.common.BuildUserTokenActivity"/> <endEvent id="endEvent"/>
<!--节点执行顺序编排--> <sequenceFlow source="startEvent" target="userLocate"/> <sequenceFlow source="userLocate" target="passwordCheck"/> <sequenceFlow source="passwordCheck" target="userAccountStatusCheck"/> <sequenceFlow source="userAccountStatusCheck" target="needBuildIdentityAction"/> <sequenceFlow source="needBuildIdentityAction" target="buildIdentityAction" condition="flow == 'identity'"/> <sequenceFlow source="needBuildIdentityAction" target="buildUserToken" condition="flow == 'login'"/> <sequenceFlow source="buildIdentityAction" target="buildUserToken"/> <sequenceFlow source="buildUserToken" target="endEvent"/> </activiti>
|
从上面的配置文件中,我们可以看出,当上下文的flow值为identity时,走buildIdentityAction节点,当上下文的flow值为login时,走buildUserToken节点,那么,在网关节点needBuildIdentityAction中,它的作用就是根据流程当前执行内容,对流程上下文的flow进行赋值,其具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.yang.business.activiti.login;
import com.yang.api.common.ErrorCode; import com.yang.api.common.exception.UicException; import com.yang.core.infrastructure.flow.activiti.context.ActivitiExecuteContext; import com.yang.core.infrastructure.flow.activiti.function.IExclusiveGateway; import org.springframework.stereotype.Component;
@Component public class NeedTransfer2IdentityExclusiveGateway implements IExclusiveGateway { @Override public void parseCredential(ActivitiExecuteContext activitiExecuteContext) { Object needIdentityObj = activitiExecuteContext.getContext().get("needIdentity"); if (needIdentityObj != null) { boolean needIdentity = (Boolean) needIdentityObj; if (needIdentity) { activitiExecuteContext.getContext().put("flow", "identity"); return; } activitiExecuteContext.getContext().put("flow", "login"); return; } throw new UicException(ErrorCode.ACTIVITI_EXECUTE_ERROR); } }
|
然后,因为我们之前将决策的逻辑放到activitiFlow类中,所以这里需要对activitiFlow的决策方法进行修改,修改内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.yang.core.infrastructure.flow.activiti.model;
import com.yang.core.infrastructure.rule.AviatorUtil; import lombok.Data;
import java.util.ArrayList; import java.util.List; import java.util.Map;
@Data public class ActivitiFlow { private String id;
private FlowNode startNode;
private FlowNode endNode;
private List<SequenceFlowNode> sequenceFlowNodeList;
private Map<String, FlowNode> nodeId2FlowNodeMap;
public FlowNode chooseNextNode(FlowNode curNode, Map<String, Object> flowContext) { String sourceNodeId = curNode.getId(); String targetNodeId = null; List<SequenceFlowNode> sequenceFlowNodes = new ArrayList<>(); for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) { if (sequenceFlowNode.getSource().equals(sourceNodeId)) { sequenceFlowNodes.add(sequenceFlowNode); } } if (sequenceFlowNodes.size() == 1) { targetNodeId = sequenceFlowNodes.get(0).getTarget(); } else { // 网关判断 for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodes) { String condition = sequenceFlowNode.getCondition(); if (AviatorUtil.match(condition, flowContext)) { targetNodeId = sequenceFlowNode.getTarget(); break; } } } return nodeId2FlowNodeMap.get(targetNodeId); }
}
|
测试
启动项目,分别测试普通账号和被盗账号,结果如下: