Activiti7(图文并茂)


一 介绍

1.1 Activiti 介绍

Activiti 是由 jBPM (BPM,Business Process Management 即业务流程管理) 的创建者 Tom Baeyens 离开 JBoss 之后建立的项目,构建在开发 jBPM 版本 1 到 4 时积累的多年经验的基础之上,旨在创建下一代的 BPM 解 决方案。

Activiti 作为一个开源的工作流引擎,它实现了BPMN 2.0规范,可以发布设计好的流程定义,并通过api进行流程调 度。 Activiti 作为一个遵从 Apache 许可的工作流和业务流程管理开源平台,其核心是基于Java的超快速、超稳定的 BPMN2.0 流程引擎,强调流程服务的可嵌入性和可扩展性,同时更加强调面向业务人员。

Activiti 流程引擎重点关注在系统开发的易用性和轻量性上。每一项 BPM 业务功能 Activiti 流程引擎都以服务的形 式提供给开发人员。通过使用这些服务,开发人员能够构建出功能丰富、轻便且高效的 BPM 应用程序。 Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快 速、稳定的BPMN 2.0流程引擎。Activiti是在ApacheV2许可下发布的,可以运行在任何类型的Java程序中,例如服 务器、集群、云服务等。

Activiti可以完美地与Spring集成。同时,基于简约思想的设计使Activiti非常轻量级。 官网:https://www.activiti.org/

1.2 Activiti 开发流程

  1. 画流程定义模型: 遵守BPMN的流程规范,使用BPMN的流程定义工具,通过 流程符号 把整个业务流程定义出来,可以将流程 定义文件字节流保存到模型数据表中(Model)。
  2. 部署流程定义: 加载画好的流程定义文件,将它转换成流程定义数据(ProcessDefinition),保存到流程定义数据表中。
  3. 启动流程(提交流程申请): 生成流程实例数据(ProcessInstance),生成第1节点任务数据(Task)。
  4. 处理人审批流程节点任务: 完成任务审批,生成审批结果,生成下一节点任务数据。

1.3 BPMN 2.0 规范是什么

业务流程模型注解(Business Process Modeling Notation - BPMN)是业务流程模型的一种标准图形注解。这个 标准是由对象管理组(Object Management Group - OMG)维护的。

标准的早期版本(1.2版以及之前)仅仅限制在模型上, 目标是在所有的利益相关者之间形成通用的理解, 在文 档,讨论和实现业务流程之上。 BPMN标准证明了它自己,现在市场上许多建模工具都使用了BPMN标准中的元素 和结构。

BPMN规范的2.0版本,当前已经处于最终阶段了, 允许添加精确的技术细节在BPMN的图形和元素中, 同时制定 BPMN元素的执行语法。 通过使用XML语言来指定业务流程的可执行语法, BPMN规范已经演变为业务流程的语 言, 可以执行在任何兼容BPMN2的流程引擎中, 同时依然可以使用强大的图形注解。

目前BPMN2.0是最新的版本,它用于在BPM上下文中进行布局和可视化的沟通。BPMN 2.0是使用一些符号来明确 业务流程设计流程图的一整套符号规范,它能增进业务建模时的沟通效率。

1.4 BPMN 2.0 基本流程符号

1.4.1 事件 Event

开始 中间事件 结束

image-20211215201353917.png

1.4.2 活动

活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程;

1.4.3 网关 Gateway

网关用来处理决策:

image-20211215201600134.png

  • 排他网关 (x)

只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继 续执行当前网关的输出流; 如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引 擎会抛出异常。 排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。

  • 并行网关 (+)

所有路径会被同时选择 分支: 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。 汇聚 :所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

  • 包含网关 (o)

可以同时执行多条线路,也可以在网关上设置条件分支:计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行 汇聚:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

  • 事件网关 (o+)

专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关 后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。

1.5 Activiti API 服务接口

Activiti 流程引擎包含了25张表,而且表之间的关系也比较复杂,比如包含各种外键约束。

按照传统的方式,有了数据库表后,就应该为每张表创建 Entity 实体类,然后为其创建对应的 DAO 接口,然 后再创建对应的 Service来实现对表数据的增删改查;

但是按照传统方式的,就会有一个很严峻的问题,表的数量太多,并且关系复杂,还要兼顾流程引擎的处理方 式,自己去搞一套,几乎不可能;

其实不需要我们去创建Entity 、 DAO、Service、Controller,因为Activiti已经把这些东西给搞好了,只需要 调用即可。

Process Engine API 和服务

引擎 API 是与 Activiti 交互的最常见方式。

可以从ProcessEngine中获取包含工作流/ BPM方法的各种服务。

ProcessEngine和服务对象是线程安全的。因此,可以为整个服务器保留对其中之一的引用。

Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们使用对应Service接口可以操作对应 的数据表。

image-20211129233459602.png

1.5.1 核心Service接口及其获取

// 会在首次调用时初始化并构建一个流程引擎,此后始终返回相同的流程引擎。
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 引擎管理类
ManagementService managementService = processEngine.getManagementService();
// 动态修改流程管理类
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
// 流程运行管理类
RuntimeService runtimeService = processEngine.getRuntimeService();
// 流程仓库管理类
RepositoryService repositoryService = processEngine.getRepositoryService();
// 任务管理类
TaskService taskService = processEngine.getTaskService();
// 历史管理类
HistoryService historyService = processEngine.getHistoryService();

二 表

2.1 建表

前提:在数据库中建库 并和配置文件中的连接信息保持一致。

在test中执行如下语句,即可建表成功:

@Autowired
ProcessEngine processEngine;

@Test
public void getProcessEngine() {
System.out.println("processEngine: " + processEngine);
}

建好表后,执行两条语句,使其完整:

ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN VERSION_ VARCHAR(255);
ALTER TABLE ACT_RE_DEPLOYMENT ADD COLUMN PROJECT_RELEASE_VERSION_ VARCHAR(255);

2.2 表介绍

Acitiviti数据库中表的命名都是以 ACT_ 开头的。第二部分是一个两个字符用例表的标识。此用例大体与服务API是 匹配的。

ACT_GE_* : GE 表示 general 。通用数据,各种情况都使用的数据 ,如存放资源文件(图片,规则等)。

ACT_HI_xxx : HI 表示history。就是这些表包含着历史的相关数据,如结束的流程实例(变量,任务等)。

ACT_RE_xxx : RE 表示repository。带此前缀的表包含的是静态信息,如,流程定义,流程的资源(图片,规则 等)。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直 很小速度很快。

ACT_RU_xxx : RU 表示 runtime。这是运行时的表存储着流程变量,用户任务,变量,职责(job)等运行时的 数据。Activiti只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的 表小且快。

ACT_EVT_* :EVT表示EVENT,流程引擎的通用事件日志记录表,方便管理员跟踪处理。

表分类 表名 说明
通用数据
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_re_deployment 部署信息表
act_re_model 流程设计模型表
act_re_procdef 流程设计模型表
流程运行表
act_ru_deadletter_job 作业死亡信息表,如果作业失败超过重试次数,则写入到此表
act_ru_event_subscr throwEvent、catchEvent时间监听信息表
act_ru_execution 运行时流程执行实例表
aact_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
act_ru_integration 运行时积分表
act_ru_job 定时异步任务数据表
act_ru_suspended_job 运行时作业暂停表, 比如流程中有一个定时任务,如果把这个任 务停止工作了,这个任务写入到此表中
act_ru_task 运行时任务节点表
act_ru_timer_job 运行时定时器作业表
act_ru_variable 运行时流程变量数据表
其他表
act_procdef_info 流程定义的动态变更信息
act_evt_log 流程引擎的通用事件日志记录表

三 关于model

3.1 model画图

画图前,先指定如下三处

image-20211215203258049.png

必须有个开始 和 结束 :

image-20211215204012091.png

加入任务:

image-20211215204434169.png

注意:双击节点可以指定节点名称

指定任务办理人:

image-20211215204809122.png

然后到下一步(此时是以写死的方式来指定办理人的)

image-20211215204918290.png

完成整个流程绘图后,点击左上角保存

image-20211215205055395.png

点击保存后出现如下框,填写完 点保存就好

image-20211215205145615.png


动态分配办理人

方式1:指定变量

image-20211215210306015.png

image-20211215210331194.png

此时是指定了一个表达式的样子来指定的动态办理人,为该表达式赋值的时机是流程到达该节点之前,如果该节点之前是 开始 ,那么就在流程启动的时候指定,如果该节点之前是 任务 ,那么就在完成上一步任务的同时指定下一步办理人。

如何加流程参数在本文档的第九节。

方式2:指定对象属性名

image-20211215210738063.png

image-20211215210757114.png

在启动流程或者完成上一步任务办理的同时,指定一个user对象进去就好了。

如何加流程参数在本文档的第九节。

