多实例节点加签

一、加签

在中国式流程中,有很多操作比如加签,驳回等,是流程引擎所不能提供的,所谓加签就是在当前审批节点新增一个用户任务,这里默认为后置加签,将新增的审批人的审批顺序置于当前人的后面。

二、节点类型

在日常流程中,可以将审批节点大致分为三个类型

  • 或签节点 当有一个审批人通过即可通过。
  • 会签节点 节点所有审批人通过方可通过。
  • 顺序会签 节点所有审批人通过方可通过且审批人中间顺序审批,一个审批完,后一个方可审批。
    接下来会对这三种情况进行加签的处理,关于节点类型,可以自己用一张业务表存储起来。
    ** 具体实现可以直接跳到第五节 **

三、命令模式与责任链模式

命令模式与责任链模式结合是activiti整个框架的基础开发模式,命令模式可以很好的将方法提供者和方法调用者解耦,以命令类的形式将每个核心方法分开,避免超级大类的产生。责任链模式在很多框架中都有使用,比如filter的实现。
activiti利用责任链模式为每条命令添加各种拦截器,比如事务拦截器,日志拦截器,在链的最后是CommandInvoker类,这里执行具体命令的execute方法。
可以看一下一个命令的调用过程,比如TaskService.complete方法,首先进入CommandExecutor的execute方法,这个类里包含了链的首部,然后会由此进入链的first的execute方法。

  public void complete(String taskId) {
    commandExecutor.execute(new CompleteTaskCmd(taskId, null));
  }
  private final CommandInterceptor first;
  @Override
  public <T> T execute(Command<T> command) {
    return execute(defaultConfig, command);
  }

  @Override
  public <T> T execute(CommandConfig config, Command<T> command) {
    return first.execute(config, command);
  }

由次直接调用到链的尾部,即CommandInvoker,这里就会执行命令的真正逻辑。

  @Override
  public <T> T execute(CommandConfig config, Command<T> command) {
    return command.execute(Context.getCommandContext());
  }

四、自定义service与command

先创建一个抽象命令类实现command与Serializable接口

@RequiredArgsConstructor
public abstract class ChangeUserCmd implements Command<Object>, Serializable {

    private static final long serialVersionUID = 1L;
    protected final String taskId;
    protected final String operatorId;
    protected final String targetId;

    @Override
    public Object execute(CommandContext commandContext) {
        ProcessEngineConfigurationImpl processEngineConfiguration = commandContext.getProcessEngineConfiguration();
        TaskService taskService = processEngineConfiguration.getTaskService();
        Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateOrAssigned(operatorId).singleResult();
        if(Objects.isNull(task)){
            throw new BusinessException("该用户不是当前节点处理人");
        }

        TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
        TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
        String taskDefinitionKey = taskEntity.getTaskDefinitionKey();
        String processDefinitionId = taskEntity.getProcessDefinitionId();
        //'签证类型 1 或签2会签会签 (超过等于一半审批通过即可)3会签 (依次审批)4会签 (须所有审批人同意)'
        //可以自己用业务表管理 这里随便拿个值
        Integer signType = 1;
        switch (signType){
            case 1:
                parallelOr(commandContext, taskEntity);
                break;
            case 3:
                sequence(commandContext, taskEntity);
                break;
            case 2:
            case 4:
                parallelAnd(commandContext, taskEntity);
                break;
            default:
        }
        return null;
    }

    /**
     * 或签节点
     * @param commandContext  上下文
     * @param taskEntity 任务实体
     * @return
     */
    public abstract void parallelOr(CommandContext commandContext, TaskEntity taskEntity);

    /**
     * 会签并行节点
     * @param commandContext
     * @param taskEntity
     * @return
     */
    public abstract void parallelAnd(CommandContext commandContext, TaskEntity taskEntity);


    /**
     * 会签依次审批节点
     * @param commandContext
     * @param taskEntity
     * @return
     */
    public abstract void sequence(CommandContext commandContext, TaskEntity taskEntity);

activiti的命令都是由RunTimeService,TaskService,HistoryService,ManagementService,RepositoryService,FormService,IdentityService这7大service调用的,我们也可以实现一个自己的service,其中的关键就是CommandExecutor类的注入,我们可以看到在这些service中,实际的调用者是CommandExecutor。
可以看到CommandExecutor的初始化在activiti的核心类ProcessEngineConfigurationImpl中的init()方法中,具体在initCommandExecutors()方法中的 initCommandExecutor()中。

