Spring学习之整合Activiti(二)

上一篇:Spring学习之整合Activiti(一)
下一篇:Spring学习之整合Activiti(三)之Id生成

Spring学习之整合Activiti(一)已经可以进入模型的新建页面了,在本篇幅中,咱们主要学习如何创建模型。

一、页面结构介绍

进入模型新建页面如下:

image.png

左边为组件列表,上方为模型可用的一些工具菜单,右下方为流程的相关信息(可编辑),之后新建的流程的相关节点信息也是在这里显示,右上方为流程的工作区。

先说一下设计器的操作三部曲

  1. 从左侧的仓库中选择组件(可以展开多个分类)
  2. 拖拽组建到工作区并调整位置
  3. 点击组件在右侧的下方设置组件属性

下面咱们以一个例子详细介绍设计流程图(其他流程根据具体业务需求类似操作):

二、画流程

1. 指定流程名称和流程相关信息
指定流程名称和流程相关信息
2. 开始节点

从左侧的仓库中选择开始事件,拖到工作区并调整好位置,设置节点id和名称(可填)。

画开始节点
3. 发起请求的用户任务节点

如下图所示,在画完开始节点后,点击红色的小黑人(用户任务),表示下一个节点为用户任务节点:

image.png

如下图,指定id(保证在整个流程中唯一。,必填),名称(必填):

image.png

可以看到,在这个节点的下方,有很多属性,我们可以根据业务需求来设置这些属性。
在本例中,该节点为发起人节点,为用户任务节点,所以我们需要为其设置执行人,为节点设置执行人有两种方法:

  1. 在属性中配置分配用户表达式;
  2. 代码设置:task.setAssignee(userId);

这里我们采用第一种,设置属性“分配用户”:

  • 点击分配用户:
image.png
  • 在弹出框里输入Assignee表达式,别忘了点击保存:
image.png

说明:
第一个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}

image.png

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. 网关事件:通过与驳回

既然是审批,就有通过与驳回,这叫网关事件。
如图点击,表示下一节点是一个网关:

image.png

为了在代码中获取下一节点的信息,需要为其设置一个唯一标识id,如果一个流程中有多个网关,建议网关id与当前审批节点(前一个节点)的id关联,比如当前审批节点的id为productInnovationCenterAudit,则该网关节点的id为isPass_productInnovationCenterAudit.

image.png

注意:
画网关节点时,建议先画驳回分支,再画通过分支。

同前几个节点一样,因为驳回后可以重新申请,则下一节点为用户任务节点,即点击小黑人,并调整重新申请节点位置:

image.png

点击驳回分支线:
设置驳回分支名称

image.png

点击流条件设置流向该分支条件:

image.png
驳回流条件.png

大括号中的isPass_productInnovationCenterAudit即为网关id。

6. 重新申请节点

因为需求要审批驳回后流到发起人,发起人可以重新申请,也可以取消申请。

设置重新申请节点如下图所示,由于执行人与发起人一致,为当前登录用户,所以只需设置分配用户:

重新申请.png
7. 网关事件:重新申请与取消申请

操作同上一个网关,并调整位置:
命名规则同上一个网关,由于当前审批节点为重新申请节点,id为businessManagerReApply,为了直观,前缀用了isReapply

image.png

我们先画重新申请:
重新申请后,流到需求审批节点,操作:

  • 点击小黑人:
image.png
  • 拖拽新建的用户节点至需求审批节点下方:
image.png
  • 点击删除按钮:
image.png
image.png
  • 将箭头拉到需求审批节点的正中心,如下
image.png
  • 点击重新申请先,设置名称和流条件,保存:
设置名称.png
设置流条件.png
image.png
8. 需求确认节点

现在画需求审批通过分支,点击网关:

image.png

由于审批通过后,进行需求确认,该节点仍然是一个用户任务节点,点击小黑人:
由于该需求确认节点可能需要多个人进行处理该节点,即为会签节点,而且通过该节点的条件为:一人驳回则退回到需求审批节点,所有人通过(没有先后)则通过到下一节点,这里是所有人通过即归档。

需求确认.png

我们按照图中顺序说明:

  1. id:唯一标识,businessManagerConfirm
  2. 名称:不用多说;
  3. 多实例类型:通过上述,通过该节点的条件可知,该节点为多实例节点,且并行(没有先后),所以选择Parrallel;
  4. 集合:即当前多实例节点的执行人集合,可以写死(确认确实有这么个人),不确定的话建议为表达式:
    ${assigneeList_businessManagerConfirm} 命名规则类似网关,防止流程中有多个多实例节点,在代码中设置多实例节点的实际执行人集合时好区分。
  5. 元素变量:集合中的单个元素变量,assignee_businessManagerConfirm,注意,该处的变量需要和分配用户中的变量保持一致。
  6. 完成条件:完成该节点的条件,通过上述可知:
    ${agreeMembers_businessManagerConfirm == nrOfInstances || backMembers_businessManagerConfirm>0}
  • nrOfInstances 为处理该节点的总人数,流程中会根据集合的size自动填充,
  • agreeMembers_businessManagerConfirm 该节点同意的人数
  • backMembers_businessManagerConfirm 该节点驳回人数
  1. 分配用户:将会签节点任务分配到具体的个人,${assignee_businessManagerConfirm},注意,该处的变量需要和元素变量保持一致。
9. 网关事件:通过与驳回

先画驳回:

  • 点击小黑人,拖动新建的用户任务节点至需求审批节点的上方,删除新建的节点,拉箭头至审批节点的正中心:
image.png
  • 编辑驳回线名称与流条件,别忘了保存:
image.png
  • 通过线:类似驳回,由于下一个节点仍然是用户任务节点,点击小黑人,然后点击通过线,设置名称和通过的流条件:
image.png
10. 归档

该流程要求由发起人归档,所以分配用户为applyuserid

image.png
11. 结束节点
  1. 归档后流程结束:
    点击实心黑圆圈,添加一个结束节点。
image.png
image.png
  1. 取消申请后结束流程
    点击重新申请的网关,选择结束Event,新增一个结束节点:
image.png

拖动结束节点至归档后的结束节点的下方:

image.png

删除新增的结束节点,拉箭头至归档后的结束节点的正中心:

image.png
image.png

最后的流程如下:

image.png

点击左上角的保存,输入模型名称和描述信息,保存即可。

image.png

即在数据库的act_re_modelact_ge_bytearray表会插入该模型信息:

image.png

上一篇:Spring学习之整合Activiti(一)
下一篇:Spring学习之整合Activiti(三)之Id生成

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容