一、加签
在中国式流程中,有很多操作比如加签,驳回等,是流程引擎所不能提供的,所谓加签就是在当前审批节点新增一个用户任务,这里默认为后置加签,将新增的审批人的审批顺序置于当前人的后面。
二、节点类型
在日常流程中,可以将审批节点大致分为三个类型
- 或签节点 当有一个审批人通过即可通过。
- 会签节点 节点所有审批人通过方可通过。
- 顺序会签 节点所有审批人通过方可通过且审批人中间顺序审批,一个审批完,后一个方可审批。
接下来会对这三种情况进行加签的处理,关于节点类型,可以自己用一张业务表存储起来。
** 具体实现可以直接跳到第五节 **
三、命令模式与责任链模式
命令模式与责任链模式结合是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的内部表都是用乐观锁操作的。