flowable 中级 - 5. 活动调用(子流程)相关

75 阅读1分钟

一、需求

业务需求要给子流程指定流程发起人,而不是触发活动调用的当前人,其实在梳理的过程中还发现一些需求,跟还需要兼容一些需求,现在罗列如下

  1. 设置子流程的发起人
  2. 增加/修改启动时流程变量
  3. 设置流程状态 businessStatus
  4. 设置流程key businessKey
  5. 父子表单数据传递

二、调研

最开始想找一个在创建子流程的过程中,找到相关的拦截器,然后进行我们的业务逻辑修改。思路大体是对的,但是一直没有找到,然后打算使用 执行监听器。

方案效果大概方式为何不行实现了哪些
执行监听器+xml(输入参数)未实现在监听器里面读取xml配置,把主流程配置的发起人继承到子流程里面发起人流程变量是继承到自己流程了,但是流程记录表里的发起人还是空设置发起人增加/修改启动时流程变量设置流程状态 businessStatus设置流程key businessKey
Behavior重写方式已实现拦截子流程发起,拿到 subProcessInstance 进行相关属性设置``设置发起人增加/修改启动时流程变量设置流程状态 businessStatus设置流程key businessKey

三、方案

3.1 执行监听器+xml 方案

虽然这个方案未能实现需求,但是还是把怎么验证的方案记录下

    <callActivity id="sid-0BB3C4FF-F2AA-4672-B013-2C87B22515B6" name="出库调用" calledElement="out_of_warehouse">
      <extensionElements>
        <flowable:in source="app_initiator"  target="initiator"></flowable:in>
        <flowable:executionListener event="start" delegateExpression="${callActivityExecutionListener}" />
      </extensionElements>
    </callActivity>

flowable:in 是引擎提供的能力,允许变量继承到子流程里面 更多可以参考

然后执行监听器如下代码