  protected void initCommandExecutor() {
    if (commandExecutor==null) {
      CommandInterceptor first = initInterceptorChain(commandInterceptors);
      commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
    }
  }

在springboot集成的activiti中,springboot会自动注入SpringProcessEngineConfiguration对象,该类继承了ProcessEngineConfigurationImpl,我们可以从这个bean中拿到CommandExecutor对象

@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DataSourceProcessEngineAutoConfiguration {

  @Configuration
  @ConditionalOnMissingClass(name= "javax.persistence.EntityManagerFactory")
  @EnableConfigurationProperties(ActivitiProperties.class)
  public static class DataSourceProcessEngineConfiguration extends AbstractProcessEngineAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @ConditionalOnMissingBean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration(
            DataSource dataSource,
            PlatformTransactionManager transactionManager,
            SpringAsyncExecutor springAsyncExecutor) throws IOException {
      
      return this.baseSpringProcessEngineConfiguration(dataSource, transactionManager, springAsyncExecutor);
    }
  }

创建自定义service,采用构造方法注入bean。只有一个构造方法的时候可以省略@autowire注解

@Slf4j
@Service
public class CustomServiceImpl extends ServiceImpl implements ICustomService {

    public CustomServiceImpl(ProcessEngineConfigurationImpl springProcessEngineConfiguration){
        this.commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        this.processEngineConfiguration = springProcessEngineConfiguration;
    }

    /**
     * 加签
     * @param taskId 任务id
     * @param operatorId 执行人id
     * @param targetId 目标人id
     */
    @Override
    public void countersign(String taskId, String operatorId, String targetId){
        commandExecutor.execute(new CounterSignCmd(taskId, operatorId, targetId));
    }
}

后续调用加签方法就调用customService.countersign方法。

五、加签实现

1.对于会签顺序审批的加签比较简单,直接获取Task相应的Execution,改变其nrOfInstances变量,然后更改多实例节点的变量assigneeList即可,nrOfInstances表示该Execution有多少个task实例,assigneeList是创建多实例节点时,其的loopCharacteristics字段的InputDataItem属性,表示该节点的执行人集合。

2.普通会签与或签的实现方式大体不差,只是设置变量的时候有些差异,或签节点因为只需要一个用户审批,所以nrOfInstances与nrOfActiveInstances不必增加。
对于这两个类型,要新建ExecutionEntity与TaskEntity,并自行设置相关属性。

public class CounterSignCmd extends ChangeUserCmd {

    private static final long serialVersionUID = 1L;

    public CounterSignCmd(String taskId, String operatorId, String targetId) {
        super(taskId, operatorId, targetId);
    }

    /**
     * 或签节点加签
     * @return
     */
    @Override
    public void parallelOr(CommandContext commandContext, TaskEntity taskEntity){
        parallel(commandContext,taskEntity,true);
    }

    /**
     * 会签并行节点加签
     * @return
     */
    @Override
    public void parallelAnd(CommandContext commandContext, TaskEntity taskEntity){
        parallel(commandContext,taskEntity,false);
    }


    /**
     * 会签依次审批节点加签
     * @return
     */
    @Override
    public void sequence(CommandContext commandContext, TaskEntity taskEntity){
        ExecutionEntity executionEntity = taskEntity.getExecution();
        Integer nrOfInstances = (Integer) executionEntity.getVariable("nrOfInstances");
        Integer loopCounter = (Integer) executionEntity.getVariable("loopCounter");
        executionEntity.setVariableLocal("nrOfInstances", nrOfInstances + 1);
        List<String> newAssigneeList = new ArrayList<>();
        List<String> assigneeList = (List<String>) executionEntity.getVariable(ActivitiTools.MUIT_LIST_NAME);
        int i=0;
        for(; i<loopCounter + 1; i++) {
            newAssigneeList.add(assigneeList.get(i));
        }
        newAssigneeList.add(targetId);
        for(; i<assigneeList.size(); i++) {
            newAssigneeList.add(assigneeList.get(i));
        }
        newAssigneeList.addAll(assigneeList);
        executionEntity.setVariable(ActivitiTools.MUIT_LIST_NAME,newAssigneeList );
    }

