流程引擎实现(二)——排他网关

背景

在流程图中,时常会涉及到分支的判断,进入判断逻辑后,我们一般只会从分支的一个出口出来,然后往下继续执行,这在流程引擎中,称之为排他网关(Exclusive Gateway),排他网关用于在流程执行过程中做出决策,基于流程中的条件来选择一个唯一的路径继续执行。

下面我们以登录为例,在登录流程中,会依次执行下列流程:

  • 账号定位:根据入参定位账号信息
  • 密码检查:根据获取的账号信息,以及输入的密码,比对密码信息是否正确
  • 状态检查:检查用户状态,判断是否被盗、冻结等
  • 登录核身判断:
    1. 状态检查通过,颁发登录token
    2. 状态检查不通过,设置核身相关信息,如核身类型等
  • 创建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);
    }

}

测试

启动项目,分别测试普通账号和被盗账号,结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值