SpringBoot 集成Activiti 流程设计器

Posted 在奋斗的大道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot 集成Activiti 流程设计器相关的知识,希望对你有一定的参考价值。

开发环境版本:

SpringBoot-2.2.1

Activiti-5.22

mysql8

IDEA2022 社区免费版

下载Activities-5.22 源码包

官网下载地址:Get started | Activiti

 

导入Activiti 流程设计器依赖静态资源文件:

将activiti-5.22.0/war/activiti-explorer.war解压
将文件夹内 diagram-viewer,editor-app,modeler.html拷贝到项目中resource/public目录下如图

 温馨提示:editor-app就是编辑器,modeler.html是编辑器的入口页面。

温馨提示:在editor-app/app-cfg.js中配置一下项目url。设置访问项目前缀,我下面设置为'',因为我没有指定SpringBoot项目名称。

至此,前端相关页面已经全部配置完毕。

SmartOA 项目pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>


    <groupId>org.example</groupId>
    <artifactId>SmartOA</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot-admin.version>2.1.1</spring-boot-admin.version>
        <spring-cloud.version>Greenwich.RC2</spring-cloud.version>
        <activiti.version>5.22.0</activiti.version>
    </properties>

    <dependencies>
        <!--springboot 与 mybatis 集成 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- 数据库连接池druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- MySQL start -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>

        <!-- spring boot start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- activiti start -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>5.22.0</version>
        </dependency>

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

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>5.22.0</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-explorer</artifactId>
            <version>5.22.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.vaadin.addons</groupId>
                    <artifactId>dcharts-widget</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-simple-workflow</artifactId>
            <version>5.22.0</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>5.22.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-codec</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-css</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-svg-dom</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-svggen</artifactId>
            <version>1.7</version>
        </dependency>
        <!-- activiit end-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>



</project>

SmartOA 项目配置文件application.properties

