(一)使用步骤
- 整合Activiti
2. 实现业务流程建模,使用BPMN实现业务流程图
3. 部署业务流程到Activiti
4. 启动流程实例
5. 查询待办任务
6. 处理待办任务
7. 结束流程
(二)流程开发
1.定义流程(使用Activiti Designer 软件)
2.部署定义好的流程(部署到数据库的表里面去)
3.启动流程实例(将这个流程运行起来)
4.流程上就会有不同的节点
5.部署好了就可以使用了, 比如说张三请假,然后只要填写完了,此时李四去看一下我可以审批的请假单有哪些,此时就涉及到任务的查询,比如作为部门经理就可以审批一下我自己部门的请假单.当它查询自己有任务的时候,就异步处理来实现任务的处理,比如说李四作为部门经理把这个张三的请假申请单审批完成了
下一步,作为总经理进来工作流平台之后也会进行任务的查询,查询如果有任务,总经理就会进入到进一步的处理,总经理处理完了请假流程之后.
此时财务经理就会就会进入工作流平台来查询自己的任务,当查询完任务之后,财务经理就进一步进行审批.于是整个流程就认为结束了.
流程定义
(一)部署
1.影响的表
部署流程成功之后会往act_re_procdef 和 ACT_RE_DEPLOYMENT 和 ACT_GE_BYTEARRAY 三张表里面插入数据
主要是act_re_procdef表里面会记录你部署的流程信息
2.普通方式部署
| @Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService; /**
* 部署成功之后会生成25张表<br />
*/<br />
@Test
public void c1单个文件部署流程() {
//得到repositoryService 就可以操作部署相关的操作了.
//1. 添加资源, png资源可以不添加,但是bpmn资源是必须要添加的
Deployment deployment = this.repositoryService.createDeployment()
.addClasspathResource(“diagram/holiday.bpmn”)
// .addClasspathResource(“diagram/holiday.png”)
//给流程添加名字
.name(“请假申请单流程”)
//开始部署
.deploy();
System.**_out.println(deployment.getName());
System.out**.println(deployment.getId());
} |
| —- |
3.zip压缩包方式部署
其它部署方式都是一次只能部署一个流程文档,如果想一次性部署多个流程文档,可以使用ZIP部署方式,可以把多个流程文档以及流程文档对应的图片或者表单等统一打包成.zip的压缩文件 或是是 .bar压缩文件
给bpmn文件和png文件放到一起 压缩成zip结尾的压缩包.
然后代码
| @Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService; /**
zip文件方式部署的
/
@Test
*public void c_zip方式部署() {
InputStream resourceAsStream =**this**.getClass().getClassLoader().getResourceAsStream(**"diagram/holiday.zip"**);<br />
ZipInputStream zipInputStream = new ZipInputStream(resourceAsStream);
Deployment deployment = this.repositoryService.createDeployment().addZipInputStream(zipInputStream)<br />
.name(**"请假单申请流程"**)<br />
.deploy();<br />
System.out.println(deployment.getId()); //1
System.out.println(deployment.getName()); //请假单申请流程
System.out.println(deployment.getCategory()); //null
System.out.println(deployment.getDeploymentTime()); // Tue Dec 31 16:45:13 CST 2019
System.out.println(deployment.getTenantId());
} | | —- |4.路径方式部署
使用场景:流程文档的内容大部分是固定不变的,只是有少部分属性在流程文档部署时需要跟外部程序进行交互从而动态填充.
比如,开发人员使用图形界面化工具绘制流程文档,有可能人员组织机构或者其他信息需要从数据库中动态查询,这时候就可以使用这种方式并且结合模版引擎技术动态渲染数据,常用的模版引擎框架有Velocity FreeMarker 等等,然后生成预期的流程文档内容,该方式就是客户端自定义流程设计器与原生设计器的一种过渡解决方案.
| @Test
public void addString() {
String resource = “shareniu_addString.bpmn”;
// 读取文件获取文件中定义的xml信息
String text = readTxtFile(“E:/activitilearing/activiti/src/main/java/com/shareniu/chapter3/common.bpmn”);
// 构造DeploymentBuilder对象
DeploymentBuilder deploymentBuilder = repositoryService
.createDeployment().addString(resource, text);
// 部署
Deployment deploy = deploymentBuilder.deploy();
}
/**
- 根据文件的路径获取文件中的内容
* - @param **filePath
* @return
*/
public static String readTxtFile(String filePath) {
StringBuffer stringBuffer = new StringBuffer();
InputStreamReader read = null;
try {
String encoding = “UTF-8”;// UTF-8编码
File file = new File(filePath);
if **(file.isFile() && file.exists()) { // 判断文件是否存在
} else {read = **new **InputStreamReader(**new **FileInputStream(file),<br />
encoding);// 考虑到编码格式<br />
BufferedReader bufferedReader = **new **BufferedReader(read);<br />
String lineTxt = **null**;<br />
**while **((lineTxt = bufferedReader.readLine()) != **null**) {<br />
stringBuffer.append(lineTxt);<br />
}<br />
**return **stringBuffer.toString();<br />
}System.**_out_**.println(**"找不到指定的文件"**);<br />
} catch (Exception e) {
System.out.println(“读取文件内容出错”);
} finally {
try {
} catch (IOException e) {read.close();<br />
}
}
return “”;
} | | —- |
5.inputStream方式部署
| @Test
public void addInputStreamTest() throws IOException {
// 定义的文件信息的流读取
InputStream inputStream = DeploymentBuilderTest.class.getClassLoader()
.getResource(“com/shareniu/chapter3/a.bpmn”).openStream();
// 流程定义的分类
String category = “shareniu_addInputStream”;
// 构造DeploymentBuilder对象
DeploymentBuilder deploymentBuilder = repositoryService
.createDeployment().category(category)
.addInputStream(resourceName, inputStream);
// 部署
Deployment deploy = deploymentBuilder.deploy();
System.out.println(deploy);
} |
| —- |
(二)查询所有已经部署好的流程定义
@Test public void c_查询所有已经部署的流程定义() { List .list(); System.out.println(list.toString()); } |
---|
下面的sql也可以查询出来
select *
from act_re_deployment ard
**left join **act_re_procdef arp **on **ard.**ID_ **= arp.**DEPLOYMENT_ID_**;<br /> <br />act_re_deployment 和 act_re_procdef 是一对多关系<br /> <br /> <br />
(三)删除部署好的流程定义
/ 删除已经部署的流程定义 背后影响的表: act_ge_bytearray act_re_deployment act_re_procdef 注意事项: 1.当我们正在执行的这一套流程没有完全审批结束的时候,此时如果要删除流程定义信息就会失败 2.如果公司层面要强制删除,可以使用repositoryService.deleteDeployment(“1”,true); //参数true代表级联删除,此时就会先删除没有完成的流程结点,最后就可以删除流程定义信息 false的值代表不级联 //级联删除是你没有跑完的流程和所有的关联记录都会被一起删除掉 当然删除和查询可以在一起去完成使用 */ @Test public void c2删除无用的流程() { // 部署的id取值范围是act_re_procdef 表的 DEPLOYMENT_ID 字段的值 //3.执行删除流程定义 参数代表流程部署的id // repositoryService.deleteDeployment(“1”); //级联删除 repositoryService.deleteDeployment(“150001”, true**); //级联删除多个 // List // for (String id : strings) { // repositoryService.deleteDeployment(id, true); // // } } |
---|
流程实例
(一)Activiti与业务系统整合开发的原理分析
假如说你有系统有请假系统,比如张三请假,李四请假,请假的内容是存到请假系统表里面的,而不是Activiti内置的25张表.
因为Activiti将来不知道你用Activiti去做什么东西,所以没有预留这些和业务有关的表,毕竟表设计是跟业务挂钩的,应该是由程序员自己手动去设计表.
所以这Activiti的25张表本身是为了保证Activiti自己能够正常运行.
假如说请假, 你有请假当事人信息,请假开始结束时间,请假事由,请假类型等等,这些请假信息数据都是存在程序员自己手动定义的数据表里面.
然后如果有一个人发起请假流程的话,就需要在这个请假表里面存储一条记录了,此时这个记录没有和Activiti关联到的,所以需要业务表和Activiti表进行关联.
关联方式是通过bussinessKey和业务表的主键进行关联(就是将业务表的主键存到Activiti表里面去.)
将来Activiti就可以通过这个业务主键来找到是谁发起了请假单,目前已经执行到了部门经理这个环节了,这样就可以做到业务系统和Activiti关联.
(二)启动流程实例同时关联businessKey和userId
主要影响的表是act_ru_execution表.
@Autowired /执行管理、包括启动、推进、删除流程实例等操作/ private RuntimeService runtimeService; @Autowired private IdentityService identityService; @Test public void c启动流程实例添加进businessKey() { /3.启动流程实例,同时还要指定业务标识businessKey 它本身就是请假单的id 第一个参数:是指流程定义key 第二个参数:业务标识businessKey(这个对于我们而言就是我们的业务的主键,比如请假流程, 这个就是请假单的那条记录的id) ,这里假设请假单的id就是1001,实际开发中,你也可以把业务表的表名拼接到里面去,这样就可以根据businessKey里面参数来区分是请假流程还是印章申请流程. businessKey = “表名+当前流程记录的主键” ; / String writtenRequestForLeaveId = “1001”; // 请假条id ProcessInstance holiday = this.runtimeService.startProcessInstanceByKey(“holiday”, writtenRequestForLeaveId); /设置当前流程是哪个用户开启的,可以在 ACTHI_PROCINST 表的 START_USER_ID 字段查询到/ identityService.setAuthenticatedUserId(processDefinitionVo.getLoginId()); String id = holiday.getId(); System.err.println(“——————-“); System.out.println(id); //获取执行的唯一标示 5001 /4.输出processInstance相关的属性,取出businessKey使用:processInstance.getBusinessKey() 将来如果希望通过程序拿回来这个值的话,就调用processInstance.getBusinessKey() 方法 就可以将业务主键重新拿回来了. 假如说这个流程实例,比如说已经执行到领导审批了,领导审批到底审批的是具体的哪一个员工的请假单 这个时候就可以通过businessKey去查询请假单表才能知道是哪个员工的请假单了 / System.out.println(holiday.getBusinessKey()); //1001 } |
---|
(三)通过tenantid启动流程实例
this.runtimeService.
startProcessInstanceByKeyAndTenantId(“holiday”, “9431c3d5efcd49698c3ce80a0d12903d”);
(四)全部流程实例挂起
公司原本已经跑了一套请假的流程,但是有一种极端的情况,这套请假流程并不可能会从头到尾执行完,比如说公司制度发生了变化,因为各种原因,原来请假流程是有问题的,不完整的,此时公司就调整了请假制度,制度一旦发生调整的时候,于是我们的一种特殊的情况就来了
1. 原本还没有批准完的流程怎么办?
2. 张三员工个人还没有批准完的流程怎么办? 这样就需要按公司的制度来了,比如说原来没走完的流程按照老的制度继续走或者是原来还没有完成的制度统一打回,让请假者按照公司的新制度重新申请请假流程.(这就是新的制度和老制度交替的情况下会出现的现象)
一旦这种制度的情况发生了,那么这个流程就需要暂时挂起了.
每一套流程都有自己唯一的标识的key(key就是 actre_procdef 表里面的KEY字段),可以通过标识的key,也可以通过流程的id进行挂起.
会影响 ACT_RE_PROCDEF 和 ACT_RU_EXECUTION 和 ACT_RU_TASK 三张表
注意
当我的流程不管跑没跑完,只要流程被挂起了,那么后续就不允许启动新的流程实例了,同时流程定义下的所有的流程实例将全部挂起暂停执行,也就是说你想执行后续的就执行不了了
| @Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService;
@Test
public void c_全部流程实例的挂起与激活() {
//3.查询流程定义的对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(“holiday”).singleResult();
//4.得到当前流程定义的实例是否都为暂停状态
boolean suspended = processDefinition.isSuspended();
String processDefinitionId = processDefinition.getId();
//5.判断
if (suspended) {
//说明是暂停,就可以激活操作
/
可以根据ID的方法,也可以根据key的方法来进行暂停.
建议直接用key进行暂停,
/
/**
* 参数1:当前流程的id<br />
* 参数2:是否激活,true 就是激活<br />
* 参数3:当前时间点,如果没有就可以给个null值.<br />
*/<br />
repositoryService.activateProcessDefinitionById(processDefinitionId, true
, null);
System.out.println(“流程定义:” + processDefinitionId + “激活”);
} else {
//挂起操作
/**参数1: 当前流程的id
* 参数2:是否挂起, true就是挂起<br />
* 参数3:当前时间点,如果没有就给个null值.<br />
*/<br />
repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
System.out.println(“流程定义:” + processDefinitionId + “挂起”);
}
} |
| —- |
(五)单个流程实例的挂起
使用场景:
1.因为请假的人数太多了,我们公司每个月最多只允许100个人请假,但是请假人太多了,会影响公司的正常业务,此时怎么办,就是给超出的流程实例挂起,让他们下个月再请假.
假如1000个人请假,100个人给与通过,剩下的900个人就给与挂起,不通过,将来就做不了后续的操作了,到下个月的时候就重新激活这些已经被挂起的流程.
2.假如张三请假,请假的理由不行,到了部门领导审批的时候,部门领导不同意,也可以把这个流程实例进行挂起.这样就可以禁止这个流程再跑了,因为这个流程继续跑也是无效的了.
流程被挂起之后,将来这个流程修正了,还是可以被激活继续使用的.这是可以的, 如果这个流程实例被挂起了,没有激活,还想让这个流程实例继续执行的话,此时会抛异常, Cannot complete a suspended task (不能完成一个被挂起的任务.)
流程激活影响的表ACT_RU_TASK 和 ACT_RU_EXECUTION
| @Autowired
/执行管理、包括启动、推进、删除流程实例等操作/
private RuntimeService runtimeService;
/**
- processInstanceId是actru_execution表的PROC_INST_ID字段
/
@Test
public void c_指定流程实例的挂起与激活() {
/3.查询流程实例对象- 这里就不能用BusinessKey进行查询了,不然会出现空指针异常,
- 应该使用ProcessInstanceId来进行查询
*
- processInstanceId是actru_execution表的PROC_INST_ID字段
- /
ProcessInstance processInstance = this.runtimeService.createProcessInstanceQuery()
.processInstanceId(“5001”).singleResult();
//4.得到当前流程定义的实例是否都为暂停状态
boolean suspended = processInstance.isSuspended();
String processInstanceId = processInstance.getId();
//5.判断
if (suspended) {
//说明是暂停,就可以激活操作
runtimeService.activateProcessInstanceById(processInstanceId);
System.out.println(“流程:” + processInstanceId + “激活”);
} else {
runtimeService.suspendProcessInstanceById(processInstanceId);
System.out.println(“流程定义:” + processInstanceId + *”挂起”);
}
} | | —- |
- 这里就不能用BusinessKey进行查询了,不然会出现空指针异常,
(六)处理流程实例
@Autowired /任务管理,可以做任务的查询的/ private TaskService taskService; @Test public void c_王五处理自己的任务() { // 最好是先查询一下有没有这个权限,如果有再去处理这个任务 Task task = this.taskService.createTaskQuery() .processDefinitionKey(“holiday”) .taskAssignee(“wangwu”) .singleResult(); if (task != null) { String id = task.getId(); //获取任务id this.taskService.complete(id); } } |
---|
(七)删除流程实例
被删除的流程信息保存在 act_hi_procinst 历史流程实例表
@Test public void c_删除流程实例() { /* 参数1 ,流程实例id (processInstanceId) 参数2 ,删除原因 / runtimeService.deleteProcessInstance(“155001”, “删除原因”);//删除流程 } @Autowired /执行管理、包括启动、推进、删除流程实例等操作/ private RuntimeService runtimeService; |
---|
(八)查询流程实例是否走完
| /**
查询流程状态(判断流程正在执行,还是结束)
/
@Test
public void c_查询流程实例是否走完() {
String processInstanceId =“170001”;
//去正在执行的任务表查询
ProcessInstance processInstance = this.*runtimeService//表示正在执行的流程实例和执行对象.createProcessInstanceQuery()//创建流程实例查询<br />
.processInstanceId(processInstanceId)//使用流程实例ID查询<br />
.singleResult();<br />
if (processInstance == null) {
System.out.println(“该流程实例走完”);
} else {
System.out.println(“该流程实例还没走完”);
}
} | | —- |(九)查询已经完成的流程实例
/历史记录的查询/ @Autowired private HistoryService historyService; @Test public void c_() { //创建历史流程实例,查询对象 HistoricProcessInstanceQuery qingjia01 = historyService.createHistoricProcessInstanceQuery().processDefinitionKey(“qingjia01”) .finished(); List if (list == null || list.size() == 0) { System.err.println(“没有已经完成的历史流程实例”); } else { for (HistoricProcessInstance historicProcessInstance : list) { System.err.println(historicProcessInstance.toString()); } } } |
---|
原生的sql语句:
| select distinct RES.*,
DEF.KEY_ as PROCDEF_KEY,
DEF.NAME_ as PROCDEF_NAME,
DEF.VERSION_ as PROCDEF_VERSION,
DEF.DEPLOYMENTID as DEPLOYMENTID
from ACTHI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF on RES.**PROC_DEF_ID = DEF.ID
WHERE DEF.KEY = ‘qingjia01’
and RES.ENDTIME is not NULL
order by RES.ID asc**; — 查询已经完成的流程实例 DEF.KEY 是bpmn的processId |
| —- |
自己改的IP地址:
| select RES.*,
DEF.KEY_ as PROCDEF_KEY,
DEF.NAME_ as PROCDEF_NAME,
DEF.VERSION_ as PROCDEF_VERSION,
DEF.DEPLOYMENTID as DEPLOYMENTID
from ACTHI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF on RES.**PROC_DEF_ID = DEF.ID
WHERE DEF.KEY = ‘qingjia01’
and RES.ENDTIME is not NULL
order by RES.ENDTIME desc**;
— 查询已经完成的流程实例 DEF.KEY_ 是bpmn的processId 根据完成时间降序 |
| —- |
(十)查询当前用户没有完成的流程实例
| @Test
public void c获取当前用户未完结的流程实例() {
/ 获取未完结的流程实例 /
List
createHistoricProcessInstanceQuery().unfinished()
.processDefinitionKey(“qingjia01”) //流程定义的key
.startedBy(“44”) //启动这个流程的用户id
.orderByProcessInstanceEndTime().desc().list();
System.**_out
} |
| —- |
为了和业务数据进行关联,自己稍微改的sql
查询参数就不写了. 要不太长了,
DEF.KEY 是部署流程定义的key ,START_USER_ID 是哪个用户开启的流程实例就是哪个用户的id
| select * from ACTHI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF
on RES.**PROC_DEF_ID = DEF.ID
left join ask_for_leave afl on afl.BUSINESS_KEY = res.BUSINESSKEY
WHERE DEF.KEY = ‘qingjia01’
and RES.END_TIME IS NULL
and RES.STARTUSER_ID = 44
order by RES.ENDTIME
desc**; |
| —- |
(十一)将任务指派给别人
| /**
任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
/
@Test
public void c_张三给任务交接给李四() {
Task task = this.*taskService**.createTaskQuery()<br />
.processDefinitionKey(**"holiday5"**)<br />
.taskAssignee(**"zhangsan"**) //设置任务的负责人<br />
.singleResult(); //5.判断是否有这个任务<br />
if (task != null) {
//交接任务为lisi ,交接任务就是一个候选人拾取用户的过程
taskService.setAssignee(task.getId(), “lisi”);
System.err.println(“任务交接完成”);
} else {
System.err.println(“张三没有任务”);
}
} | | —- |历史任务
(一)查询当前登录用户未完成的历史任务
这个是没有网关的查询方式 ,有网关的查询方式可能不适用于这个,
这个主要的表就是acthi_taskinst 表的END_TIME字段如果为null的话,说明当前的历史任务是未完成的,然后acthi_taskinst表的PROC_INST_ID(流程实例id)字段可以和别的表做关联去查询出来其它需要的条件,ASSIGNEE_这个字段可以去查询出来当前环节该谁审批了
water_agent是业务表,可以理解为用户表,ask_for_leave表是请假记录表,也是业务表.
| select afl.id as askForLeaveId
, afl.cause
, aht.ASSIGNEE_ as assignee
, wa.agent_name as agentName
from askfor_leave afl
inner join act_hi_taskinst aht
on afl.process_instance_id = aht.**PROC_INST_ID
inner join water_agent wa on wa.agentid = aht.ASSIGNEE
where 1 = 1
and afl.loginid = ‘44’
and aht.END_TIME is null
order by afl.update_time desc**; |
| —- |
(二)查询所有已经完成的流程实例(根据流程定义的key)
查询所有已经完成的流程实例 通过部署流程的key来查询(actre_procdef 表的key字段查询的)
ENDTIME 不为空就说明执行完了,如果为空就说明没有执行完
| select distinct RES.*,
DEF.KEY_ as PROCDEF_KEY,
DEF.NAME_ as PROCDEF_NAME,
DEF.VERSION_ as PROCDEF_VERSION,
DEF.DEPLOYMENTID as DEPLOYMENTID
from ACTHI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF on RES.**PROC_DEF_ID = DEF.ID
WHERE DEF.KEY = ‘qingjia01’
and RES.ENDTIME is not null
order by RES.ENDTIME desc**;
|
| —- |
(三)查询所有未完成的流程实例(根据流程定义的key)
查询所有没有完成的流程实例 通过部署流程的key来查询(actre_procdef 表的key字段查询的)
ENDTIME 为空就说明流程实例没有执行完
| select distinct RES.*,
DEF.KEY_ as PROCDEF_KEY,
DEF.NAME_ as PROCDEF_NAME,
DEF.VERSION_ as PROCDEF_VERSION,
DEF.DEPLOYMENTID as DEPLOYMENTID
from ACTHI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF on RES.**PROC_DEF_ID = DEF.ID
WHERE DEF.KEY = ‘qingjia01’
and RES.ENDTIME is null
order by RES.ENDTIME desc**; |
| —- |
任务分配的方式
流程图的Assignee 是执行人一开始是直接写死的,但是实际情况下,Assignee的执行人不是固定的,很多时候Assignee不是固定的,比如公司1000个员工,到底谁去请假,1000个员工任何人都可以的,所以我的任务分配为指定的某一个人(Assignee写死了),这样的话,显然是不合适的.
任务分配方式:
1. 固定的方式分配
2. UEL表达式方式分配(UEL 是 java EE6规范的一部分,UEL(Unified Expression Language)即统一表达式语言,activiti支持两个 UEL表达式:UEL-value 和 UEL-method。)
3. UEL-method方式
UEL方式
${assignee} 相当于一个Key ,在流程实例启动的时候需要给我们的变量去设置相应的值(通过runtimeService去设置),设置的值可以是一个Map集合,需要key对应上才行.
或者是
这个Assignee具体是放什么值,取决于这个${assignee}真正的值是什么,比如说${assignee}这个值是赵六,这个变量的值是什么时候给呢?后面是会用程序代码来去指定分配指定的,而程序代码中的值又可能来自于我们用户通过前端界面去提供的,比如说将来谁登录,那么流程图中对应的部门经理就是谁.比如说 公司有五个部门,那么每个部门都有自己的部门经理等等,这个时候Assignee就不应该是固定的值
UEL-method方式
通过调用的一些get方法来看到效果
这个是通过对象打点的方式,这样就会直接调用对象的get()方法,这样非常类似于Java里面的JSP的EL表达式.
userBean 是 spring容器中的一个 bean,表示调用该 bean 的 getUserId()方法。
UEL-method 与UEL-value 结合
再比如:
${ldapService.findManagerForEmployee(emp)}
ldapService 是 spring容器的一个bean,findManagerForEmployee是该 bean 的一个方法,emp 是 activiti
流程变量,emp作为参数传到 ldapService.findManagerForEmployee 方法中。
其它
表达式支持解析基础类型、bean、list、array 和 map,也可作为条件判断。
如下:
${order.price > 100 && order.price < 250}
(二)个人任务的查询
查询任务负责人的待办任务
| @Autowired
/任务管理,可以做任务的查询的/
private TaskService taskService;
/**
- 查询的话主要作用于ACTRU_TASK 这张表里面
/
@Test
*public void c个人任务的查询() {
// 流程定义key
String processDefinitionKey = “holiday”;
// 任务负责人
String assignee = “zhangsan”;
Listlist = taskService.createTaskQuery()//
if (list != null && list.size() != 0) {.processDefinitionKey(processDefinitionKey)//<br />
.includeProcessVariables().taskAssignee(assignee).list();<br />
for (Task task : list) {
}System.**_out_**.println(**"----------------------------"**);<br />
System.**_out_**.println(**"流程实例id:" **+ task.getProcessInstanceId());//流程实例id:5001<br />
System.**_out_**.println(**"任务id:" **+ task.getId());//任务id:5004<br />
System.**_out_**.println(**"任务负责人:" **+ task.getAssignee());//任务负责人:zhangsan<br />
System.**_out_**.println(**"任务名称:" **+ task.getName());//任务名称:填写请假申请单<br />
}
} | | —- |
(三)通过 businessKey 查询业务数据
需求:
在 activiti实际应用时,查询待办任务可能要显示出业务系统的一些相关信息,比如:查询待审批请假单任务列表需要将请假单的日期、请假天数等信息显示出来,请假天数等信息在业务系统中存在,而并没有在 activiti数据库中存在,所以是无法通过 activiti的 api查询到请假天数等信息。
实现:
在查询待办任务时,通过 businessKey(业务标识 )关联查询业务系统的请假单表,查询出请假天数等信息。
|
@Autowired
/执行管理、包括启动、推进、删除流程实例等操作/
private RuntimeService runtimeService;
@Autowired
/任务管理,可以做任务的查询的/
private TaskService taskService;
/**
- 通过 businessKey 查询业务数据
- 主要查询的表:
ACTRU_EXECUTION
/
@Test
*public void c通过businessKey查询业务数据() {
// 流程定义key
String processDefinitionKey = “holiday”;
// 任务负责人
String assignee = “zhangsan”;
// 创建TaskService
Listlist = taskService.createTaskQuery()// .processDefinitionKey(processDefinitionKey)//<br />
.includeProcessVariables().taskAssignee(assignee).list();<br />
if (list != null && list.size() != 0) {
for (Task task : list) {System.**_out_**.println(**"----------------------------"**);<br />
//获取流程实例id<br />
System.**_out_**.println(**"流程实例id:" **+ task.getProcessInstanceId());//流程实例id:5001<br />
//通过流程实例id获取到流程实例对象,<br />
ProcessInstance processInstance = **this**.**runtimeService**.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId())<br />
.singleResult();<br />
//通过流程实例对象获取到了businessKey<br />
String businessKey = processInstance.getBusinessKey();<br />
//获取到了businessKey的话,就可以通过这个BusinessKey来查询数据库里面的表数据了<br />
System.**_out_**.println(**"获取到了businessKey,就可以通过这个Key来关联查询业务表里面的数据了" **+ businessKey); //1001<br />
}
}
} | | —- |(四)完成任务
| @Autowired
/任务管理,可以做任务的查询的/
private TaskService taskService;
/**注意:在实际应用中,完成任务前需要校验任务的负责人是否具有该任务的办理权限。
/
@Test
public void c_3任务的完成() {
// 流程定义key
String processDefinitionKey = “holiday”;
// 任务负责人
String assignee = “zhangsan”;
// 创建TaskService
Listlist = *taskService .createTaskQuery()//.processDefinitionKey(processDefinitionKey)//<br />
.includeProcessVariables().taskAssignee(assignee).list();<br />
//查是否有这个任务
if (list != null && list.size() != 0) {
for (Task task : list) {//获取任务id<br />
String id = task.getId();<br />
//完成任务<br />
**this**.**taskService**.complete(id);<br />
}
}
} | | —- |流程变量
流程变量在多分支情况下是很有必要的.
流程变量在 activiti中是一个非常重要的角色,流程运转有时需要靠流程变量,业务系统和 activiti结合时少不了流程变量,流程变量就是 activiti在管理工作流时根据管理需要而设置的变量。
比如在请假流程流转时如果请假天数大于 3 天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。
注意:虽然流程变量中可以存储业务数据可以通过 activiti 的 的 api 查询流程变量从而实现 查询业务数据,但是不建议这样使用,因为业务数据查询由业务系统负责,activiti执行需要而创建(比如说业务系统的请假单详情数据,这个就不要存到流程变量里面了)。
简单的流程的话会是这样一条线的
但是在复杂的情况下流程就会有多个分支,比如如果请假少于三天就由人事存档,否则就由必须总经理审批,然后人事存档.这就是一个流程分支.
在流程运行中如何知道请假是小于等于三天还是大于三天,就需要在流程运行的过程中加入流程变量了来保存请假天数,假如说现在的流程变量是holidayNum 在执行这个流程的时候给这个holidayNum的值设置成了5,这样流程变量就会有具体的值了,此时流程就会根据流程变量来考虑走哪个节点分支了.
(一)流程变量的数据类型
注意:如果将 pojo 存储到流程变量中,必须实现序列化接口 serializable,为了防止由于新增字段无法反序列化,需要生成 serialVersionUID.
(二)流程变量作用域
- 流程实例(processInstance)
范围最大,可以称为global变量,global变量中变量名不允许重复,设置相同名称的变量,后设置的值会覆盖前面设置的变量值
2. 任务(task)
local变量的范围只是作用于某一个任务,local变量由于在不同的任务或者不同的执行实例中,作用域互相不影响,变量名可以相同没有影响,local变量名可以和global变量名相同 ,没有影响.
3. 执行实例(execution)
流程变量的作用域默认是一个流程实例(processInstance ,就是这个流程变量在流程启动到流程结束的过程中都是起作用的,流程实例一旦跑完了,下次又起来一个新的实例的话,上一个实例所对应的变量,在当前这个实例中是不起作用的).
(三)流程变量使用方法
第一步:设置流程变量
第二步:通过 UEL表达式使用流程变量
1> 可以在 assignee 处设置 UEL表达式,表达式的值为任务的负责人
比如:${assignee},assignee 就是一个流程变量名称Activiti获取 UEL 表达式的值 ,即流程变量 assignee 的值 ,将 assignee 的值作为任务的负责人进行任务分配
2> 可以在连线上设置 UEL表达式,决定流程走向
比如:${price>=10000}和${price<10000}: price 就是一个流程变量名称,uel 表达式结果类型为布尔类型
如果 UEL表达式是 true,要决定 流程执行走向。
在连接线上设置的话就需要使用Condition上面去写uel表达式了.
要想达到分支效果的话,Condition必须要有具体的取值的
也可以用对象.属性的取值
下面是holiday实体类里面的num属性取值.
1.流程启动之后就开始设置流程变量
| 案例:ZJJ_Activiti_2020/01/07_11:45:03_kbxnm | | —- |
num 值是流程连接线定义的值
assignee 是流程节点定义的值
| /**
启动流程实例
/
@Test
public void c_启动流程实例() {
// 定义流程变量
Mapvariables = new HashMap 参数1 是流程定义的key();
// 如果num 大于等于3的话,张三李四王五赵六 四个人依次审批,
// 如果num小于3的话,王五不需要审批,张三李四审批完了,直接赵六去存档就可以了.
variables.put(“num”, 1);
variables.put(“assignee1”, “zhangsan”); //assignee变量
variables.put(“assignee2”, “lisi”); //assignee变量
variables.put(“assignee3”, “wangwu”); //assignee变量
variables.put(“assignee4”, “zhaoliu”); //assignee变量
/**
- 参数二就是设置的流程变量
/
ProcessInstance holiday4 = this.runtimeService.startProcessInstanceByKey(“holiday4”, variables);
System.out.println(*”流 程 实 例 id:” + holiday4.getProcessInstanceId());
} | | —- |
2.任务办理同时设置流程变量
- 参数二就是设置的流程变量
案例:ZJJ_Activiti_2020/01/07_11:45:25_k10gf |
---|
什么是任务办理时设置流程变量.
一个流程有填写请假单申请,部门审批申请,总经理审批,人事经理存档,我可以在填写请假单申请,或者部门审批申请之后给流程变量完成之后进行赋值操作,这就是任务办理时设置流程变量.
num 是流程图连接线设置的变量占位符
| /**
- 李四完成自己的任务
同时设置流程变量
/
@Test
public void c_李四完成自己的任务() {
Task task = this.taskService.createTaskQuery().processDefinitionKey(“holiday4”).taskAssignee(“lisi”).singleResult();
if (task != null) {
HashMapvariables = new HashMap<>();
// 如果num 大于等于3的话,张三李四王五赵六 四个人依次审批,
// 如果num小于3的话,王五不需要审批,张三李四审批完了,直接赵六去存档就可以了.
variables.put(“num”, 5);
/* 参数二是设置流程变量
*/<br />
this.taskService.complete(task.getId(), variables);
System.err.println(“李四的任务完成了. “);
} else {
System.err.println(“李四没有任务…”);
}
} | | —- |3.通过当前任务的id设置流程变量
| 案例:ZJJ_Activiti_2020/01/07_11:46:06_acg9p | | —- |
通过当前任务id设置流程变量,
需要注意,当前任务必须还在进行中,任务ID必须要在act_ru_task 表里面存在,如果当前任务已经结束,你再设置值就会报错了, 和任务办理时设置流程变量是差不多一样,只是绑定流程变量的时间节点不一样而已
num是流程图连接线上设置的变量占位符
| @Test
public void c根据任务id设置流程变量() {
Task task = this.taskService.createTaskQuery().processDefinitionKey(“holiday4”).taskAssignee(“zhangsan”).singleResult();
if (task != null) {
String id = task.getId();
// 根据任务id设置流程变量的值
this.taskService.setVariable(id, “num”, 2);
System.**_err.println(“已经根据任务id设置完了流程变量了”);
} else {
System.err.println(“张三没有任务”**);
}
} |
| —- |
4.通过当前流程实例id设置流程变量
案例:ZJJ_Activiti_2020/01/07_11:45:39_t2n7j |
---|
通过当前流程实例id设置流程变量,
需要注意,当前流程实例必须是未执行完成的,如果当前流程实例已经执行完了的话,你再设置就无效了.
| /**
是在这个ACTRU_VARIABLE 表设置
/
@Test
*public void c通过实例id来设置流程变量() {
//4.通过实例id,来设置流程变量
//第一个参数:流程实例的id
//第二个参数:流程变量名
//第三个变量:流程变量名,所对应的值
this.runtimeService.setVariable(“72501”, “num”, 1);
} | | —- |(四)设置local流程变量
1.任务办理时候设置local流程变量
local流程变量只是作用于当前的任务的(当前节点,比如说部门领导审批节点),我们局部变量它的应用场景将来是有局限性的,它只是当前这个任务结束之前,一旦当前的任务结束之后,这个local流程变量就会失效,可以通过查询历史任务查询。
说明:
设置作用域为任务的 local变量,每个任务可以设置同名的变量,互不影响,因为我们的local变量是当前节点的,对下面的节点是不可见的.
|
// 办理任务时设置local流程变量
@Test
public void completTask() {
//任务id
String taskId = “”;
TaskService taskService = processEngine.getTaskService();
// 定义流程变量
Map
Holiday holiday = new Holiday();
holiday.setNum(3);
// 定义流程变量
Map
//变量名是holiday,变量值是holiday对象
variables.put(“holiday”, holiday);
// 设置local变量,作用域为该任务
taskService.setVariablesLocal(tasked, variables);
taskService.complete(taskId);
} |
| —- |
2.通过当前任务的id设置local流程变量
注意:
任务 id 必须是当前待办任务 id,act_ru_task 中存在。
| @Test
public void setLocalVariableByTaskId() {
//当前待办任务id
String taskId = “1404”;
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(3);
//通过任务设置流程变量
taskService.setVariableLocal(taskId, “holiday”, holiday);
//一次设置多个值
//taskService.setVariablesLocal(taskId, variables)
} |
| —- |
3.获取local流程变量
在部门经理审核、总经理审核、人事经理审核时设置 local 变量,可通过 historyService 查询每个历史任务时将流程变量的值也查询出来。
| // 创建历史任务查询对象
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService .createHistoricTaskInstanceQuery();
// 查询结果包括 local变量
historicTaskInstanceQuery.includeTaskLocalVariables();
for( HistoricTaskInstance historicTaskInstance :list) {
System.out.println(“==============================”);
System.out.println(“ 任 务 id : “ + historicTaskInstance.getId());
System.out.println(“ 任 务 名 称 : “ + historicTaskInstance.getName());
System.out.println(“ 任 务 负 责 人 : “ + historicTaskInstance.getAssignee());
System.out.println(“ 任 务 local 变 量 : “ + historicTaskInstance.getTaskLocalVariables());
} |
| —- |
(五)注意事项
1、 如果 UEL表达式中流程变量名不存在则报错。
2、 如果 UEL表达式中流程变量值为空NULL,流程不按 UEL 表达式去执行,而流程结束 。
3、 如果 UEL表达式都不符合条件,流程结束
4、 如果连线不设置条件,会走 flow 序号小的那条线
(六)流程变量操作的时候表的变化
设置流程变量会在当前执行流程变量表插入记录,同时也会在历史流程变量表也插入记录。
SELECT FROM actru_variable #当前流程变量表
记录当前运行流程实例可使用的流程变量,包括 global和 local变量
Id:主键
Type:变量类型
Name:变量名称
Executionid:所属流程实例执行 id,global和 local变量都存储
Procinst_id:所属流程实例 id,global和 local变量都存储
Taskid:所属任务 id,local变量存储
Bytearray:serializable 类型变量存储对应act_ge_bytearray 表的 id
Double:double 类型变量值
Long:long类型变量值
Text:text 类型变量值
SELECT FROM act_hi_varinst #历史流程变量表
记录所有已创建的流程变量,包括 global和 local变量
字段意义参考当前流程变量表.
组任务
在以前的流程图里面某节点设置任务执行人的时候 Assignee只能设置一个值, 如果说将来真正不知道具体的任务是由谁来做,比如说我们公司的总经理其实有三个人,其中一个是正的总经理,然后有两个是副总结里,那我们的请假流程走流程的时候将来走到总经理这个环节的时候,可能正总经理做审核是可以的,我们副总经理去完成这个工作也是可以的,这个时候相当于我这个节点的任务有三个人都是可以去完成这个任务的,这个时候就会用到组任务了.
设置候选人,多个候选人之间用逗号分隔开
(一)组任务办理流程
第一步:查询组任务
指定候选人,查询该候选人当前的待办任务。
候选人不能直接办理任务(假如说待办任务有三个人,三个人都同时办理这个任务,那么这个任务就乱了)。
第二步:拾取(claim)任务
虽然作为一个候选人组,我们组的人都能看到任务,这个组里面必须有一个人给这个任务拾取过来(谁拾取到任务谁就能处理这个任务了)
*如果拾取后不想办理该任务?
假如说拾取了这个任务,发现这个任务不是那么好批准,就可以给拾取的个人任务归还到组里边,将个人任务变成了组任务,那么这个组的别的候选人又可以拾取任务了.
第三步:查询个人任务
拾取完任务之后,就可以像查询方式同个人任务部分,根据 assignee 查询用户负责的个人任务。
第四步:办理个人任务
(二)数据表操作说明
我们做任务的拾取,交接和归还就是给assignee赋值的过程
SELECT FROM act_ru_task #任务执行表,记录当前执行的任务,由于该任务当前是组任务,所有assignee 为空,当拾取任务后该字段就是拾取用户的 id
SELECT FROM act_ru_identitylink #任务参与者,记录当前参考任务用户或组,当前任务如果设置了候选人,会向该表插入候选人记录,有几个候选就插入几个
于 act_ru_identitylink 对应的还有一张历史表 act_hi_identitylink,向 act_ru_identitylink 插入记录的同时也会向历史表插入记录。任务完成
(三)查询组任务里面候选用户的任务
zhangsan是 Candidate Users 那里配置的组任务
| @Test
public void c查询候选用户的组任务() {
List
.createTaskQuery()//返回一个新的动态查询任务
.taskCandidateUser(“zhangsan”) //设置候选用户
.list();
for (Task task : zhangsan) {
System.**_out
System.out.println(task.getId());
System.out.println(task.getName());
System.out**.println(task.getAssignee());//这里assignee为null,说明当前的zhangsan只是一个候选人,并不是任务的执行人
}
} |
| —- |
(四)拾取任务
zhangsan是 Candidate Users 那里配置的组任务 ,拾取完任务之后再用zhangsan去查询的话,assignee就有值了,如果没拾取的话去查询assignee是没值的,因为此时张三只是组任务里面的备用候选人,并不是这个任务的主负责人.
| /**
- 测试zhangsan用户,来拾取组任务
- 抽取任务的过程就是将候选用户转化为真正任务的负责人(让任务的assignee有值)
/
@Test
public void c_张三拾取用户() {
Task task = this.*taskService
if (task != null) { // 如果不为空,就代码当前有组任务**.createTaskQuery()//返回一个新的动态查询任务<br />
.processDefinitionKey(**"holiday5"**) //仅选择具有给定流程定义键的流程实例一部分的任务<br />
.taskCandidateUser(**"zhangsan"**) //设置候选用户<br />
.singleResult();<br />
/**
taskService.claim(task.getId(), “zhangsan”);* 参数1 : 任务ID<br />
* 参数2: 具体的候选用户名<br />
* 注意,只是拾取了,但是任务依然还是需要审核的这一环节<br />
*/<br />
System.out.println(“张三任务拾取完毕!”);
} else {
System.err.println(“张三没有这个任务”);
}
} | | —- |
| @Test
public void c张三查询自己的任务() {
List
.processDefinitionKey(“holiday5”)
.taskAssignee(“zhangsan”)
.list();
for (Task task : list) {
System.**_out
System.out.println(task.getId());
System.out.println(task.getName());
System.out**.println(task.getAssignee());//获取任务的执行人,这里再打印assignee就有值了,因为已经拾取了任务
}
} |
| —- |
(五)任务交接
任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
任务交接就是设置assignee的过程, 下面的李四再去查询自己任务的话就有值了.
| @Test
public void c张三给任务交接给李四() {
Task task = this.taskService
.createTaskQuery()
.processDefinitionKey(“holiday5”)
.taskAssignee(“zhangsan”) //设置任务的负责人
.singleResult(); //5.判断是否有这个任务
if (task != null) {
//交接任务为lisi ,交接任务就是一个候选人拾取用户的过程
taskService.setAssignee(task.getId(), “lisi”);
System.**_err.println(“任务交接完成”);
} else {
System.err.println(“张三没有任务”**);
}
} |
| —- |
(六)归还任务组
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
说明:建议归还任务前校验该用户是否是该任务的负责人
也可以通过 setAssignee 方法将任务委托给其它用户负责,注意被委托的用户可以不是候选人(建议不要这样使用)
// 归还组任务,由个人任务变为组任务,还可以进行任务交接 @Test public void setAssigneeToGroupTask() { // 查询任务使用TaskService TaskService taskService = processEngine.getTaskService(); // 当前待办任务 String taskId = “6004”; // 任务负责人 String userId = “zhangsan2”; // 校验userId是否是taskId的负责人,如果是负责人才可以归还组任务 Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee(userId).singleResult(); if (task != null) { // 如果设置为null,归还组任务,该 任务没有负责人 taskService.setAssignee(taskId, null); } } |
---|
网关
主要有四种网关:
1. 并行网关:
2. 排他网关:只能有一个分支流程会走
3. 包含网关:
4. 事件网关(事件网关用的不是很多)
假如下面的num没有赋值成功,为null, null不大于3也不小于3,这样的话就会出现问题了.解决办法就是使用网关,即使两个都出现了问题,也能默认走一个流程.
网关能保证流程更合理的去执行
(一)排他网关
排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为 true,如果为 true则执行该分支,注意,排他网关只会选择一个为 true 的分支执行。(即使有两个分支条件都为 true,排他网关也会只选择一条分支去执行)
假如说后面代码写错了上面的流程 num>3 下面流程num>=1 这样的话两个条件都为true(后面两个节点都会执行),此时act_ru_task表里面就会出现两条记录了,总经理审批和人事经理审批这两个都会出现了,这样的话流程就走乱了.
这就是为什么去使用排他网关的原因.
使用了排他网关,即使两个都为true了,也可以只能走一个. 需要注意的是,如果两个分支条件都为true的话,排他网关默认会走id最小的值.
如果排他网关两个条件都是false的话,就会抛出异常:
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway’exclusivegateway1’ could be selected for continuing the processa
org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivit
yBehavior.java:85)
(二)并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork 分支:
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join 汇聚:
所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
假如说人事经理存档之后, 需要财务会计和行政考勤两个节点同时并行进行(不一定是两个节点并行执行,也可以分支为多个节点,比如三个分支,五个节点分支都可以的), 这时候就使用到了并行网关, 然后在财务会计和行政考勤都没处理完之前这个流程是无法进行下一步的,谁先做完谁就等待别的节点,直到节点完成之后,才会进入下一环节, 这就是并行网关的作用.
并行网关与其它网关的区别,并行网关不会解析连接线上的属性,即使解析线中定义了条件,也会被忽略.
1.并行网关影响到的数据库
当执行到并行网关数据库跟踪如下:
当前任务表:SELECT FROM act_ru_task #当前任务表
上图中:有两个(多个)任务当前执行。
通过流程实例执行表:SELECT FROM actru_execution #流程实例的执行表
上图中,说明当前流程实例有多个分支(两个)在运行。
对并行任务的执行:
并行任务执行不分前后,由任务的负责人去执行即可。
当完成并任务中一个任务后:
已完成的任务在当前任务表 act_ru_task已被删除。
在流程实例执行表:SELECT FROM act_ru_execution 有中多个分支存在且有并行网关的汇聚结点。
有并行网关的汇聚结点:说明有一个分支已经到汇聚,等待其它的分支到达。
当所有分支任务都完成,都到达汇聚结点后:
流程实例执行表:SELECT FROM act_ru_execution,执行流程实例不存在,说明流程执行结束。
总结:所有分支到达汇聚结点,并行网关执行完成。
(三)包含网关
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流(连接线)上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支:
所有外出顺序流的条件都会被解析,结果为true 的顺序流会以并行方式继续执行, 会为每个顺序流
创建一个分支。
汇聚:
所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程 token 的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
1.包含网关和其它网关区别
包含网关和排他网关区别
排他网关只能选择某一个分支去执行,而包含网关可以选择多个分支去执行.
包含网关和并行网关区别
包含网关所有的分支条件都会被解析,结果为true的连接线就会走下面的,如果连接线为false的话,就不会走下面的分支.
也就是说可以选择五个下面分支,假如五个分支有三个分支的连接线为true的话,那么就走这三个节点,两个分支连接线为false的话就不走这两个节点.
当这三个并行执行完汇聚到包含网关的结束汇聚点之后,这个包含网关就执行结束了.
2.包含网关影响的表
如果包含网关设置的条件中,流程变量不存在,报错;
org.activiti.engine.ActivitiException: Unknown property used in expression: ${userType==’1’ ||
userType==’2’}
需要在流程启动时设置流程变量 userType
当执行到包含网关:
流程实例执行表:SELECT * FROM actru_execution
第一条记录:包含网关分支。
后两条记录:两个分支:常规项体检,抽血化验
当前任务表:ACT_RU_TASK
上图中,常规项体检,抽血化验都是当前的任务,在并行执行。
如果有一个分支执行到汇聚:
先走到汇聚结点的分支,要等待其它分支走到汇聚。
等所有分支走到汇聚,包含网关就执行完成。
包含网关执行完成,分支和汇聚就从 act_ru_execution 删除。
小结:在分支时,需要判断条件, 符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。
监听器分配
在bpmn编辑器那里左侧有个 Task Listeners 添加任务监听器
Event 是选取触发事件的条件:
任务监听器是发生对应的任务相关事件时执行自定义 java 逻辑 或表达式。任务相当事件包括:
Create:任务创建后触发
Assignment:任务分配后触发
Delete:任务完成后触发
All:所有事件发生都触发
如果是分配Assignee的话就需要Create的任务事件了(在任务创建之后触发),不过这种分配Assignee的方式不太合适.
Type是指定具体的Class:
当我们的监听器一旦触发这个工作的时候,我们这个时候就需要调用一个类
Class 是指定触发触发监听器的时候执行的类:
这时候就需要指定你编写的class ,然后编写的class类也是要有要求的,作为监听器的类是必须要实现一个TaskListener的接口
实现完了就需要重写TaskListener接口的notify的方法.
| /**
Created by Administrator on 2018/11/10.
/
public class MyTaskListener implements TaskListener {
@Override
*public void notify(DelegateTask delegateTask) {delegateTask.setAssignee(**"zhangsan"**);<br />
}
} | | —- |将bpmn保存为图片
bpmn本质是xml,需要转成图片才能给其它人查看,不然的话,别人没有IDEA的 actiBPM 插件的话,客户就看不了图片了.
解决办法,将bpmn文件重新复制一份并且后缀名改成xml文件的,然后:
然后:
就可以保存到你指定的位置了.
中文乱码解决方案
根据自己所安装的版本来决定,我使用的是 64 位的 idea,所以在 idea64.exe.vmoptions 文件的最后
一行追加一条命令: -Dfile.encoding=UTF-8
如下所示:
一定注意,不要有空格,否则重启 IDEA 时会打不开,然后 重启 IDEA,把原来的 png 图片删掉,再重新生成,即可解决乱码问题
ActivitiDesigner的actiBPM
安装
Settings - Plugins 里面去安装 actiBPM 插件. 安装的时候可能会失败,因为是国外的插件,就需要百度找别的方式了.
UserTask
其它
(一)什么是工作流
https://baike.baidu.com/item/OA%E7%B3%BB%E7%BB%9F/10677850
工作流早期是企业OA,CRM,流程审批,现在工作中大量应用到电商购物,金融出行等等系统,工作流引擎驱动业务发展在互联网公司快速盛行
Activiti 6.0技术是Java中高级工程师的进阶利器,掌握工作流引擎技术可以提升技术架构和业务建模能力,技术架构能力和业务建模能力是高级工程师乃至架构师核心竞争力,最直接的就是工作流引擎技术更容易通过大型互联网公司的面试并获得高薪.
1.工作流:工作的一个流程,事物发展的一个业务过程
流程:
请假流程:员工申请——部门经理——-总经理——-人事存档
传统方式下? 请假条的传递来实现
无纸化办公? 线上申请——线上审批——一条请假记录
在计算机的帮助下,能够实现流程的自动化控制,就称为工作流.
(二)为什么需要工作流
- 产品需求遗漏,开发上线后需求经常改
2. 业务代码如此复杂,老大说DDL就在本周五
3. 时间流逝,文档缺失,PM同学让RD刨代码梳理流程
假如说有个请假流程,程序员用硬编码来实现:
status 为0
员工: status 0 未提交 1提交
部门经理: status 2同意 3 不同意
主管: status 4同意 5不同意
人事存档: status 6同意 7不同意
流程存档的时候假如存了个 status为4 ,后来需求发生变化了,主管被取消了,此时status 4 和 5 就不能用了… 这样程序就不能使用了,只能去修改程序.
此时如果用了Activiti就可以实现业务流程变化之后,程序代码不需要发生改变,这就是我们的Activiti的好处之一.
(三)使用工作流的好处
1. 可以快速响应,灵活调整线上流程
2. 业务和开发基于流程模型去沟通,基于业务建模快速部署(可以减少一些沟通的障碍)
3. 流程可视化,方便查看流程的运行进展
(四)工作流技术选型
两种底层技术实现是有很大的不同的,jBPM数据持久化是Hibernate,Activiti使用的是Mybatis.
(五)工作流实现原理分析
在没有专门的工作流引擎之前,我们之前为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。这样不用角色的用户,通过状态字段的取值来决定记录是否显示。
针对有权限可以查看的记录,当前用户根据自己的角色来决定审批是否合格的操作。如果合格将状态字段设置一个值,来代表合格;当然如果不合格也需要设置一个值来代表不合格的情况。
这是一种最为原始的方式。通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。
那么有没有专业的方式来实现工作流的管理呢?并且可以做到业务流程变化之后,我们的程序可以不用改变,如果可以实现这样的效果,那么我们的业务系统的适应能力就得到了极大提升。
如何可以做到我们在业务流程发生变更后,我们的业务系统代码可以不发生改变?此时我们就来分析一下原理。
具体分析过程如下图所示:
内部核心机制实现原理分析
1. 先将流程图画好
张三登录系统之后
2. 将流程图每个节点的数据读取并放入我们的MySQL的数据表中去
3. 从表中读取第一条记录,并且处理它,处理完毕之后就删除这行表记录.然还开始读取第二条记录,读取完毕就删掉第二条记录,当所有的数据都处理完毕之后,表里面数据都删除掉了.
当李四进来之后,还重复上面2 3 步骤.处理完毕就删掉了.
最后经理审批进来之后还重复上面 2 3 步骤,处理完毕就删掉了.
实现这个自动化的过程
1. 原物流程图要规范化,要遵守一套标准(就是bpmn格式的流程图)
2. 这个业务流程图本质上是一个xml文件,这样可以存入所要的数据
3. 读取业务流程图的过程就是解析xml文件的过程
4. 读取一个业务流程图中的节点就相当于是解析一个xml结构,进一步将数据插入到MySQL的表中,形成一条记录.
5. 将所有的节点都读取并存入MySQL表中.
6. 后面只要读取MySQL表中的记录就可以了,读一条记录就相当于读一个节点.这个节点信息就是一个一个存入到我们MySQL一行一行的,在数据库中读取一行就相当于读取一个节点的数据,如果我能做到不断的读取节点,就相当于流程在不断的推进,所以说业务流程的推进后面就转换为读表中的数据,并且处理数据.处理结束时,这一行数据就可以删除掉了.这样就相当于这块儿工作干完了.(删除MySQL一行一行的数据就相当于工作干完的过程.)
(六)使用场景
1.关键业务流程:订单、报价处理、合同审核、客户电话处理、供应链管理等
2. 行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请、购买申请、日报,周报等凡是原来手工流转处理的行政表单。
3. 人事管理类:员工培训安排、绩效考评、职位变动处理、员工档案信息管理等。
4. 财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等。
5. 客户服务类:客户信息管理、客户投诉、请求处理、售后服务管理等。
6. 特殊服务类:ISO系列对应流程、质量管理对应流程、产品数据信息管理、贸易公司报关处理、物流公司货物跟踪处理等各种通过表单逐步手工流转完成的任务均可应用工作流软件自动规范地实施。
Activiti服务架构图
Activiti.cfg.xml 是配置文件
ProcessEngineConfiguration 流程引擎的配置类
ProcessEngine 可以得到各种各样的service
API介绍
(一)ProcessEnigneConfiguration
ProcessEnigneConfiguration 可以读取activiti.cfg.xml文件获取配置信息,可以通过配置对象构建出流程引擎ProcessEnigne(流程引擎对象),通过流程引擎对象可以获取到工作流引擎的一些核心API(RepositoryService 和RuntimeService和XXXService等等)
(二)ProcessEnigne 流程引擎对象
这个是最重要的流程引擎对象,可以通过ProcessEnigne获取到一些其它的服务,并且可以生产Activiti的工作环境(25张表)
(三)RepositoryService
activiti 的资源管理类
负责对流程定义的文件进行管理,主要操作静态文件,比如流程文件中的xml和流程图片,通过RepositoryService部署流程对象会涉及到两个实体对象,一个是部署对象一个是资源对象,部署对象和资源对象是一对多关系,一次部署可以包含多个资源文件.常见的一次流程部署就是把流程的xml和编译图片部署到数据库里面.
如果我想查找我们的部署信息可以用这个Service
是 activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此 service 将流程定义文件的内容部署到计算机。
除了部署流程定义以外还可以:
查询引擎中的发布包和流程定义。
暂停或激活发布包,对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。
获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。
获得流程定义的 pojo 版本, 可以用来通过 java 解析流程,而不必通过 xml。
(四)RuntimeService
activiti 的流程运行管理类
用于对流程进行控制的API,可以用于启动一个流程实例,针对制定的流程实例进行暂停和挂起和继续,即或执行,可以查询正在运行中的流程实例和执行对象,可以对流程图中的上下文进行设置和获取
如果我想获取整个运行状态,我可以用到这个Service.
(五)TaskService
activiti 的任务管理类
主要是管理运行的Task,也就是人工任务,可以对人工任务进行增删改查操作,也可以对用户任务设置指定的操作权限,指定的用户和用户组可以同时对用户的上下文变量进行获取.
如果我想查询某个用户的任务.比如张三目前要做的任务是什么,我可以用这个TaskService这个类.
(六)IdentityService
是对用户和用户组的管理,创建用户和用户组,并维护用户组的关系
(七)FormService
可以解析流程定义中的表单,对表单输入类型和格式进行渲染.
(八)HistoryService
activiti 的历史管理类
主要提供对运行结束的流程实例的查询功能,提供了基于流程纬度和用户任务纬度删除操作,方便我们统计任务的流程和执行过程的变化
如果我需要查找历史信息的话,可以用这个Service.
| HistoryService historyService = processEngine.getHistoryService();
/historyService相当于操作acthi的表们的接口,比如查询历史信息等等,
这个Service相当于把acthi这些的表的的dao持久层实体类等等都帮我们封装好了./ |
| —- |
(九)ManagementService
对流程引擎基础的管理,使用比较少,还提供了对定制任务job的管理
(十)DynamicBpmService
相对侵入性比较高的功能,可以动态的对定义模型做修改.一般不推荐使用.
(十一)ServiceTask
不需要人工参与,但是需要我们系统自动完成的一些操作.,ServiceTask使用场景,比如说库存校验,一个库存在接口. 或者是风控的接口,比如用户的ip和手机号等等去校验这个用户是否有风险.
流程定义查询与删除
查看流程定义的名称或者id或者是目前的版本等等,就可以查流程定义信息来直接的解决这个问题了.正常开发,这些数据可能会直接写成接口交给前端展示到页面上去.
查询
| @Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService;
@Test
public void a1流程定义的查询() {
//3.得到ProcessDefinitionQuery对象,可以认为它就是一个查询器
ProcessDefinitionQuery processDefinitionQuery = this.repositoryService.createProcessDefinitionQuery();
//4.设置条件,并查询出当前的所有流程定义 查询条件:流程定义的key=holiday
/假如说公司有好几十套流程,有调岗的流程,请假的流程等等,这样的话就有很多流程,就需要条件查询了.
这个可能会展示到网页上去是比较好的.
/
//orderByProcessDefinitionVersion() 设置排序方式,根据流程定义的版本号进行排序
List
//当然还可以根据多个进行排序
.orderByProcessDefinitionVersion()
.desc().list();
if (list != null) {
//5.输出流程定义信息
for (ProcessDefinition processDefinition : list) {
System.**_out
System.out.println(“流程定义名称:” + processDefinition.getName());
System.out.println(“流程定义的Key:” + processDefinition.getKey());
System.out.println(“流程定义的版本号:” + processDefinition.getVersion());
System.out.println(“流程部署的ID:” + processDefinition.getDeploymentId());
// 流程定义ID:holiday:1:4
// 流程定义名称:请假流程
// 流程定义的Key:holiday
// 流程定义的版本号:1
// 流程部署的ID:1
}
} else {
System.err.println(“流程查询为空”**);
}
} |
| —- |
删除
|
@Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService;
/**
* 删除已经部署的流程定义
* <p>
* 背后影响的表:<br />
* act_ge_bytearray<br />
* act_re_deployment<br />
* act_re_procdef<br />
* <p>
* 注意事项:
* 1.当我们正在执行的这一套流程没有完全审批结束的时候,此时如果要删除流程定义信息就会失败
* 2.如果公司层面要强制删除,可以使用repositoryService.deleteDeployment("1",true);<br />
* //参数true代表级联删除,此时就会先删除没有完成的流程结点,最后就可以删除流程定义信息 false的值代表不级联
* //级联删除是你没有跑完的流程和所有的关联记录都会被一起删除掉
*<br />
* <p>
* 当然删除和查询可以在一起去完成使用
*/<br />
@Test
public void c_2删除无用的流程() {
//3.执行删除流程定义 参数代表流程部署的id
repositoryService.deleteDeployment(“1”);
//级联删除
// repositoryService.deleteDeployment(“1”,true);
} |
| —- |
下载流程到电脑本地
| @Autowired
/管理流程定义,部署相关的Service/
private RepositoryService repositoryService;
@Test
public void c保存流程资源文件到本地() throws IOException {
//3.得到查询器:ProcessDefinitionQuery对象
ProcessDefinitionQuery processDefinitionQuery = this.repositoryService.createProcessDefinitionQuery();
//4.设置查询条件
//参数是流程定义的key
ProcessDefinitionQuery holiday = processDefinitionQuery.processDefinitionKey(“holiday”);
//5.执行查询操作,查询出想要的流程定义
//因为holiday流程只有一个,所以就用singleResult() 获取一个流程结果
ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
if (processDefinition != null) {
//6.通过流程定义信息,得到部署ID
String deploymentId = processDefinition.getDeploymentId();
//7.通过repositoryService的方法,实现读取图片信息及bpmn文件信息(输入流)
//getResourceAsStream()方法的参数说明:第一个参数部署id,第二个参数代表资源名称
//processDefinition.getDiagramResourceName() 代表获取png图片资源的名称
//processDefinition.getResourceName()代表获取bpmn文件的名称
InputStream pngIs = repositoryService
.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
InputStream bpmnIs = repositoryService
.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//8.构建出OutputStream流
//processDefinition.getDiagramResourceName() 这个是获取存数据库是什么名字,这个就是什么名字
String basePath = “C:\\Activiti7开发计划\\资料\\“;
// 如果不存在就创建这个路径下的文件夹
// 不创建的话会抛出异常出来
File file = new File(basePath);
if (!file.exists()) {
file.mkdirs();
}
/ 需要注意的是, 如果当前资源下有这个文件的话,
会直接被覆盖掉
/
OutputStream pngOs =
new FileOutputStream(basePath + processDefinition.getDiagramResourceName());
//processDefinition.getResourceName 获取 bpmn文件的名字
OutputStream bpmnOs =
new FileOutputStream(basePath + processDefinition.getResourceName());
//9.输入流,输出流的转换 commons-io-xx.jar中的方法
IOUtils._copy(pngIs, pngOs);
IOUtils.copy(bpmnIs, bpmnOs);
//10.关闭流
pngOs.close();
bpmnOs.close();
pngIs.close();
bpmnIs.close();
} else {
System.err.println(“流程为空”);
}
} |
| —- |
BPMN2.0规范
是一套业务流程模型与符号建模标准,它可以使用精准的执行语义来描述元素的操作.并且以XML为载体,让业务流程逻辑以符号可视化业务
流程图可以分为五部分,最重要的就是流对象,流对象包括了实现和网关,流对象通过连接对象用于表示数据的流转,流程中的数据流转主要是通过连接对象来描述的.
泳道是用于对业务做一个范围维度的区分,比如部门和角色来区分流程的范围,描述对象并不影响流程的运行,只是作为流程图的可读性做了一些补充性的描述.
工作流详解
Exclusive Gateway 排他网关的意义,我们在这里面审批的意义可以选择一条线路去走,也可以流转到比如说人事审批等等.
BPM
BPM(Business Process Management),即业务流程管理,是一种以规范化的构造端到端的卓越业务流程为中心,以持续的提高组织业务绩效为目的系统化方法,常见商业管理教育如EMBA、MBA等均将 BPM 包含在内。
企业流程管理主要是对企业内部改革,改变企业职能管理机构重叠、中间层次多、流程不闭环等,做到机构不重叠、业务不重复,达到缩短流程周期、节约运作资本、提高企业效益的作用。
BPM 软件
BPM 软件就是根据企业中业务环境的变化,推进人与人之间、人与系统之间以及系统与系统之间的整合及调整的经营方法与解决方案的 IT 工具。 通常以 Internet方式实现信息传递、数据同步、业务监控和企业业务流程的持续升级优化,从而实现跨应用、跨部门、跨合作伙伴与客户的企业运作。通过 BPM 软件对企业内部及外部的业务流程的整个生命周期进行建模、自动化、管理监控和优化,使企业成本降低,利润得以大幅提升。
BPM 软件在企业中应用领域广泛,凡是有业务流程的地方都可以 BPM 软件进行管理,比如企业人事办公管理、采购流程管理、公文审批流程管理、财务管理等。
BPMN
BPMN(Business Process Model And Notation)- 业务流程模型和符号 是由 BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用 BPMN 提供的符号可以创建业务流程。 2004 年 5 月发布了 BPMN1.0 规范.BPMI 于 2005 年 9 月并入 OMG(The ObjectManagement Group对象管理组织)组织。OMG 于 2011 年 1月发布BPMN2.0 的最终版本。
BPMN 是目前被各 BPM 厂商广泛接受的 BPM 标准。Activiti 就是使用 BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号,比如:
Event 用一个圆圈表示,它是流程中运行过程中发生的事情。
活动用圆角矩形表示,一个流程由一个活动或多个活动组成
一个 bpmn 图形的例子:
首先当事人发起一个请假单;
其次他所在部门的经理对请假单进行审核;
然后人事经理进行复核并进行备案;
最后请假流程结束。
Bpmn 图形其实是通过 xml表示业务流程,上边的.bpmn 文件使用文本编辑器打开: