背景
在传统的MVC架构中,和业务逻辑相关的代码一般是编写在service层,但随着业务的发展,service层会不断充斥各种逻辑,导致service层过于臃肿、庞大,此外,职责定义不够清晰,如何对service层进行瘦身,以达到职责分离的效果,成为后续开发中不断需要面对的一个难题。
通过流程引擎,将service层的各种业务逻辑,拆分到不同的activitiy节点中,从而达到职责分离的效果,此外,通过对这些activity节点进行编排,能有效进行流程追踪和监控,以及实现流程标准化。
目前主流的开源流程引擎有activiti、flowable、camunda,在我的实际工作项目中,也用到了流程引擎,基于对流程引擎的好奇和兴趣,决定自己实现一个简单的流程引擎,一方面是锻炼自己的编程能力,另一方面是加深自己对流程引擎的一些理解和认识,下面是具体的实现思路。
流程引擎解析
无论是什么类型的流程引擎,在执行的时候,都是依据配置文件中配置的流程执行顺序,挨个执行相应的流程节点,通过这些流程节点的活动和编排的顺序,完成相关的业务逻辑,从而对外提供服务。
首先我们定义流程文件解析接口:
public interface IActivitiFileParser {
ActivitiFlow parseActivitiFlow(String file);
}
以注册流程为例,假设我们的流程配置文件定义如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<activiti id="register_flow">
<!--节点定义-->
<startEvent id="startEvent"/>
<serviceTask id="registerFormCheck" name="registerFormCheck" description="注册表单校验" class="com.yang.application.register.activiti.RegisterFormCheckActivity"/>
<serviceTask id="userConflictCheck" name="userConflictCheck" description="用户冲突定位" class="com.yang.application.register.activiti.UserConflictCheckActivity"/>
<serviceTask id="encryptPassword" name="encryptPassword" description="密码加密" class="com.yang.application.register.activiti.EncryptPasswordActivity"/>
<serviceTask id="userRoleRich" name="userRoleRich" description="用户权限填充" class="com.yang.application.register.activiti.UserRoleRichActivity"/>
<serviceTask id="createUser" name="createUser" description="创建用户" class="com.yang.application.register.activiti.CreateUserActivity"/>
<serviceTask id="buildUserToken" name="buildUserToken" description="创建用户token" class="com.yang.application.register.activiti.BuildUserTokenActivity"/>
<endEvent id="endEvent"/>
<!--节点执行顺序编排-->
<sequenceFlow source="startEvent" target="registerFormCheck"/>
<sequenceFlow source="registerFormCheck" target="userConflictCheck"/>
<sequenceFlow source="userConflictCheck" target="encryptPassword"/>
<sequenceFlow source="encryptPassword" target="userRoleRich"/>
<sequenceFlow source="userRoleRich" target="createUser"/>
<sequenceFlow source="createUser" target="buildUserToken"/>
<sequenceFlow source="buildUserToken" target="endEvent"/>
</activiti>
这里,我们将流程节点分为下列四类:
类型 | 定义 |
---|---|
startEvent | 开始事件 |
serviceTask | 服务任务,执行对应的业务逻辑活动 |
endEvent | 结束事件 |
sequenceFlow | 流程线,用于定义流程节点之间的执行先后顺序 |
我们将这些类型,抽象到相关的枚举类中
package com.yang.infrastructure.flow.activiti.enums;
public enum FlowNodeType {
START_EVENT("startEvent"),
SERVICE_TASK("serviceTask"),
END_EVENT("endEvent"),
SEQUENCE_FLOW("sequenceFlow");
private String code;
FlowNodeType(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static FlowNodeType parseByCode(String code) {
for (FlowNodeType value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
}
对于上述四种类型,可以进一步划分为两类——节点类和节点连线类,我们为此定义两种模型:
// 节点类
@Data
public class FlowNode implements Serializable {
private String id;
private String name;
private String description;
private Object target;
private String classPath;
private FlowNodeType flowNodeType;
}
// 节点连线类
@Data
public class SequenceFlowNode implements Serializable {
private String source;
private String target;
}
然后我们定义流程文件相关的聚合类,该类有两个属性,一个是流程id(也就是流程文件名称),另一个是该流程中的流程节点(这里以List来实现,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;
}
}
然后对于ActivitiFlow的构造,主要是根据流程连线和流程节点来实现,对于不同的流程文件解析类,他们负责的只是流程连线、流程节点的解析,构造过程则是通用的,因此我们将其构造过程提炼为抽象类
public abstract class AbstractActivitiFileParser implements IActivitiFileParser{
/**
* 填充流程节点内容
* @param flowNode
* @param flowNodeType
*/
protected void richFlowNode(FlowNode flowNode, FlowNodeType flowNodeType) {
if (flowNodeType != FlowNodeType.SERVICE_TASK) {
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) {
List<SequenceFlowNode> sortSequenceFlowNodeList = sortSequenceFlowNodeList(sequenceFlowNodeList);
ActivitiFlow activitiFlow = new ActivitiFlow();
activitiFlow.setId(fileName);
Map<String, FlowNode> id2FlowNodeMap = flowNodeList.stream()
.collect(Collectors.toMap(FlowNode::getId, Function.identity()));
SequenceFlowNode iterator = null;
for (int i = 0; i < sortSequenceFlowNodeList.size(); i++) {
iterator = sortSequenceFlowNodeList.get(i);
String source = iterator.getSource();
FlowNode flowNode = id2FlowNodeMap.get(source);
activitiFlow.addFlowNode(flowNode);
}
FlowNode endNode = id2FlowNodeMap.get(iterator.getTarget());
activitiFlow.addFlowNode(endNode);
return activitiFlow;
}
/**
* 按照开始到结束,对连线进行排序
* @param sequenceFlowNodeList
* @return
*/
private List<SequenceFlowNode> sortSequenceFlowNodeList(List<SequenceFlowNode> sequenceFlowNodeList) {
Map<String, SequenceFlowNode> source2SequenceFlowNodeMap = sequenceFlowNodeList.stream()
.collect(Collectors.toMap(SequenceFlowNode::getSource, Function.identity()));
Map<String, String> target2SourceMap = new HashMap<>();
for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) {
target2SourceMap.put(sequenceFlowNode.getTarget(), sequenceFlowNode.getSource());
}
// 定位开始节点
SequenceFlowNode startSequenceFlowNode = null;
for (SequenceFlowNode sequenceFlowNode : sequenceFlowNodeList) {
String from = target2SourceMap.get(sequenceFlowNode.getSource());
if (from == null) {
startSequenceFlowNode = sequenceFlowNode;
break;
}
}
if (startSequenceFlowNode == null) {
throw new UicException(ErrorCode.ACTIVITI_PARSER_CIRCULATION_ERROR);
}
List<SequenceFlowNode> sortSequenceFlowNodeList = new ArrayList<>();
SequenceFlowNode iterator = startSequenceFlowNode;
sortSequenceFlowNodeList.add(iterator);
while (true) {
String target = iterator.getTarget();
iterator = source2SequenceFlowNodeMap.get(target);
if (iterator == null) {
break;
}
sortSequenceFlowNodeList.add(iterator);
}
return sortSequenceFlowNodeList;
}
}
最一般流程引擎的流程编排配置文件,是xml类型的,因此这里实现一个Xml配置文件的解析类,用于解析我们的xml流程文件,具体实现内容如下:
package com.yang.infrastructure.flow.activiti.parser;
import com.yang.infrastructure.flow.activiti.enums.FlowNodeType;
import com.yang.infrastructure.flow.activiti.model.ActivitiFlow;
import com.yang.infrastructure.flow.activiti.model.FlowNode;
import com.yang.infrastructure.flow.activiti.model.SequenceFlowNode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class XmlActivitiFileParser extends AbstractActivitiFileParser {
private static final String ACTIVITI_PREFIX = "yang-engine";
@Override
public ActivitiFlow parseActivitiFlow(String fileName) {
return parseActivitiFlow(ACTIVITI_PREFIX, fileName);
}
public ActivitiFlow parseActivitiFlow(String directory, String fileName) {
String fullFileName = directory + File.separator + fileName;
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fullFileName);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(inputStream);
// 解析出流程节点
List<FlowNode> flowNodeList = parseFlowNodeList(document);
// 解析流程执行顺序
List<SequenceFlowNode> sequenceFlowNodeList = parseSequenceFlowNodeList(document);
String activitiId = parseActivitiId(document);
if (StringUtils.isEmpty(activitiId)) {
activitiId = fileName;
}
return buildActivitiFlow(activitiId, flowNodeList, sequenceFlowNodeList);
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
/**
* 解析流程id
* @param document
* @return
*/
private String parseActivitiId(Document document) {
NodeList nodeList = document.getElementsByTagName("activiti");
Node activitiNode = nodeList.item(0);
NamedNodeMap attributes = activitiNode.getAttributes();
Node id = attributes.getNamedItem("id");
if (id != null) {
return id.getNodeValue();
}
return null;
}
/**
* 解析流程节点列表
* @param document
* @return
*/
private List<FlowNode> parseFlowNodeList(Document document) {
List<FlowNode> flowNodeList = new ArrayList<>();
NodeList activitiNodeList = document.getElementsByTagName("activiti");
if (activitiNodeList.getLength() <= 0) {
return flowNodeList;
}
Node activitiNode = activitiNodeList.item(0);
NodeList nodeList = activitiNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
FlowNodeType flowNodeType = FlowNodeType.parseByCode(node.getNodeName());
if (flowNodeType == null || flowNodeType == FlowNodeType.SEQUENCE_FLOW) {
continue;
}
NamedNodeMap attributes = node.getAttributes();
FlowNode flowNode = parseFlowNode(attributes, flowNodeType);
flowNodeList.add(flowNode);
}
return flowNodeList;
}
/**
* 解析流程节点
* @param attributes
* @param flowNodeType
* @return
*/
private FlowNode parseFlowNode(NamedNodeMap attributes, FlowNodeType flowNodeType) {
if (attributes == null || attributes.getLength() <= 0) {
return null;
}
FlowNode flowNode = new FlowNode();
Node idNode = attributes.getNamedItem("id");
Node nameNode = attributes.getNamedItem("name");
Node descriptionNode = attributes.getNamedItem("description");
Node classNode = attributes.getNamedItem("class");
if (idNode == null) {
return null;
}
flowNode.setId(idNode.getNodeValue());
if (nameNode != null) {
flowNode.setName(nameNode.getNodeValue());
}
if (descriptionNode != null) {
flowNode.setDescription(descriptionNode.getNodeValue());
}
if (classNode != null) {
flowNode.setClassPath(classNode.getNodeValue());
}
flowNode.setFlowNodeType(flowNodeType);
richFlowNode(flowNode, flowNodeType);
return flowNode;
}
/**
* 解析流程执行顺序节点列表
* @param document
* @return
*/
private List<SequenceFlowNode> parseSequenceFlowNodeList(Document document) {
NodeList nodeList = document.getElementsByTagName(FlowNodeType.SEQUENCE_FLOW.getCode());
List<SequenceFlowNode> sequenceFlowNodes = new ArrayList<>();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
NamedNodeMap attributes = node.getAttributes();
SequenceFlowNode sequenceFlowNode = parseSequenceFlowNode(attributes);
if (sequenceFlowNode != null) {
sequenceFlowNodes.add(sequenceFlowNode);
}
}
return sequenceFlowNodes;
}
/**
* 解析流程执行顺序节点
* @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());
return sequenceFlowNode;
}
return null;
}
public static void main(String[] args) {
IActivitiFileParser activitiFileParser = new XmlActivitiFileParser();
ActivitiFlow activitiFlow = activitiFileParser.parseActivitiFlow("register_flow.xml");
}
}
最后添加对应的测试方法,测试上述方式是否能成功解析流程引擎配置文件:
@Test
public void testParseActivitiFile() {
IActivitiFileParser iActivitiFileParser = new XmlActivitiFileParser();
ActivitiFlow activitiFlow = iActivitiFileParser.parseActivitiFlow("register_flow.xml");
for (FlowNode flowNode : activitiFlow.getFlowNodeList()) {
System.out.println(flowNode);
}
System.out.println(activitiFlow);
}
解析结果如下:
懒加载流程到缓存中
创建ActivitiFlow流程管理类,在第一次获取ActivitiFlow,将其加载到缓存中,以减少后续的流程配置文件解析次数。
package com.yang.infrastructure.flow.activiti;
import com.yang.infrastructure.common.ErrorCode;
import com.yang.infrastructure.common.exception.UicException;
import com.yang.infrastructure.flow.activiti.model.ActivitiFlow;
import com.yang.infrastructure.flow.activiti.parser.IActivitiFileParser;
import com.yang.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;
}
}
流程引擎执行
定义流程引擎入参和出参,这里采用泛形的方式,定义对应的入参和出参,因为我们流程引擎不感知具体的业务参数类型,只需提供一个通用的底层交互协议。
@Data
public class ActivitiEngineRequest <T> implements Serializable {
/**
* 场景code
*/
private String scenarioCode;
private T baseRequest;
}
@Data
public class ActivitiEngineResponse <T> implements Serializable {
/**
* 场景code
*/
private String scenarioCode;
private T response;
}
然后我们定义流程引擎执行接口
public interface IActivitiService {
void execute(ActivitiEngineRequest request, ActivitiEngineResponse response);
}
对于startEvent, endEvent, serviceTask这几种流程节点类型,依次去实现对应的IActivitiService,因为目前暂时只用到serviceTask,所以下面只列出serviceTask的实现
public interface IServiceTask<DomainRequest, DomainResponse> extends IActivitiService {
default void execute(ActivitiEngineRequest request, ActivitiEngineResponse response) {
DomainRequest domainRequest = buildDomainRequest(request, response);
if (domainRequest != null) {
DomainResponse domainResponse = apply(domainRequest);
attachment(domainResponse, response);
return;
}
doElse(request, response);
}
/**
* 默认不实现,一般先转为domain Request,然后调用领域层方法,进行转化,doElse适用于目前领域层无法支持相关实现,顾需要在activity层进行适配的逻辑
* @param request
* @param response
*/
default void doElse(ActivitiEngineRequest request, ActivitiEngineResponse response) {}
/**
* 入参转化
* @param request
* @param response
* @return
*/
DomainRequest buildDomainRequest(ActivitiEngineRequest request, ActivitiEngineResponse response);
/**
* 领域服务调用
* @param domainRequest
* @return
*/
DomainResponse apply(DomainRequest domainRequest);
/**
* 领域服务调用结果填充
* @param domainResponse
* @param response
*/
void attachment(DomainResponse domainResponse, ActivitiEngineResponse response);
}
在IServiceTask的流程引擎调用中,这里的实现思路是将流程引擎入参先转化为对应的领域入参,然后调用领域服务执行对应的领域操作,最后,将领域服务返回结果,转化或填充到我们的流程引擎出参当中。示例代码如下:
@Component
public class EncryptPasswordActivity implements IServiceTask<UserPasswordEncryptDomainRequest, UserPasswordEncryptDomainResponse> {
@Autowired
private UserPasswordDomainService userPasswordDomainService;
@Override
public UserPasswordEncryptDomainRequest buildDomainRequest(ActivitiEngineRequest request, ActivitiEngineResponse response) {
RegisterRequest registerRequest = (RegisterRequest) request.getBaseRequest();
UserPasswordEncryptDomainRequest userPasswordEncryptDomainRequest = new UserPasswordEncryptDomainRequest();
String encryptType = registerRequest.getExtendMaps().get("encryptType");
EncryptTypeEnum encryptTypeEnum = EncryptTypeEnum.parseByType(encryptType);
userPasswordEncryptDomainRequest.setEncryptType(encryptTypeEnum);
userPasswordEncryptDomainRequest.setPlainPassword(registerRequest.getPassword());
return userPasswordEncryptDomainRequest;
}
@Override
public UserPasswordEncryptDomainResponse apply(UserPasswordEncryptDomainRequest userPasswordEncryptDomainRequest) {
return userPasswordDomainService.encryptPassword(userPasswordEncryptDomainRequest);
}
@Override
public void attachment(UserPasswordEncryptDomainResponse userPasswordEncryptDomainResponse, ActivitiEngineResponse response) {
UserModel userModel = (UserModel) response.getResponse();
userModel.setEncryptPassword(userPasswordEncryptDomainResponse.getEncryptPassword());
}
}
但是这有一个缺陷,就是我们的领域服务一般都是比较原子的,当我们要执行的操作涉及多个原子操作时,上面这种思路就无法支撑,所以这里加了一个doElse来兼容,示例代码如下:
@Component
public class UserConflictCheckActivity implements IServiceTask<UserQueryDomainRequest, UserQueryDomainResponse> {
@Autowired
private UserDomainService userDomainService;
@Override
public UserQueryDomainRequest buildDomainRequest(ActivitiEngineRequest request, ActivitiEngineResponse response) {
return null;
}
@Override
public UserQueryDomainResponse apply(UserQueryDomainRequest userQueryDomainRequest) {
return null;
}
@Override
public void doElse(ActivitiEngineRequest request, ActivitiEngineResponse response) {
RegisterRequest registerRequest = (RegisterRequest) request.getBaseRequest();
if (StringUtils.isNotEmpty(registerRequest.getLoginId())) {
UserQueryDomainRequest userQueryDomainRequest = new UserQueryDomainRequest.UserQueryDomainRequestBuilder()
.userQueryType(UserQueryType.LOGIN_ID)
.queryMessage(registerRequest.getLoginId())
.build();
UserQueryDomainResponse userQueryDomainResponse = userDomainService.query(userQueryDomainRequest);
if (!CollectionUtils.isEmpty(userQueryDomainResponse.getUserAccountList())) {
throw new UicException(ErrorCode.USER_CONFLICT_ERROR);
}
}
UserQueryDomainRequest userQueryDomainRequest = new UserQueryDomainRequest.UserQueryDomainRequestBuilder()
.userQueryType(UserQueryType.EMAIL)
.queryMessage(registerRequest.getEmail())
.build();
UserQueryDomainResponse userQueryDomainResponse = userDomainService.query(userQueryDomainRequest);
if (!CollectionUtils.isEmpty(userQueryDomainResponse.getUserAccountList())) {
throw new UicException(ErrorCode.USER_CONFLICT_ERROR);
}
}
@Override
public void attachment(UserQueryDomainResponse userQueryDomainResponse, ActivitiEngineResponse response) {
}
}
最后,我们修改ActivitiFlowManager, 增加执行方法,执行的思路是,先解析对应的流程配置,然后根据流程节点类型,依次执行对应的流程方法
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<>();
for (FlowNode flowNode : activitiFlow.getFlowNodeList()) {
FlowNodeType flowNodeType = flowNode.getFlowNodeType();
switch (flowNodeType) {
case START_EVENT:
break;
case END_EVENT:
break;
case SERVICE_TASK:
executeServiceTask(activitiEngineRequest, activitiEngineResponse, flowNode);
break;
}
}
return activitiEngineResponse;
}
private static <Request, Response> void executeServiceTask(ActivitiEngineRequest<Request> activitiEngineRequest, ActivitiEngineResponse<Response> activitiEngineResponse, FlowNode flowNode) {
IServiceTask iServiceTask = (IServiceTask) flowNode.getTarget();
iServiceTask.execute(activitiEngineRequest, activitiEngineResponse);
}
}
测试代码如下:
@Override
public ResultT<String> register2(RegisterRequest registerRequest) {
ActivitiEngineRequest<RegisterRequest> activitiEngineRequest = new ActivitiEngineRequest<>();
activitiEngineRequest.setBaseRequest(registerRequest);
ActivitiEngineResponse<UserModel> activitiEngineResponse = ActivitiFlowManager.startEngine("register_flow.xml", activitiEngineRequest);
UserModel userModel = activitiEngineResponse.getResponse();
if (userModel != null && userModel.getUserToken() != null) {
String token = userModel.getUserToken().getToken();
return ResultT.success(token);
}
return ResultT.fail();
}
@Test
public void testEngineRegister() {
RegisterRequest registerRequest = new RegisterRequest();
registerRequest.setLoginId("test1");
registerRequest.setEmail("2827523200@qq.com");
registerRequest.setPassword("hello1234");
ResultT<String> resultT = userService.register2(registerRequest);
System.out.println(resultT);
System.out.println(resultT.getData());
}
执行结果如下: