最近工作中用到Activiti 工作流,自学了相关知识,记载如下
1. 基础知识:
1.1 建模言语BPMN
-
BPM软件在企业中使用非常广泛,但凡有业 务流程的当地都能够运用BPM进行处理。比方企业人事工作处理、采购流程处理
-
BPMN是Business Process Model And Notation 事务流程模型和符号,便是用来描绘事务流程的一种建模规范。运用BPMN能够快速界说事务流程。
-
整个BPMN是用一组符号来描绘事务流程中产生的各种事情的。BPMN经过在这些符号事情之间连线来描绘一个完好的事务流程。
-
而关于一个完好的BPMN图形流程,其实终究是经过XML进行描绘的
一般,会将BPMN流程终究保存为一个.bpmn的文件,然后能够运用文本编辑器翻开进行检查。而图形与xml文件之间,会有专门的软件来进行转换。
1.2 表结构解读
activiti的表都以act_最初。第二个部分表明表的用处。用处也和服务的API对应
-
ACT_RE_ : ‘RE’表明repository(存储库)。 这个前缀的表包含了流程界说和流程静态资源 (图片,规则,等等)
-
ACT_RU_: ‘RU’表明runtime。 这些运转时的表,包含流程实例,使命,变量,异步使命,等运转中的数据。 Activiti只在流程实例履行进程中保存这些数据, 在流程完毕时就会删去这些记载。 这样运转时表能够一直很小速度很快
-
ACT_ID_ : ‘ID’表明identity。 这些表包含身份信息,比方用户,组等等
-
ACT_HI_: ‘HI’表明history。 这些表包含前史数据,比方前史流程实例, 变量,使命等等
-
ACT_GE_*: 通用数据, 用于不同场景下
1.3 activiti 工作流不支持分布式
原因是:在Activiti工作流的act_ge_property表中一般情况下有3条记载:
- next.dbid
- schema.history
- schema.version
其间next.dbid对应的值为数据库中当前最近一次增加后的最大记载id,每次增加的步长为2500,protected int idBlockSize = 2500; (在ProcessEngineConfiguration类中)
- Activiti中一切的id(如:Task的id,Execution的id,ProcessInstance的id等)都是经过IdGenerator来生成的
- IdGenerator的默认完结是
/**2 * @author Tom Baeyens3 */4 public class DbIdGenerator implements IdGenerator {5 6 protected int idBlockSize;7 protected long nextId = 0;8 protected long lastId = -1;9
10 protected CommandExecutor commandExecutor;
11 protected CommandConfig commandConfig;
12
13 public synchronized String getNextId() {
14 if (lastId<nextId) {
15 getNewBlock();
16 }
17 long _nextId = nextId++;
18 return Long.toString(_nextId);
19 }
20
21 protected synchronized void getNewBlock() {
22 IdBlock idBlock = commandExecutor.execute(commandConfig, new GetNextIdBlockCmd(idBlockSize));
23 this.nextId = idBlock.getNextId();
24 this.lastId = idBlock.getLastId();
25 }
从上面的代码能够看出,获取下一个id的办法是加锁的,
也便是在一台服务器上id的增加是没有问题的,可是假如将Activiti布置在多台服务器上就会有两个问题
- 从代码的第17,18行能够看出id是本地自增,假如有多台服务器就会出现id相同的情况(由并发写形成的);
- 获取lastId的办法是操作同一个数据库的,会有问题,代码22中经过履行GetNextIdBlockCmd来获取数据库中的next.dbid的值,假如在多台服务器上因为一台服务器修改后,其他服务器无法知道
2. Activiti 入门
界说和实例的概念
-
流程界说:ProcessDefinition
- 类 Class : 离任工作流界说
-
流程实例:ProcessInstance
- 目标 Obj : 张三离任单流程
2.1. 加载最新的流程界说id
查询一切流程界说,顺次放入map调集;
- 注:这些界说是SpringBoot 读取文件夹,自动布置到数据库中的。
/**
* 加载最新的流程界说的id
*
* @see [相关类/办法](可选)
* @since [产品/模块版本](可选)
*/
private void findLastVersionProcessDefinition() {
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().orderByProcessDefinitionVersion().asc().list();
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(pd.getName() + " ---- " + pd.getId());
InitializationBean.activitiMap.put(pd.getName(), pd.getId());
}
}
}
2.2. 发动流程实例
依据流程界说id,发动流程实例(StartEvent)
// 发动流程
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(activitiProcdId).singleResult();
Map<String, String> param = new HashMap<String, String>();
param.put("departureCode", departureCode);
ProcessInstance pi = formService.submitStartFormData(processDefinition.getId(), param);
logHelper.LoggerSendINFO("离任申请:" + departureCode + " , 已提交,进程实例id: " + pi.getId());
System.out.println("离任申请:" + departureCode + " , 已提交,进程实例id: " + pi.getId());
return pi.getId();
2.3. 部分经理批阅
成果符号字段:
回绝离任: String departureRemark = “false”;
同意离任:departureRemark = “true”;
@Override
public Boolean approvalTask(String departureCode, String procId, String departureRemark) {
// 查询task
Task task = this.queryTask(procId);
if (null != task) {
Map<String, String> properties = new HashMap<String, String>();
properties.put("departureCode", departureCode);
properties.put("departureRemark", departureRemark);
formService.submitTaskFormData(task.getId(), properties);
return true;
}
return false;
}
/**
* 依据流程实例id 查询使命节点
*
* @param procId 流程实例id
* @return 使命节点
*/
private Task queryTask(String procId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(procId).list();
return tasks.get(0);
}
2.4.财政批阅
// 查询task
Task task = this.queryTask(procId);
if (null != task) {
Map<String, String> properties = new HashMap<String, String>();
properties.put("departureCode", departureCode);
formService.submitTaskFormData(task.getId(), properties);
return true;
}else{
throw new RuntimeException("工作流找不到");
}
/**
* 依据流程实例id 查询使命节点
*
* @param procId 流程实例id
* @return 使命节点
*/
private Task queryTask(String procId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(procId).list();
return tasks.get(0);
}
2.5. 流程完毕 EndEvent
3. Activiti 进阶
流程界说 ProcessDefinition 和流程实例 ProcessInstance是Activiti中非常重要的两个概念。
他们的联系其实类似于JAVA中类和目标的概念。
流程界说ProcessDefinition是以BPMN文件界说的一个工作流程,是一组工作规范;
流程实例ProcessInstance则是指一个详细的事务流程。例如某个员工建议一次请假,就会实例化一个请假的流程实例,而且每个不同的流程实例之间是互不影响的。
在后台的表结构中,有许多张表都包含了流程界说ProcessDefinetion和流程实例ProcessInstance的字段。流程界说的字段一般是PROC_DEF_ID,而流程实例的字段一般是PROC_INST_ID。
3.1 发动流程实例时,添加Businesskey
当咱们去检查下startProcessInstanceByKey这个办法时,会看到这个办法有好几个
重载的完结办法,能够传一些不同的参数。其间几个重要的参数包含:
-
String processDefinitionKey:流程界说的唯一键 不能为空
-
String businessKey:每个线程实例上下文中相关的唯一键。这个也是咱们这一章节要介绍的重点。
-
Map<String,Object> variables:在线程实例中传递的流程变量。这个流程变量能够在整个流程实例中运用,后面会介绍到。
-
String tenantId:租户ID,这是Activiti的多租户设计。相当于每个租户能够上来获取一个相对独立的运转环境。
3.2 挂起和激活 流程实例
- 一种是将整个流程界说Process Definition挂起,这样,这个流程界说下的一切流程实例都将挂起,无法持续履行
- 另一种办法是将某一个详细的流程实例挂起。
3.3 流程变量
流程变量的类型是Map<String,Object>
Map<String, String> param = new HashMap<String, String>();
param.put("departureCode", departureCode);
ProcessInstance pi = formService.submitStartFormData(processDefinition.getId(), param);
装备了这个流程后,能够配合流程变量运用。例如:履行完结后,能够在act_ru_variable表中看到方才map中的数据
流程变量比事务关键字要强大许多。变量值不仅仅是字符串,也能够是POJO目标。可是当需要将一个POJO目标放入流程变量时,要注意这个目标必需要完结序列化接口
流程变量的效果域
变量的效果域能够设置为Global和Local两种
- Global变量
这个是流程变量的默认效果域,表明是一个完好的流程实例。 Global变量中变量名不能重复。假如设置了相同的变量名,后面设置的值会直接掩盖前面设置的变量值。
- Local 变量
Local变量的效果域只针对一个使命或一个履行实例的规模,没有流程实例大。
Local变量因为效果在不同的使命或不同的履行实例中,所以不同变量的效果域是互不影响的,变量名能够相同。Local变量名也能够和Global变量名相同,不会有影响。
运用流程变量
界说好流程变量后,就能够在整个流程界说中运用这些流程变量了。
设置Global流程变量
1) 发动流程时设置变量
2) 使命处理时设置变量
3) 经过当前流程实例设置
4) 经过当前使命设置
3.4 网关
网关是用来操控流程流向的重要组件,一般都会要结合流程变量来运用。
3.4.1 排他网关ExclusiveGateway(常用)
排他网关,用来在流程中完结决策。
当流程履行到这个网关,一切分支都会判断条件是否为true,假如为true则履行该分支
注意 :
-
排他网关只会挑选一个为true的分支履行。假如有两个分支条件都为true,排他网关会挑选id值较小的一条分支去履行。
-
假如从网关出去的线一切条件都不满意则体系抛出反常。
3.4.2 并行网关ParallelGateway
并行网关允许将流程分红多条分支,也能够把多条分支会聚到一起,并行网关的功能是依据进入和外出次序流的:并行网关在事务使用中常用于会签使命,会签使命即多个参与者一起处理的使命
与其他网关的首要区别是,并行网关不会解析条件。 即使次序流中界说了条件,也会被疏忽。
说明:此时会要求技能经理和项目经理都进行批阅。而连线上的条件会被疏忽
技能经理和项目经理是两个execution分支,在act_ru_execution表有两条记载分别是技能经理和项目经理,act_ru_execution还有一条记载表明该流程实例。
待技能经理和项目经理使命悉数完结,在会聚点会聚,经过parallelGateway并行网关。
并行网关在事务使用中常用于会签使命,会签使命即多个参与者一起处理的使命。
3.4.3 包含网关InclusiveGateway
包含网关能够看做是排他网关和并行网关的结合体。
包含网关的功能是依据进入和外出次序流的:
- 分支: 一切外出次序流的条件都会被解析,成果为true的次序流会以并行办法持续履行, 会为每个次序流创立一个分支。
- 会聚: 一切并行分支抵达包含网关,会进入等候状况, 直到每个包含流程token的进入次序流的分支都抵达。
3.4.4 事情网关EventGateway
3.5 组使命分配
3.5.1 设置多个候选责任人
某个订单合同,需要找部分经理级其他负责人签字。而公司中有多个部分经理,事务上只需要找其间恣意一个人完结批阅就能够了。
这种场景下,咱们就无法经过设置流程变量的办法来设置负责人。这时,就需要用到Activiti供给的另一个利器-使命提名人Candidate Users。
3.5.2 组使命处理流程
给使命分配了提名人后,后续就需要这些提名人自动招领自己的事务,然后进行处理。
- 先查询使命
- 再招领使命(也能够退还使命)
- 最后完结使命
4. Activiti7与SpringBoot整合开发
Activiti与Spring整合的基本思想是将Activiti最为中心的ProcessEngine类交由Spring容器进行处理。
1.POM 依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2.装备文件
spring:
activiti:
activityFontName: '宋体'
labelFontName: '宋体'
annotationFontName: '宋体'
# false:默认值。activiti在发动时,会对比数据库表中保存的版本。假如没有表或许版本不匹配,将抛出反常
# true:activiti会对数据库中一切表进行更新操作,假如表不存在,则会自动创立
# create_drop:在activiti发动时创立表,在封闭时删去表(有必要手动封闭引擎,才干删去表)
# drop-create:在activiti发动时删去本来的旧表,然后再创立新表(不需要手动封闭引擎)
database-schema-update: true
# 自动布置验证设置:true-敞开(默认)、false-封闭
check-process-definitions: true
#
async-executor-enabled: true
job-executor-activate: true
# 自界说流程文件位置
process-definition-location-prefix: classpath:/processes/
#记载前史等级 可装备的前史等级有none, activity, audit, full
#none:不保存任何的前史数据,因而,在流程履行进程中,这是最高效的。
#activity:等级高于none,保存流程实例与流程行为,其他数据不保存。
#audit:除activity等级会保存的数据外,还会保存悉数的流程使命及其特点。audit为history的默认值。
#full:保存前史数据的最高等级,除了会保存audit级其他数据外,还会保存其他悉数流程相关的细节数据,包含一些流程参数等。
history-level: full
5. 专项补充:
Activiti 中心类
activiti工作流结构其实是一个半成品项目,所以它自带了25张数据库表,而且它还有service层,能够在咱们ssm结构整合好oa体系后,就能够直接@Autowired注入到咱们的controller或许service层就能够了。
RepositoryService-仓储服务
Activiti的资源处理类,供给了处理和操控流程发布包和流程界说的操作。
//仓储服务
@Autowired
private RepositoryService repositoryService;
-
仓储服务能够用来布置咱们的流程图,还能够创立咱们的流程布置查询目标,用于查询刚刚布置的流程列表,便于咱们处理流程,办法如下。
//这个是布置流程的办法,流程图以inputStream流的形式传入
DeploymentBuilder builder = repositoryService.createDeployment();
builder.name(process.getName());
builder.addInputStream(fileName, inputStream);
Deployment deployment = builder.deploy();
//这个是流程布置列表查询的办法
DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery();
//能够依据许多条件查询,我这是依据布置名称模糊查询
List<Deployment> list = deploymentQuery.deploymentNameLike("%"+name+"%")
RuntimeService-activiti运转时服务
Activiti的流程运转处理类。能够从这个服务类中获取许多关于流程履行相关的信息
//运转时服务
@Autowired
private RuntimeService runtimeService;
运转时服务首要用来敞开流程实例,一个流程实例对应多个使命,也便是多个流程节点,好比方请假批阅是一个流程实例,部分主管,部分经理,总经理都是节点,咱们敞开服务是经过流程界说key或许流程界说id来敞开的,办法如下:
//首要依据布置id创立流程界说
ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(form.getDeployId()).singleResult();
//然后依据流程界说id或许key敞开流程实例
ProcessInstance proInst = runtimeService.startProcessInstanceById(def.getId());
补白:当咱们用仓储服务布置了流程图之后,就会产生一个流程布置id,一个流程布置id对应一个流程界说,一个流程界说对应多个流程实例,一个流程实例对应多个使命节点,
这样的逻辑应该明白吧,打个比方便是我设计了一个手机图纸(流程界说),是能够供N多个人生产出手机并去运用的,这些人便是流程实例,手机里面的各种功能便是使命节点。
TaskService-使命服务
//使命服务
@Autowired
private TaskService taskService;
使命服务是用来能够用来收取,完结,查询使命列表功能的,运用办法分别如下:
//依据使命id和用户收取使命
taskService.claim(String taskId, String userId)
//依据使命id完结自己节点的使命
taskService.complete(String taskId)
//创立使命查询目标之后依据提名人也便是使命处理人查询自己的使命列表
taskService.createTaskQuery().taskAssignee(String assignee)
HistoryService-前史服务
//前史服务
@Autowired
private HistoryService historyService;
前史服务能够检查批阅人曾经批阅完结了哪些项目,批阅项目总共花了多少时刻,以及在哪个环节比较耗费时刻等等,便于批阅人检查前史信息,办法如下。
//依据批阅人检查该批阅人批阅了哪些项目
List<HistoricTaskInstance> = historyService.createHistoricTaskInstanceQuery().
taskAssignee(String assignee).finished().list();
前史使命目标HistoricTaskInstance,它里面封装了使命开端时刻,完毕时刻,该节点花费的时刻等等信息。
FormService-表单服务
//表单服务
@Autowired
private FormService formService;
IdentityService-实体服务
首要是操效果户信息,用户分组信息等,组信息包含如部分表和职位表,我一般都是自己建表来存储用户信息和组信息的
//实体服务
@Autowired
private IdentityService identityService;
Activiti 监听器的运用
有位网友写的挺好的,我就不赘述了,点击检查
blog.csdn.net/qq_30739519…
如有帮助,动动小手点个赞!