server.prot=8082
# ???????
spring.datasource.url=jdbc:mysql://localhost:3306/smart_oa?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# druid ??
# ?????????????
spring.datasource.druid.initial-size=5
# ???????
spring.datasource.druid.max-active=30
# ???????
spring.datasource.druid.min-idle=5
# ????????????????
spring.datasource.druid.max-wait=60000
# ???????????????????????????????
spring.datasource.druid.time-between-eviction-runs-millis=60000
# ????????????????
spring.datasource.druid.min-evictable-idle-time-millis=300000
# ???????????sql??????????
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
# ?????true?????????????????????????????????timeBetweenEvictionRunsMillis???validationQuery?????????
spring.datasource.druid.test-while-idle=true
# ???????validationQuery?????????????????????
spring.datasource.druid.test-on-borrow=false
# ???????validationQuery?????????????????????
spring.datasource.druid.test-on-return=false
# ????preparedStatement????PSCache?PSCache???????????????????oracle??mysql??????
spring.datasource.druid.pool-prepared-statements=true
# ???PSCache???????0????0??poolPreparedStatements???????true?
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50
# ?????????filters????????sql????
spring.datasource.druid.filters=stat,wall,slf4j
# ??connectProperties?????mergeSql????SQL??
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# ????DruidDataSource?????
spring.datasource.druid.use-global-data-source-stat=true
##### WebStatFilter?? #######
#??StatFilter
spring.datasource.druid.web-stat-filter.enabled=true
#??????
spring.datasource.druid.web-stat-filter.url-pattern=/*
#????????url
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#??session????
spring.datasource.druid.web-stat-filter.session-stat-enable=true
#??sessionStatMaxCount?1000?
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#spring.datasource.druid.web-stat-filter.principal-session-name=
#spring.datasource.druid.web-stat-filter.principal-cookie-name=
#spring.datasource.druid.web-stat-filter.profile-enable=
##### StatViewServlet?? #######
#?????????
spring.datasource.druid.stat-view-servlet.enabled=true
#?????????
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#?? Reset All ??
spring.datasource.druid.stat-view-servlet.reset-enable=false
#???????
spring.datasource.druid.stat-view-servlet.login-username=admin
#??????
spring.datasource.druid.stat-view-servlet.login-password=123
#??????allow?????????????????
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#????deny???allow????deny???????allow??????????
spring.datasource.druid.stat-view-servlet.deny=
# mybatis ??
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.zzg.model
# activiti ??
spring.activiti.database-schema-update=true
spring.activiti.check-process-definitions=false

# ????????
logging.level.com.zzg.mapper=debug

 SmartOA 项目 添加Activiti自带Controller

将activiti-5.22.0\\libs\\activiti-modeler-5.22.0-sources.jar,将StencilsetRestResource.java,
ModelEditorJsonRestResource.java,ModelSaveRestResource.java三个文件拷贝到controller目录

温馨提示:类名上不要忘记添加@RestController 注解标签。

SmartOA项目禁用Activiti自动集成的security的权限验证

package com.zzg;

import org.activiti.spring.boot.SecurityAutoConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
@MapperScan("com.zzg.mapper")
public class OAApplication 
    public static void main(String[] args) 
        SpringApplication.run(OAApplication.class, args);
    

SmartOA项目 新增Activiti 流程设计器Controller

功能说明:在该Controller新增一个model(模型),根据返回的model(模型)编号跳转至Activiti流程设计器HTML页面。

package com.zzg.controller;


import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.Task;

import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;



@RequestMapping("/activiti")
@RestController
public class ActivitiController 
    private static Logger logger= LoggerFactory.getLogger(ActivitiController.class);
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private ProcessEngine processEngine;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 根据Model部署流程
     */
    @PostMapping(value = "deploy/modelId")
    public void deploy(@PathVariable("modelId") String modelId) 
        try 
            Model modelData = repositoryService.getModel(modelId);
            ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            byte[] bpmnBytes = null;

            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            bpmnBytes = new BpmnXMLConverter().convertToXML(model);

            String processName = modelData.getName() + ".bpmn20.xml";
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionResourceName(processName).list();
            for (ProcessDefinition definition:list)
                //删除原有流程定义,正在使用的流程定义无法删除
                repositoryService.deleteDeployment(definition.getDeploymentId());
            
            Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes)).deploy();
            System.out.println("流程部署id----"+deployment.getId());
         catch (Exception e) 
            logger.error("根据模型部署流程失败:modelId=", modelId, e);
        
    

    /**
     * 新建模型
     * @return
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/create")
    public ModelAndView newModel() throws UnsupportedEncodingException 
//    	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//    	RepositoryService repositoryService = processEngine.getRepositoryService();
//    	ObjectMapper objectMapper = new ObjectMapper();
        //初始化一个空模型
        Model model = repositoryService.newModel();

        //设置一些默认信息,可以用参数接收
        int revision = 1;
        String key = "process";
        String name = "process";
        String description = "新建流程模型";

        //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);


        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();

        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));
        return new ModelAndView("redirect:/modeler.html?modelId=" + id);
    

    /**
     * 模型编辑
     * @param modelId
     * @return
     */
    @GetMapping("/update/modelId")
    public ModelAndView updateModel(@PathVariable String modelId) 
        System.out.println("用户点击模型编辑");
        return new ModelAndView("redirect:/modeler.html?modelId=" + modelId);
    


    /**
     * 导出指定模型xml 文件
     * @param modelId
     * @param response
     */
    @GetMapping(value = "export/modelId")
    public void export(@PathVariable String modelId, HttpServletResponse response) 
        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);
            OutputStream outputStream = response.getOutputStream();
            IOUtils.copy(in, outputStream);
            String filename = bpmnModel.getMainProcess().getId() + ".bpmn.xml";
            response.setHeader("content-type", "application/octet-stream");
            response.setContentType("application/octet-stream;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
            outputStream.flush();
            outputStream.close();
         catch (Exception e) 
            logger.error("导出model的xml文件失败:", e.getMessage(), e);
        
    

    @GetMapping("import")
    public ModelAndView importXml(HttpServletResponse response) 

        try 
            File file = new File("E:\\\\activiti_xml\\\\未定义.bpmn.xml");

            InputStream fileInputStream = new FileInputStream(file);
            Deployment deployment = repositoryService.createDeployment()
                    .addInputStream("请假流程" + ".bpmn", fileInputStream)
                    .deploy();
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
            String modelId = changeProcessToModel(processDefinition);
            return new ModelAndView("redirect:/modeler.html?modelId=" + modelId);
         catch (Exception e) 
           logger.error("模型基于xml导入异常:",e.getMessage(), e);
        
        return new ModelAndView("redirect:/modelError.html");
    

    /**
     * 流程转化为可编辑模型
     *
     * @param processDefinition
     */

    public String changeProcessToModel(ProcessDefinition processDefinition) 

        Model modelData = repositoryService.newModel();
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 初始化Model
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setName(processDefinition.getName());
        modelData.setKey(processDefinition.getKey());

        // 保存模型
        repositoryService.saveModel(modelData);
        String deploymentId = processDefinition.getDeploymentId();
        String processDefineResourceName = null;
        // 通过deploymentId取得某个部署的资源的名称
        List<String> resourceNames = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
        if (resourceNames != null && resourceNames.size() > 0) 
            for (String temp : resourceNames) 
                if (temp.indexOf(".bpmn") > 0) 
                    processDefineResourceName = temp;
                
            
        
        InputStream bpmnStream = processEngine.getRepositoryService().getResourceAsStream(deploymentId, processDefineResourceName);
        createModelByInputStream(bpmnStream, modelData.getId());
        return modelData.getId();
    

    public void createModelByInputStream(InputStream bpmnStream, String ModelID) 
        XMLInputFactory xif;
        InputStreamReader in = null;
        XMLStreamReader xtr = null;
        try 
            xif = XMLInputFactory.newFactory();
            in = new InputStreamReader(bpmnStream, "UTF-8");
            xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = (new BpmnXMLConverter()).convertToBpmnModel(xtr);
            ObjectNode modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);
            repositoryService.addModelEditorSource(ModelID, modelNode.toString().getBytes("UTF-8"));
         catch (XMLStreamException e) 
            e.printStackTrace();
         catch (UnsupportedEncodingException e) 
            e.printStackTrace();
         finally 
            if (xtr != null) 
                try 
                    xtr.close();
                 catch (XMLStreamException e) 
                    e.printStackTrace();
                
            
            if (in != null) 
                try 
                    in.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
            if (bpmnStream != null) 
                try 
                    bpmnStream.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

    @GetMapping("png/taskId")
    public void currentProcessInstanceImage(@PathVariable("taskId") String taskId, HttpServletResponse response) throws IOException 
        int index;
        InputStream inputStream = this.currentProcessInstanceImage(taskId);
        OutputStream out = response.getOutputStream();
        response.setContentType("image/png");
        byte[] bytes = new byte[1024];
        while ((index = inputStream.read(bytes)) != -1) 
            out.write(bytes, 0, index);
            out.flush();
        
        out.close();
        inputStream.close();

    

    /**
     * 获取当前任务流程图
     *
     * @param taskId
     * @return
     */
    public InputStream currentProcessInstanceImage(String taskId) 

        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(task.getProcessDefinitionId());
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        // ID 为 流程定义Key
        org.activiti.bpmn.model.Process process = bpmnModel.getProcessById(processDefinition.getKey());

        //Process process = bpmnModel.getProcessById(processDefinition.getKey());

