文章源码托管:github.com/OUYANGSIHAI… 欢迎 star !!!java
原本想着闲来无事,前面在项目中刚刚用到了工做流 Activiti 框架,写写博客的,可是,事情老是纷纷杂杂,一直拖延到如今,这一节本来想要写一下关于 Activiti 的 API
,可是,想着太多这样的博客了,并且显得太生硬,难以理解,因此,这些 API
就在实际的 demo 中来说解。git
在开始作工做流以前,咱们首先应该把具体的业务在工做流的部署流程图体现出来,而且都测试经过,这样就至关于成功了一半,后面的具体业务的开发就相对轻松一些了。github
首先,咱们先看一看在 idea 中有哪些控件,经常使用的控件进行了标注。spring
下面咱们讲一下创建一个流程图的具体过程。sql
首先,咱们须要拉入一个开始节点到 bpmn
文件中,这是图像化的界面,只须要拉入便可。数据库
而后,咱们从控件中拉入一个 UserTask
用户任务节点到 bpmn
文件中。编程
这样子就有了两个审批节点了,若是还须要其余的一些业务需求,咱们还能够加入一些网关,这里就暂时不加了。bash
最后,咱们只须要一个结束节点 EndEvent
就完成了这个工做流的部署图的绘制。微信
咱们最后看一下完整的例子。框架
看似已经完成了整个流程图的绘制,但美中不足的是咱们目前并无设置导师审批和辅导员审批到底由谁来审批,因此,咱们仍是须要来瞅一瞅怎么设置审批人员。
首先,咱们须要选中一个审批节点,例如,选中导师审批这个节点。
其次,咱们就显而易见的能够在 idea 编辑器的左侧看到一个名为 BPMN editor
的属性框,里面包括一个用户任务节点的能够设置的全部属性。
**注意:**候选用户、候选组、任务监听器,这三个属性这里暂时不讲,后面再补充。
因为,这一步咱们须要设置审批人,因此,咱们须要在 Assignee
这个属性中设置咱们的审批人。
如上图,这里设置导师审批这个节点的审批人为 sihai
。设置审批人除了直接设置以外,还有两种方式设置,后面再补充。
另一个审批节点也经过这种方式设置就能够完成审批人的设置了。
very good,这样就基本完成了一个流程图的建立。接下来,咱们将经过实例来具体讲解Activiti 的 API 的讲解。
在上面这个流程图的建立中,咱们尚未生成 png 图片,因此,若是不知道如何生成的,能够参考以前的这篇文章:Activiti工做流从入门到入土:整合spring。
既然是讲解 API ,那么仍是先看一下主要有哪些 API 吧,这样才有一个总体把握。
这些 API 具体怎么用,接下来一一道来。
既然是流程定义,那确定少不了如何部署流程定义了。
@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 文件内容自动生成流程图)。两个文件不是很大,都是以二进制形式存储在数据库中。
/**
* 部署流程定义(从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的输入流用做部署流程定义,其余使用并没有区别。
部署了流程定义以后,咱们应该想查看一下流程定义的一些信息。
/**
* 查询流程定义
*/
@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()
方法来设置一些查询参数,好比经过条件、降序升序等。
经过删除部署 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 删除信息。这里的做用主要是查询图片,经过图片能够在后面作流程展现用的。咱们看看具体怎么查看。
/**
* 查看流程图
*
* @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盘下,能够找到图片。
/** * 查询最新版本的流程定义 */
@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("*********************************************************************************");
}
}
}
复制代码
输出结果:
一、部署流程定义用到了 Activiti 的下面的几张表。
二、咱们发现部署流程定义的操做都是在 RepositoryService
这个类下进行操做的,咱们只须要经过 getRepositoryService()
拿到对象,经过链式规则就能够进行部署流程定义的全部操做。
这一节,咱们经过一个完整的例子,来总结一下前面讲过的一些基本的知识,这样可以更好的学习前面以及后面的知识点,这也算是一个过渡的章节。
回到第一节的创建流程图,咱们已经将基本的 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(){
//一、流程定义的key,经过这个key来启动流程实例
String processDefinitionKey = "hello";
//二、与正在执行的流程实例和执行对象相关的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
}
复制代码
步骤 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
,获取优质学习资源。