@Slf4j
@Component("callActivityExecutionListener")
public class CallActivityExecutionListen implements ExecutionListener {
​
  @Override
  public void notify(DelegateExecution execution) {
​
    CallActivity callActivity = (CallActivity)execution.getCurrentFlowElement();
    String sponsor = null; // 给子流程设置的发起人
    Map<String, List<ExtensionElement>> extensionElements = callActivity.getExtensionElements();
    for(Map.Entry<String, List<ExtensionElement>> entry: extensionElements.entrySet()) {
      // 这一层读取的就是 button 类似的标签
      String attributeName = entry.getKey();
      List<ExtensionElement> attributeValueList = entry.getValue();
      if (attributeName.equals("sponsor")) {
        for(ExtensionElement item: attributeValueList) {
          // 处理 类似button  ExtensionElement 里面的属性
          Map<String, List<ExtensionAttribute>> extensionElementAttributes = item.getAttributes();
          for (Map.Entry<String, List<ExtensionAttribute>> tmpEntry : extensionElementAttributes.entrySet()) {
            String value = tmpEntry.getValue().get(0).getValue();
            sponsor = value; //  把子流程的发起人找到
          }
        }
      }
    }
​
    List<IOParameter> ioParameterList = callActivity.getInParameters();
    for (IOParameter ioParameter: ioParameterList) {
      String source = ioParameter.getSource();
      if (source.equals("app_initiator")) {
        execution.setVariable(source, sponsor); // 把子流程的发起人先给到父流程,然后用引擎的能力进行传递
      }
    }
  }

结果实验下来,流程发起人还是没有设置成功,虽然子流程已经有了发起人这个流程变量

3.2 Behavior重写方式

在调研偶然发现一篇文章 自定义CallActivity子流程发起人。文章里面有跟我们一样的需求。

更改发起主要是通过

identityService.setAuthenticatedUserId(starter);

或者设置subProcessInstance相关的属性设置属性即可

subProcessInstance.setStartUserId(startUserId);;

3.2.1 具体实现

3.2.1.1 重写 callActivityBehavior
 
/**
 * 重写CallActivityBehavior 主要是为了给子流程设置发起人
 *
 *  发起人在 CallActivity xml 里面配置的-sponsor
 *    String initiator = getSubProcessInitiator(callActivity);
 *    subProcessInstance.setStartUserId(initiator);
 *
 */
@Slf4j
public class CustomCallActivityBehavior extends CallActivityBehavior {
​
  private static final Logger LOGGER = LoggerFactory.getLogger(CustomCallActivityBehavior.class);
​
  public CustomCallActivityBehavior(CallActivity callActivity) {
    super(callActivity);
  }
​
  @Override
  public void execute(DelegateExecution execution) {
​
    ExecutionEntity executionEntity = (ExecutionEntity) execution;
    CallActivity callActivity = (CallActivity) executionEntity.getCurrentFlowElement();
​
    CommandContext commandContext = CommandContextUtil.getCommandContext();
​
    ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
​
    ProcessDefinition processDefinition = getProcessDefinition(execution, callActivity, processEngineConfiguration);
​
    // Get model from cache
    Process subProcess = ProcessDefinitionUtil.getProcess(processDefinition.getId());
    if (subProcess == null) {
      throw new FlowableException("Cannot start a sub process instance. Process model " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") could not be found");
    }
​
    FlowElement initialFlowElement = subProcess.getInitialFlowElement();
    if (initialFlowElement == null) {
      throw new FlowableException("No start element found for process definition " + processDefinition.getId());
    }
​
    // Do not start a process instance if the process definition is suspended
    if (ProcessDefinitionUtil.isProcessDefinitionSuspended(processDefinition.getId())) {
      throw new FlowableException("Cannot start process instance. Process definition " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") is suspended");
    }
​
    ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
    ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
​
    String businessKey = null;
    if (!StringUtils.isEmpty(callActivity.getBusinessKey())) {
      Expression expression = expressionManager.createExpression(callActivity.getBusinessKey());
      businessKey = expression.getValue(execution).toString();
​
    } else if (callActivity.isInheritBusinessKey()) {
      ExecutionEntity processInstance = executionEntityManager.findById(execution.getProcessInstanceId());
      businessKey = processInstance.getBusinessKey();
    }
​
    StartSubProcessInstanceBeforeContext instanceBeforeContext = new StartSubProcessInstanceBeforeContext(businessKey, null,
      callActivity.getProcessInstanceName(), new HashMap<>(), new HashMap<>(), executionEntity, callActivity.getInParameters(),
      callActivity.isInheritVariables(), initialFlowElement.getId(), initialFlowElement, subProcess, processDefinition);
​
    if (processEngineConfiguration.getStartProcessInstanceInterceptor() != null) {
      processEngineConfiguration.getStartProcessInstanceInterceptor().beforeStartSubProcessInstance(instanceBeforeContext);
    }
​
    ExecutionEntity subProcessInstance = processEngineConfiguration.getExecutionEntityManager().createSubprocessInstance(
      instanceBeforeContext.getProcessDefinition(), instanceBeforeContext.getCallActivityExecution(),
      instanceBeforeContext.getBusinessKey(), instanceBeforeContext.getInitialActivityId());
​
    String initiator = getSubProcessInitiator(callActivity);
    subProcessInstance.setStartUserId(initiator);
    subProcessInstance.setBusinessStatus(ProcStatus.PROCESS.name()); // 设置子流程的流程状态
    setEventEntity();
​
    FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
    if (eventDispatcher != null && eventDispatcher.isEnabled()) {
      eventDispatcher.dispatchEvent(
        FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.PROCESS_CREATED, subProcessInstance),
        processEngineConfiguration.getEngineCfgKey());
    }
​
    // process template-defined data objects
    subProcessInstance.setVariables(processDataObjects(subProcess.getDataObjects(), initiator));
​
    if (instanceBeforeContext.isInheritVariables()) {
​
      Map<String, Object> executionVariables = execution.getVariables();
      Map<String, Object> transientVariables = execution.getTransientVariables();
      for (Map.Entry<String, Object> entry : executionVariables.entrySet()) {
​
        // The executionVariables contain all variables, including the transient variables.
        // Hence why that map is iterated and the transient variables are split off
        String variableName = entry.getKey();
        if (transientVariables.containsKey(variableName)) {
          instanceBeforeContext.getTransientVariables().put(variableName, entry.getValue());
​
        } else {
          instanceBeforeContext.getVariables().put(variableName, entry.getValue());
​
        }
      }
​
    }
​
    // copy process variables
    for (IOParameter inParameter : instanceBeforeContext.getInParameters()) {
​
      Object value = null;
      if (StringUtils.isNotEmpty(inParameter.getSourceExpression())) {
        Expression expression = expressionManager.createExpression(inParameter.getSourceExpression().trim());
        value = expression.getValue(execution);
​
      } else {
        value = execution.getVariable(inParameter.getSource());
      }
​
      String variableName = null;
      if (StringUtils.isNotEmpty(inParameter.getTargetExpression())) {
        Expression expression = expressionManager.createExpression(inParameter.getTargetExpression());
        Object variableNameValue = expression.getValue(execution);
        if (variableNameValue != null) {
          variableName = variableNameValue.toString();
        } else {
          LOGGER.warn("In parameter target expression {} did not resolve to a variable name, this is most likely a programmatic error",
            inParameter.getTargetExpression());
        }
​
      } else if (StringUtils.isNotEmpty(inParameter.getTarget())){
        variableName = inParameter.getTarget();
​
      }
​
      instanceBeforeContext.getVariables().put(variableName, value);
    }
​
    if (!instanceBeforeContext.getVariables().isEmpty()) {
      initializeVariables(subProcessInstance, instanceBeforeContext.getVariables());
    }
​
    if (!instanceBeforeContext.getTransientVariables().isEmpty()) {
      initializeTransientVariables(subProcessInstance, instanceBeforeContext.getTransientVariables());
    }
​
    // Process instance name is resolved after setting the variables on the process instance, so they can be used in the expression
    String processInstanceName = null;
    if (StringUtils.isNotEmpty(instanceBeforeContext.getProcessInstanceName())) {
      Expression processInstanceNameExpression = expressionManager.createExpression(instanceBeforeContext.getProcessInstanceName());
      processInstanceName = processInstanceNameExpression.getValue(subProcessInstance).toString();
      subProcessInstance.setName(processInstanceName);
    }
​
    if (eventDispatcher != null && eventDispatcher.isEnabled()) {
      eventDispatcher.dispatchEvent(FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.ENTITY_INITIALIZED, subProcessInstance),
        processEngineConfiguration.getEngineCfgKey());
    }
​
    if (processEngineConfiguration.isEnableEntityLinks()) {
      EntityLinkUtil.createEntityLinks(execution.getProcessInstanceId(), executionEntity.getId(), callActivity.getId(),
        subProcessInstance.getId(), ScopeTypes.BPMN);
    }
​
    if (StringUtils.isNotEmpty(callActivity.getProcessInstanceIdVariableName())) {
      Expression expression = expressionManager.createExpression(callActivity.getProcessInstanceIdVariableName());
      String idVariableName = (String) expression.getValue(execution);
      if (StringUtils.isNotEmpty(idVariableName)) {
        execution.setVariable(idVariableName, subProcessInstance.getId());
      }
    }
​
    // Create the first execution that will visit all the process definition elements
    ExecutionEntity subProcessInitialExecution = executionEntityManager.createChildExecution(subProcessInstance);
    subProcessInitialExecution.setCurrentFlowElement(instanceBeforeContext.getInitialFlowElement());
​
    if (processEngineConfiguration.getStartProcessInstanceInterceptor() != null) {
      StartSubProcessInstanceAfterContext instanceAfterContext = new StartSubProcessInstanceAfterContext(subProcessInstance, subProcessInitialExecution,
        instanceBeforeContext.getVariables(), instanceBeforeContext.getTransientVariables(), instanceBeforeContext.getCallActivityExecution(),
        instanceBeforeContext.getInParameters(), instanceBeforeContext.getInitialFlowElement(), instanceBeforeContext.getProcess(),
        instanceBeforeContext.getProcessDefinition());
​
      processEngineConfiguration.getStartProcessInstanceInterceptor().afterStartSubProcessInstance(instanceAfterContext);
    }
​
    processEngineConfiguration.getActivityInstanceEntityManager().recordSubProcessInstanceStart(executionEntity, subProcessInstance);
​
    CommandContextUtil.getAgenda().planContinueProcessOperation(subProcessInitialExecution);
​
    if (eventDispatcher != null && eventDispatcher.isEnabled()) {
      Map<String, Object> allVariables = new HashMap<>();
      allVariables.putAll(instanceBeforeContext.getVariables());
      allVariables.putAll(instanceBeforeContext.getTransientVariables());
      eventDispatcher.dispatchEvent(FlowableEventBuilder.createProcessStartedEvent(subProcessInitialExecution, allVariables, false),
        processEngineConfiguration.getEngineCfgKey());
    }
  }
​
  /**
   * 重写设置流程变量,把子流程的发起人变量设置进去,解决后面任务可能引用了发起人变量
   * org.flowable.common.engine.api.FlowableException: Unknown property used in expression: ${initiator}Unknown property used in expression: ${initiator}
   *
   * @param dataObjects
   * @param initiator
   * @return
   */
  protected Map<String, Object> processDataObjects(Collection<ValuedDataObject> dataObjects, String initiator) {
    Map<String, Object> variablesMap = new HashMap<>();
    // convert data objects to process variables
    if (dataObjects != null) {
      variablesMap = new HashMap<>(dataObjects.size());
      for (ValuedDataObject dataObject : dataObjects) {
        variablesMap.put(dataObject.getName(), dataObject.getValue());
      }
    }
    variablesMap.put("initiator", initiator);
    return variablesMap;
  }
​
  /**
   * 调用子流程后 需要兼容后续的消息发送 TODO 还需要再根据情况再完善
   */
  private void setEventEntity(){
    RuntimeService runtimeService = SpringContextUtil.getBean(RuntimeService.class);
    CustomerEventEntity customerEventEntity = new CustomerEventEntity();
    ProcessDTO processDTO = new ProcessDTO();
    processDTO.setProcStatus(ProcStatus.PROCESS.name());
    customerEventEntity.setProcessDTO(processDTO);
    customerEventEntity.setProcessDTO(processDTO);
    customerEventEntity.setUser(SessionLoad.getUser());
    customerEventEntity.setEventType(EventType.LAUNCH);
    runtimeService.dispatchEvent(FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.CUSTOM, customerEventEntity));
  }
​
  private String getSubProcessInitiator(CallActivity callActivity){
    String sponsor = null; // 给子流程设置的发起人
    Map<String, List<ExtensionElement>> extensionElements = callActivity.getExtensionElements();
    for(Map.Entry<String, List<ExtensionElement>> entry: extensionElements.entrySet()) {
      // 这一层读取的就是 button 类似的标签
      String attributeName = entry.getKey();
      List<ExtensionElement> attributeValueList = entry.getValue();
      if (attributeName.equals("sponsor")) {
        for(ExtensionElement item: attributeValueList) {
          // 处理 类似button  ExtensionElement 里面的属性
          Map<String, List<ExtensionAttribute>> extensionElementAttributes = item.getAttributes();
          for (Map.Entry<String, List<ExtensionAttribute>> tmpEntry : extensionElementAttributes.entrySet()) {
            String value = tmpEntry.getValue().get(0).getValue();
            sponsor = value; //  把子流程的发起人找到
          }
        }
      }
    }
    return sponsor;
  }
} 
3.2.1.2 自定义 ActivityBehaviorFactory
 public class CustomActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
  @Override
  public CallActivityBehavior createCallActivityBehavior(CallActivity callActivity) {
    return new CustomCallActivityBehavior(callActivity);
  }
}
 
3.2.1.3 设置到引擎
engineConfiguration.setActivityBehaviorFactory(new CustomActivityBehaviorFactory());

3.2.2 其他需求实现

通过该种方式,还可以设置流程变量,流程状态,流程key, 自定义发起事件

可可以参考 3.2.1.1 里面代码

3.3 子流程businessKey设置

平台把应用ID放在了businessKey里面,需要兼容平台,调研得到引擎支持给子流程设置businessKey, 只需要配置xml即可,如下

<callActivity id="callSubProcess" calledElement="checkCreditProcess" flowable:businessKey="${myVariable}"> ... </callActivity>