Merge branch 'feature/countersign_ext' into dev

# Conflicts:
#	workflow-engine-core/src/main/java/cn/axzo/workflow/core/conf/FlowableConfiguration.java
This commit is contained in:
wangli 2025-10-20 11:16:54 +08:00
commit 74af59508d
9 changed files with 272 additions and 9 deletions

View File

@ -20,12 +20,14 @@ import cn.axzo.workflow.core.engine.job.AsyncExtTaskInstJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncRejectTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncRemindTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncResetApproversUserTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncTransferUserTaskJobHandler;
import cn.axzo.workflow.core.engine.job.NextActivityConfigCheckJobHandler;
import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearTraceExceptionHandler;
import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler;
import cn.axzo.workflow.core.engine.persistence.CustomMybatisHistoricProcessInstanceDataManager;
import cn.axzo.workflow.core.service.BpmnProcessActivityService;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.azxo.framework.common.constatns.Constants;
import cn.hutool.core.util.ReflectUtil;
@ -82,6 +84,7 @@ public class FlowableConfiguration {
ObjectProvider<FlowableEventListener> listeners,
CustomActivityBehaviorFactory customActivityBehaviorFactory,
ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService,
BpmnProcessActivityService bpmnProcessActivityService,
List<JobProcessor> jobProcessors,
NacosServiceManager nacosServiceManager,
@ -109,12 +112,11 @@ public class FlowableConfiguration {
configuration.addCustomJobHandler(new AsyncApproveTaskJobHandler());
configuration.addCustomJobHandler(new AsyncBackTaskJobHandler());
configuration.addCustomJobHandler(new AsyncCancelProcessInstanceJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService, extAxDynamicSignRecordService));
configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler());
// configuration.addCustomJobHandler(new AsyncTermNodeAlterJobHandler(refreshProperties));
configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService));
configuration.addCustomJobHandler(new AsyncTermNodeAlterJobHandler(refreshProperties));
configuration.addCustomJobHandler(new AsyncActivityLeaveJobHandler(bpmnProcessActivityService));
configuration.addCustomJobHandler(new AsyncActivityCallbackJobHandler());
configuration.addCustomJobHandler(new AsyncApproveTaskWithFormJobHandler());

View File

@ -8,6 +8,8 @@ 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.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
@ -45,6 +47,7 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ASSIGNE
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.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;
@ -75,11 +78,14 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
private final List<AttachmentDTO> attachmentList;
private final List<BpmnTaskDelegateAssigner> targetTaskAssigneeList;
private final ExtAxHiTaskInstService extAxHiTaskInstService;
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
public CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum countersignType, String originTaskId,
BpmnTaskDelegateAssigner originTaskAssignee, String advice,
List<AttachmentDTO> attachmentList,
List<BpmnTaskDelegateAssigner> targetTaskAssigneeList, ExtAxHiTaskInstService extAxHiTaskInstService) {
List<BpmnTaskDelegateAssigner> targetTaskAssigneeList,
ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService) {
this.countersignType = countersignType;
this.originTaskId = originTaskId;
this.originTaskAssignee = originTaskAssignee;
@ -87,6 +93,7 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
this.attachmentList = attachmentList;
this.targetTaskAssigneeList = targetTaskAssigneeList;
this.extAxHiTaskInstService = extAxHiTaskInstService;
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
}
@Override
@ -191,9 +198,17 @@ public class CustomCountersignUserTaskCmd extends AbstractCommand<Void> implemen
// 重新连接顺序流 (Sequence Flow)
CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask);
ExtAxDynamicSignRecord entity = new ExtAxDynamicSignRecord();
entity.setProcessInstanceId(processInstance.getProcessInstanceId());
entity.setProcessDefinitionId(processInstance.getProcessDefinitionId());
entity.setOriginalActivityId(originalUserTask.getId());
entity.setTargetActivityId(newUserTask.getId());
entity.setCounterSignType(FORWARD_COUNTERSIGN.getType());
entity.setAssignerList(valuTargetAssigneeList);
extAxDynamicSignRecordService.saveOrUpdate(entity);
runtimeService.createChangeActivityStateBuilder()
.processInstanceId(task.getProcessInstanceId())
// .moveExecutionsToSingleActivityId(Collections.singletonList(task.getExecutionId()), newUserTask.getId())
.moveActivityIdTo(task.getTaskDefinitionKey(), newUserTask.getId())
.changeState();
}

View File

