Spring学习之整合Activiti(二)
上一篇Spring学习之整合Activiti(一)已经可以进入模型的新建页面了,在本篇幅中,咱们主要学习如何创建模型。
一、页面结构介绍
进入模型新建页面如下:
image.png左边为组件列表,上方为模型可用的一些工具菜单,右下方为流程的相关信息(可编辑),之后新建的流程的相关节点信息也是在这里显示,右上方为流程的工作区。
先说一下设计器的操作三部曲:
- 从左侧的仓库中选择组件(可以展开多个分类)
- 拖拽组建到工作区并调整位置
- 点击组件在右侧的下方设置组件属性
下面咱们以一个例子详细介绍设计流程图(其他流程根据具体业务需求类似操作):
二、画流程
1. 指定流程名称和流程相关信息
指定流程名称和流程相关信息2. 开始节点
从左侧的仓库中选择开始事件,拖到工作区并调整好位置,设置节点id和名称(可填)。
画开始节点3. 发起请求的用户任务节点
如下图所示,在画完开始节点后,点击红色的小黑人(用户任务),表示下一个节点为用户任务节点:
image.png如下图,指定id(保证在整个流程中唯一。,必填),名称(必填):
image.png可以看到,在这个节点的下方,有很多属性,我们可以根据业务需求来设置这些属性。
在本例中,该节点为发起人节点,为用户任务节点,所以我们需要为其设置执行人,为节点设置执行人有两种方法:
- 在属性中配置分配用户表达式;
- 代码设置:
task.setAssignee(userId);
这里我们采用第一种,设置属性“分配用户”:
- 点击分配用户:
- 在弹出框里输入Assignee表达式,别忘了点击保存:
说明:
第一个Assignee:节点指定执行人,大括号里的applyuserid
为变量,在之后启动流程时需要为其赋值,否则报异常。
第二个 Candidate users:节点候选人,可以为多个人,用逗号隔开。表示在该节点可能有多个人处理该任务(视具体需求而定),代码中用
task.addCandidateUsers(Collection<String> candidateUsers);
第三个 Candidate groups:节点候选组,可以为多个组,且组内的每个人都可以处理该节点任务(视具体需求而定)。
task.addCandidateGroups(Collection<String> candidateGroups);
发起人节点到这里我们配置完毕。发起人提出要求后接下来就需要审批节点,有些流程只需要一级审批点,有些则需要多级审批,则流程中我们需要根据需求画多个审批节点。
4. 审批节点
同画发起人节点,点击小黑人表名下一节点为用户任务节点:
image.png我们用任务监听器为当前用户任务节点设置执行人,所以属性不需要配置分配用户,直接配置create任务监听器:
按照下图中所标顺序配置即可。
Event类型选的是create
,因为设置该节点执行人需要在初始化该节点之前设置,即创建该节点时就指定。
因为我们用spring管理bean,将监听类交由spring管理, 所以这里选择代理表达式的方式: Delegate Expression:${cusTaskListener}
。
CusTaskListener.java为:
package net.northking.activiti.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import net.northking.activiti.entity.CusUserTask;
import net.northking.service.activiti.CusUserTaskService;
import net.northking.util.StringUtil;
@Component
public class CusTaskListener implements TaskListener {
private static final long serialVersionUID = 1L;
@Resource
protected RepositoryService repositoryService;
@Resource
private CusUserTaskService cusUserTaskService;
@Resource
private RuntimeService runtimeService;
@Override
public void notify(DelegateTask delegateTask) {
setUserTasks(delegateTask);
}
/**
* 设置用户节点处理人
*
* @param delegateTask
*/
private void setUserTasks(DelegateTask delegateTask) {
try {
String processInstanceId = delegateTask.getProcessInstanceId();
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId).singleResult();
final String businessKey = pi.getBusinessKey();
List<CusUserTask> taskList = this.cusUserTaskService.findByProcDefKey(businessKey);
String taskDefinitionKey = delegateTask.getTaskDefinitionKey();
for (CusUserTask userTask : taskList) {
String taskKey = userTask.getTaskDefKey();
if (taskDefinitionKey.equals(taskKey)) {
setAssigneeToUsersTask(delegateTask, businessKey, userTask);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 普通用户节点设置特定执行人或候选人<br/>
* 只要有一人通过即为通过
* @param delegateTask
* @param businessKey
* @param projectId
* @param userTask
*/
private void setAssigneeToUsersTask(DelegateTask delegateTask, final String businessKey,
CusUserTask userTask) throws Exception{
String taskType = userTask.getTaskType();
String userIds = userTask.getCandidate_ids();
String groupIds = userTask.getGroup_id();
switch (taskType) {
case CusUserTask.TYPE_ASSIGNEE: {
System.out.println("CusTaskListener assignee userIds: " + userIds);
delegateTask.setAssignee(userIds);
break;
}
case CusUserTask.TYPE_CANDIDATEUSER: {
System.out.println("CusTaskListener 候选用户审批 userIds: " + userIds);
String[] assigneeIds = null;
if (StringUtil.isNotEmpty(userIds)) {
assigneeIds = userIds.split(",");
}
List<String> assigneeList = getAssigneeList(delegateTask, assigneeIds);
if(null == assigneeList)return;
delegateTask.addCandidateUsers(assigneeList);
break;
}
case CusUserTask.TYPE_CANDIDATEGROUP: {
System.out.println("CusTaskListener 候选组审批 groupIds: " + groupIds);
/**
* 设置候选人,一个通过即为通过 由于我们采用的是项目的用户管理系统,所以这里不能直接设置候选组,
* 需要根据groupId到项目的用户系统查询具体的用户然后作为候选人设置到工作流
*/
String[] candidateUserIds = getCandidateIds(businessKey, groupIds);
List<String> assigneeList = getAssigneeList(delegateTask, candidateUserIds);
if(null == assigneeList)return;
delegateTask.addCandidateUsers(assigneeList);
break;
}
}
}
private List<String> getAssigneeList(DelegateTask delegateTask, String[] candidateUserIds) throws Exception {
List<String> assigneeList = new ArrayList<>();
if (null != candidateUserIds) {
assigneeList = Arrays.asList(candidateUserIds);
}
System.out.println("CusTaskListener getAssigneeList candidateUserIds:" + candidateUserIds
+ ";assigneeList:" + assigneeList.size());
if (assigneeList.size() < 1) {
autoPass(delegateTask, delegateTask.getTaskDefinitionKey());
return null;
}
return assigneeList;
}
/**
* 自动跳过
* @param delegateTask
* @param taskDefKey
* @throws Exception
*/
private void autoPass(DelegateTask delegateTask, String taskDefKey) throws Exception {
ActivityImpl nextNodeInfo = ProcessDefinitionCache.get().getNextNodeInfo(repositoryService,runtimeService,delegateTask.getProcessInstanceId(),taskDefKey);
System.out.println("CusTaskListener setAssigneeList nextNodeid:"+nextNodeInfo.getId());
if(nextNodeInfo.getId().contains("reapply")) {
delegateTask.setVariable(nextNodeInfo.getId(), "false");//reapply_projectManagerAudit等等
}else {
delegateTask.setVariable(nextNodeInfo.getId(), "true");//isPass_projectManagerAudit等等
}
}
private String[] getCandidateIds(String businessKey, String groupIds) {
// final String businessId = businessKey.contains(":") ? businessKey.split(":")[1] : "";//业务id:可能是projectId,也可能是userId等等
String[] roleCodes = groupIds.split(",");
return roleCodes;
}
}
5. 网关事件:通过与驳回
既然是审批,就有通过与驳回,这叫网关事件。
如图点击,表示下一节点是一个网关:
为了在代码中获取下一节点的信息,需要为其设置一个唯一标识id,如果一个流程中有多个网关,建议网关id与当前审批节点(前一个节点)的id关联,比如当前审批节点的id为productInnovationCenterAudit
,则该网关节点的id为isPass_productInnovationCenterAudit
.
注意:
画网关节点时,建议先画驳回分支,再画通过分支。
同前几个节点一样,因为驳回后可以重新申请,则下一节点为用户任务节点,即点击小黑人,并调整重新申请节点位置:
image.png点击驳回分支线:
设置驳回分支名称
点击流条件设置流向该分支条件:
image.png 驳回流条件.png大括号中的isPass_productInnovationCenterAudit即为网关id。
6. 重新申请节点
因为需求要审批驳回后流到发起人,发起人可以重新申请,也可以取消申请。
设置重新申请节点如下图所示,由于执行人与发起人一致,为当前登录用户,所以只需设置分配用户:
重新申请.png7. 网关事件:重新申请与取消申请
操作同上一个网关,并调整位置:
命名规则同上一个网关,由于当前审批节点为重新申请节点,id为businessManagerReApply
,为了直观,前缀用了isReapply
我们先画重新申请:
重新申请后,流到需求审批节点,操作:
- 点击小黑人:
- 拖拽新建的用户节点至需求审批节点下方:
- 点击删除按钮:
- 将箭头拉到需求审批节点的正中心,如下
- 点击重新申请先,设置名称和流条件,保存:
8. 需求确认节点
现在画需求审批通过分支,点击网关:
image.png由于审批通过后,进行需求确认,该节点仍然是一个用户任务节点,点击小黑人:
由于该需求确认节点可能需要多个人进行处理该节点,即为会签节点,而且通过该节点的条件为:一人驳回则退回到需求审批节点,所有人通过(没有先后)则通过到下一节点,这里是所有人通过即归档。
我们按照图中顺序说明:
- id:唯一标识,
businessManagerConfirm
- 名称:不用多说;
- 多实例类型:通过上述,通过该节点的条件可知,该节点为多实例节点,且并行(没有先后),所以选择Parrallel;
- 集合:即当前多实例节点的执行人集合,可以写死(确认确实有这么个人),不确定的话建议为表达式:
${assigneeList_businessManagerConfirm}
命名规则类似网关,防止流程中有多个多实例节点,在代码中设置多实例节点的实际执行人集合时好区分。 - 元素变量:集合中的单个元素变量,
assignee_businessManagerConfirm
,注意,该处的变量需要和分配用户中的变量保持一致。 - 完成条件:完成该节点的条件,通过上述可知:
${agreeMembers_businessManagerConfirm == nrOfInstances || backMembers_businessManagerConfirm>0}
-
nrOfInstances
为处理该节点的总人数,流程中会根据集合的size自动填充, -
agreeMembers_businessManagerConfirm
该节点同意的人数 -
backMembers_businessManagerConfirm
该节点驳回人数
- 分配用户:将会签节点任务分配到具体的个人,
${assignee_businessManagerConfirm}
,注意,该处的变量需要和元素变量保持一致。
9. 网关事件:通过与驳回
先画驳回:
- 点击小黑人,拖动新建的用户任务节点至需求审批节点的上方,删除新建的节点,拉箭头至审批节点的正中心:
- 编辑驳回线名称与流条件,别忘了保存:
- 通过线:类似驳回,由于下一个节点仍然是用户任务节点,点击小黑人,然后点击通过线,设置名称和通过的流条件:
10. 归档
该流程要求由发起人归档,所以分配用户为applyuserid
11. 结束节点
- 归档后流程结束:
点击实心黑圆圈,添加一个结束节点。
- 取消申请后结束流程
点击重新申请的网关,选择结束Event,新增一个结束节点:
拖动结束节点至归档后的结束节点的下方:
image.png删除新增的结束节点,拉箭头至归档后的结束节点的正中心:
image.png image.png最后的流程如下:
image.png点击左上角的保存,输入模型名称和描述信息,保存即可。
image.png即在数据库的act_re_model
和act_ge_bytearray
表会插入该模型信息: