activiti6工作流开发
activiti6工作流开发
文章目录
转载请标明出处:鸭梨的药丸哥
工作流数据库准备
activiti6依赖于SQL数据库,要运行activiti6需要准备好SQL数据库,在activiti部署前要求准备好表的创建。
Activiti有3种表创建方式:
DB_SCHEMA_UPDATE_FALSE
//不能自动创建表,需要表存在DB_SCHEMA_UPDATE_CREATE_DROP
//启动引擎时创建表,关闭引擎时删除表DB_SCHEMA_UPDATE_TRUE
//如果表不存在,自动创建表
Activiti有3种启动方式:
- 通过编码的方式,
ProcessEngineConfiguration
硬编码配置信息,然后启动activiti - 自定义配置文件方式,读过读取指定指定xml配置文件的配置启动activiti
- 通过默认方式(读取classpath路径下activiti.cfg.xml文件)
启动方式一
通过编码的方式启动activiti,启动时注意表的创建方式,如果需要完全初始化的方式启动,请使用DB_SCHEMA_UPDATE_CREATE_DROP
。
//生成工作流需要的表
public void initTable(){
//流程引擎的配置对象,这种方式不需要配置文件
ProcessEngineConfiguration engineConfiguration = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
//加载配置,然后需要得到工作流的核心对象
ProcessEngine processEngine = engineConfiguration.setJdbcDriver("com.mysql.cj.jdbc.Driver")//驱动
.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false")//jdbc url
.setJdbcUsername("root")//数据库账号
.setJdbcPassword("xxx")//数据库密码
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE) //设置表的更新(这里使用如果表不存在,自动创建表)
.buildProcessEngine(); //创建引擎
}
启动方式二
添加配置信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建工作流引擎的核心对象,id的名称不可以修改-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 加载数据源 -->
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"></property>
<property name="jdbcUsername" value="root"></property>
<property name="jdbcPassword" value="xxxxx"></property>
<!-- 设置数据库表的更新-->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
使用指定配置文件
//生成工作流需要的表
public void initTable(){
//指定加载配置
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//创建引擎
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
}
启动方式三
//如果没有初始引擎,会读取activiti.cfg.xml文件去创建引擎,并返回
private ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
表的生成
日志表:
act_evt_log 存储事件处理日志,方便管理员跟踪处理
通用数据表:
act_ge_bytearray 二进制数据表,一些文件存在这个表。
act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录
历史数据表:
act_hi_actinst 历史节点表
act_hi_attachment 历史附件表
act_hi_comment 历史意见表
act_hi_detail 历史详情表,提供历史变量的查询
act_hi_identitylink 历史流程人员表
act_hi_procinst 历史流程实例表
act_hi_taskinst 历史任务实例表
act_hi_varinst 历史变量表
用户组织表:
act_id_group 用户组信息表
act_id_info 用户扩展信息表
act_id_membership 用户与用户组对应信息表
act_id_user 用户信息表
资源流程规则表:
act_procdef_info 流程定义信息
act_re_deployment 部署信息表
act_re_model 流程设计模型部署表
act_re_procdef 流程定义数据表
运行时数据库表
act_ru_event_subscr 监听表
act_ru_execution 运行时流程执行实例表
act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
act_ru_job 运行时定时任务数据表
act_ru_task 运行时任务节点表
act_ru_variable 运行时流程变量数据表
绘制流程图
如果是idea2019版本之前的可以使用actiBPM插件进行绘制。
如果是idea2019版本之后,则需要使用activiti bpmn visualizer等其他插件进行代替。
下面步骤将按照这个流程进行。
流程部署
使用bpmn文件部署流程
//部署流程
public String deploy(){
//通过流程引擎对象得到部署的服务层对象
RepositoryService repositoryService = processEngine.getRepositoryService();
//调用部署(部署过程中会自动生成流程声明)
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("store.bpmn") //部署的流程图
.name("入库审批流程") //部署流程的名字(名字记录到act_re_procdef)
.deploy(); //部署
return deploy.getId();//返回部署id(对应act_re_deployment中的id)
}
使用压缩包部署流程
使用压缩包时,压缩包里面必须有bpmn文件,否则是无法进行解压部署的
//部署流程
public String deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
//获取classpath路径下的store.zip文件输入流
InputStream in = this.getClass().getClassLoader().getResourceAsStream("store.zip");
//封装成ZipInputStream
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deploy = repositoryService.createDeployment()
.addZipInputStream(zipInputStream) //zip资源
.name("入库审批流程") //部署流程的名字
.deploy(); //部署
return deploy.getId();
}
流程部署分析
- 在流程定义数据表(
act_re_procdef
)中生成流程声明,id是流程图的id:版本:序列号(系统生成),KEY是流程图的id。 - 在二进制数据表(
act_ge_bytearray
)中保存数据文件,如bpmn,png(流程图片会自动生成)等 - 在部署信息表(
act_re_deployment
) 中生成一条部署信息
流程启动
每启动一个流程,那么就会在数据库生成相应的流程实例信息。
通过流程声明KEY启动
流程声明KEY于定义该流程声明的流程图id一致
//启动流程
public void start(){
//需要得到运行时的服务层对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据key来启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess_1");//流程图的id(act_re_procdef中的KEY)
//得到流程信息
System.out.println("实例ID:"+processInstance.getId());//对于act_ru_execution中的id
System.out.println("流程定义ID:"+processInstance.getProcessDefinitionId());
}
通过流程定义ID启动
//启动流程
public void start(){
//需要得到运行时的服务层对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据key来启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceById("myProcess_1:1:2504");//act_re_procdef中的id
//得到流程信息
System.out.println("实例ID:"+processInstance.getId());
System.out.println("流程定义ID:"+processInstance.getProcessDefinitionId());
}
流程启动分析
当流程启动后,会生成流程实例信息,act_ru_execution记录基本的流程实例信息,act_ru_task记录当前有哪些任务在运行,act_ru_identitylink记录任务相关的人物信息。
- 在运行时流程任务表(act_ru_task),跟新执行任务信息。其中act_ru_task的
PROC_INST_ID_
记录这该任务与哪个流程实例信息绑定。PROC_DEF_ID_
记录的是流程声明的id(act_re_procdef中的ID) - 跟新运行时流程执行表(act_ru_execution),跟新现在正在执行的流程实例信息。
- 跟新执行主体相关信息表(act_ru_identitylink),跟新执行任务的相关人物信息
- 在历史流程人员表(act_hi_identitylink)生成一条历史记录
任务查找
通过任务代理人名称查询
//查询任务
public void find(){
//得到任务的服务层对象
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.taskAssignee("维修工")//查询条件(根据代理人查询)
.list();
for (Task task : taskList) {
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("执行者:"+task.getAssignee());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
}
}
通过流程实例id查询
根据正在运行的流程实例的id查找当前要实现的任务
public void find(){
//得到任务的服务层对象
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processInstanceId("5001")//跟act_ru_execution的id相同
.list();
for (Task task : taskList) {
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("执行者:"+task.getAssignee());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
}
}
任务执行
通过任务id执行任务
//完成任务
public void complete(){
//得到任务服务层的对象
TaskService taskService = processEngine.getTaskService();
taskService.complete("5004");//任务id
}
任务执行分析
-
如果当前流程实例的所有任务已经完成,那么运行时流程执行实例表(
act_ru_execution
)流程实例信息将会销毁,以及对应在运行时任务节点表(act_ru_task
)的任务信息销毁(因为已经执行完了),运行时流程人员表(act_ru_identitylink
)对应的流程人员会被销毁(任务完成了,不需要了)。 -
如果当前流程实例尚未完成,那么将会跟新运行时任务节点表(
act_ru_task
)的任务信息,并更新运行时流程人员表(act_ru_identitylink
)对应的流程人员
流程变量
流程实例能捆绑变量,如:提交请假申请(往往会捆绑一个请假单),通过申请单的id那么就可以获取申请了多少假期(申请单应该存储在另一个业务系统中)。
为了能捆绑第三方业务系统的信息,activiti提供了流程执行的过程中捆绑变量的功能,通过进行变量的捆绑,每个流程实例都可以拥有独自的变量信息。
流程中的变量会跟随流程的进行而传递,也就是说上一个任务设置的变量在下一个任务中也都能获取,不会因为流程任务状态更新而导致变量丢失不见的问题
假设现在在第三方业务系统上有一个账单信息实体类,必须实现序列化:
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
public class RepairBillDetail implements Serializable {
//序列化id
private static final long serialVersionUID = -1914045242557039080L;
private Long id; //维修单id
private String repairInfo; //账单描述信息
private BigDecimal price; //维修价格
}
启动流程时传递变量
//启动时捆绑变量
public void startByVar(){
//账单实体类
RepairBillDetail repairBillDetail = new RepairBillDetail(10001L, "维修账单", BigDecimal.valueOf(500.5));
//构建变量
HashMap<String, Object> map = new HashMap<>();
map.put("bill",repairBillDetail);
RuntimeService runtimeService = processEngine.getRuntimeService();
//根据流程声明ID启动,并传递变量variables
ProcessInstance processInstance = runtimeService.startProcessInstanceById("myProcess_1:1:2504",map);
//得到流程信息
System.out.println("实例ID:"+processInstance.getId());
System.out.println("流程定义ID:"+processInstance.getProcessDefinitionId());
}
指定任务传递变量
public void setVarByTaskId(){
TaskService taskService = processEngine.getTaskService();
String taskId = "5012";
RepairBillDetail repairBillDetail = new RepairBillDetail(10001L, "维修账单2", BigDecimal.valueOf(500.5));
//根据任务id设置变量(注意,该变量将会在整个流程实例中生效)
taskService.setVariable(taskId,"bill2",repairBillDetail);
Map<String, Object> variables = taskService.getVariables(taskId);
System.out.println(variables);
}
完成任务时传递变量
public void completeAndSetVar(){
RepairBillDetail repairBillDetail = new RepairBillDetail(10001L, "维修账单3", BigDecimal.valueOf(500.5));
HashMap<String, Object> map = new HashMap<>();
map.put("bill",repairBillDetail);
map.put("var2","字符串变量");
map.put("var3",100);
String taskId = "5012";
TaskService taskService = processEngine.getTaskService();
//根据任务id完成任务,并设置var
taskService.complete(taskId,map);
}
获取变量
public void getVarByTaskId(){
//根据任务id获取变量,何时都能获取变量,只需要传递当前正在执行的任务id就能获取到上一个任务设置的变量
Map<String, Object> variables = taskService.getVariables(taskId);
System.out.println(variables);
}
变量存储分析
- 普通的变量是存储在运行时流程变量数据表(
act_ru_variable
),比如Long,String等 - 对象变量是存储在运行时流程变量数据表(
act_ru_variable
),二进制数据表(act_ge_bytearray
)这两张表中,其中act_ru_variable
记录着变量的详情,而对象序列化数据是存储在act_ge_bytearray
。
变量在流程中使用案例
画图插件的问题
在IDEA2020版本后插件actiBPM
已经不能兼容使用了,这里我使用画图工具activiti BPMN visualizer
。
这里使用activiti BPMN visualizer
插件进行流程图的开发。
请假示例
画下面流程图,其中请假的人使用变量 s t u N a m e 来 代 替 , 班 主 任 使 用 {stuName}来代替,班主任使用 stuName来代替,班主任使用{tchName}来动态指定,系主任使用${deanName}指定,应该注意的是这些变量应该是来自第三方业务系统的。
简单的流程代码
//请假工作流示例
public class LeaveFlow {
//流程引擎
private final ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//部署
public Deployment deploy(String resource, String name){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("test.bpmn20.xml")
.name("请假申请流程")
.deploy();
System.out.println("部署信息id:"+deployment.getId());
return deployment;
}
//启动流程
public ProcessInstance start(ProcessDefinition definition,String assignee){
RuntimeService runtimeService = processEngine.getRuntimeService();
HashMap<String, Object> map = new HashMap<>();
map.put("stuName",assignee);//谁启动流程就添加谁为请假人员
ProcessInstance processInstance = runtimeService.startProcessInstanceById(definition.getId(),map);
return processInstance;
}
//成为任务
public void completTask(String taskId, Map var){
TaskService taskService = processEngine.getTaskService();
taskService.complete(taskId,var);
}
//寻找任务
public Task findTask(ProcessInstance instance,String assignee){
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
.processInstanceId(instance.getId())
.taskAssignee(assignee)
.singleResult();
return task;
}
public ProcessDefinition findProcessDefinition(Deployment deployment){
RepositoryService repositoryService = processEngine.getRepositoryService();
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
return processDefinition;
}
public Deployment findDeployment(String deployName){
RepositoryService repositoryService = processEngine.getRepositoryService();
//根据deployment名字查找部署信息
Deployment deployment = repositoryService.createDeploymentQuery()
.deploymentName(deployName)
.singleResult();
return deployment;
}
public static void main(String[] args) {
LeaveFlow leaveFlow = new LeaveFlow();
String resure = "test.bpmn20.xml";
String deployName = "请假申请流程";
//部署
//Deployment deploy = leaveFlow.deploy(resure, deployName);
//部署过可以通过,部署名获取deployment
Deployment deploy = leaveFlow.findDeployment(deployName);
//查找流程定义
ProcessDefinition processDefinition = leaveFlow.findProcessDefinition(deploy);
//启动流程(张三点击请假,启动流程)
ProcessInstance processInstance = leaveFlow.start(processDefinition, "张三");
//查找张三任务
Task task = leaveFlow.findTask(processInstance, "张三");
//设置变量
HashMap hashMap = new HashMap() {{
put("days",5); //设置申请天数(张三填)
put("tchName","陈老师"); //设置老师(张三的老师)
}};
//张三完成任务
leaveFlow.completTask(task.getId(),hashMap);
//查找陈老师的任务
Task task2 = leaveFlow.findTask(processInstance, "陈老师");
//设置变量
hashMap.put("deanName","张主任");
//陈老师完成任务
leaveFlow.completTask(task2.getId(),hashMap);
//查找张主任的任务
Task task3 = leaveFlow.findTask(processInstance, "张主任");
//完成审批
if (task3!=null)leaveFlow.completTask(task3.getId(),null);
}
}
流程分析
步骤流程如下:
- 部署,在(
act_re_deployment
)(act_re_procdef
)(act_ge_bytearray
)分别生成一条或条信息。分别是流程部署信息(包含部署id,部署名称等),流程定义(包含定义id,流程图识别符key,),二进制数据(包含两条,分别是图片,流程声明文件) - 启动流程,通过流程定义的id或者流程定义中的key(流程图id)启动流程,生成一个流程实例。流程实例存储在(
act_ru_execution
),而当前正要执行的任务存放在(act_ru_task
)。 - 执行任务,执行任务需要指定要实现任务的id,通常通过
流程实例id
和执行人name
去锁定某个流程实例中的任务节点,锁定任务节点后,完成任务。 - 条件判断和变量赋值,不同的任务有不同的判断条件和变量,根据条件去确定下一个任务节点,条件中的变量可以在流程执行的过程中进行添加,如条件判断变量,任务执行者变量等。
流程网关
activiti中有两种流程网关,一种是并行网关,一种是排他网关,包含网关,事件网关。
排他网关
排他官网流程跟上面的请假示例相识,是根据条件来判断流程执行步骤,设置变量即可能进行流程判断了。
并行网关
并行网关没有判断条件,当请假任务完成后,将会同时生成两个任务节点,要完成两个任务节点才能进入下一任务节点。如下,要完成班主任审核,系主任审核才能完成请假流程。
包含网关
包含网关是排他和并行的结合,如下:当满足请假天数days>3这个添加时,需要班主任和系主任同时审核,当days<=3时,只需要班主任审核。
流程任务
ServiceTask
服务任务ServiceTask,通过委派模式供activiti执行第三方任务代码。
委派监听类如下:
/**
* @author liangwy
* @see org.activiti.engine.delegate.JavaDelegate
* @since 2022-4-29
*/
public class ServiceTask implements JavaDelegate {
@Autowired
private MyService myService;
//委派任务(实现JavaDelegate接口)
@Override
public void execute(DelegateExecution execution) throws Exception {
//执行自己的业务逻辑
myService.doSome();
//设置流程变量,供下面的任务使用
execution.setVariable("var","设置变量");
}
}
MailTask
邮件发送任务,使用POP3邮件接受协议和SMTP邮件发送协议实现邮件发送。
添加配置信息
只需要在
<!--配置邮件服务器-->
<property name="mailServerHost" value="smtp.qq.com"/>
<property name="mailServerPort" value="465"/>
<!--默认来源,没设置来源时会用该默认值代替-->
<property name="mailServerDefaultFrom" value="xxxxx@qq.com"/>
<property name="mailServerUsername" value="xxxxx@qq.com"/>
<!--授权码-->
<property name="mailServerPassword" value="123456487"/>
<property name="mailServerUseSSL" value="true"/>
画图