源代码地址:https://gitee.com/ZSXYX/activiti.git
1、安装插件
首先安装下图所示activiti,不确定是哪个插件有用的,有时间可排除下
在resources下创建一个文件夹:processes,右键,新建
生成:
选中act.bpmn20.xml右键
展示如下空白面板,右键画图
画图完毕,右键保存为act.png图片
2、配置流程引擎
接下来,我们连接数据库,在resources下新建activiti.cfg.xml
文件内容如下:
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/activiti?serverTimezone=GMT%2B8"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="root"/>
<!-- activiti数据库表处理策略 -->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
3、创建表
接着:在数据库中新建一个数据库
执行如下图,在数据库中生成25张表:
表名如下:
具体表名:
生成25张表
代码如下:
/**
* 生成 activiti的数据库表
*/
@Test
public void testCreateDbTable() {
//使用classpath下的activiti.cfg.xml中的配置创建processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
}
4、部署流程定义
代码如下:
/**
* 部署流程定义
* 将上面在设计器中定义的流程部署到activiti数据库中,就是流程定义部署。
* 通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
* 单个文件部署方式
* 分别将bpmn文件和png图片文件部署。
* act_re_procdef :id 中会存在一个ProcessInstanceByKey : act
*/
@Test
public void testDeployment(){
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、使用RepositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/act.bpmn20.xml") // 添加bpmn资源
.addClasspathResource("processes/act.png") // 添加png资源
.name("请假申请流程")
.deploy();
// 4、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
运行后,会在数据库中生成一条:
如果你的activiti-spring-boot-starter是7.1.0.M5的话,多半就会报错,很明显sql少字段,这个应该是一个官方的BUG
如果运行报错:
Unknown column 'VERSION_' in 'field list'
解决方式:
在act_re_deployment 中添加两个字段:
VERSION_、PROJECT_RELEASE_VERSION_
2:
/**
* 3、启动流程实例
* 针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,
* 类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,
* 出差申请单发起一个出差单也需要启动一个流程实例。
*/
@Test
public void testStartProcess(){
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
//启动流程的时候【动态设置每个步骤的执行人】,map的key值需要与Leave.bpmn中对应
Map<String, Object> variables=new HashMap<String, Object>();
variables.put("user1", "张三");
variables.put("user2", "李四");
// 3、根据流程定义Id启动流程
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("aaa","1005",variables); //会在act_hi_varinst中新增两条数据
// 输出内容
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动Id:" + processInstance.getActivityId());
}
5、启动流程实例
代码如下:
/**
* 启动流程实例
* 针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,
* 类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,
* 出差申请单发起一个出差单也需要启动一个流程实例。
*/
@Test
public void testStartProcess(){
// 1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取RunTimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 3、根据流程定义Id启动流程
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("act","1001");
// 输出内容
System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("流程实例id:" + processInstance.getId());
System.out.println("当前活动Id:" + processInstance.getActivityId());
}
启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。会在库中生成:
Activiti的act_ru_execution中存储业务标识
说明:
流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同
一个流程实例运行完成,此表中与流程实例相关的记录删除。
SELECT * FROM act_ru_task #任务执行表,记录当前执行的任务
说明:启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。
SELECT * FROM act_ru_identitylink #任务参与者,记录当前参与任务的用户或组
SELECT * FROM act_hi_procinst #流程实例历史表
流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
SELECT * FROM act_hi_taskinst #任务历史表,记录所有任务
开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。
SELECT * FROM act_hi_actinst #活动历史表,记录所有活动
6、查询流程实例
流程在运行过程中可以查询流程实例的状态,当前运行结点等信息。
代码如下:
/**
* 查询当前个人待执行的任务
* 流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。
*/
@Test
public void testFindPersonalTaskList() {
// 任务负责人
String assignee = "zhangsan";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 创建TaskService
TaskService taskService = processEngine.getTaskService();
// 根据流程key 和 任务负责人 查询任务
List<Task> list = taskService.createTaskQuery()
.processDefinitionKey("act") //流程Key
.taskAssignee(assignee)//只查询该任务负责人的任务
.list();
for (Task task : list) {
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
需求:
在activiti实际应用时,查询流程实例列表时可能要显示出业务系统的一些相关信息,比如:查询当前运行的出差流程列表需要将出差单名称、出差天数等信息显示出来,出差天数等信息在业务系统中存在,而并没有在activiti数据库中存在,所以是无法通过activiti的api查询到出差天数等信息。
实现:
在查询流程实例时,通过businessKey(业务标识 )关联查询业务系统的出差单表,查询出出差天数等信息。
通过下面的代码就可以获取activiti中所对应实例保存的业务Key。而这个业务Key一般都会保存相关联的业务操作表的主键,再通过主键ID去查询业务信息,比如通过出差单的ID,去查询更多的请假信息(出差人,出差时间,出差天数,出差目的地等)
String businessKey = processInstance.getBusinessKey();
在activiti的act_ru_execution表,字段BUSINESS_KEY就是存放业务KEY的。
7、流程任务处理
任务负责人查询待办任务,选择任务进行处理,完成任务
// 任务负责人查询待办任务,选择任务进行处理,完成任务。
@Test
public void completTask(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取taskService
TaskService taskService = processEngine.getTaskService();
// 根据流程key 和 任务的负责人 查询任务
// 返回一个任务对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("act") //流程Key
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();
// 完成任务,参数:任务id
taskService.complete(task.getId());
}
8、流程定义信息查询
查询流程相关信息,包含流程定义,流程部署,流程定义版本
结果:
代码:
// 完成任务
// 任务负责人查询待办任务,选择任务进行处理,完成任务。
@Test
public void completTask(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取taskService
TaskService taskService = processEngine.getTaskService();
// 根据流程key 和 任务的负责人 查询任务
// 返回一个任务对象
Task task = taskService.createTaskQuery()
.processDefinitionKey("act") //流程Key
.taskAssignee("zhangsan") //要查询的负责人
.singleResult();
// 完成任务,参数:任务id
taskService.complete(task.getId());
}
/**
* 查询流程定义
* 查询流程相关信息,包含流程定义,流程部署,流程定义版本
*/
@Test
public void queryProcessDefinition(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 得到ProcessDefinitionQuery 对象
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
// 查询出当前所有的流程定义
// 条件:processDefinitionKey =evection
// orderByProcessDefinitionVersion 按照版本排序
// desc倒叙
// list 返回集合
List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("act")
.orderByProcessDefinitionVersion()
.desc()
.list();
// 输出流程定义信息
for (ProcessDefinition processDefinition : definitionList) {
System.out.println("流程定义 id="+processDefinition.getId());
System.out.println("流程定义 name="+processDefinition.getName());
System.out.println("流程定义 key="+processDefinition.getKey());
System.out.println("流程定义 Version="+processDefinition.getVersion());
System.out.println("流程部署ID ="+processDefinition.getDeploymentId());
}
}
9、 流程删除
### The error occurred while setting parameters
### SQL: delete from ACT_RE_PROCDEF where DEPLOYMENT_ID_ = ?
### Cause: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`activiti`.`act_ru_execution`, CONSTRAINT `ACT_FK_EXE_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`))
说明:
使用repositoryService删除流程定义,历史表信息不会被删除
如果该流程定义下没有正在运行的流程,则可以用普通删除。
如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除。
先删除没有完成流程节点,最后就可以完全删除流程定义信息
项目开发中级联删除操作一般只开放给超级管理员使用.
疑问?
当使用了级联删除仍然失败,请大神赐教
错误日志:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`activiti`.`act_ru_execution`, CONSTRAINT `ACT_FK_EXE_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`))
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)
at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:354)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:46)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
... 78 more
相关代码:
//流程删除
@Test
public void deleteDeployment() {
// 流程部署id
String deploymentId = "57501";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 通过流程引擎获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
repositoryService.deleteDeployment(deploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
repositoryService.deleteDeployment(deploymentId, true);
}
10 流程资源下载
现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,可以从数据库中把资源文件下载到本地。
解决方案有:
jdbc对blob类型,clob类型数据读取出来,保存到文件目录
使用activiti的api来实现
使用commons-io.jar 解决IO的操作
引入commons-io依赖包
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
通过流程定义对象获取流程定义资源,获取bpmn和png
结果:
说明:
deploymentId为流程部署ID
resource_name为act_ge_bytearray表中NAME_列的值
使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称
使用repositoryService的getResourceAsStream方法传入部署ID和资源图片名称可以获取部署下指定名称文件的输入流
最后的将输入流中的图片资源进行输出。
代码:
/**
* 现在我们的流程资源文件已经上传到数据库了,如果其他用户想要查看这些资源文件,
* 可以从数据库中把资源文件下载到本地。
*/
@Test
public void queryBpmnFile() throws IOException {
// 1、得到引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 2、获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("act")
.list();
// 4、通过流程定义信息,得到部署ID
for (ProcessDefinition processDefinition: list) {
String deploymentId = processDefinition.getDeploymentId();
// 5、通过repositoryService的方法,实现读取图片信息和bpmn信息
// png图片的流
InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
// bpmn文件的流
InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
// 6、构造OutputStream流
File file_png = new File("D:\\test\\act.bpmn20.xml");
File file_bpmn = new File("D:\\test\\act.png");
FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
FileOutputStream pngOut = new FileOutputStream(file_png);
// 7、输入流,输出流的转换
IOUtils.copy(pngInput, pngOut);
IOUtils.copy(bpmnInput, bpmnOut);
// 8、关闭流
pngOut.close();
bpmnOut.close();
pngInput.close();
bpmnInput.close();
}
}
11流程历史信息的查看
即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。
结果:
代码:
/**
* 查看历史信息
* 即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。
* 所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。
*/
@Test
public void findHistoryInfo(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取HistoryService
HistoryService historyService = processEngine.getHistoryService();
// 获取 actinst表的查询对象
HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
// 查询 actinst表,条件:根据 InstanceId 查询
// instanceQuery.processInstanceId("2501");
// 查询 actinst表,条件:根据 DefinitionId 查询
instanceQuery.processDefinitionId("act:1:52504");
// 增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
// 查询所有内容
List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
// 输出
for (HistoricActivityInstance hi : activityInstanceList) {
System.out.println(hi.getActivityId());
System.out.println(hi.getActivityName());
System.out.println(hi.getProcessDefinitionId());
System.out.println(hi.getProcessInstanceId());
System.out.println("<==========================>");
}
}
12挂起、激活流程实例
某些情况可能由于流程变更需要将当前运行的流程暂停而不是直接删除,流程暂停后将不会继续执行。
流程定义可以对应多个流程实例
如:每月最后一天不处理出差申请,需要挂起这个流程定义下的流程实例
当有这种流程实例没有跑完的时候,如果已经被挂起,就不能继续处理了
只有当流程实例激活后,整个流程才能继续向下处理
全部流程实例挂起
操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:
流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。
测试多个实例,则可以再运行一次addBusinessKey,修改1002,表示另一个人发起出差流程申请
结果:
再次执行:
操作数据库
act_ru_task
act_re_procdef
act_ru_execution
都修改了SUSPENSION_STATE_暂停状态
代码:
/**
* 全部流程实例挂起与激活
* 全部流程实例挂起
* 操作流程定义为挂起状态,该流程定义下边所有的流程实例全部暂停:
* 流程定义为挂起状态该流程定义将不允许启动新的流程实例,同时该流程定义下所有的流程实例将全部挂起暂停执行。
* 测试多个实例,则可以再运行一次addBusinessKey,修改1002,表示另一个人发起出差流程申请
*/
@Test
public void SuspendAllProcessInstance(){
// 获取流程引擎processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取repositoryService
RepositoryService repositoryService = processEngine.getRepositoryService();
// 查询流程定义的对象
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().
processDefinitionKey("act").
list();
for (ProcessDefinition processDefinition : list) {
// 得到当前流程定义的实例是否都为暂停状态
boolean suspended = processDefinition.isSuspended();
// 流程定义id
String processDefinitionId = processDefinition.getId();
// 判断是否为暂停
if (suspended) {
// 如果是暂停,可以执行激活操作 ,参数1 :流程定义id ,参数2:是否激活,参数3:激活时间
repositoryService.activateProcessDefinitionById(processDefinitionId,
true,
null
);
System.out.println("流程定义:" + processDefinitionId + ",已激活");
} else {
// 如果是激活状态,可以暂停,参数1 :流程定义id ,参数2:是否暂停,参数3:暂停时间
repositoryService.suspendProcessDefinitionById(processDefinitionId,
true,
null);
System.out.println("流程定义:" + processDefinitionId + ",已挂起");
}
}
}
13单个流程实例挂起
操作流程实例对象,针对单个流程执行挂起操作
act_hi_actinst,act_hi_identitylink,act_hi_procinst,act_hi_taskinst,act_ru_identitylink,act_ru_task,act_ru_execution这些表都有流程实例id,字段名都是PROC_INST_ID_,这里我使用act_ru_execution查询**
代码:
/**
* 单个流程实例挂起与激活
*/
@Test
public void SuspendSingleProcessInstance(){
// 获取processEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// RuntimeService
RuntimeService runtimeService = processEngine.getRuntimeService();
// 查询流程定义的对象
ProcessInstance processInstance = runtimeService.
createProcessInstanceQuery().
processInstanceId("55001").
singleResult();
// 得到当前流程定义的实例是否都为暂停状态
boolean suspended = processInstance.isSuspended();
// 流程定义id
String processDefinitionId = processInstance.getId();
// 判断是否为暂停
if(suspended){
// 如果是暂停,可以执行激活操作 ,参数:流程定义id
runtimeService.activateProcessInstanceById(processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已激活");
}else{
// 如果是激活状态,可以暂停,参数:流程定义id
runtimeService.suspendProcessInstanceById( processDefinitionId);
System.out.println("流程定义:"+processDefinitionId+",已挂起");
}
}
14测试完成个人任务,测试暂停了流程实例,是否还可以继续
对于已完成的任务
对于新建的任务
代码:
/**
* 测试完成个人任务,测试暂停了流程实例,是否还可以继续下一步,会报错“Cannot complete a suspended task”
*/
@Test
public void completTask1(){
// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取操作任务的服务 TaskService
TaskService taskService = processEngine.getTaskService();
// 完成任务,参数:流程实例id,完成zhangsan的任务
Task task = taskService.createTaskQuery()
.processInstanceId("77501")
.taskAssignee("zhangsan")
.singleResult();
System.out.println("流程实例id="+task.getProcessInstanceId());
System.out.println("任务Id="+task.getId());
System.out.println("任务负责人="+task.getAssignee());
System.out.println("任务名称="+task.getName());
taskService.complete(task.getId());
}