@ -8,7 +8,6 @@ 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;
@ -23,6 +22,7 @@ import org.flowable.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import static cn.axzo.workflow.common.code.ConvertorRespCode.CREATE_BPMN_PRE_SIGN_ERROR;
@ -36,6 +36,8 @@ import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_D
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.common.constant.BpmnConstants.SEQUENCE_FLOW_ID;
import static cn.axzo.workflow.core.common.utils.BpmnJsonConverterUtil.id;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setExecutionListeners;
import static cn.axzo.workflow.core.converter.json.UserTaskJsonConverter.setTaskListeners;
@ -167,12 +169,19 @@ public class CustomBpmnModelHelper {
incomingFlow.setTargetRef(newUserTask.getId());
// 如果需要也可以更新FlowElement中的引用但通常改TargetRef即可
}
newUserTask.setIncomingFlows(incomingFlows);
// 3. 创建一个新的顺序流 newUserTask 指向 originalUserTask
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("pre_sign_dynamic" + UUID.fastUUID());
newSequenceFlow.setId(id(SEQUENCE_FLOW_ID + "_PreSign"));
newSequenceFlow.setSourceRef(newUserTask.getId());
newSequenceFlow.setSourceFlowElement(newUserTask);
newSequenceFlow.setTargetRef(originalUserTask.getId());
newSequenceFlow.setTargetFlowElement(originalUserTask);
newUserTask.setOutgoingFlows(Collections.singletonList(newSequenceFlow));
process.addFlowElement(newSequenceFlow);
originalUserTask.setIncomingFlows(Collections.singletonList(newSequenceFlow));
}
}

View File

@ -3,6 +3,7 @@ package cn.axzo.workflow.core.engine.job;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
@ -19,9 +20,12 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple
public static final String TYPE = "async-countersign-task";
private final ExtAxHiTaskInstService extAxHiTaskInstService;
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService) {
public AsyncCountersignUserTaskJobHandler(ExtAxHiTaskInstService extAxHiTaskInstService,
ExtAxDynamicSignRecordService extAxDynamicSignRecordService) {
this.extAxHiTaskInstService = extAxHiTaskInstService;
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
}
@Override
@ -42,6 +46,7 @@ public class AsyncCountersignUserTaskJobHandler extends AbstractJobHandler imple
dto.getAdvice(),
dto.getAttachmentList(),
dto.getTargetAssignerList(),
extAxHiTaskInstService));
extAxHiTaskInstService,
extAxDynamicSignRecordService));
}
}

View File

@ -0,0 +1,53 @@
package cn.axzo.workflow.core.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.conf.handler.ListAssigneeTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
/**
* 扩展动态前后加签记录,用于应用重启后的模型内容恢复
*
* @author wangli
* @since 2025-10-17 18:23
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "ext_ax_dynamic_sign_record", autoResultMap = true)
@Data
@ToString(callSuper = true)
public class ExtAxDynamicSignRecord extends BaseEntity<ExtAxDynamicSignRecord> {
/**
* 流程实例 ID
*/
private String processInstanceId;
/**
* 流程实例对应的流程定义 ID
*/
private String processDefinitionId;
/**
* 加签原节点
*/
private String originalActivityId;
/**
* 加签目标节点
*/
private String targetActivityId;
/**
* 加签类型前加签/后加签
* {@link BpmnCountersignTypeEnum}
*/
private String counterSignType;
/**
* 被加签的审批人
*/
@TableField(value = "assigner_list", typeHandler = ListAssigneeTypeHandler.class)
private List<BpmnTaskDelegateAssigner> assignerList;
}

View File

@ -0,0 +1,14 @@
package cn.axzo.workflow.core.repository.mapper;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* 动态前后加签记录 Mapper
*
* @author wangli
* @since 2025-10-17 18:28
*/
@Mapper
public interface ExtAxDynamicSignRecordMapper extends BaseMapperX<ExtAxDynamicSignRecord> {
}

View File

@ -0,0 +1,13 @@
package cn.axzo.workflow.core.service;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 动态前后加签记录服务接口
*
* @author wangli
* @since 2025-10-17 18:30
*/
public interface ExtAxDynamicSignRecordService extends IService<ExtAxDynamicSignRecord> {
}

View File

@ -0,0 +1,24 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.repository.mapper.ExtAxDynamicSignRecordMapper;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 流程签署业务信息记录
*
* @author wangli
* @since 2025-04-02 10:06
*/
@Service
@Slf4j
public class ExtAxDynamicSignRecordServiceImpl extends ServiceImpl<ExtAxDynamicSignRecordMapper, ExtAxDynamicSignRecord> implements ExtAxDynamicSignRecordService {
@Resource
private ExtAxDynamicSignRecordMapper extAxDynamicSignRecordMapper;
}

View File

@ -0,0 +1,128 @@
package cn.axzo.workflow.server.initializer;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.core.engine.cmd.helper.CustomBpmnModelHelper;
import cn.axzo.workflow.core.repository.entity.ExtAxDynamicSignRecord;
import cn.axzo.workflow.core.service.ExtAxDynamicSignRecordService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.persistence.deploy.DeploymentManager;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.BACK_COUNTERSIGN;
import static cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum.FORWARD_COUNTERSIGN;
/**
* 在框架提供 Web 服务前恢复动态前后加签的流程实例的内存中的模型
*
* @author wangli
* @since 2025-10-17 17:43
*/
@Slf4j
@Component
public class RecoverDynamicCounterSignProcess implements SmartLifecycle {
private final ExtAxDynamicSignRecordService extAxDynamicSignRecordService;
private final SpringProcessEngineConfiguration springProcessEngineConfiguration;
private boolean isRunning = false;
public RecoverDynamicCounterSignProcess(ExtAxDynamicSignRecordService extAxDynamicSignRecordService,
SpringProcessEngineConfiguration springProcessEngineConfiguration) {
this.extAxDynamicSignRecordService = extAxDynamicSignRecordService;
this.springProcessEngineConfiguration = springProcessEngineConfiguration;
}
@Override
public void start() {
// TODO 恢复动态会签中的流程实例
log.info("executing before web server start");
// 查询有过加签的审批实例
List<ExtAxDynamicSignRecord> list = extAxDynamicSignRecordService.list();
if (CollectionUtils.isEmpty(list)) {
return;
}
RuntimeService runtimeService = springProcessEngineConfiguration.getRuntimeService();
// 获取还在审批中的实例
Set<String> processInstanceIds = list.stream().map(ExtAxDynamicSignRecord::getProcessInstanceId).collect(Collectors.toSet());
Set<String> runningProcessIds = runtimeService.createProcessInstanceQuery()
.processInstanceIds(processInstanceIds)
.active()
.list().stream().map(ProcessInstance::getProcessInstanceId).collect(Collectors.toSet());
if (CollectionUtils.isEmpty(runningProcessIds)) {
return;
}
Map<String, List<ExtAxDynamicSignRecord>> grouped = list.stream()
.collect(Collectors.groupingBy(
ExtAxDynamicSignRecord::getProcessInstanceId,
Collectors.collectingAndThen(
Collectors.toList(),
l -> {
l.sort(Comparator.comparing(
ExtAxDynamicSignRecord::getCreateAt,
Comparator.nullsFirst(Comparator.naturalOrder())
));
return l;
}
)
));
RepositoryService repositoryService = springProcessEngineConfiguration.getRepositoryService();
DeploymentManager deploymentManager = springProcessEngineConfiguration.getDeploymentManager();
grouped.forEach((k, v) -> {
if (runningProcessIds.contains(k)) {
log.info("recover dynamic counter sign process instance id: {}", k);
ProcessDefinition processDefinitionEntity = deploymentManager.findDeployedProcessDefinitionById(v.get(0).getProcessDefinitionId());
Process process = deploymentManager.resolveProcessDefinition(processDefinitionEntity).getProcess();
v.forEach(sign -> {
if (Objects.equals(sign.getCounterSignType(), FORWARD_COUNTERSIGN.getType())) {
UserTask originalUserTask = (UserTask) process.getFlowElement(sign.getOriginalActivityId());
BpmnFlowNodeMode nodeMode = Objects.equals(originalUserTask.getLoopCharacteristics().getCompletionCondition(), AND_SIGN_EXPRESSION) ? BpmnFlowNodeMode.AND : BpmnFlowNodeMode.OR;
// 创建前加签节点
UserTask newUserTask = CustomBpmnModelHelper.createUserTask(springProcessEngineConfiguration, originalUserTask, sign.getTargetActivityId(), nodeMode, sign.getAssignerList());
process.addFlowElement(newUserTask);
CustomBpmnModelHelper.rewireSequenceFlows(process, originalUserTask, newUserTask);
} else if (Objects.equals(sign.getCounterSignType(), BACK_COUNTERSIGN.getType())) {
}
});
}
});
isRunning = true;
}
@Override
public void stop() {
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE - 2; // 确保在Web服务器启动前执行
}
}