Merge branch 'feature/countersign_ext' into dev

This commit is contained in:
wangli 2025-10-17 13:41:01 +08:00
commit 770b5bc9d3
7 changed files with 225 additions and 41 deletions

View File

@ -21,6 +21,8 @@ public enum ConvertorRespCode implements IModuleRespCode {
CONVERTOR_OPERATION_NUMBER_TYPE_ERROR("006", "条件节点(数字)运算符【{}】暂不支持"),
CONVERTOR_OPERATION_RADIO_TYPE_ERROR("007", "条件节点(单选)运算符【{}】暂不支持"),
CONVERTOR_OPERATION_CHECKBOX_TYPE_ERROR("008", "条件节点(复选)运算符【{}】暂不支持"),
CREATE_BPMN_USER_TASK_ERROR("009", "创建 UserTask 失败, 不支持的节点模式【{}】"),
CREATE_BPMN_PRE_SIGN_ERROR("010", "创建前加签节点失败,原因:【{}】"),
;
private final String code;

View File

@ -47,6 +47,7 @@ public interface BpmnConstants {
String BIZ_NODE_ALTER = "[_BIZ_NODE_ALTER_]";
String INITIATOR_SPECIFY = "[_INITIATOR_SPECIFY_]";
String SIGNATURE_COLLECTION = "[_SIGNATURE_COLLECTION_]";
String FORWARD_COUNTERSIGN_COUNT = "[_FORWARD_COUNTERSIGN_COUNT_]";
String PROCESS_PREFIX = "Flowable";
@Deprecated
String OLD_TASK_ASSIGNEE_SKIP_FLAT = "taskSkip";
@ -264,4 +265,8 @@ public interface BpmnConstants {
* 提级审批变量标识
*/
String SUPPORT_UPGRADE_VARIABLE = "[_SUPPORT_UPGRADE_]";
/**
* 前加签节点 ID 片段
*/
String FORWARD_ACTIVITY_FRAGMENT = "[forward_sign]";
}

View File

@ -12,6 +12,7 @@ public enum BpmnFlowNodeMode {
GENERAL("GENERAL", "普通节点"),
OR("OR", "或签节点"),
AND("AND", "会签节点"),
SEQUENCE("SEQUENCE", "顺序节点"),
EXCEPTIONAL("EXCEPTIONAL", "异常"),
@JsonEnumDefaultValue
UNKNOWN("UNKNOWN", "未知"),

View File

@ -686,7 +686,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
* @param node
* @param userTask
*/
private static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) {
public static void setExecutionListeners(BpmnJsonNode node, UserTask userTask) {
List<FlowableListener> executionListeners = new ArrayList<>();
// 设置执行监听
@ -725,7 +725,7 @@ public class UserTaskJsonConverter extends AbstractBpmnJsonConverter<UserTask> {
*
* @param userTask
*/
private static void setTaskListeners(UserTask userTask) {
public static void setTaskListeners(UserTask userTask) {
List<FlowableListener> taskListeners = new ArrayList<>();
// 设置任务监听
FlowableListener taskListener = new FlowableListener();

View File

@ -5,6 +5,7 @@ import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper;
import cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper;
import cn.axzo.workflow.core.engine.model.AddComment;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
@ -13,25 +14,24 @@ import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.cfg.IdGenerator;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -42,10 +42,9 @@ import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.OtherRespCode.ASSIGNEE_NODE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ASSIGNER_SHOW_NUMBER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN;
import static cn.axzo.workflow.common.constant.BpmnConstants.FORWARD_ACTIVITY_FRAGMENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.FORWARD_COUNTERSIGN_COUNT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.COUNTERSIGN;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCategoryVersion;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addMultiTask;
@ -169,39 +168,34 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
private void forwardCountSign(CommandContext commandContext, TaskEntity task, List<BpmnTaskDelegateAssigner> valuTargetAssigneeList) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
IdGenerator idGenerator = processEngineConfiguration.getIdGenerator();
TaskService taskService = processEngineConfiguration.getTaskService();
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
// 记录前加签的审批人快照
runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + INTERNAL_ACTIVITY_FORWARD_COUNTERSIGN + task.getId(), valuTargetAssigneeList);
if (task instanceof TaskEntityImpl) {
TaskEntityImpl taskEntity = (TaskEntityImpl) task;
taskEntity.setCountEnabled(true);
taskEntity.setOwner(task.getAssignee());
taskEntity.setAssignee(null);
taskEntity.setScopeType(FORWARD_COUNTERSIGN.getType());
taskService.saveTask(taskEntity);
valuTargetAssigneeList.forEach(e -> {
TaskEntityImpl subTask = (TaskEntityImpl) taskService.newTask(idGenerator.getNextId());
subTask.setName(task.getName());
subTask.setDescription(task.getDescription());
subTask.setCategory(task.getCategory());
subTask.setParentTaskId(task.getId());
subTask.setProcessDefinitionId(task.getProcessDefinitionId());
subTask.setProcessInstanceId(task.getProcessInstanceId());
subTask.setTaskDefinitionKey(task.getTaskDefinitionKey());
subTask.setTaskDefinitionId(task.getTaskDefinitionId());
subTask.setPriority(task.getPriority());
subTask.setCreateTime(new Date());
subTask.setTenantId(task.getTenantId());
// subTask.setAssignee(e.buildAssigneeId());
taskService.saveTask(subTask);
// 设置快照信息
subTask.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + subTask.getId(), e.toJson());
taskService.setAssignee(subTask.getId(), e.buildAssigneeId());
});
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId());
UserTask originalUserTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());
// 获取当前实例前加签次数
Long forwardCounterSignCount = runtimeService.getVariable(processInstance.getId(), FORWARD_COUNTERSIGN_COUNT, Long.class);
if (Objects.isNull(forwardCounterSignCount)) {
forwardCounterSignCount = 0L;
} else {
forwardCounterSignCount = forwardCounterSignCount + 1;
}
BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR;
// 创建前加签节点
UserTask newUserTask = CustomBpmnModelHelper.createUserTask(processEngineConfiguration, originalUserTask, originalUserTask.getId() + FORWARD_ACTIVITY_FRAGMENT + forwardCounterSignCount, nodeMode, valuTargetAssigneeList);
// 加入模型
process.addFlowElement(newUserTask);
// 重新连接顺序流 (Sequence Flow)
CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask);
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
// .moveExecutionsToSingleActivityId(Collections.singletonList(task.getExecutionId()), newUserTask.getId())
.moveActivityIdTo(task.getTaskDefinitionKey(), newUserTask.getId())
.changeState();
}
/**

View File

@ -0,0 +1,178 @@
package cn.axzo.workflow.core.engine.cmd.helper;
import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.springframework.util.StringUtils;
import java.util.List;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_PRE_SIGN_ERROR;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_USER_TASK_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONLY_ONE;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setExecutionListeners;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setTaskListeners;
/**
* 动态操作 BPMN 定义内容的帮助类
*
* @author wangli
* @since 2025-10-15 17:05
*/
@Slf4j
public class CustomBpmnModelHelper {
private CustomBpmnModelHelper() {
}
/**
* 创建 BPMN 中的 UserTask
*
* @param processEngineConfiguration
* @param originalUserTask
* @param nodeMode
*/
public static UserTask createUserTask(ProcessEngineConfigurationImpl processEngineConfiguration, UserTask originalUserTask, String customActivityId, BpmnFlowNodeMode nodeMode, List<BpmnTaskDelegateAssigner> assigners) {
UserTask newUserTask = new UserTask();
if (StringUtils.hasText(customActivityId)) {
newUserTask.setId(customActivityId);
} else {
newUserTask.setId(originalUserTask.getId());
}
newUserTask.setName(originalUserTask.getName());
newUserTask.setDocumentation("" + originalUserTask.getId() + "节点前加签生成");
MultiInstanceLoopCharacteristics loopCharacteristics = new MultiInstanceLoopCharacteristics();
loopCharacteristics.setInputDataItem(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId());
loopCharacteristics.setElementVariable("assigneeName");
newUserTask.setLoopCharacteristics(loopCharacteristics);
newUserTask.setAssignee("${assigneeName}");
switch (nodeMode) {
case AND:
loopCharacteristics.setSequential(false);
loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
break;
case OR:
loopCharacteristics.setSequential(false);
loopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONLY_ONE);
break;
case SEQUENCE:
loopCharacteristics.setSequential(true);
loopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
break;
default:
throw new WorkflowEngineException(CREATE_BPMN_USER_TASK_ERROR, nodeMode.getType());
}
setTaskListeners(newUserTask);
setExecutionListeners(new BpmnJsonNode(), newUserTask);
ExtensionElement approvalMethodElement = new ExtensionElement();
approvalMethodElement.setName(CONFIG_APPROVAL_METHOD);
ExtensionAttribute approvalMethodValueAttribute = new ExtensionAttribute();
approvalMethodValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approvalMethodValueAttribute.setValue(ApprovalMethodEnum.human.getType());
approvalMethodElement.addAttribute(approvalMethodValueAttribute);
ExtensionAttribute approvalMethodDescAttribute = new ExtensionAttribute();
approvalMethodDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approvalMethodDescAttribute.setValue("审批方式");
approvalMethodElement.addAttribute(approvalMethodDescAttribute);
newUserTask.addExtensionElement(approvalMethodElement);
ExtensionElement approverSpecifyElement = new ExtensionElement();
approverSpecifyElement.setName(CONFIG_APPROVER_SPECIFY);
ExtensionAttribute approverSpecifyValueAttribute = new ExtensionAttribute();
approverSpecifyValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approverSpecifyValueAttribute.setValue(ApproverSpecifyEnum.fixedPerson.getType());
approverSpecifyElement.addAttribute(approverSpecifyValueAttribute);
ExtensionAttribute approverSpecifyDescAttribute = new ExtensionAttribute();
approverSpecifyDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approverSpecifyDescAttribute.setValue("审批人指定");
approverSpecifyElement.addAttribute(approverSpecifyDescAttribute);
approverSpecifyElement.setElementText(JSON.toJSONString(assigners));
newUserTask.addExtensionElement(approverSpecifyElement);
ExtensionElement nodeTypeElement = new ExtensionElement();
nodeTypeElement.setName(CONFIG_NODE_TYPE);
nodeTypeElement.setElementText(BpmnFlowNodeType.NODE_TASK.getType());
newUserTask.addExtensionElement(nodeTypeElement);
// TODO 追加设置审批人为空的配置因为查找审批人模式是固定人员会执行退场离职校验导致最终审批人可能为空
// 审批人为空时
ExtensionElement approverEmptyHandleTypeElement = new ExtensionElement();
approverEmptyHandleTypeElement.setName(CONFIG_APPROVER_EMPTY_HANDLE_TYPE);
ExtensionAttribute approverEmptyHandleTypeValueAttribute = new ExtensionAttribute();
approverEmptyHandleTypeValueAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
approverEmptyHandleTypeValueAttribute.setValue(ApproverEmptyHandleTypeEnum.autoPassed.getType());
approverEmptyHandleTypeElement.setElementText("[]");
approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeValueAttribute);
ExtensionAttribute approverEmptyHandleTypeDescAttribute = new ExtensionAttribute();
approverEmptyHandleTypeDescAttribute.setName(ELEMENT_ATTRIBUTE_DESC);
approverEmptyHandleTypeDescAttribute.setValue("审批人为空时");
approverEmptyHandleTypeElement.addAttribute(approverEmptyHandleTypeDescAttribute);
newUserTask.addExtensionElement(approverEmptyHandleTypeElement);
ActivityBehaviorFactory activityBehaviorFactory = processEngineConfiguration.getActivityBehaviorFactory();
UserTaskActivityBehavior userTaskActivityBehavior = activityBehaviorFactory.createUserTaskActivityBehavior(newUserTask);
// TODO 这里设置的 userTaskActivityBehavior 似乎是不对的需要
ParallelMultiInstanceBehavior behavior = activityBehaviorFactory.createParallelMultiInstanceBehavior(newUserTask, userTaskActivityBehavior);
behavior.setCollectionVariable(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + newUserTask.getId());
behavior.setCollectionElementVariable("assigneeName");
behavior.setCompletionCondition(loopCharacteristics.getCompletionCondition());
newUserTask.setBehavior(behavior);
return newUserTask;
}
public static void rewireSequenceFlows(Process process, UserTask originalUserTask, UserTask newUserTask) {
// 1. 找到所有指向原始节点的输入流
List<SequenceFlow> incomingFlows = originalUserTask.getIncomingFlows();
if (incomingFlows.isEmpty()) {
throw new WorkflowEngineException(CREATE_BPMN_PRE_SIGN_ERROR, "节点 " + originalUserTask.getId() + " 没有输入流,无法进行前加签");
}
// 2. 将这些输入流的目标从 originalUserTask 修改为 newUserTask
for (SequenceFlow incomingFlow : incomingFlows) {
incomingFlow.setTargetRef(newUserTask.getId());
// 如果需要也可以更新FlowElement中的引用但通常改TargetRef即可
}
// 3. 创建一个新的顺序流 newUserTask 指向 originalUserTask
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("pre_sign_dynamic" + UUID.fastUUID());
newSequenceFlow.setSourceRef(newUserTask.getId());
newSequenceFlow.setTargetRef(originalUserTask.getId());
process.addFlowElement(newSequenceFlow);
}
}

View File

@ -78,6 +78,10 @@ import static org.flowable.task.api.Task.DEFAULT_PRIORITY;
@Slf4j
public class CustomTaskHelper {
private CustomTaskHelper() {
}
public static void addMultiTask(CommandContext commandContext, TaskEntity originTask,
BpmnTaskDelegateAssigner newTaskAssignee) {
if (Objects.isNull(originTask)) {
@ -352,8 +356,8 @@ public class CustomTaskHelper {
*/
public static TaskEntity createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService
, String processInstanceId, String nodeName, String taskDefinitionKey, String advice,
BpmnTaskDelegateAssigner assigner,
String extTaskInstStatus, AddComment addComment) {
BpmnTaskDelegateAssigner assigner,
String extTaskInstStatus, AddComment addComment) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoryService historyService = processEngineConfiguration.getHistoryService();