1、整合activiti6.0

添加依赖

spring boot中maven 添加activiti6.0依赖

<dependency>
	<groupId>org.activiti</groupId>
	<artifactId>activiti-spring-boot-starter-basic</artifactId>
	<version>6.0.0</version>
</dependency>

activiti配置

在application.yaml中添加

activiti:
    check-process-definitions: false # activti是否自动部署,不加载bpmn文件
    db-identity-used: true #是否使用activti自带的用户体系  activit自带用户表中可以没有数据但是表一定要存在
    database-schema-update: true #是否每次都更新数据库
    history-level: full  #full最高级别

spring boot的启动类@SpringBootApplication需要排除Security,否则启动项目的时候会报错,并可能与Shiro或其他插件有影响。

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)

actiBPM插件使用,流程测试

完成以上步骤就可以使用工作流了,测试工作流的话可以在整合完成在线设计以后一起测试。因为在idea中画activiti流程的插件actiBPM体验不是很好。如果用插件画流程图不如使用eclipse。
如果是在idea编辑器中没有整合在线设计的话可以用下面的方式测试一下是否整合成功。

安装插件actiBPM。

idea2020好像就搜索不到这个插件了,只能去插件市场下载,手动添加上去。

链接:https://plugins.jetbrains.com/search?products=idea&search=actiBPM。

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_java

新建bpmn文件

在resources目录下新建test01.bpmn文件。绘制一个如下的简单流程。(为什么说这个插件不好用呢。因为由很多事件找不到了,像信号启动事件,信号结束事件,子流程等等都找不到在什么地方。而且不知道这么设置节点的属性,监听等等。奇奇怪怪,玩不明白。不知道是不是我的打开方式不对。反正玩不明白这个插件)

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_html_02


这个bpmn文件可以在notepad++da打开

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/testm1604023359723" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1604023359723" name="" targetNamespace="http://www.activiti.org/testm1604023359723" typeLanguage="http://www.w3.org/2001/XMLSchema">
  <process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">
    <startEvent id="_2" name="StartEvent"/>
    <userTask activiti:exclusive="true" id="_3" name="apply"/>
    <userTask activiti:exclusive="true" id="_4" name="Approval"/>
    <endEvent id="_5" name="EndEvent"/>
    <sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
    <sequenceFlow id="_7" sourceRef="_3" targetRef="_4"/>
    <sequenceFlow id="_8" sourceRef="_4" targetRef="_5"/>
  </process>
  <bpmndi:BPMNDiagram documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
    <bpmndi:BPMNPlane bpmnElement="myProcess_1">
      <bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
        <dc:Bounds height="32.0" width="32.0" x="300.0" y="50.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
        <dc:Bounds height="55.0" width="85.0" x="275.0" y="135.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
        <dc:Bounds height="55.0" width="85.0" x="285.0" y="250.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
        <dc:Bounds height="32.0" width="32.0" x="330.0" y="365.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_3">
        <di:waypoint x="316.0" y="82.0"/>
        <di:waypoint x="316.0" y="135.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_3" targetElement="_4">
        <di:waypoint x="322.5" y="190.0"/>
        <di:waypoint x="322.5" y="250.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_4" targetElement="_5">
        <di:waypoint x="346.0" y="305.0"/>
        <di:waypoint x="346.0" y="365.0"/>
        <bpmndi:BPMNLabel>
          <dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

看这个标签,id="myProcess_1"就是在启动流程的时候会用到(再吐槽一下这个插件,找不到再那边设置这个id,修改这个id我都是打开这个文件直接修改的,真玩不明白。)

<process id="myProcess_1" isClosed="false" isExecutable="true" processType="None">

部署文件

流程文件有了以后需要部署,部署后会在act_re_procdef(流程定义表中插入流程定义)。KEY_这个字段就是启动流程的key

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_03

启动流程,完成任务

@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
/**
 * 测试启动
 */
public void testStart(String key){
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);
    System.out.println("流程启动成功,流程id:"+processInstance.getId());
}

/**
 * 测试任务完成
 */
public void  testCompleteTask(String processInstanceId){
    //根据流程id获取当前任务
    org.activiti.engine.task.Task task = taskService.createTaskQuery()
            .processInstanceId(processInstanceId)
            .active()
            .singleResult();
    taskService.complete(task.getId());
    System.out.println();
    System.out.println("完成任务,任务名称:"+task.getName()+"任务id:"+task.getId());
    //获取当前任务后面的任务节点
    List<Task> nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
    if(nextTask!=null && nextTask.size() > 0){
        System.out.println();
        System.out.println("下个任务名称:"+nextTask.get(0).getName()+"下个任务id:"+nextTask.get(0).getId());
    }else {
        System.out.println();
        System.out.println("流程结束");
    }
}

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_html_04

2、整合在线设计

在线设计组件下载

下载链接:https://github.com/Activiti/Activiti/releases/tag/activiti-5.22.0

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_05

导入组件

下载activiti-5.22.0解压

导入的Java类:解压activiti-modeler-5.22.0-sources.jar(在activiti-5.22.0\activiti-5.22.0\libs目录)。解压后将activiti-modeler-5.22.0-sources\org\activiti\rest\editor下的三个类复制到项目中![在这里插入图片描述](

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_System_06

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_java_07


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_html_08


导入静态资源:解压activiti-explorer.war(在activiti-5.22.0\activiti-5.22.0\wars目录下),解压后将diagram-viewer,editor-app问价夹和modeler.html,stencilest.json拷贝到项目static目录下。汉化需要修改stencilest.json,按版本百度。

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_activiti_09


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_10


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_11


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_12


确认app-cfg.js中contextRoot和第一步导入java类步骤中的三个类的@RequestMapping的value相同。这三个默认@RequestMapping(value = “services”),如果修改这个url,app-cfg.js中contextRoot也需要修改成一样的value。

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_System_13


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_System_14

访问在线设计

访问在线设计直接访问modeler.html就可以了。注意:在直接访问modeler.html的时

候必须要带上参数modelId=xxxx(示例:http://localhost:8080/modeler.html?modelId=7501)。否则打开页面回事空白的,因为在线设计传入modelId才知道修改那个model

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_15


没有modelId打开在线设计的时候会出现空白,model可以通过java代码创建

public ModelAndView newModel() throws UnsupportedEncodingException {
        //初始化一个空模型
        Model model = repositoryService.newModel();

        //设置一些默认信息,可以用参数接收
        int revision = 1;
        String key = "process";
        String name = "new-process";
        String description = "";

        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);

        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());

        repositoryService.saveModel(model);
        String id = model.getId();

        //ModelEditorSource
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace","http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset" , stencilSetNode);
        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));
        return new ModelAndView("redirect:/modeler.html?modelId=" + id);
}

这个样子如果需要修改model就要到手动查找一下modelId,并不是很方便。可以自己写一个model-list.html。展示所有的model,提供新建按钮等等。
model-list.html代码如下:

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>model列表</title>
</head>
<body>
<div class="info"><span th:text="${info}"></span></div>
<a href="/activiti/page/create">新建</a>

<table class="table">
    <thead>
    <tr>
        <th>ID</th>
        <th>模型名称</th>
        <th>key</th>
        <th>版本</th>
        <th>部署ID</th>
        <th>创建时间</th>
        <th>最后更新时间</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="data : ${models}">
        <td th:text="${data.id}"></td>
        <td><a th:href="@{/modeler.html(modelId=${data.id})}" class="font-blue" th:text="${data.name}"></a>
        </td>
        <td th:text="${data.key}"></td>
        <td th:text="${data.version}"></td>
        <td th:text="${data.deploymentId}"></td>
        <td th:text="${data.createTime}"> 2018-02-25 17:28:35</td>
        <td th:text="${data.lastUpdateTime}"> 2018-02-25 17:28:35</td>
        <td>
            <a th:href="${'/activiti/deploy/'+data.key}" th:attrappend="objectId=${data.id}" lass="font-blue deployBtn">发布</a>
            <a th:href="${'/activiti/exportXML/'+data.id}" class="font-blue">导出</a>
        </td>
    </tr>
    </tbody>
</table>
</body>
</html>
@Controller
@RequestMapping("activiti/page")
public class PageController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 查询所有模型,返回默模型列表视图
     * @param model
     * @return
     */
    @GetMapping
    public String toModelListPage(org.springframework.ui.Model model){
        List<Model> models = repositoryService.createModelQuery().orderByCreateTime().desc().list();
        model.addAttribute("models",models);
        return "page/model/model-list";
    }

    /**
     * 创建一个空model,重定向到modeler.html
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/create")
    public ModelAndView newModel() throws UnsupportedEncodingException {
        //初始化一个空模型
        Model model = repositoryService.newModel();

        //设置一些默认信息,可以用参数接收
        int revision = 1;
        String key = "process";
        String name = "new-process";
        String description = "";

        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);

        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());

        repositoryService.saveModel(model);
        String id = model.getId();

        //ModelEditorSource
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace","http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset" , stencilSetNode);
        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));
        return new ModelAndView("redirect:/modeler.html?modelId=" + id);
    }

}

导出model为xml方法

public void export(@PathVariable("modelId") String modelId, HttpServletResponse response) {
        response.setContentType("text/html; charset=UTF-8"); //转码

        try {
            Model modelData = repositoryService.getModel(modelId);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);

            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);

            String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
            response.setContentType("application/xml");
            response.setHeader("Content-Disposition", "attachment; filename=" + filename);
            IOUtils.copy(in, response.getOutputStream());  //这句必须放到setHeader下面,否则10K以上的xml无法导出,
            response.flushBuffer();
        } catch (Exception e) {
            throw new BaseException("activiti model导出xml异常");

        }
    }

保存model出错

保存model调用ModelSaveRestResource类中的saveModel方法。在方法中@RequestBody序列化。所以在保存model的时候如果出现参数接受不了的问题就需要这边的代码

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_System_16


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_xml_17

测试

绘制流程后保存

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_System_18


Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_activiti_19


在model-list可以看到modelId也可以编辑和新建model

Spring Boot 如何整合activiti 流程引擎 springboot和activiti整合_activiti_20


部署流程代码如下,启动流程,完成任务就和actiBPM插件那边一样

/**
 * 根据modelId部署流程
 */
public String deploy(String id){
        //获取模型
        Model modelData = repositoryService.getModel(id);
        byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
        if (bytes == null) {
            return "模型数据为空,请先设计流程并成功保存,再进行发布。";
        }
        JsonNode modelNode = null;
        try {
            modelNode = new ObjectMapper().readTree(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
        if(model.getProcesses().size()==0){
            return "数据模型不符要求,请至少设计一条主线流程。";
        }
        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);

        //发布流程
        String processName = modelData.getName() + ".bpmn20.xml";
        Deployment deployment = null;
        try {
            deployment = repositoryService.createDeployment()
                    .name(modelData.getName())
                    .addString(processName, new String(bpmnBytes, "UTF-8"))
                    .deploy();
        } catch (UnsupportedEncodingException e) {
           e.printStackTrace();
        }
        modelData.setDeploymentId(deployment.getId());
        repositoryService.saveModel(modelData);
        return "流程发布成功";
    }