流程部署管理

部署一个流程,需要加载两个流程资源文件:

  1. .bpmn流程定义文件
  2. .png流程图文件

部署流程,其实就是Activiti框架读取资源文件,并在数据表中生成相应部署实例的记录(包括部署ID等)。流程部署后,才能继续生成流程实例、开启流程。这篇笔记介绍如何部署流程,以及如何读取、删除部署的资源。Activiti中,和部署相关的方法都在RepositoryService接口中。

注:其中,流程图文件是可选的,Activiti能够自动根据流程定义生成流程图,但是它生成的流程图有中文乱码的问题,所以一般还是建议两个文件都部署。

部署流程资源

之前我们写过的例子中,流程资源文件是通过放置在src/main/resources下,然后通过classpath方式加载的;除此之外,在使用activiti-explorer时,我们也可以通过.zip包的形式加载流程。

从classpath加载资源

从classpath加载流程定义的方式,一般适用于不需要用户或管理员自定义部署流程的简单应用。流程定义文件由程序员编写并放置在Java类目录下,系统只支持固定的几个流程。

这里我们有如下目录形式的代码结构:

src/main/resources/
|_activiti
    |_UserTaskProcess.bpmn
    |_UserTaskProcess.png

从classpath部署流程的例子代码如下:

// 获取repositoryService
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();

// 流程定义文件
String bpmnClasspath = "activiti/UserTaskProcess.bpmn";
String pngClasspath = "activiti/UserTaskProcess.png";

// 构建部署
DeploymentBuilder builder = repositoryService.createDeployment();
builder.addClasspathResource(bpmnClasspath);
builder.addClasspathResource(pngClasspath);
builder.deploy();

注意:Activiti引擎部署流程后,流程内容会以longblob字段形式存储在数据库表(act_ge_bytearray)中,而不是需要再次读取classpath下的文件。

从zip包中加载资源

从zip包加载流程定义,一般适用于允许系统管理员用户自行上传流程的较复杂的系统。流程定义的.bpmn.png两个文件压缩到同一个.zip压缩包中,然后交给Activiti引擎读取。

因为zip包一定是通过客户端(浏览器)上传的,这里我们基于SpringMVC来编写一个例子。具体有关Spring和Activiti整合的内容,将在后续章节详细介绍。

UploadProcessController.java

package com.gacfox.demo.controller;

import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipInputStream;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.DeploymentBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping(value = "/upload")
public class UploadProcessController {

    @RequestMapping(value = "", method = RequestMethod.GET)
    public String index() {
        return "upload";
    }

    @RequestMapping(value = "/do_upload", method = RequestMethod.POST)
    public String doUpload(@RequestParam(name = "file") MultipartFile file) {

        // 获取repositoryService
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();

        try {
            // 获取上传的zip输入流
            InputStream is = file.getInputStream();
            ZipInputStream zis = new ZipInputStream(is);

            // 构建部署
            DeploymentBuilder builder = repositoryService.createDeployment();
            builder.addZipInputStream(zis);
            builder.deploy();

        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }

        return "success";
    }
}

实际上,部署流程的代码和之前classpath方式区别不大,都是使用DeploymentBuilder这个类来读取输入资源,只不过这里读取的是zip输入流ZipInputStream

读取流程资源

读取部署列表

Activiti的查询接口能够以列表的形式返回所有已部署的流程,下面是一个读取部署列表的例子:

DeployProcessController.java

package com.gacfox.demo.controller;

import java.util.List;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "/deploy")
public class DeployProcessController {

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String listDeployment(Model model) {
        // 获取repositoryService
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();

        // 查询全部部署的流程
        List<ProcessDefinition> definitions = repositoryService.createProcessDefinitionQuery().list();

        model.addAttribute("definitions", definitions);
        return "definitions";
    }
}

下面例子页面,以表格的形式展示读取的相关信息。

definitions.html

<!doctype html>
<html lang="zh-cmn-Hans" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>流程部署管理</title>
</head>

<body>
    <div>
        <table border="1">
            <tr>
                <th>流程定义ID</th>
                <th>部署ID</th>
                <th>流程名</th>
                <th>流程定义键</th>
                <th>描述信息</th>
                <th>流程版本</th>
                <th>流程定义BPMN资源名</th>
                <th>流程定义PNG资源名</th>
                <th>操作</th>
            </tr>
            <tr th:each="d : ${definitions}">
                <td th:text="${d.id}"></td>
                <td th:text="${d.deploymentId}"></td>
                <td th:text="${d.name}"></td>
                <td th:text="${d.key}"></td>
                <td th:text="${d.description}"></td>
                <td th:text="${d.version}"></td>
                <td th:text="${d.resourceName}"></td>
                <td th:text="${d.diagramResourceName}"></td>
                <td>
                    <a th:href="@{/deploy/deployment_img(pid=${d.id},resourceName=${d.resourceName})}">XML</a>
                    <a th:href="@{/deploy/deployment_img(pid=${d.id},resourceName=${d.diagramResourceName})}">流程图</a>
                    <a th:href="@{/deploy/delete(pid=${d.id})}">删除</a>
                </td>
            </tr>
        </table>
    </div>
</body>

</html>

查看流程XML和流程图

流程部署到系统后,查看流程定义和流程图是个很常见的需求,我们只要从Activiti部署的资源中读取两个资源文件就行了。下面是读取部署资源的例子代码:

@RequestMapping(value = "/deployment_img", method = RequestMethod.GET)
public void getProcessImage(String pid, String resourceName, HttpServletResponse response) {
    // 获取repositoryService
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();

    // 读取部署资源
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(pid).singleResult();

    // 输出内容
    InputStream is = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
    byte[] buffer = new byte[1024];
    int len = -1;
    try {
        while ((len = is.read(buffer, 0, 1024)) != -1) {
            response.getOutputStream().write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

删除部署

删除部署实现就非常简单了,也是调用RepositoryService相关方法即可。

@RequestMapping(value = "/delete", method = RequestMethod.GET)
public String deleteDeployment(String deploymentId) {
    // 获取repositoryService
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();

    // 删除部署
    repositoryService.deleteDeployment(deploymentId);

    return "success";
}
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。