Activiti快速使用

Activiti快速使用

  • 生命周期
  • 服务架构:
    • ProcessEngineConfiguration
    • ProcessEngine创建
    • Service
  • 流程定义与实例
    • 部署
    • 启动流程实例
    • 查询processDefinition
    • 查询ProcessInstance
    • 激活与挂起
    • 删除
    • 资源查询
    • 历史信息查询
    • 查询任务列表TaskService
    • 处理任务
    • BusinessKey
  • 分配任务
    • 个人任务
    • 流程变量的设置
      • local流程变量设置
    • 组任务
  • 网关
  • 在业务系统中的使用
    • 环境部署
      • 数据库的配置
        • 在业务系统中扩展业务流程的数据库
    • 流程部署
    • 流程定义
    • 流程申请
    • 提交审核

使用步骤:

  1. 整合activiti
  2. 实现业务流程建模,使用BPMN实现业务流程图
  3. 部署业务流程到activiti
  4. 启动流程实例
  5. 查询待办事务
  6. 处理待办任务
  7. 结束流程

生命周期

Activiti快速使用_第1张图片

静态阶段 : 包含流程设计和流程部署 —xml模型
运行时阶段: 用户发起流程,各责任人对流程进行审核驳回等操作,Activiti 自动的根据流程状态
历史阶段:对于以关闭或审核通过的历史流程,进行查询的管理

Activiti内置数据库的作用:
流程部署:将绘制好的流程上传到引擎
流程定义:部署之后,流程定义对象
流程实例:用户发起流程
流程任务:流程实例中每个节点的处理任务

服务架构:

Activiti快速使用_第2张图片

activiti 的引擎配置文件,包括: ProcessEngineConfiguration 的定义、数据源定义、事务管理器等,
此文件其实就是一个 spring 配置文件

ProcessEngineConfiguration

两种配置方式:
一:Standalone,通常在 activiti.cfg.xml 配置文件中定义一个 id 为 processEngineConfiguration 的 bean,这里会使用 spring 的依赖注入来构建引擎。

<bean id="processEngineConfiguration"  class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfig  
uration">  
	  
	<property name="dataSource" ref="dataSource" />  
	  
	<property name="databaseSchemaUpdate" value="true"/>  
bean>

二:与spring整合配置文件

  
<bean id="processEngineConfiguration"  
class="org.activiti.spring.SpringProcessEngineConfiguration">  
  
<property name="dataSource" ref="dataSource" />  
  
<property name="transactionManager" ref="transactionManager" />  
  
<property name="databaseSchemaUpdate" value="drop-create" />  
  
<property name="jobExecutorActivate" value="false" />  
bean>  
  

<bean  id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration"  
ref="processEngineConfiguration" />  
bean>  
  
<bean id="repositoryService" factory-bean="processEngine"  
factory-method="getRepositoryService" />  
  
<bean id="runtimeService" factory-bean="processEngine"  
factory-method="getRuntimeService" />  
  
<bean id="taskService" factory-bean="processEngine"  
factory-method="getTaskService" />  
  
<bean id="historyService" factory-bean="processEngine"  
factory-method="getHistoryService" />  
  
<bean id="identityService" factory-bean="processEngine"  
factory-method="getIdentityService" />  
  
<bean id="managementService" factory-bean="processEngine"  
factory-method="getManagementService" />

创建ProcessEngineConfiguration对象:

//要求 activiti.cfg.xml 中必须有一个 processEngineConfiguration 的 bean
ProcessEngineConfiguration configuration =  ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml")

//也可以使用下边的方法,更改 bean 的名字
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource, String beanName);

ProcessEngine创建

//通过ProcessEngineConfiguration创建ProcessEngine  
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();

//使用classpath下的activiti.cfg.xml中的配置创建processEngine (activiti.cfg.xml 文件名及路径固定,且 activiti.cfg.xml 文件中有 processEngineConfiguration 的配置) 
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

Service

Service 描述
RepositoryService activiti 的资源管理类,部署查询流程定义,暂停激活,获取多种资源
RuntimeService activiti 的流程运行管理类
TaskService activiti 的任务管理类
HistoryService activiti 的历史管理类
ManagerService activiti 的引擎管理类
使用processEngine.getXXXService()创建

流程定义与实例

部署

  • 流程定义的部署
  • activiti表有哪些?
  • act_re_deployment 部署信息
  • act_re_procdef 流程定义的一些信息,该表中KEY作为唯一识别
  • act_ge_bytearray 流程定义的bpmn文件及png文件
    一次部署一个流程,这样部署表和流程定义表是一对一有关系,方便读取流程部署及流程定义信息。

单个文件方式

//1.创建ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到RepositoryService实例  
RepositoryService repositoryService = processEngine.getRepositoryService();  
  
//3.进行部署  
Deployment deployment = repositoryService.createDeployment()  
        .addClasspathResource("diagram/holiday2.bpmn20.xml")  //添加bpmn资源  
        .addClasspathResource("diagram/holiday2.bpmn20.png")  
        .name("请假申请单流程")  
        .deploy();

以上传文件方式部署
public void deployProcess(MultipartFile file, String companyId) throws IOException {  
   //1.获取上传的文件名  
   String fileName = file.getOriginalFilename();  
   //2.通过repositoryService进行流程部署  
   //DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();  
   //文件名称,文件的bytes数组  
   //deploymentBuilder.addBytes(fileName,file.getBytes()); //部署流程  
   //deploymentBuilder.tenantId(companyId);//不同系统用一个流程定义来启动流程实例,tenantId用以区分同一个流程定义下分属不同系统的流程实例  
   DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().addBytes(fileName, file.getBytes()).tenantId(companyId);  
   Deployment deploy = deploymentBuilder.deploy();  
   //3.打印部署结果  
   System.out.println(deploy);  
}

压缩包方式

//1.创建ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到RepositoryService实例  
RepositoryService repositoryService = processEngine.getRepositoryService();  
  
//3.转化出ZipInputStream流对象  
InputStream is = ActivitiDeployment.class.getClassLoader().getResourceAsStream("diagram/holidayBPMN.zip");  
  
//将 inputstream流转化为ZipInputStream流  
ZipInputStream zipInputStream = new ZipInputStream(is);  
  
//3.进行部署  
Deployment deployment = repositoryService.createDeployment()  
        .addZipInputStream(zipInputStream)        .name("请假申请单流程")  
        .deploy();//部署

Deployment

@Internal  
public interface Deployment {  
    String getId();  
  
    String getName();  
  
    Date getDeploymentTime();  
  
    String getCategory();  
  
    String getKey();  
  
    String getTenantId();  
}

启动流程实例

背后影响的表:

  • act_hi_actinst 已完成的活动信息
    act_hi_identitylink 参与者信息
    act_hi_procinst 流程实例
    act_hi_taskinst 任务实例
    act_ru_execution 执行表
    act_ru_identitylink 参与者信息
    act_ru_task 任务
//1.得到ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到RunService对象  
RuntimeService runtimeService = processEngine.getRuntimeService();  
  
//3.创建流程实例  流程定义的key需要知道 processDefinitionKey
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey);

查询processDefinition

// 流程定义key  
String processDefinitionKey = "holiday";  
// 获取repositoryService  
RepositoryService repositoryService = processEngine.getRepositoryService();  
// 查询流程定义  
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();  
//遍历查询结果  
List<ProcessDefinition> list = processDefinitionQuery  
	.processDefinitionKey(processDefinitionKey)
	.orderByProcessDefinitionVersion().desc().list();

//通过tenantId查询
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(companyId)  
      .latestVersion().list();

ProcessDefinition

@Internal  
public interface ProcessDefinition {  
    String getId();  
  
    String getCategory();  
  
    String getName();  
  
    String getKey();  
  
    String getDescription();  
  
    int getVersion();  
  
    String getResourceName();  
  
    String getDeploymentId();  
  
    String getDiagramResourceName();  
  
    boolean hasStartFormKey();  
  
    boolean hasGraphicalNotation();  
  
    boolean isSuspended();  
  
    String getTenantId();  
  
    String getEngineVersion();  
}

查询ProcessInstance

流程在运行过程中可以查询流程实例的状态,当前运行结点等信息

//1.得到ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.获取RuntimeService  
RuntimeService runtimeService = processEngine.getRuntimeService();  
  
// 流程定义key  
String processDefinitionKey = "holiday";  
  
//3.得到ProcessInstanceQuery对象,可以认为它就是一个查询器  
//4.设置条件,并查询出当前的所有流程定义   查询条件:流程定义的key=holiday  
//orderByProcessDefinitionVersion() 设置排序方式,根据流程定义的版本号进行排序 ProcessInstance中没有version  
List<ProcessInstance> list = runtimeService  
        .createProcessInstanceQuery()  
        .processDefinitionKey(processDefinitionKey)//  
        .list();
//5.输出流程定义信息  
for(ProcessInstance processInstance :list){  
    System.out.println("----------------------------");  
    System.out.println("流程实例id: "            + processInstance.getProcessInstanceId());  
    System.out.println("所属流程定义id: "            + processInstance.getProcessDefinitionId());  
    System.out.println("是否执行完成: " + processInstance.isEnded());  
    System.out.println("是否暂停: " + processInstance.isSuspended());  
    System.out.println(" 当 前 活 动 标 识 : " +  
            processInstance.getActivityId());

根据自己的业务id查询流程实例如自定义proc_instance表中的processId

ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey( String processInstanceBusinessKey).singleResult();

激活与挂起

全部流程实例RepositoryService

//1.得到ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到RepositoryService  
RepositoryService repositoryService = processEngine.getRepositoryService();  
  
//3.查询流程定义的对象,可以根据id或key查询,但此时id需要去表中查看  
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()  
        .processDefinitionKey("holiday").singleResult();  
//或根据processKey和tenantId查询
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey)  
      .processDefinitionTenantId(companyId).latestVersion().singleResult();
  
//4.得到当前流程定义的实例是否都为暂停状态  
boolean suspended = processDefinition.isSuspended();  
  
String processDefinitionId = processDefinition.getId();  
//5.判断  
if(suspended){  
    //说明是暂停,就可以激活操作,可以根据id或key,此时id已可以查出  
    repositoryService.activateProcessDefinitionById(processDefinitionId,true  
    ,null);  
    System.out.println("流程定义:"+processDefinitionId+"激活");  
}else{  
    repositoryService.suspendProcessDefinitionById(processDefinitionId,true,null);  
    System.out.println("流程定义:"+processDefinitionId+"挂起");  
}

单个流程实例

//1.得到ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到RuntimeService  
RuntimeService runtimeService = processEngine.getRuntimeService();  
  
//3.查询流程实例对象  .processEngineInstanceBusinessKey("holiday")报错  
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()  
        .processInstanceId("2501").singleResult();  
//4.得到当前流程定义的实例是否都为暂停状态  
boolean suspended = processInstance.isSuspended();  
  
String processInstanceId = processInstance.getId();  
//5.判断  
if(suspended){  
    //说明是暂停,就可以激活操作  
    runtimeService.activateProcessInstanceById(processInstanceId);    System.out.println("流程:"+processInstanceId+"激活");  
}else{  
    runtimeService.suspendProcessInstanceById(processInstanceId);    System.out.println("流程:"+processInstanceId+"挂起");  
}

删除

背后影响的表:

  • act_ge_bytearray act_re_deployment act_re_procdef
    如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关
    记录全部删除。 项目开发中使用级联删除的情况比较多, 删除操作一般只开放给超级管理员使
    用。
/**  
 * 注意事项:  
 *     1.当我们正在执行的这一套流程没有完全审批结束的时候,此时如果要删除流程定义信息就会失败  
 *     2.如果公司层面要强制删除,可以使用repositoryService.deleteDeployment("1",true);  
 *     //参数true代表级联删除,此时就会先删除没有完成的流程结点,最后就可以删除流程定义信息  false的值代表不级联  
 *  
 */
 repositoryService.deleteDeployment(deploymentId);
 repositoryService.deleteDeployment(deploymentId, true);

删除流程实例

runtimeService.deleteProcessInstance( String processInstanceId,
    String deleteReason)

资源查询

通过流程定义对象获取流程定义资源,获取 bpmn 和 png。

		//1.得到ProcessEngine对象  
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
        //2.得到RepositoryService对象  
        RepositoryService repositoryService = processEngine.getRepositoryService();  
  
        //3.得到查询器:ProcessDefinitionQuery对象  
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();  
  
        //4.设置查询条件  processDefinitionKey,也可通过id查询
        processDefinitionQuery.processDefinitionKey("holiday");//参数是流程定义的key  
  
        //5.执行查询操作,查询出想要的流程定义(目前只有一条,用singleResult)  
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();  
  
        //6.通过流程定义信息,得到部署ID  
        String deploymentId = processDefinition.getDeploymentId();  
  
        //7.通过repositoryService的方法,实现读取图片信息及bpmn文件信息(输入流)  
        //getResourceAsStream()方法的参数说明:第一个参数部署id,第二个参数代表资源名称  
        //processDefinition.getDiagramResourceName() 代表获取png图片资源的名称  
        //processDefinition.getResourceName()代表获取bpmn文件的名称  
        /*InputStream pngIs = repositoryService  
                .getResourceAsStream(deploymentId,processDefinition.getDiagramResourceName());*/        
        InputStream bpmnIs = repositoryService  
                .getResourceAsStream(deploymentId,processDefinition.getResourceName());  
  
        //8.构建出OutputStream流  
        System.out.println(processDefinition.getDiagramResourceName());//diagram/  
        OutputStream pngOs =                new FileOutputStream("path\\"+processDefinition.getDiagramResourceName());  
*/  
        OutputStream bpmnOs =  
                new FileOutputStream("path\\"+processDefinition.getResourceName());  
  
        //9.输入流,输出流的转换  commons-io-xx.jar中的方法  
        //IOUtils.copy(pngIs,pngOs);  
        IOUtils.copy(bpmnIs,bpmnOs);  
        //10.关闭流  
        //pngOs.close();  
        bpmnOs.close();  
        //pngIs.close();  
        bpmnIs.close();

通过查询流程部署信息获取流程定义资源。

// 通过流程引擎获取repositoryService  
RepositoryService repositoryService = processEngine.getRepositoryService();  
//读取资源名称  
List<String> resources =  repositoryService.getDeploymentResourceNames(deploymentId);  
String resource_image = null;  
//获取图片  
for(String resource_name :resources){  
	if(resource_name.indexOf(".png")>=0){  
		resource_image = resource_name;  
	}  
}
//图片输入流
InputStream inputStream =repositoryService.getResourceAsStream(deploymentId, resource_image);
File exportFile = new File("d:/holiday.png");
FileOutputStream fileOutputStream = newFileOutputStream(exportFile);
byte[] buffer = new byte[1024];
int len = -1;
//输出图片
while((len = inputStream.read(buffer))!=-1){
	fileOutputStream.write(buffer, 0, len);
}
inputStream.close();
fileOutputStream.close();

说明:

  1. deploymentId 为流程部署 ID
  2. resource_name 为 act_ge_bytearray 表中 NAME_列的值
  3. 使用 repositoryService 的 getDeploymentResourceNames方法可以获取指定部署下得所有文件的名
  4. 使用 repositoryService 的 getResourceAsStream 方法传入部署 ID和资源图片名称可以获取部署下
    指定名称文件的输入流
  5. 最后的将输入流中的图片资源进行输出。

历史信息查询

//1.得到ProcessEngine对象  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到HistoryService  
HistoryService historyService = processEngine.getHistoryService();  
  
//3.得到HistoricActivitiInstanceQuery对象  
HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService.createHistoricActivityInstanceQuery();  
  
historicActivityInstanceQuery.processInstanceId("2501");//设置流程实例的id PROC_INST_ID_  
  
//4.执行查询  
List<HistoricActivityInstance> list = historicActivityInstanceQuery  
        .orderByHistoricActivityInstanceStartTime().asc().list();//排序StartTime
//5.遍历查询结果  
for (HistoricActivityInstance instance :list){

查询任务列表TaskService

//1.得到ProcessEngine对象 这是简单方式,将 activiti.cfg.xml 文件名及路径固定,且 activiti.cfg.xml 文件中有 processEngineConfiguration 的配置  
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();  
  
//2.得到TaskService对象  
TaskService taskService = processEngine.getTaskService();  
  
//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询  
List<Task> taskList = taskService.createTaskQuery()  
        .processDefinitionKey("holiday")  
        .taskAssignee("lisi")  
        .list();

//根据流程实例ID获取任务节点
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();

处理任务

背后操作的表:

  • act_hi_actinst 完成上一节点会插入下一节点
    act_hi_identitylink
    act_hi_taskinst act_ru_identitylink act_ru_task 删除已完成节点,插入下一节点
for(Task task :taskList){
	...
	taskService.complete(task.getId());

BusinessKey

字段 BUSINESS_KEY 就是存放业务 KEY 的。再通过主键 ID 去查询业务信息,比如通过请假单的 ID,去查询更多的
请假信息(请假人,请假时间,请假天数,请假事由等)

  • 业务标识与流程实例一一对应,存储业务标识就是根据业务标识来关联查询业务系统的数据。
  • 本质:act_ru_execution表中的businessKey的字段要存入业务标识
  • 比如: 请假流程启动一个流程实例,就可以将请假单的 id 作为业务标识存储到 activiti 中,将来查询activiti 的流程实例信息就可以获取请假单的 id 从而关联查询业务系统数据库得到请假单信息。
  • 流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键 id 和流程实例 id 相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例 id 不相同的记录。 不论当前有几个分支总会有一条记录的执行表的主键和流程实例 id 相同
  • 一个流程实例运行完成,此表中与流程实例相关的记录删除。
//第一个参数:是指流程定义key  
//第二个参数:业务标识businessKey  
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday", "请假流程人id");  
  
//4.输出processInstance相关的属性,取出businessKey使用:processInstance.getBusinessKey()  
System.out.println(processInstance.getBusinessKey());

分配任务

个人任务

固定分配:Assignee指定
UEL 表达式: UEL-value
- ${assignee0}${user.assignee}, assignee 这个变量是 activiti 的一个流程变量。user 也是 activiti 的一个流程变量, user.assignee 表示通过调用 user 的 getter 方法获取值。
UEL-method:
${userBean.getHolidayId()}userBean 是 spring 容器中的一个 bean,表示调用该 bean 的 getUserId()方法。
UEL-method 与 UEL-value 结合:
${ldapService.findManagerForEmployee(emp)} ldapService 是 spring 容器的一个 bean,findManagerForEmployee 是该 bean 的一个方法, emp 是 activiti 流程变量, emp 作为参数传到 ldapService.findManagerForEmployee 方法中。
表达式支持解析基础类型、 bean、 list、 array 和 map,也可作为条件判断:
${order.price > 100 && order.price < 250}

流程变量的设置

启动流程时设置

ProcessInstance processInstance=runtimeService.startProcessInstanceByKey( String processDefinitionKey,
    java.util.Map<String, Object> variables);

任务办理时设置taskService.complete(String taskId,
java.util.Map variables);

通过当前流程实例id设置全局变量,该流程实例必须未执行完成

//第一个参数:流程实例的id  
//第二个参数:流程变量名  
//第三个变量:流程变量名,所对应的值
runtimeService.setVariable(String executionId,
    String variableName,
    Object value);
//一次设置多个值  
runtimeService.setVariables(executionId, variables)

通过当前任务设置

taskService.setVariable( String taskId,
    String variableName,
    Object value);  
//一次设置多个值  
taskService.setVariables(taskId, variables)

local流程变量设置

任务办理时

// 设置local变量,作用域为该任务,每个任务可以设置同名的变量,互不影响  
taskService.setVariablesLocal(tasked, variables);  
taskService.complete(taskId);

通过当前任务设置

taskService.setVariableLocal( String taskId,
    String variableName,
    Object value);  
//一次设置多个值  
taskService.setVariablesLocal(taskId, variables)

组任务

candidate-users(候选人),多个候选人之间用逗号分开

流程:
启动流程实例,有任务申请之后
第一步:查询组任务
指定候选人,查询该候选人当前的待办任务。
候选人不能办理任务。

List<Task> list = taskService.createTaskQuery()  
        .processDefinitionKey(key)  //流程定义的key      
        .taskCandidateUser(candidate_users)//设置候选用户  
        .list();

第二步:拾取(claim)任务
该组任务的所有候选人都能拾取。
将候选人的组任务,变成个人任务。原来候选人就变成了该任务的负责人。

  • 如果拾取后不想办理该任务?
    - 需要将已经拾取的个人任务归还到组里边,将个人任务变成了组任务。
if(task!=null){  
    taskService.claim(task.getId(),candidate_users);//第一个参数任务ID,第二个参数为具体的候选用户名  
    System.out.println("任务拾取完毕!");
}
//任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人 taskService.setAssignee(task.getId(),"lisi")
//如果只是归还任务 taskService.setAssignee(task.getId(),null)建议不要这样使用
if(task!=null){  
    taskService.setAssignee(task.getId(),"lisi");//交接任务为lisi  ,交接任务就是一个候选人拾取用户的过程  
    System.out.println("交接任务完成~!");  
}

第三步:查询个人任务
查询方式同个人任务部分,根据 assignee 查询用户负责的个人任务。

List<Task> list = taskService.createTaskQuery()  
        .processDefinitionKey(key)        .taskAssignee(assignee)  //设置任务的负责人  
        .list();

第四步:办理个人任务taskService.complete(task.getId());

网关

排他网关:排他网关只会选择一个为 true 的分支执行。 (即使有两个分支条件都为 true, 排他网关也会只
选择一条分支去执行),如果从网关出去的线所有条件都不满足则系统抛出异常,经过排他网关必须要有一条且只有一条分支走。

并行网关:

  • fork 分支:
    并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
  • join 汇聚:
    所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
    并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。

包含网关:

  • 分支:
    所有外出顺序流的条件都会被解析,结果为 true 的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
  • 汇聚:
    所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程 token 的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
    先走到汇聚结点的分支,要等待其它分支走到汇聚。 等所有分支走到汇聚,包含网关就执行完成。
    包含网关执行完成,分支和汇聚就从 act_ru_execution 删除

在业务系统中的使用

环境部署


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.itheima.activitigroupId>
    <artifactId>activiti01artifactId>
    <version>1.0-SNAPSHOTversion>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <version>2.6.9version>
            plugin>

        plugins>
    build>

    <properties>
        <slf4j.version>2.0.0slf4j.version>
        <log4j.version>1.2.17log4j.version>
    properties>

    <dependencies>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-engineartifactId>
            <version>7.0.0.SR1version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatisartifactId>
                    <groupId>org.mybatisgroupId>
                exclusion>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-springartifactId>
            <version>7.0.0.SR1version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-bpmn-modelartifactId>
            <version>7.0.0.SR1version>
        dependency>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-bpmn-converterartifactId>
            <version>7.0.0.SR1version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-json-converterartifactId>
            <version>7.0.0.SR1version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.activitigroupId>
            <artifactId>activiti-bpmn-layoutartifactId>
            <version>7.0.0.SR1version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-apiartifactId>
                    <groupId>org.slf4jgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.activiti.cloudgroupId>
            <artifactId>activiti-cloud-services-apiartifactId>
            <version>7.0.0.Beta1version>
            <exclusions>
                <exclusion>
                    <artifactId>activiti-engineartifactId>
                    <groupId>org.activitigroupId>
                exclusion>
                <exclusion>
                    <artifactId>jackson-coreartifactId>
                    <groupId>com.fasterxml.jackson.coregroupId>
                exclusion>
                <exclusion>
                    <artifactId>spring-contextartifactId>
                    <groupId>org.springframeworkgroupId>
                exclusion>
                <exclusion>
                    <artifactId>jackson-databindartifactId>
                    <groupId>com.fasterxml.jackson.coregroupId>
                exclusion>
                <exclusion>
                    <artifactId>activiti-bpmn-modelartifactId>
                    <groupId>org.activitigroupId>
                exclusion>
                <exclusion>
                    <artifactId>spring-beansartifactId>
                    <groupId>org.springframeworkgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.29version>
        dependency>

        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
        dependency>

        

        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>${log4j.version}version>
        dependency>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>${slf4j.version}version>
        dependency>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-log4j12artifactId>
            <version>${slf4j.version}version>
            <exclusions>
                <exclusion>
                    <artifactId>log4jartifactId>
                    <groupId>log4jgroupId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-nopartifactId>
            <version>${slf4j.version}version>
        dependency>

        


        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.11version>
        dependency>

        
        <dependency>
            <groupId>commons-dbcpgroupId>
            <artifactId>commons-dbcpartifactId>
            <version>1.4version>
        dependency>
        
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
            <version>2.11.0version>
        dependency>

    dependencies>


    <repositories>
        <repository>
            <id>alfrescoid>
            <name>Activiti Releasesname>
            <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases/url>
            <releases>
                <enabled>trueenabled>
            releases>
        repository>

    repositories>


project>

**Springboot整合activiti时:
activiti-spring-boot默认集成了spring security用于权限管理如需禁用security启动类中屏蔽ActivitiSpringIdentityAutoConfiguration,再增加一个配置类即可 Application中禁用权限相关集成

package com.meijm.activiti;
import org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = {ActivitiSpringIdentityAutoConfiguration.class})
public class ActivitiApplication {
    public static void main(String[] args) {        
	    SpringApplication.run(ActivitiApplication.class);    }
}

数据库的配置

数据库连接池添加activiti相关数据库如act

datasource:  
  act:  
    jdbc-url: jdbc:mysql://localhost:3306/act?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT  
    username: root  
    password: 123456  
    driver-class-name: com.mysql.jdbc.Driver

  activiti:  
	history-level: full  
	db-history-used: true

如果activiti 初始化表 问题:Error querying database. Cause: java.sql.SQLSyntaxErrorException: Table ‘activiti_dev.act_ge_property’ doesn’t exist: 因为mysql使用schema标识库名而不是catalog,因此mysql会扫描所有的库来找表,如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。
设置nullCatalogMeansCurrent=true,表示mysql默认当前数据库操作,在mysql-connector-java 5.xxx该参数默认为true,在6.xxx以上默认为false,因此需要设置nullCatalogMeansCurrent=true。
useUnicode=true``&characterEncoding=utf8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC

多数据源配置
将activiti数据库连接池作为默认主源,业务数据库连接池明确指定DataSource

@Configuration  
public class AuditDatasourceConfig extends AbstractProcessEngineAutoConfiguration {  
    //主数据源,用来支持act数据库  
    @Bean//创建对象  
    @Primary//默认主源  
    @ConfigurationProperties(prefix = "spring.datasource.act")//从application.yml配置文件中拿到act的信息  
    @Qualifier("activitiDataSource")  
    public DataSource activitiDataSource() {  
        return DataSourceBuilder.create().build();  
    }  
  
    //业务数据源,使用jpa开发(entityManager和transactionManager)  
    @Bean  
    @ConfigurationProperties(prefix = "spring.datasource.ihrm")  
    @Qualifier("ihrmDataSource")  
    public DataSource ihrmDataSource() {  
        return DataSourceBuilder.create().build();  
    }  
}

业务数据库的配置(使用SpringDataJpa开发,entityManager和transactionManager)

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Primary;  
import org.springframework.core.env.Environment;  
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;  
import org.springframework.orm.jpa.JpaTransactionManager;  
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;  
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;  
import org.springframework.transaction.PlatformTransactionManager;  
  
import javax.sql.DataSource;  
import java.util.HashMap;  
  
/**  
 * 业务数据库的配置  
 */  
@Configuration  
//自己去定义jpa相关信息  
@EnableJpaRepositories(  
      //代理的dao接口所在的包,这里指定了dao的位置,要想业务生效只能将相关dao放到这个位置  
   basePackages = "com.ihrm.audit.dao",  
   entityManagerFactoryRef = "ihrmEntityManager",//entityManager工厂需自己创建  
   transactionManagerRef = "ihrmTransactionManager"  
)  
public class JpaRepositoriesConfig {  
  
   @Autowired  
   private Environment env;  
  
   //entityManager和transactionManager都需要指定datasource  
   @Autowired  
   @Qualifier("ihrmDataSource")  
   private DataSource ihrmDataSource;  
  
   //创建entityManagerFactory工厂  
   @Bean  
   @Primary   public LocalContainerEntityManagerFactoryBean ihrmEntityManager() {  
      LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();  
      em.setDataSource(ihrmDataSource);  
      //配置扫描的实体类包(springdatajpa会自动扫描),要用到的实体类统一放到这个位置  
      em.setPackagesToScan(new String[] { "com.ihrm.audit.entity","com.ihrm.domain.system" });  
      HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();  
      em.setJpaVendorAdapter(vendorAdapter);  
      HashMap<String, Object> properties = new HashMap<>();  
      properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));  
      properties.put("hibernate.show_sql", "true");  
      properties.put("hibernate.format_sql",  "true");  
      properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));  
      properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");  
      properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");  
      em.setJpaPropertyMap(properties);  
      return em;  
   }  
  
   //创建事务管理器  
   @Primary  
   @Bean   public PlatformTransactionManager ihrmTransactionManager() {  
      JpaTransactionManager transactionManager = new JpaTransactionManager();  
      transactionManager.setEntityManagerFactory( ihrmEntityManager().getObject());  
      return transactionManager;  
   }  
}

在业务系统中扩展业务流程的数据库

在activiti中具有自己的用户,用户组等概念。但是在实际的项目中很少用到,并且流程实例的
发起,审核往往会和具体的业务挂钩。为了更好的进行流程控制并逐步审核,所以我们需要自己定义一
些列表,结合已有的用户数据进行操作:
审批列表查询
流程申请信息查询
发起流程
提交流程(通过,驳回,撤销)
新添加几个表
proc_instance业务流程实例表(保存业务流程的数据)
proc_data申请的业务数据以json字符串形式存入如{"startTime":"2020-03-24T09:31:37.000Z","endTime":"2020-03-28T09:31:40.000Z","reasonsForApplication":"111","processName":"请假","duration":12,"userId":"1075383135459430400","processKey":"process_leave"}

CREATE TABLE `proc_instance` (
  `process_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '流程实例ID',
  `process_key` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '流程标识',
  `process_name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '流程名称',
  `process_definition_id` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '流程定义ID',
  `process_state` varchar(3) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '流程状态(0已提交;1审批中;2审批\r\n通过;3审批不通过;4撤销)',
  `user_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '申请人ID',
  `username` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '申请人',
  `proc_apply_time` datetime DEFAULT NULL COMMENT '申请时间',
  `proc_curr_node_user_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '当前节点审批人ID',
  `proc_curr_node_user_name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '当前节点审批人',
  `proc_end_time` datetime DEFAULT NULL COMMENT '结束流程时间',
  `proc_data` longtext CHARACTER SET utf8mb3 COLLATE utf8_general_ci,
  `department_id` varchar(40) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL,
  `department_name` varchar(40) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL,
  `time_of_entry` datetime DEFAULT NULL,
  PRIMARY KEY (`process_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT

proc_task_instance业务流程的任务(业务任务明细数据,每个节点的审批意见,状态)

CREATE TABLE `proc_task_instance` (
  `process_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '流程实例ID',
  `task_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务实例ID',
  `task_key` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务节点key',
  `task_name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '任务节点',
  `should_user_id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '应审批用户ID',
  `should_user_name` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '应审批用户',
  `handle_user_id` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '实际处理用户ID',
  `handle_user_name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '实际处理用户',
  `handle_time` datetime DEFAULT NULL COMMENT '处理时间',
  `handle_opinion` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '处理意见',
  `handle_type` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '处理类型(2审批通过;3审批不通过;4\r\n撤销)',
  PRIMARY KEY (`process_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT

proc_user_group自定义业务流程的用户组表

CREATE TABLE `proc_user_group` (
  `id` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '组名',
  `param` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '入参',
  `isql` varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '对应sql',
  `isvalid` varchar(2) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '有效标记',
  `create_user` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `update_user` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '最后更新人',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '最后更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT

为了更加灵活的处理流程,加入了自定义组表。在此表中通过SQL语句的形式和流程中的侯选组进行关
联。也就意味着我们在操作流程的时候,需要通过组名称获取到待执行sql,执行得到获选人

添加相应的实体类和dao层

最后在网关中加入微服务相应配置。

流程部署

/**  
 * 部署新流程  
 *     前端将绘制好的流程模型图(bpmn)文件上传到方法中  
 *     参数 : 上传的文件  
 *          MultipartFile  
 */@RequestMapping(value = "/deploy",method = RequestMethod.POST)  
public Result deployProcess(@RequestParam("file") MultipartFile file) throws IOException {  
   processService.deployProcess(file,companyId);  
   return new Result(ResultCode.SUCCESS);  
}

/**  
 * 流程部署  
 * @param file  上传bpmn文件  
 * @param companyId  企业id  
 */public void deployProcess(MultipartFile file, String companyId) throws IOException {  
   //1.获取上传的文件名  
   String fileName = file.getOriginalFilename();  
   //2.通过repositoryService进行流程部署  
   //DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();  
   //文件名称,文件的bytes数组  
   //deploymentBuilder.addBytes(fileName,file.getBytes()); //部署流程  
   //deploymentBuilder.tenantId(companyId);  //不同系统用一个流程定义来启动流程实例,tenantId用以区分同一个流程定义下分属不同系统的流程实例
   DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().addBytes(fileName, file.getBytes()).tenantId(companyId);  
   Deployment deploy = deploymentBuilder.deploy();  
   //3.打印部署结果  
   System.out.println(deploy);  
}

流程定义

查询所有

@RequestMapping(value = "/definition",method = RequestMethod.GET)  
public Result definitionList() throws IOException {  
   //调用service查询  
   List list = processService.getProcessDefinitionList(companyId);  
   return new Result(ResultCode.SUCCESS,list);  
}

//根据企业id查询所有的流程定义对象  
public List getProcessDefinitionList(String companyId) {  
   //借助repositoryService,createProcessDefinitionQuery得到流程定义查询对象,  
   List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(companyId)  
         .latestVersion().list();  
   return list;  
}

设置流程的挂起与激活状态

@RequestMapping(value = "/suspend/{processKey}",method = RequestMethod.GET)  
public Result suspendProcess(@PathVariable String processKey) throws IOException {  
   processService.suspendProcess(processKey,companyId);  
   return new Result(ResultCode.SUCCESS);  
}

//挂起或者激活流程  
public void suspendProcess(String processKey,String companyId) {  
   //1.根据processKey查询流程定义,只要涉及到查询createProcessDefinitionQuery,processDefinitionKey设置key条件,TenantId租户id,singleResult唯一对象  
   ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey)  
         .processDefinitionTenantId(companyId).latestVersion().singleResult();  
   //2.判断是否为挂起状态  
   if(definition.isSuspended()) {  
      //2.1 如果是挂起状态:设置为激活  
      repositoryService.activateProcessDefinitionById(definition.getId());  
   }else {  
      //2.2 如果不是激活状态: 设置为挂起  
      repositoryService.suspendProcessDefinitionById(definition.getId());  
   }  
}

查询申请列表(查询proc_instance表)

/**  
 * 查询申请列表  
 *  参数:  
 *      page,size *  业务参数:  这些参数封装到ProcInstance对象中  
 *      审批类型  
 *      审批状态(多个,每个状态之间使用","隔开)  
 *      当前节点的待处理人  
 */  
@RequestMapping(value = "/instance/{page}/{size}",method = RequestMethod.PUT)  
public Result instanceList(@RequestBody ProcInstance instance,@PathVariable int page,@PathVariable int size) throws IOException {  
   //1.调用service分页查询(得到的是springdatajpa封装的page对象),还应该传递companyId,在数据库中暂未设置  
   Page pages = auditService.getInstanceList(instance,page,size);  
   //2.page对象转化为自己的pageResult对象  
   PageResult pr = new PageResult(pages.getTotalElements(),pages.getContent());  
   //3.返回  
   return new Result(ResultCode.SUCCESS,pr);  
}

/**  
 * 查询所有的业务的申请流程  
 */  
public Page getInstanceList(ProcInstance instance, int page, int size) {  
   //1.使用Specification查询,构造Specification  
   Specification<ProcInstance> spec = new Specification<ProcInstance>() {  
      //2.构造查询条件 (根据传入参数判断,构造)  
      public Predicate toPredicate(Root<ProcInstance> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {  
         //一list形式接收多个条件  
         List<Predicate> list = new ArrayList<>();  
         //审批类型      -- processKey         if(!StringUtils.isEmpty(instance.getProcessKey())) {  
            list.add(cb.equal(root.get("processKey").as(String.class),instance.getProcessKey()));  
         }  
         //审批状态(多个,每个状态之间使用","隔开)        --processState  
         if(!StringUtils.isEmpty(instance.getProcessState())) {  
            Expression<String> exp = root.<String>get("processState");  
            list.add(exp.in(instance.getProcessState().split(",")));  
         }  
         //当前节点的待处理人     --procCurrNodeUserId ,待处理人可能有多个,所以不能用equal用like+%  
         if(!StringUtils.isEmpty(instance.getProcCurrNodeUserId())) {  
            list.add(cb.like(root.get("procCurrNodeUserId").as(String.class),"%"+instance.getProcCurrNodeUserId()+"%"));  
         }  
         //发起人 -- userId         if(!StringUtils.isEmpty(instance.getUserId())) {  
            list.add(cb.equal(root.get("userId").as(String.class),instance.getUserId()));  
         }  
         return cb.and(list.toArray(new Predicate[list.size()]));  
      }  
   };  
   //3.调用dao进行Specification查询,带分页的findAll  
   return procInstanceDao.findAll(spec,PageRequest.of(page-1,size));//  
}

需要注意的时,对于待审批的流程需要传入参数(ProcCurrNodeUserId),这也就以为者此字段
需要进行维护(如发起申请之后,需要查询下个节点的审批人,更新到此字段)。

查询申请详情(即查询某一条proc_instance表的记录)

/**  
 * 查询申请的详情数据  
 *  参数 : 申请对象的id  
 */@RequestMapping(value = "/instance/{id}",method = RequestMethod.GET)  
public Result instanceDetail(@PathVariable String id) throws IOException {  
   //调用service根据id查询  
   ProcInstance instance = auditService.findInstanceDetail(id);  
   return new Result(ResultCode.SUCCESS,instance);  
}

//根据id查询申请数据响应  
public ProcInstance findInstanceDetail(String id) {  
   return procInstanceDao.findById(id).get();  
}

根据id获取发起的业务流程。在返回值中包含一个string类型字符串 procData
procData针对不同的流程,内容是不一样的。为了更加灵活的控制流程业务数据,需要将发起流
程的所有信息已json的形式保存到此字段中

流程申请

构造业务数据

  • 通用的业务属性保存到基本字段中
  • 不同申请的数据已json的形式存入到 ProcData 字段中
    查询流程定义 processDefinitionKey,processDefinitionTenantId
  • 对于已经挂起的流程,抛出异常
    开启流程 (获取流程实例)startProcessInstanceById
  • 需要设置流程参数(流程变量)(如请假时设置的请假天数days)
  • 获取任务节点(根据上面获取的流程实例processInstanceId(processInstance.getId())
    执行第一个task
  • 根据我们的流程定义,第一个task即为流程的发起
  • 保存业务明细数据
    获取下个节点数据 (根据上面获取的流程实例processInstanceId(processInstance.getId())
  • 为了方便统一的进行查询,我们在业务表中定义了此流程的待审批人,此字段需要进行维护
  • 通过API查询下一个Task
  • 获取下一个task的侯选组 findCurrUsers(next, user)
  • 根据侯选组查询已有的侯选组数据,获得对应的SQL语句
  • 执行SQL语句获取所有候选人数据

获取候选人组:通过下一任务id,taskService.getIdentityLinksForTask(nextTask.getId())获取时间参与者内包含GroupId,通过procUserGroupDao.findById(groupId).get();查询自定义组表,根据param替换组表中的sql语句

@RequestMapping(value = "/startProcess",method = RequestMethod.POST)  
public Result startProcess(@RequestBody Map map) throws IOException {  
   //调用service  
   auditService.startProcess(map,companyId);  
   return new Result(ResultCode.SUCCESS);  
}

//流程申请  
public void startProcess(Map map, String companyId) {  
   //1.构造业务数据  
   String userId =(String) map.get("userId");  
   String processKey =(String) map.get("processKey");  
   String processName =(String) map.get("processName");  
   //用户数据通过远程微服务  
   User user = feignClientService.getUserInfoByUserId(userId);  
   ProcInstance instance = new ProcInstance();  
   BeanUtils.copyProperties(user,instance);  
   instance.setUserId(userId);  
   instance.setProcessId(idWorker.nextId()+"");  
   instance.setProcApplyTime(new Date());  
   instance.setProcessKey(processKey);  
   instance.setProcessName(processName);  
   instance.setProcessState("1");  
   //procData中的数据为json形式  
   String data = JSON.toJSONString(map);  
   instance.setProcData(data);  
   //2.查询流程定义  
   ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey)  
         .processDefinitionTenantId(companyId).latestVersion().singleResult();  
   //3.开启流程,用到runtimeService  
   Map vars = new HashMap();  
   if("process_leave".equals(processKey)) {  
      //请假  
      vars.put("days",map.get("duration"));  
   }  
   //startProcessInstanceById(流程定义的id,业务数据id,内置的参数) ,启动流程实例时设置流程变量 
   ProcessInstance processInstance = runtimeService.startProcessInstanceById(definition.getId(), instance.getProcessId(), vars);  
   //4.自动执行第一个任务节点  
   //4.1 获取待执行的任务节点 taskservice   Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();  
   //4.2 执行即可  
   taskService.complete(task.getId());  
   //5.获取下一个节点数据,填充业务数据中当前待审批人  
   //获取下一个节点,这行语句表示获取的永远都是待处理的节点  
   Task next = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();  
   if(next != null) {  
      List<User> users = findCurrUsers(next, user);  
      String usernames = "", userIdS = "";  
      for (User user1 : users) {  
         usernames += user1.getUsername() + " ";  
         userIdS += user1.getId();  
      }  
      instance.setProcCurrNodeUserId(userIdS);  
      instance.setProcCurrNodeUserName(usernames);  
   }  
  
   procInstanceDao.save(instance);  
   ProcTaskInstance pti = new ProcTaskInstance();  
   pti.setTaskId(idWorker.nextId()+"");  
   pti.setProcessId(instance.getProcessId());  
   pti.setHandleTime(new Date());  
   //处理类型(2审批通过;3审批不通过;4\r\n撤销
   pti.setHandleType("2");  
   pti.setHandleUserId(userId);  
   pti.setHandleUserName(user.getUsername());  
   pti.setTaskKey(task.getTaskDefinitionKey());  
   pti.setTaskName(task.getName());  
   //处理意见
   pti.setHandleOpinion("发起申请");  
   procTaskInstanceDao.save(pti);  
}

private List<User> findCurrUsers(Task nextTask,User user) {  
   //查询任务的节点数据(候选人组)  
   List<IdentityLink> list = taskService.getIdentityLinksForTask(nextTask.getId());  
   List<User> users = new ArrayList<>();  
   for (IdentityLink identityLink : list) {  
      String groupId = identityLink.getGroupId(); //候选人组id  
      ProcUserGroup userGroup = procUserGroupDao.findById(groupId).get();//查询userGroup  
      String param = userGroup.getParam();  
      String paramValue = null;  
      if ("user_id".equals(param)) {  
         paramValue = user.getId();  
      }  
      else if ("department_id".equals(param)) {  
         paramValue = user.getDepartmentId();  
      }  
      else if ("company_id".equals(param)) {  
         paramValue = user.getCompanyId();  
      }  
      //根据userGroup得到sql语句,sql语句中有些参数需要替换如${department_id}  
      String sql = userGroup.getIsql().replaceAll("\\$\\{" + param + "\\}", paramValue);  
      //执行proc_user_group表中获取到的sql语句  
      Query query = entityManager.createNativeQuery(sql);  
      query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(User.class));  
      users.addAll(query.getResultList());  
   }  
   return users;  
}

提交审核

1.查询业务流程对象
2.设置业务流程状态
3.根据不同的操作类型,完成不同的业务处理
4.更新业务流程对象,保存业务任务对象

/**  
 * 提交审核,传递接收任务实例对象  
 *      handleType; // 处理类型(2审批通过;3审批不通过;4撤销)  
 */  
@RequestMapping(value = "/instance/commit",method = RequestMethod.PUT)  
public Result commit(@RequestBody ProcTaskInstance taskInstance) throws IOException {  
   //调用service  
   auditService.commit(taskInstance,companyId);  
   return new Result(ResultCode.SUCCESS);  
}

/**  
    * 提交审核  
    *      handleType; // 处理类型(2审批通过;3审批不通过;4撤销)  
    *  
    *  ProcTaskInstance    *       handleOpinion: "xxxxxxc"                   操作说明  
          handleType: "4"                            处理类型(2审批通过;3审批不通过;4撤销)  
          handleUserId: "1063705989926227968"        处理人  
          processId: "1175593305465352192"           业务流程id  
    *    */   public void commit(ProcTaskInstance taskInstance, String companyId) {  
      //1.查询业务流程对象  
      String processId = taskInstance.getProcessId();  
      ProcInstance instance = procInstanceDao.findById(processId).get();  
      //2.设置业务流程状态(0已提交;1审批中;2审批通过;3审批不通过;4撤销)  
      instance.setProcessState(taskInstance.getHandleType());  
      //3.根据不同的操作类型,完成不同的业务处理  
      //查询出activiti中的流程实例 (根据自己的业务id查询activiti中的流程实例)  
      ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(processId).singleResult();  
      User user = feignClientService.getUserInfoByUserId(taskInstance.getHandleUserId());  
      if ("2".equals(taskInstance.getHandleType())) {  
         //3.1 如果审核通过,完成当前的任务  
         //查询出当前节点,并完成当前节点任务  
         Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();  
         taskService.complete(task.getId());  
         //查询出下一个任务节点,如果存在下一个流程没有结束  
         Task next = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();  
         if(next != null) {  
            List<User> users = findCurrUsers(next, user);  
            String usernames = "", userIdS = "";  
            for (User user1 : users) {  
               usernames += user1.getUsername() + " ";  
               userIdS += user1.getId();  
            }  
            instance.setProcCurrNodeUserId(userIdS);  
            instance.setProcCurrNodeUserName(usernames);  
            //流程没有结束将状态改回1  
            instance.setProcessState("1");  
         }else{  
            //如果不存在下一个节点,任务结束  
            instance.setProcessState("2");  
         }  
  
      } else {  
         //3.2 如果审核不通过,或者撤销(删除activiti流程),不能直接传递processId,这是我们自己定义的id,应该删除activiti的id,先获取activiti中的流程实例  
         runtimeService.deleteProcessInstance(processInstance.getId(),taskInstance.getHandleOpinion());  
      }  
      //4.更新业务流程对象,保存业务任务对象  
      procInstanceDao.save(instance);  
      taskInstance.setTaskId(idWorker.nextId()+"");  
      taskInstance.setHandleUserName(user.getUsername());  
      taskInstance.setHandleTime(new Date());  
      procTaskInstanceDao.save(taskInstance);  
   }  
  
   //根据processId查询业务流程明细  
   public List<ProcTaskInstance> findTasksByProcess(String id) {  
      return procTaskInstanceDao.findByProcessId(id);  
   }  
}

查询流程明细

//查询流程任务明细  
@RequestMapping(value = "/instance/tasks/{id}",method = RequestMethod.GET)  
public Result tasks(@PathVariable String id) throws IOException {  
   //调用service  
   return new Result(ResultCode.SUCCESS,auditService.findTasksByProcess(id));  
}

//根据processId查询业务流程明细  
public List<ProcTaskInstance> findTasksByProcess(String id) {  
   return procTaskInstanceDao.findByProcessId(id);  
}

你可能感兴趣的:(spring,java,spring,boot)