方式3:方法表达式

image-20211215211141413.png

image-20211215211200717.png

注意:userService是Spring容器中的一个bean实例,对应调用getUsername()方法。

如何加流程参数在本文档的第九节。

3.2 操作model

3.2.1 模型查询对象

@Autowired
RepositoryService repositoryService;
/**
* 查询所有流程定义模型
*/
@Test
public void modelList() {
    ModelQuery query = repositoryService.createModelQuery();
    List<Model> list = query.orderByCreateTime()
        .desc()
        .list();
    for (Model model : list) {
        System.out.print("模型id:" + model.getId());
        System.out.print(", 模型名称:" + model.getName());
        System.out.print(",模型描述: " + model.getMetaInfo());
        System.out.print(",模型标识key:" + model.getKey());
        System.out.println(",模型版本号:" + model.getVersion());
    }
}

3.2.2 删除模型对象

@Autowired
RepositoryService repositoryService; 
/**
 * 删除流程定义模型
 * 会影响到的表:ACT_RE_MODE 和 ACT_GE_BYTEARRAY
 */
 @Test
 public void deleteModel() {
     //	modelId 为表ACT_RE_MODE 中的ID字段
     String modelId = "0a499790-7c1d-11f6-9001-2c337a6d7e1d";
     repositoryService.deleteModel(modelId);
     System.out.println("删除成功");
 }

3.2.3 导出流程定义模型资源的zip压缩包

@Autowired
RepositoryService repositoryService;
@Test
public void exportZip() throws IOException {
    // 1. 查询模型基本信息 (modelId 为表ACT_RE_MODE 中的ID字段)
    String modelId = "da0fbf5c-7c1d-11f6-949f-2c337a6d7e1d";
    Model model = repositoryService.getModel(modelId);
    if(model != null) {
        // 2. 查询流程定义模型的json字节码
        byte[] bpmnJsonBytes = repositoryService.getModelEditorSource(modelId);
        // 2.1 将json字节码转换为xml字节码
        byte[] xmlBytes = bpmnJsonXmlBytes(bpmnJsonBytes);
        if(xmlBytes == null) {
            System.out.println("模型数据为空-请先设计流程定义模型,再导出");
        }else {
            // 压缩包文件名
            String zipName = model.getName() + "." + model.getKey() + ".zip";
            File file = new File("D:/" + zipName);
            FileOutputStream outputStream = new FileOutputStream(file);
            // 实例化zip输出流
            ZipOutputStream zipos = new ZipOutputStream(outputStream);

            // 将xml添加到压缩包中(指定xml文件名:请假流程.bpmn20.xml )
            zipos.putNextEntry(new ZipEntry(model.getName() + ".bpmn20.xml"));
            zipos.write(xmlBytes);

            // 3. 查询流程定义模型的图片字节码
            byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);
            if(pngBytes != null) {
                // 图片文件名(请假流程.leaveProcess.png)
                zipos.putNextEntry(new ZipEntry(model.getName() + "." + model.getKey() + ".png"));
                zipos.write(pngBytes);
            }
            // 4. 以压缩包的方式导出流程定义模型文件
            zipos.closeEntry();
            zipos.close();
            System.out.println("导出流程定义模型zip成功");
        }
    }else {
        System.out.println("模型不存在");
    }
}


private byte[] bpmnJsonXmlBytes(byte[] jsonBytes) throws IOException {
    if(jsonBytes == null) {
        return null;
    }

    // 1. json字节码转成 BpmnModel 对象
    JsonNode jsonNode = objectMapper.readTree(jsonBytes);
    BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);

    if(bpmnModel.getProcesses().size() == 0) {
        return null;
    }
    // 2. BpmnModel 对象转为xml字节码
    byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
    return xmlBytes;
}

3.2.4 导出流程定义模型资源的.xml文件

@Test
public void exportXml() throws IOException {
    // 1. 查询模型基本信息
    String modelId = "da0fbf5c-7c1d-11f6-949f-2c337a6d7e1d";
    // 查询json字节码
    byte[] bytes = repositoryService.getModelEditorSource(modelId);

    String filename = null;
    ByteArrayInputStream in = null;
    if(bytes != null) {
        // 1. json字节码转成 BpmnModel 对象
        JsonNode jsonNode = objectMapper.readTree(bytes);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
        if(bpmnModel.getProcesses().size() > 0) {
            // 2. BpmnModel 对象转为xml字节码
            byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
            in = new ByteArrayInputStream(xmlBytes);
            filename = StringUtils.isBlank(bpmnModel.getMainProcess().getName())
                ? bpmnModel.getMainProcess().getId() : bpmnModel.getMainProcess().getName();
        }
    }

    if(filename == null) {
        filename = "模型数据为空,请先设计流程,再导出";
        in = new ByteArrayInputStream(filename.getBytes("utf-8"));
    }

    // 文件输出流
    FileOutputStream out = new FileOutputStream(new File("D:/" + filename + ".bpmn20.xml"));

    IOUtils.copy(in, out);

    out.close();
    in.close();
    System.out.println("导出xml成功");
}

3.2.5 通过流程定义模型数据部署流程定义

 /**
 * 会影响到的表:
 * ACT_RE_PROCDEF
 * ACT_RE_DEPLOYMENT
 * ACT_GE_BYTEARRAY
 * ACT_RE_MODEL 更新流程部署id,将模型与部署的流程定义绑定
 * @throws Exception
 */
 @Test
 public void deploy() throws Exception {
     // 1. 查询流程定义模型json字节码
     String modelId = "b8efa921-da43-11eb-8aa7-2c337a6d7e1d";
     byte[] jsonBytes = repositoryService.getModelEditorSource(modelId);
     if(jsonBytes == null) {
     System.out.println("模型数据为空,请先设计流程定义模型,再进行部署");
     return;
     }
     // 将json字节码转为 xml 字节码,因为bpmn2.0规范中关于流程模型的描述是xml格式的,而activiti遵守了这个规范
     byte[] xmlBytes = bpmnJsonXmlBytes(jsonBytes);
     if(xmlBytes == null) {
     System.out.println("数据模型不符合要求,请至少设计一条主线流程");
     return;
     }
     // 2. 查询流程定义模型的图片
     byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);

    // 查询模型的基本信息
    Model model = repositoryService.getModel(modelId);

    // xml资源的名称 ,对应act_ge_bytearray表中的name_字段
    String processName = model.getName() + ".bpmn20.xml";
    // 图片资源名称,对应act_ge_bytearray表中的name_字段
    String pngName = model.getName() + "." + model.getKey() + ".png";

    // 3. 调用部署相关的api方法进行部署流程定义
    Deployment deployment = repositoryService.createDeployment()
    .name(model.getName()) // 部署名称
    .addString(processName, new String(xmlBytes, "UTF-8")) // bpmn20.xml资源
    .addBytes(pngName, pngBytes) // png资源
    .deploy();

    // 更新 部署id 到流程定义模型数据表中
    model.setDeploymentId(deployment.getId());
    repositoryService.saveModel(model);

    System.out.println("部署成功");
}

四 操作部署信息

4.1 部署

4.1.1通过zip压缩包进行部署流程定义

 /**
 * 通过zip压缩包进行部署流程定义
 */
@Test
public void deployByZip() throws Exception {

    File file = new File("D:/请假流程模型xx.leaveProcess.zip");
    // 创建输入流
    FileInputStream inputStream = new FileInputStream(file);

    // 读取zip资源压缩包,转成输入流
    ZipInputStream zipInputStream = new ZipInputStream(inputStream);
    Deployment deployment = repositoryService.createDeployment()
        .addZipInputStream(zipInputStream)
        .name("请假申请流程222-压缩包")
        .deploy();

    // 4. 输出部署结果
    System.out.println("部署ID: " + deployment.getId());
    System.out.println("部署名称:" + deployment.getName());
}

4.1.2 通过.xml文件部署

@Test
public void deployByFile() throws FileNotFoundException {
    File file = new File("D:/请假申请流程.bpmn20.xml");
    // 文件输入流
    FileInputStream inputStream = new FileInputStream(file);
    // 资源名称
    String filename = file.getName();

    // 调用相关api方法进行部署
    Deployment deployment = repositoryService.createDeployment()
        .name("请假申请流程")
        .addInputStream(filename, inputStream)
        .deploy();
    // 输出部署结果
    System.out.println("部署ID: " + deployment.getId());
    System.out.println("部署名称:" + deployment.getName());
}

4.2 删除部署流程定义

/**
 * 根据部署ID来删除部署的流程定义数据
 * 影响到的表:
 * ACT_GE_BYTEARRAY
 * ACT_RE_DEPLOYMENT
 * ACT_RE_PROCDEF
 * ACT_RU_EVENT_SUBSCR
 * ACT_RU_IDENTITYLINK
 */
@Test
public void delete() {
    try {
        String deploymentId = "f88ccd79-db17-11eb-a1e0-2c337a6d7e1d";
        // 默认不是级联删除操作,如果有正在执行的流程实例,则删除时会抛出异常,并且不会删除历史表数据
        //repositoryService.deleteDeployment(deploymentId);

        // 如果为true则是级联删除操作(小心使用~)
        //	如果流程定义启动了对应的流程实例,也可以进行删除,并且会删除历史数据
        //repositoryService.deleteDeployment(deploymentId, true);
        System.out.println("删除流程定义数据成功");
    }catch (Exception e) {
        e.printStackTrace();
        if(e.getMessage().indexOf("a foreign key constraint fails") > 0) {
            System.out.println("有正在执行的流程实例,不允许删除");
        }else {
            System.out.println("删除失败,原因: " + e.getMessage());
        }
    }

}

4.3 启动流程

@Autowired
RuntimeService runtimeService;

@Test
public void startProcessInstance() {
    // 启动流程实例(流程定义key processDefinitionKey)
    // 通过流程定义key启动的流程实例 ,找的是最新版本的流程定义数据
    // 根据key部署 这个案例里"leaveProcess" 为部署的key
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveProcess");

    System.out.println("流程定义id: " + processInstance.getProcessDefinitionId());
    System.out.println("流程实例id: " + processInstance.getId());
}

五 操作流程定义

5.1 分页查询

@Autowired
RepositoryService repositoryService;

/**
 * 分页条件查询流程定义列表数据
 */
@Test
public void getProcDefList() {
    ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();

    String name = "请假";
    if(StringUtils.isNotBlank(name)) {
        // 条件查询
        query.processDefinitionNameLike("%"+name+"%");
    }
    // 如果多个相同key, 只查询最新版本的流程定义,
    query.latestVersion();
    // 按key降序
    query.orderByProcessDefinitionKey().desc();

    // 当前页码
    int current = 1;
    // 每页显示多少条
    int size = 5;
    // 当前页第1条数据的下标
    int firstResult = (current-1) * size;
    // 分页查询
    List<ProcessDefinition> processDefinitionList = query.listPage(firstResult, size);

    for (ProcessDefinition pd : processDefinitionList) {
        System.out.print("流程部署id:" + pd.getDeploymentId());
        System.out.print(",流程定义id:" + pd.getId());
        System.out.print(",流程定义key: " + pd.getKey());
        System.out.print(",流程定义名称:" + pd.getName());
        System.out.print(",版本号:" + pd.getVersion());
        System.out.println(",状态:" + ( pd.isSuspended() ? "挂起(暂停)" : "激活(开启)") );
    }

    // 总记录数
    long total = query.count();
    System.out.println("满足条件的流程定义总记录:" + total);

}

5.2 修改流程定义状态

/**
* 挂起或激活流程定义 :对应 act_re_procdef 表中的 SUSPENSION_STATE_ 字段,1是激活,2是挂起
* - 流程定义被挂起:此流程定义下的所有流程实例不允许继续往后流转了,就被停止了。
* - 流程定义被激活:此流程定义下的所有流程实例允许继续往后流转。
* - 为什么会被挂起?
*   - 可能当前公司的请假流程发现了一些不合理的地方,然后就把此流程定义挂起。
*   - 流程不合理解决办法:
*     - 方式一:可以先挂起流程定义,然后更新流程定义,然后激活流程定义。
*     - 方式二:挂起了就不激活了,重新创建一个新的请假流程定义。
*/
@Test
public void updateProcDefState() {
    //	processDefinitionId 为 act_re_procdef 表id
    String processDefinitionId = "leaveProcess:2:f6a379a1-7cdb-11f6-8052-2c337a6d7e1d";
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
        .processDefinitionId(processDefinitionId)
        .singleResult();
    // 判断是否挂起,true则挂起,false则激活
    if(processDefinition.isSuspended()) {
        // 将当前为挂起状态更新为激活状态
        // 参数说明:参数1:流程定义id,参数2:是否激活(true是否级联对应流程实例,激活了则对应流程实例都可以审批),参数3:什么时候激活,如果为null则立即激活,如果为具体时间则到达此时间后激活
        repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
    }else {
        // 将当前为激活状态更新为挂起状态
        // 参数说明:参数1:流程定义id,参数2:是否挂起(true是否级联对应流程实例,挂起了则对应流程实例都不可以审批),参数3:什么时候挂起,如果为null则立即挂起,如果为具体时间则到达此时间后挂起
        repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
    }
}

5.3 导出流程定义文件

 /**
 * 导出流程定义文件(xml或png)
 */
@Test
public void exportProcDefFile() throws Exception{
    String processDefinitionId = "leaveProcess:2:f6a379a1-7cdb-11f6-8052-2c337a6d7e1d";
    ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);

    // 获取的是 xml 资源名
    String resourceName = processDefinition.getResourceName();
    // 获取 png 图片资源名
    resourceName = processDefinition.getDiagramResourceName();
    // 查询到相关的资源输入流 (deploymentId, resourceName)
    InputStream input =
        repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);

    // 创建输出流
    File file = new File("D:/" + resourceName);
    FileOutputStream output = new FileOutputStream(file);

    IOUtils.copy(input, output);

    input.close();
    output.close();
    System.out.println("流程定义资源文件导出成功: " + resourceName);
}

六 操作流程实例

6.1 启动流程实例

@Autowired
RuntimeService runtimeService;

/**
*  涉及到的数据表:
*  ACT_HI_TASKINST
*  ACT_HI_PROCINST 流程实例
*  ACT_HI_ACTINST 节点实例表
*  ACT_HI_IDENTITYLINK 流程实例相关办理人
*  ACT_RU_EXECUTION
*  ACT_RU_TASK
*  ACT_RU_IDENTITYLINK
*/
@Test
public void startProcessInstance() {
    // 流程定义唯一标识key
    String processKey = "leaveProcess";
    // 业务id
    String businessKey = "10000";
    // 启动当前流程实例的用户(会保存到 act_hi_procinst 表中 start_user_id 字段中)
    Authentication.setAuthenticatedUserId("catch");

    // 启动流程实例(流程定义唯一标识key, 业务id),采用流程key对应的最新版本的流程定义数据
    ProcessInstance pi = runtimeService.startProcessInstanceByKey(processKey, businessKey);

    // 将流程定义名称 作为 流程实例名称
    runtimeService.setProcessInstanceName(pi.getProcessInstanceId(), pi.getProcessDefinitionName());

    System.out.println("启动流程实例成功:" + pi.getProcessInstanceId());
}

6.2 查询流程实例

6.2.1 查询正在运行的流程实例

/**
* 查询正在运行中的流程实例
* 核心表:act_ru_execution ,其中 parent_id 为空,就是正在运行的流程实例
*/
@Test
public void getProcInstListRunning() {
    List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
        .processInstanceNameLike("%请假%")
        .list();
    for (ProcessInstance pi : list) {
        System.out.println("流程定义key: " + pi.getProcessDefinitionKey());
        System.out.println("流程定义版本号:" + pi.getProcessDefinitionVersion());
        System.out.println("流程实例Id: " + pi.getProcessInstanceId());
        System.out.println("流程实例名称:" + pi.getName());
        System.out.println("业务key(业务主键id):" + pi.getBusinessKey());
        System.out.println("发起人:" + pi.getStartUserId());
        System.out.println("流程实例状态:" + ( pi.isSuspended() ? "已挂起(暂停)" : "已激活(启动)" ) );
    }
}

6.3 激活或挂起流程实例

注意:区别于5.2,5.2是修改流程定义的状态,6.3是修改流程实例的状态。

 /**
 * 激活或挂起流程实例
 * 流程定义:
 *   激活:激活流程定义后,对应所有的流程实例都可以继续向下流转
 *   挂起:挂起流程定义后,对应所有的流程实例都不可以继续向下流转
 * 流程实例:
 *  激活:激活流程实例后,此流程实例可继续向下流转。
 *  挂起:挂起流程实例后,此流程实例不可以向下流转。
 */
@Test
public void updateProcInstState() {
    String procInstId = "c1b97f6f-7d0f-11f6-bc11-2c337a6d7e1d";
    // 1. 查询指定流程实例的数据
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
        .processInstanceId(procInstId)
        .singleResult();
    // 2. 判断当前流程实例的状态
    if(processInstance.isSuspended()) {
        // 如果是已挂起,则更新为激活状态
        runtimeService.activateProcessInstanceById(procInstId);
        System.out.println("激活流程实例成功");
    }else {
        // 如果是已激活,则更新为挂起状态
        runtimeService.suspendProcessInstanceById(procInstId);
        System.out.println("挂起流程实例成功");
    }
}

6.4 删除流程实例

@Autowired
HistoryService historyService;
/**
* 删除流程实例:
* 涉及到到的数据表:
* ACT_RU_IDENTITYLINK
* ACT_RU_TASK
* ACT_RU_EXECUTION
*/
@Test
public void deleteProcInst() {
    String procInstId = "11d21740-7f7d-11f6-9f0c-2c337a6d7e1d";

    // 1. 查询指定流程实例的数据
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
        .processInstanceId(procInstId)
        .singleResult();
    if(processInstance == null) {
        System.out.println("流程实例不存在");
        return;
    }
        
    // 删除流程实例(流程实例id,删除原因), 不会删除流程实例相关历史数据
    runtimeService.deleteProcessInstance(procInstId, "xxx作废了当前流程申请");

    // 删除流程实例历史数据
    //historyService.deleteHistoricProcessInstance(procInstId);
}

七 操作任务

7.1 查询指定办理人任务

@Test
public void findWaitTask() {
    // 办理人
    String assignee = "snow";
    // 查询个人待办任务
    List<Task> list = taskService.createTaskQuery()
        .taskAssignee(assignee) // 具体办理人
        .orderByTaskCreateTime()
        .desc()
        .list();
    for (Task task : list) {
        System.out.println("流程实例ID: " + task.getProcessInstanceId());
        System.out.println("任务id: " + task.getId());
        System.out.println("任务名称:" + task.getName());
        System.out.println("任务办理人:" + task.getAssignee());
    }
}

7.2 查询指定候选人任务

@Test
public void findWaitTask() {
    // 办理人
    String assignee = "catch";
    List<Task> list = taskService.createTaskQuery()
        .taskCandidateOrAssigned(assignee) // 作为候选人或者是办理人
        .orderByTaskCreateTime()
        .desc()
        .list();
    for (Task task : list) {
        System.out.println("流程实例ID: " + task.getProcessInstanceId());
        System.out.println("任务id: " + task.getId());
        System.out.println("任务名称:" + task.getName());
        System.out.println("任务办理人:" + task.getAssignee());
    }
}

7.3 办理个人任务

/**
 * 完成任务
 */
@Test
public void completeTask() {
    String taskId = "cdbdce28-7e6e-11f6-9939-2c337a6d7e1d";
    //	办理前 获取到当前登录人 进行判断 不能办理别人的任务
    //	判断完成后,办理
    taskService.complete(taskId);
}

7.4 拾取候选人任务

候选人 是 出现在组任务中的概念,个人任务不存在候选人的说法;

在组任务中,可以指定多个办理人,多个办理人就是该组任务的候选人。

/**
* 拾取候选任务
*/
@Test
public void claimTask() {
    String taskId = "6903fa10-7e79-11f6-8210-2c337a6d7e1d";
    String userId = "catch";
    // 注意:即使拾取任务的办理人,不在候选人中,也可以进行拾取成功。
    // 所以最好在拾取之前判断是否为当前任务的候选人
    /*List<IdentityLink> identityLinkList = taskService.getIdentityLinksForTask(taskId);
        for (IdentityLink identityLink : identityLinkList) {
            System.out.println(identityLink.getUserId());
        }*/

    // 拾取候选任务,将 catch 作为该组任务的办理人
    taskService.claim(taskId, userId);
}

7.5 归还组任务

 /**
 * 任务办理人归还组任务中
 */
@Test
public void assigneeToGroupTask() {
    String taskId = "6903fa10-7e79-11f6-8210-2c337a6d7e1d";
    String assignee = "catch";  //办理人

    // 1. 查询办理人任务
    Task task = taskService.createTaskQuery()
        //.processDefinitionKey("此处是key")
        .taskAssignee(assignee)
        .singleResult();
    // 2. 归还组任务中
    if(task != null) {
        // 直接将办理人设置为null,即归还到了组任务中
        taskService.setAssignee(task.getId(), null);
    }
}

7.6 转办任务

 /**
 * 转办任务
 */
@Test
public void turnTask() {
    String taskId = "6903fa10-7e79-11f6-8210-2c337a6d7e1d";
    String assignee = "catch";  //办理人
    String candidateuser = "jones"; // 转办的目标办理人

    // 1. 查询办理人任务
    Task task = taskService.createTaskQuery()
        //.processDefinitionKey("此处写key")
        .taskAssignee(assignee)
        .taskId(taskId)
        .singleResult();

    // 将任务转办给 jones 用户
    if(task != null) {
        taskService.setAssignee(task.getId(), candidateuser);
    }
}

7.7 办理组任务

将组任务拾取后,就成为了个人任务。办理同7.3。

八 历史数据

8.1 查询个人已办任务

表 ACT_HI_TASKINST

@Autowired
HistoryService historyService;
@Test
public void findCompleteTask() {
    // 查询
    List<HistoricTaskInstance> taskList = historyService.createHistoricTaskInstanceQuery()
        .taskAssignee("catch") // 办理人
        .includeProcessVariables()
        .orderByTaskCreateTime()
        .desc() // 任务创建时间降序排列
        .finished()
        .list();
    for (HistoricTaskInstance task : taskList) {
        System.out.print(" 任务ID: " + task.getId());
        System.out.print(" ,任务名称: " + task.getName());
        System.out.print(" ,任务开始时间: " + task.getStartTime());
        System.out.print(" ,任务结束时间: " + task.getEndTime());
        System.out.print(" ,办理人: " + task.getAssignee());
        System.out.print(" ,流程定义id: " + task.getProcessDefinitionId());
        System.out.print(" ,流程实例id: " + task.getProcessInstanceId());
        System.out.print(" ,业务id: " + task.getBusinessKey());
        System.out.println(",流程变量:" + task.getProcessVariables());
    }

}

8.2 查询流程实例的办理历史节点信息

 @Test
public void historyInfo() {
    // 1. 获取查询对象
    HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery();

    // 2. 开始查询
    List<HistoricActivityInstance> list = query.processInstanceId("流程实例id")
        .orderByHistoricActivityInstanceStartTime()
        .asc()
        .list();
    for (HistoricActivityInstance hi : list) {
        System.out.print("流程定义id:" + hi.getProcessDefinitionId());
        System.out.print(",流程实例Id: " + hi.getProcessInstanceId());
        System.out.print(",节点id: " + hi.getActivityId());
        System.out.print(",节点名称:" + hi.getActivityName());
        System.out.println(",任务办理人:" + hi.getAssignee());
    }
}

8.3 查询已经结束的历史流程实例

@Test
public void getProcInstListFinish() {

    List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
        .orderByProcessInstanceEndTime()
        .desc()
        .finished()
        .list();

    for (HistoricProcessInstance hpi : list) {
        System.out.print("流程实例id: " + hpi.getId());
        System.out.print(",流程实例名称:" + hpi.getName());
        System.out.print(", 流程标识key:" + hpi.getProcessDefinitionKey());
        System.out.print(", 流程定义版本号:" + hpi.getProcessDefinitionVersion());
        System.out.print(", 业务id:" + hpi.getBusinessKey());
        System.out.print(", 流程发起人:" + hpi.getStartUserId());
        System.out.print(", 开始时间:" + hpi.getStartTime());
        System.out.print(", 结束时间:" + hpi.getEndTime());
        System.out.println(", 删除原因详情:" + hpi.getDeleteReason());
    }
}

8.4 删除已结束的历史流程实例

/**
* 删除已结束的流程实例
* ACT_HI_DETAIL
* ACT_HI_VARINST
* ACT_HI_TASKINST
* ACT_HI_PROCINST
* ACT_HI_ACTINST
* ACT_HI_IDENTITYLINK
* ACT_HI_COMMENT
*/
@Test
public void deleteFinishProcInst() {
    String procInstId = "流程实例id";
    // 1. 查询流程实例是否已结束
    HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
        .processInstanceId(procInstId)
        .finished()
        .singleResult();
    if(instance == null) {
        System.out.println("流程实例未结束或不存在");
        return;
    }
    // 2. 删除已结束流程实例(如果实例未结束,会抛出异常,如果需要删除
    historyService.deleteHistoricProcessInstance(procInstId);
}

九 加流程参数

9.1 启动时加入

@Autowired
RuntimeService runtimeService;
// 启动流程实例
@Test
public void startProcess() {
    // 流程变量
    Map<String, Object> variables = new HashMap<>();
    // 加入参数
    variables.put("duration", 2);
    runtimeService.startProcessInstanceByKey("testInclusiveGateway", variables);
}

9.2 办理任务时加入流程参数

@Autowired
TaskService taskService;
@Test
public void complete() {
    String taskId = "5a17a43e-8010-11f6-9aa6-2c337a6d7e1d";
  
    // 流程变量
    Map<String, Object> variables = new HashMap<>();
    // 请假天数,
    variables.put("duration", 5);
    taskService.complete(taskId, variables);
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>