一、需求
业务需求要给子流程指定流程发起人,而不是触发活动调用的当前人,其实在梳理的过程中还发现一些需求,跟还需要兼容一些需求,现在罗列如下
- 设置子流程的发起人
- 增加/修改启动时流程变量
- 设置流程状态 businessStatus
- 设置流程key businessKey
- 父子表单数据传递
二、调研
最开始想找一个在创建子流程的过程中,找到相关的拦截器,然后进行我们的业务逻辑修改。思路大体是对的,但是一直没有找到,然后打算使用 执行监听器。
方案 | 效果 | 大概方式 | 为何不行 | 实现了哪些 |
---|---|---|---|---|
执行监听器+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>