    /**
     * 普通会签/或签
     * @param commandContext
     * @param taskEntity
     * @param flag
     */
    private void parallel(CommandContext commandContext, TaskEntity taskEntity,Boolean flag){
        ProcessEngineConfigurationImpl processEngineConfiguration = commandContext.getProcessEngineConfiguration();

        ExecutionEntity executionEntity = taskEntity.getExecution();
        //创建新的执行流
        ExecutionEntity parentExecutionEntity = executionEntity.getParent();
        ExecutionEntity newExecutionEntity = parentExecutionEntity.createExecution();
        newExecutionEntity.setActive(true);
        newExecutionEntity.setConcurrent(true);
        newExecutionEntity.setScope(false);
        //创建新的任务
        TaskEntity newTaskEntity = new TaskEntity();
        newTaskEntity.setCreateTime(new Date());
        newTaskEntity.setTaskDefinition(taskEntity.getTaskDefinition());
        newTaskEntity.setProcessDefinitionId(taskEntity.getProcessDefinitionId());
        newTaskEntity.setTaskDefinitionKey(taskEntity.getTaskDefinitionKey());
        newTaskEntity.setProcessInstanceId(taskEntity.getProcessInstanceId());
        newTaskEntity.setExecutionId(newExecutionEntity.getId());
        newTaskEntity.setName(taskEntity.getName());
        newTaskEntity.setId(processEngineConfiguration.getIdGenerator().getNextId());
        newTaskEntity.setExecution(newExecutionEntity);
        newTaskEntity.setAssignee(targetId);
        processEngineConfiguration.getTaskService().saveTask(newTaskEntity);

        //历史任务没有流程id 执行id 流程定义id TODO

        //更改节点完成条件
        if(flag){
            Integer nrOfInstances = (Integer)executionEntity.getVariable("nrOfInstances");
            Integer nrOfActiveInstances = (Integer)executionEntity.getVariable("nrOfActiveInstances");
            executionEntity.setVariable("nrOfInstances", nrOfInstances + 1);
            executionEntity.setVariable("nrOfActiveInstances", nrOfActiveInstances + 1);
        }

    }

}

这时还有一个小问题,历史任务表里面新增的任务没有流程id 执行id 流程定义id,需要自行处理,这里要注意一下,activiti的内部表都是用乐观锁操作的。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Activiti 是一个开源的工作流引擎,支持并行多实例节点加签功能。在流程定义中,可以通过添加多实例属性来定义一个节点为并行多实例节点。并行多实例节点可以将一个任务同时分配给多个参与者处理。 要将一个节点定义为并行多实例节点,可以在节点的XML中添加以下属性: ``` <userTask id="task1" name="Task 1" activiti:assignee="${assignee}" activiti:multiInstanceLoopCharacteristics="_activiti_parallel"> ... </userTask> ``` 其中,`activiti:assignee` 属性用于指定任务的执行人,可以使用表达式来动态指定。`activiti:multiInstanceLoopCharacteristics` 属性用于指定并行多实例的循环特性。 在节点定义中,可以添加多实例的属性和子元素来定义循环的次数和条件,比如: ``` <activiti:multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user"> <activiti:loopCardinality>${nrOfCandidates}</activiti:loopCardinality> ... </activiti:multiInstanceLoopCharacteristics> ``` 其中,`isSequential` 属性用于指定循环需要按顺序执行还是并行执行。`activiti:collection` 属性用于指定循环的集合,可以是一个变量或表达式。`activiti:elementVariable` 属性用于指定循环中的变量名。`activiti:loopCardinality` 属性用于指定循环的次数。 通过加签功能,可以将任务同时分配给多个参与者处理,提高处理效率。每个参与者可以独立处理任务,并在处理完成后将结果反馈给流程引擎。流程引擎会根据加签节点的配置,汇总参与者的处理结果,并决定流程的后续走向。 总而言之,Activiti 的并行多实例节点加签功能可以帮助流程引擎实现任务的并行处理,提高工作效率和流程的灵活性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值