//        UserTask userTask = (UserTask) process.getFlowElement(task.getTaskDefinitionKey());
        // 流程节点ID
        FlowElement flowElement = process.getFlowElement(task.getTaskDefinitionKey());

        DefaultProcessDiagramGenerator generator = new DefaultProcessDiagramGenerator();


        List<String> highLightedActivities = new ArrayList<>();
        highLightedActivities.add(flowElement.getId());

//     生成流程图
//        InputStream inputStream = generator.generateJpgDiagram(bpmnModel);
//        InputStream inputStream = generator.generatePngDiagram(bpmnModel);
//        InputStream inputStream = generator.generateDiagram(bpmnModel, "jpg", highLightedActivities);

// 生成图片
        InputStream inputStream = generator.generateDiagram(bpmnModel, "jpg", highLightedActivities, Collections.emptyList(), "宋体", "宋体", "宋体", null, 2.0);
        return inputStream;
    




SmartOA项目 Activiti 汉化

Activiti汉化:将网络上下载的Activiti 汉化文件拷贝至在resources下。

 SmartOA项目启动后,activiti 关联表自动创建

  SmartOA项目启动后,访问http://localhost:8080/activiti/create

 GitHub地址:https://github.com/zhouzhiwengang/SpringBoot-Project.git

以上是关于SpringBoot 集成Activiti 流程设计器的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot2集成Activiti6

activiti流程图在线绘制业务申请审批流转跟进催办,springboot集成工作流基础框架

activiti流程图在线绘制业务申请审批流转跟进催办,springboot集成工作流基础

SpringBoot 集成Activiti 实现Model(模型)管理

Activiti 7 springboot 工作流引擎

37Springboot集成Flowable