背景
在流程图中,时常会涉及到分支的判断,进入判断逻辑后,我们一般只会从分支的一个出口出来,然后往下继续执行,这在流程引擎中,称之为排他网关(Exclusive Gateway),排他网关用于在流程执行过程中做出决策,基于流程中的条件来选择一个唯一的路径继续执行。
下面我们以登录为例,在登录流程中,会依次执行下列流程:
- 账号定位:根据入参定位账号信息
- 密码检查:根据获取的账号信息,以及输入的密码,比对密码信息是否正确
- 状态检查:检查用户状态,判断是否被盗、冻结等
- 登录核身判断:
- 状态检查通过,颁发登录token
- 状态检查不通过,设置核身相关信息,如核身类型等
- 创建token:状态检查通过,则下发登录态token,若不通过,下发核身token
具体的流程图如下所示:
设计思路
原先的执行方式
- 根据流程id,解析流程配置文件,获取ActivitiFlow
- 遍历ActivitiFlow的所有节点,依次执行节点活动
修改后的执行方式
- 根据流程id,解析流程配置文件,获取ActivitiFlow
- 执行流程节点活动
- 对于ServiceTask,转为IServiceTask执行execute方法
- 对于排他网关,转为IExeclusiveGateway执行parseCredential方法
- 节点执行完毕后,根据sequenceFlow连线信息,获取下一个节点(对于网关,会加上凭证credential的判断,来决策下一个节点)
实现
首先,我们根据上述流程图,定义流程文件内容如下:
<?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字段,用于表示当上下文中凭证信息为相关值时,才可以走这条连线。
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不感知,因为之前涉及的时候,不存在一个来源对应多个去处的连线,所以获取节点顺序,依次执行即可。
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模型进行改造,使其能支持我们的一对多逻辑,同时,对模型进行充血,将一些决策逻辑收敛到模型中。
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字段的解析:
/**
* 解析流程执行顺序节点
* @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
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类,对排他网关做定制处理,根据网关返回的凭证值,决策出下一个节点是哪一个。
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类定义如下:
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);
}
其具体实现示例如下:
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类的内容如下:
@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类,其内容如下:
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, 所以要修改之前用到过那两个类的接口和实现:
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来作为规则引擎进行决策,首先引入相关的依赖:
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.1.4</version>
</dependency>
然后创建相关的工具类
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条件字段
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;
}
然后修改流程配置文件
<?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进行赋值,其具体实现如下:
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的决策方法进行修改,修改内容如下:
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);
}
}
测试
启动项目,分别测试普通账号和被盗账号,结果如下: