共计 13437 个字符,预计需要花费 34 分钟才能阅读完成。
文章源码托管:https://github.com/OUYANGSIHA…
欢迎 star!!!
本来想着闲来无事,前面在项目中刚刚用到了工作流 Activiti 框架,写写博客的,但是,事情总是纷纷杂杂,一直拖延到现在,这一节原本想要写一下关于 Activiti 的 API
,但是,想着太多这样的博客了,而且显得太生硬,难以理解,所以,这些 API
就在实际的 demo 中来讲解。
一、建立流程图
在开始做工作流之前,我们首先应该把具体的业务在工作流的部署流程图体现出来,并且都测试通过,这样就相当于成功了一半,后面的具体业务的开发就相对轻松一些了。
首先,我们先看一看在 idea 中有哪些控件,常用的控件进行了标注。
下面我们讲一下建立一个流程图的具体过程。
首先,我们需要拉入一个 开始节点 到 bpmn
文件中,这是图像化的界面,只需要拉入即可。
然后,我们从控件中拉入一个 UserTask
用户任务节点到 bpmn
文件中。
这样子就有了两个审批节点了,如果还需要其他的一些业务需求,我们还可以加入一些 网关,这里就暂时不加了。
最后,我们只需要一个结束节点 EndEvent
就完成了这个工作流的部署图的绘制。
我们最后看一下完整的例子。
看似已经完成了整个流程图的绘制,但美中不足的是我们目前并没有设置 导师审批 和辅导员审批 到底由谁来审批,所以,我们还是需要来瞅一瞅怎么设置 审批人员。
首先,我们需要 选中一个审批节点,例如,选中导师审批这个节点。
其次,我们就显而易见的可以在 idea 编辑器的左侧看到一个名为 BPMN editor
的属性框,里面包括一个 用户任务节点的可以设置的所有属性。
注意:候选用户、候选组、任务监听器,这三个属性这里暂时不讲,后面再补充。
由于,这一步我们需要设置审批人,所以,我们需要在 Assignee
这个属性中设置我们的审批人。
如上图,这里设置 导师审批 这个节点的审批人为 sihai
。设置审批人除了直接设置之外,还有两种方式设置,后面再补充。
另外一个审批节点也通过这种方式设置就可以完成审批人的设置了。
very good,这样就基本完成了一个流程图的创建。接下来,我们将通过实例来具体讲解Activiti 的 API 的讲解。
二、实例讲解 API
在上面这个流程图的创建中,我们还没有生成 png 图片,所以,如果不知道如何生成的,可以参考之前的这篇文章:Activiti 工作流从入门到入土:整合 spring。
既然是讲解 API,那么还是先看一下主要有哪些 API 吧,这样才有一个整体把握。
这些 API 具体怎么用,接下来一一道来。
2.1 流程定义
既然是流程定义,那肯定少不了如何部署流程定义了。
2.1.1 部署流程定义方法 1
@Autowired
private ProcessEngine processEngine;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private HistoryService historyService;
/**
* 部署流程定义(从 classpath)*/
@Test
public void deploymentProcessDefinition_classpath(){Deployment deployment = processEngine.getRepositoryService()// 与流程定义和部署对象相关的 Service
.createDeployment()// 创建一个部署对象
.name("流程定义")// 添加部署的名称
.addClasspathResource("bpmn/hello.bpmn")// 从 classpath 的资源中加载,一次只能加载一个文件
.addClasspathResource("bpmn/hello.png")// 从 classpath 的资源中加载,一次只能加载一个文件
.deploy();// 完成部署
System.out.println("部署 ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
}
注意:这里用的是整合 spring 之后的 junit 测试环境,如何整合 spring 请看这篇文章:Activiti 工作流从入门到入土:整合 spring。
输出结果:
这样,我们就部署了这个流程。那么具体是怎么操作的呢,我们再来看看整个过程。
- 获取流程引擎对象:这个跟 spring 整合了。
- 通过流程引擎获取了一个 RepositoryService 对象(仓库对象)
- 由仓库的服务对象产生一个部署对象配置对象,用来封装部署操作的相关配置。
- 这是一个链式编程,在部署配置对象中设置显示名,上传流程定义规则文件
- 向数据库表中存放流程定义的规则信息。
其实,这一步操作,用到了 Activiti 数据库中的三张表,分别是:act_re_deployment(部署对象表),act_re_procdef(流程定义表),act_ge_bytearray(资源文件表)。
我们看看这三张表的变化:
1)act_re_deployment
可以看到,部署 ID 和部署名称就存在这张表中。
2)act_re_procdef
这张表中,存放了部署的 Deployment_ID 部署流程的 id、bpmn 资源文件名称、png 图片名称等信息。
3)act_ge_bytearray
存储流程定义相关的部署信息。即流程定义文档的存放地。每部署一次就会增加两条记录,一条是关于 bpmn 规则文件的,一条是图片的(如果部署时只指定了 bpmn 一个文件,activiti 会在部署时解析 bpmn 文件内容自动生成流程图)。两个文件不是很大,都是以二进制形式存储在数据库中。
2.1.2 部署流程定义方法 2
/**
* 部署流程定义(从 zip)*/
@Test
public void deploymentProcessDefinition_zip(){InputStream in = this.getClass().getClassLoader().getResourceAsStream("bpmn/hello.zip");
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = processEngine.getRepositoryService()// 与流程定义和部署对象相关的 Service
.createDeployment()// 创建一个部署对象
.name("流程定义")// 添加部署的名称
.addZipInputStream(zipInputStream)// 指定 zip 格式的文件完成部署
.deploy();// 完成部署
System.out.println("部署 ID:"+deployment.getId());//
System.out.println("部署名称:"+deployment.getName());//
}
项目结构如下:
输出结果:
如此看来,也是没有任何问题的,唯一的区别只是压缩成 zip 格式的文件,使用 zip 的输入流用作部署流程定义,其他使用并无区别。
部署了流程定义之后,我们应该想查看一下流程定义的一些信息。
2.1.3 查看流程定义
/**
* 查询流程定义
*/
@Test
public void findProcessDefinition(){List<ProcessDefinition> list = processEngine.getRepositoryService()// 与流程定义和部署对象相关的 Service
.createProcessDefinitionQuery()// 创建一个流程定义的查询
/** 指定查询条件,where 条件 */
// .deploymentId(deploymentId)// 使用部署对象 ID 查询
// .processDefinitionId(processDefinitionId)// 使用流程定义 ID 查询
// .processDefinitionKey(processDefinitionKey)// 使用流程定义的 key 查询
// .processDefinitionNameLike(processDefinitionNameLike)// 使用流程定义的名称模糊查询
/** 排序 */
.orderByProcessDefinitionVersion().asc()// 按照版本的升序排列
// .orderByProcessDefinitionName().desc()// 按照流程定义的名称降序排列
/** 返回的结果集 */
.list();// 返回一个集合列表,封装流程定义
// .singleResult();// 返回惟一结果集
// .count();// 返回结果集数量
// .listPage(firstResult, maxResults);// 分页查询
if(list!=null && list.size()>0){for(ProcessDefinition pd:list){System.out.println("流程定义 ID:"+pd.getId());// 流程定义的 key+ 版本 + 随机生成数
System.out.println("流程定义的名称:"+pd.getName());// 对应 hello.bpmn 文件中的 name 属性值
System.out.println("流程定义的 key:"+pd.getKey());// 对应 hello.bpmn 文件中的 id 属性值
System.out.println("流程定义的版本:"+pd.getVersion());// 当流程定义的 key 值相同的相同下,版本升级,默认 1
System.out.println("资源名称 bpmn 文件:"+pd.getResourceName());
System.out.println("资源名称 png 文件:"+pd.getDiagramResourceName());
System.out.println("部署对象 ID:"+pd.getDeploymentId());
System.out.println("*********************************************");
}
}
}
输出结果:
查询流程定义小结:
- 流程定义和部署对象相关的 Service 都是
RepositoryService
,后面会发现关于流程定义的都是RepositoryService
。 - 通过这个
createProcessDefinitionQuery()
方法来设置一些查询参数,比如通过条件、降序升序等。
2.1.4 删除流程定义
通过删除部署 ID 为 2501 的信息。
/**
* 删除流程定义
*/
@Test
public void deleteProcessDefinition(){
// 使用部署 ID,完成删除,指定部署对象 id 为 2501 删除
String deploymentId = "2501";
/**
* 不带级联的删除
* 只能删除没有启动的流程,如果流程启动,就会抛出异常
*/
// processEngine.getRepositoryService()//
// .deleteDeployment(deploymentId);
/**
* 级联删除
* 不管流程是否启动,都能可以删除
*/
processEngine.getRepositoryService()//
.deleteDeployment(deploymentId, true);
System.out.println("删除成功!");
}
输出结果:
到数据库查看,发现 act_re_deployment
中的数据已经不存在了。
- 这里还是通过
getRepositoryService()
方法获取部署定义对象,然后指定 ID 删除信息。
2.1.5 获取流程定义文档的资源
这里的作用主要是查询图片,通过图片可以在后面做流程展示用的。我们看看具体怎么查看。
/**
* 查看流程图
*
* @throws IOException
*/
@Test
public void viewPic() throws IOException {
/** 将生成图片放到文件夹下 */
String deploymentId = "5001";
// 获取图片资源名称
List<String> list = processEngine.getRepositoryService()//
.getDeploymentResourceNames(deploymentId);
// 定义图片资源的名称
String resourceName = "";
if (list != null && list.size() > 0) {for (String name : list) {if (name.indexOf(".png") >= 0) {resourceName = name;}
}
}
// 获取图片的输入流
InputStream in = processEngine.getRepositoryService()//
.getResourceAsStream(deploymentId, resourceName);
// 将图片生成到 F 盘的目录下
File file = new File("F:/" + resourceName);
// 将输入流的图片写到磁盘
FileUtils.copyInputStreamToFile(in, file);
}
在 F 盘下,可以找到图片。
2.1.6 查询最新版本的流程定义
/**
* 查询最新版本的流程定义
*/
@Test
public void findLastVersionProcessDefinition() {List<ProcessDefinition> list = processEngine.getRepositoryService()//
.createProcessDefinitionQuery()//
.orderByProcessDefinitionVersion().asc()// 使用流程定义的版本升序排列
.list();
/**
map 集合的特点:当 map 集合 key 值相同的情况下,后一次的值将替换前一次的值
*/
Map<String, ProcessDefinition> map = new LinkedHashMap<String, ProcessDefinition>();
if (list != null && list.size() > 0) {for (ProcessDefinition pd : list) {map.put(pd.getKey(), pd);
}
}
List<ProcessDefinition> pdList = new ArrayList<ProcessDefinition>(map.values());
if (pdList != null && pdList.size() > 0) {for (ProcessDefinition pd : pdList) {System.out.println("流程定义 ID:" + pd.getId());// 流程定义的 key+ 版本 + 随机生成数
System.out.println("流程定义的名称:" + pd.getName());// 对应 hello.bpmn 文件中的 name 属性值
System.out.println("流程定义的 key:" + pd.getKey());// 对应 hello.bpmn 文件中的 id 属性值
System.out.println("流程定义的版本:" + pd.getVersion());// 当流程定义的 key 值相同的相同下,版本升级,默认 1
System.out.println("资源名称 bpmn 文件:" + pd.getResourceName());
System.out.println("资源名称 png 文件:" + pd.getDiagramResourceName());
System.out.println("部署对象 ID:" + pd.getDeploymentId());
System.out.println("*********************************************************************************");
}
}
}
输出结果:
2.1.7 流程定义总结
1、部署流程定义用到了 Activiti 的下面的几张表。
- act_re_deployment:部署对象表
- act_re_procdef:流程定义表
- act_ge_bytearray:资源文件表
- act_ge_property:主键生成策略表
2、我们发现部署流程定义的操作都是在 RepositoryService
这个类下进行操作的,我们只需要通过 getRepositoryService()
拿到对象,通过链式规则就可以进行部署流程定义的所有操作。
2.2 工作流完整实例的使用
这一节,我们通过一个完整的例子,来总结一下前面讲过的一些基本的知识,这样能够更好的学习前面以及后面的知识点,这也算是一个过渡的章节。
回到 第一节的建立流程图,我们已经将基本的 bpmn 图已经建立好了,但是,需要做一个完整的实例,我们还是需要补充一些内容的,这样才能够把这样的一个实例做好,我们先把第一节的那个 bpmn 图拿过来。
首先,我们需要明确:这个图到目前为止,我们只是简简单单的把流程给画出来了,比如,我们需要审核的时候,是需要具体到某一个具体的人员去审核的,所以,我们需要给每个节点设置审核的具体人员。
注意:设置节点的审核人员后面还会分一节细讲,这里只是做一个简单的实例,所以,只需要这里能够看懂,做好就 ok 了。
设置审核人员步骤
首先,我们需要选中一个节点,例如,下图中的“导师审批”节点。
接下来,在左边的工具栏,我们会看到好多选项,有一项为 Assignee,我们需要在这个选项中设置我们这个节点需要设置的审批人。
Assignee 设置格式:直接使用英文或者中文都可以,例如,sihai
,更复杂的设置后面再讲。
下面的节点设置也是跟上面一模一样。
辅导员审批的审批人员是:欧阳思海。
perfect,这样流程图的任务就完成了,下面我们就可以进行这个实例的测试阶段了。
1)部署流程定义
部署流程定义,在前面的章节已经讲过了,有两种方式进行处理,一种是加载 bpmn 文件和 png 文件,还有一种是将这两个文件压缩成 zip 格式的压缩文件,然后加载。这里我们使用第一种方式进行处理。
/**
* 部署流程定义(从 classpath)*/
@Test
public void deploymentProcessDefinition_classpath() {Deployment deployment = processEngine.getRepositoryService()// 与流程定义和部署对象相关的 Service
.createDeployment()// 创建一个部署对象
.name("hello")// 添加部署的名称
.addClasspathResource("bpmn/hello.bpmn")// 从 classpath 的资源中加载,一次只能加载一个文件
.addClasspathResource("bpmn/hello.png")// 从 classpath 的资源中加载,一次只能加载一个文件
.deploy();// 完成部署
log.info("部署 ID:" + deployment.getId());
log.info("部署名称:" + deployment.getName());
}
现在流程定义已经有了,下面我们就需要启动这个流程实例。
关于关于这一步做了什么事情,可以在前面的章节查看。
2)启动流程实例
/**
* 启动流程实例
*/
@Test
public void startProcessInstance(){
//1、流程定义的 key,通过这个 key 来启动流程实例
String processDefinitionKey = "hello";
//2、与正在执行的流程实例和执行对象相关的 Service
// startProcessInstanceByKey 方法还可以设置其他的参数,比如流程变量。ProcessInstance pi = processEngine.getRuntimeService()
.startProcessInstanceByKey(processDefinitionKey);// 使用流程定义的 key 启动流程实例,key 对应 helloworld.bpmn 文件中 id 的属性值,使用 key 值启动,默认是按照最新版本的流程定义启动
log.info("流程实例 ID:"+pi.getId());// 流程实例 ID
log.info("流程定义 ID:"+pi.getProcessDefinitionId());// 流程定义 ID
}
注意: processDefinitionKey 是 bpmn 文件的名称。
步骤
1 获取到 runtimeService 实例。
2 通过 bpmn 文件的名称,也就是 processDefinitionKey 来启动流程实例。
3 启动流程后,流程的任务就走到了 导师审批 节点。
下面就是查询个人任务了,我们可以查询导师审批节点的任务。
3)查询个人任务
/**
* 查询当前人的个人任务
*/
@Test
public void findPersonalTask(){
String assignee = "sihai";
List<Task> list = processEngine.getTaskService()// 与正在执行的任务管理相关的 Service
.createTaskQuery()// 创建任务查询对象
/** 查询条件(where 部分)*/
.taskAssignee(assignee)// 指定个人任务查询,指定办理人
// .taskCandidateUser(candidateUser)// 组任务的办理人查询
// .processDefinitionId(processDefinitionId)// 使用流程定义 ID 查询
// .processInstanceId(processInstanceId)// 使用流程实例 ID 查询
// .executionId(executionId)// 使用执行对象 ID 查询
/** 排序 */
.orderByTaskCreateTime().asc()// 使用创建时间的升序排列
/** 返回结果集 */
// .singleResult()// 返回惟一结果集
// .count()// 返回结果集的数量
// .listPage(firstResult, maxResults);// 分页查询
.list();// 返回列表
if(list!=null && list.size()>0){for(Task task:list){log.info("任务 ID:"+task.getId());
log.info("任务名称:"+task.getName());
log.info("任务的创建时间:"+task.getCreateTime());
log.info("任务的办理人:"+task.getAssignee());
log.info("流程实例 ID:"+task.getProcessInstanceId());
log.info("执行对象 ID:"+task.getExecutionId());
log.info("流程定义 ID:"+task.getProcessDefinitionId());
log.info("********************************************");
}
}
}
通过 sihai
这个审批人,查询到了下面的信息。
分析步骤
1 首先通过 getTaskService 方法,获取到 TaskService 对象。
2 通过 createTaskQuery 方法创建查询对象。
3 通过 taskAssignee 方法设置审核人。
4 对于结果的返回,我们可以通过 orderByTaskCreateTime().asc() 设置排序等其他信息。
这里需要注意一点,查询到的一个重要的信息是:任务 id(taskId),下一步,我们需要通过这个任务 id,来完成任务。
4)办理个人任务
/**
* 完成我的任务
*/
@Test
public void completePersonalTask() {
// 任务 ID,上一步查询得到的。String taskId = "7504";
processEngine.getTaskService()// 与正在执行的任务管理相关的 Service
.complete(taskId);
log.info("完成任务:任务 ID:" + taskId);
}
通过上一步的任务 id:7504,完成任务。
步骤
1 首先,通过 getTaskService 方法拿到 TaskService 对象。
2 调用 complete 方法,给定具体的任务 id 完成任务。
5)查询流程状态(判断流程走到哪一个节点)
这个接口还是十分需要的,当我们在具体的业务中,我们需要判断我们的流程的状态是什么状态,或者说我们的流程走到了哪一个节点的时候,这一个接口就让我们实现业务省了非常多的事情。
/**
* 查询流程状态(判断流程走到哪一个节点)*/
@Test
public void isProcessActive() {
String processInstanceId = "7501";
ProcessInstance pi = processEngine.getRuntimeService()// 表示正在执行的流程实例和执行对象
.createProcessInstanceQuery()// 创建流程实例查询
.processInstanceId(processInstanceId)// 使用流程实例 ID 查询
.singleResult();
if (pi == null) {log.info("流程已经结束");
} else {log.info("流程没有结束");
// 获取任务状态
log.info("节点 id:" + pi.getActivityId());
}
}
步骤:
1 获取到流程实例 ProcessInstance 对象。
2 通过 getActivityId 方法获取到实例 Id(节点 id)。
那么拿到了 节点 Id
有什么作用呢?
其实,有了这个 Id 之后,我们就可以判断流程走到哪一步了。例如,上面的输出的节点 id 是 _4
,这个 _4 就是对应 辅导员审批节点的 id
,所以,我们就可以判读流程其实是已经走到这个节点了,后期需要在页面显示流程状态的时候就发挥作用了。
6)查询流程执行的历史信息
通过查看 activiti 5 的官方 API 接口,发现查看历史信息有下面的查询接口。
下面我们通过上面的实例对下面的方法一一进行测试。
历史活动实例查询接口
/**
* 历史活动查询接口
*/
@Test
public void findHistoryActivity() {
String processInstanceId = "7501";
List<HistoricActivityInstance> hais = processEngine.getHistoryService()//
.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.list();
for (HistoricActivityInstance hai : hais) {log.info("活动 id:" + hai.getActivityId()
+ "审批人:" + hai.getAssignee()
+ "任务 id:" + hai.getTaskId());
log.info("************************************");
}
}
通过这个接口不仅仅查到这些信息,还有其他的方法,可以获取更多的关于 历史活动 的其他信息。
历史流程实例查询接口
/**
* 查询历史流程实例
*/
@Test
public void findHistoryProcessInstance() {
String processInstanceId = "7501";
HistoricProcessInstance hpi = processEngine.getHistoryService()// 与历史数据(历史表)相关的 Service
.createHistoricProcessInstanceQuery()// 创建历史流程实例查询
.processInstanceId(processInstanceId)// 使用流程实例 ID 查询
.orderByProcessInstanceStartTime().asc().singleResult();
log.info(hpi.getId() + "" + hpi.getProcessDefinitionId() +" "+ hpi.getStartTime() +" "+ hpi.getEndTime() +" " + hpi.getDurationInMillis());
}
这个接口可以查询到关于 历史流程实例 的所有信息。
历史任务实例查询接口
/**
* 查询历史任务
*/
@Test
public void findHistoryTask() {
String processInstanceId = "7501";
List<HistoricTaskInstance> list = processEngine.getHistoryService()// 与历史数据(历史表)相关的 Service
.createHistoricTaskInstanceQuery()// 创建历史任务实例查询
.processInstanceId(processInstanceId)//
.orderByHistoricTaskInstanceStartTime().asc().list();
if (list != null && list.size() > 0) {for (HistoricTaskInstance hti : list) {log.info("\n 任务 Id:" + hti.getId() + "任务名称:" + hti.getName() + "流程实例 Id:" + hti.getProcessInstanceId() + "\n 开始时间:"
+ hti.getStartTime() + "结束时间:" + hti.getEndTime() + "持续时间:" + hti.getDurationInMillis());
}
}
}
这个查询接口可以查询到 历史任务信息。
历史流程变量查询接口
/**
* 查询历史流程变量
*/
@Test
public void findHistoryProcessVariables() {
String processInstanceId = "7501";
List<HistoricVariableInstance> list = processEngine.getHistoryService()//
.createHistoricVariableInstanceQuery()// 创建一个历史的流程变量查询对象
.processInstanceId(processInstanceId)//
.list();
if (list != null && list.size() > 0) {for (HistoricVariableInstance hvi : list) {log.info("\n" + hvi.getId() + "" + hvi.getProcessInstanceId() +"\n" + hvi.getVariableName()
+ "" + hvi.getVariableTypeName() +" " + hvi.getValue());
}
}
}
在这个实例中没有设置流程变量,所以,这里是查询不到任何历史信息的。
这个接口主要是关于 历史流程变量 的设置的一些信息。
历史本地接口查询接口
/**
* 通过执行 sql 来查询历史数据,由于 activiti 底层就是数据库表。*/
@Test
public void findHistoryByNative() {HistoricProcessInstance hpi = processEngine.getHistoryService()
.createNativeHistoricProcessInstanceQuery()
.sql("查询底层数据库表的 sql 语句")
.singleResult();
log.info("\n" + hpi.getId() + "" + hpi.getProcessDefinitionId() +" " + hpi.getStartTime()
+ "\n" + hpi.getEndTime() + " " + hpi.getDurationInMillis());
}
这个接口是提供直接通过 sql 语句
来查询历史信息的,我们只需要在 sql()
方法中写原生的 sql 语句就可以进行数据查询。
写到这里,我想应该通过这样的一个完整的实例将 Activiti 工作流的 API 都介绍的差不多了,这一节到这里也就要说拜拜了。再回看一下文章开头的 API 接口,这也算是这一节的总结。
文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的 微信公众号 :
好好学 java
,获取优质学习资源。