Flowable在线流程图绘制:从入门到实战

前言

在企业级应用开发中,工作流引擎是不可或缺的组件。Flowable作为一个轻量级、开源的工作流和业务流程管理(BPM)平台,被广泛应用于各类业务系统中。然而,传统的流程设计方式需要借助专门的建模工具(如Flowable Modeler、Camunda Modeler等),操作复杂且不够直观。

本文将介绍如何基于 Flowable 6.8.0bpmn-js 构建一个在线流程图绘制系统,实现:

  • 在线绘制 BPMN 2.0 流程图
  • 流程定义保存到 MySQL 数据库
  • 一键部署流程到 Flowable 引擎
  • 完整的前后端分离架构

一、为什么选择 bpmn-js?

1.1 bpmn-js 简介

bpmn-js 是一个由 Camunda 开发的 BPMN 2.0 建模工具库,纯 JavaScript 实现,可以直接在浏览器中运行。它提供了:

  • 完整的 BPMN 2.0 支持:支持所有 BPMN 2.0 元素和属性
  • 轻量级:无需安装任何插件或软件
  • 可定制:支持自定义样式和行为
  • 开源免费:采用开源协议,可商用

1.2 bpmn-js vs 传统建模工具

特性bpmn-jsFlowable ModelerEclipse Plugin
安装方式无需安装需要部署需要安装
使用环境浏览器独立应用Eclipse IDE
协作支持易于集成困难困难
定制能力
学习曲线平缓陡峭陡峭

二、系统架构设计

2.1 整体架构

本系统采用前后端分离的架构设计,主要包括以下层次:

表现层(Presentation Layer)

  • Vue.js 3.x 前端框架
  • bpmn-js 流程设计器
  • Element Plus UI 组件库

业务服务层(Business Service Layer)

  • Spring Boot 2.7 应用框架
  • 流程设计服务、部署服务、实例服务
  • RESTful API 接口层

流程引擎层(Process Engine Layer)

  • Flowable 6.8.0 流程引擎
  • 流程定义引擎、任务引擎、历史引擎

数据存储层(Data Storage Layer)

  • MySQL 8.0 存储流程定义、实例、任务数据
  • Redis 6.x 缓存、会话管理
  • MinIO 对象存储(可选)

2.2 核心交互流程

  1. 用户在前端界面点击"新建流程"
  2. 前端请求后端 API 创建流程定义
  3. 后端调用 Flowable 的 RepositoryService
  4. Flowable 引擎将流程定义保存到数据库
  5. 返回流程定义 ID 给前端
  6. 用户使用 bpmn-js 绘制流程图
  7. 保存时将 BPMN XML 发送到后端
  8. 后端更新数据库中的流程定义

三、环境准备

3.1 开发环境要求

软件版本说明
JDK1.8+Java 开发环境
Maven3.6+项目构建工具
MySQL5.7+ 或 8.0+关系数据库
Node.js14+前端开发(可选)

3.2 技术栈版本

后端技术栈

  • Spring Boot 2.7.18
  • Flowable 6.8.0
  • MyBatis Plus 3.5.2
  • MySQL Connector 8.0.28

前端技术栈

  • bpmn-js 14.0.0
  • jQuery 3.6.0
  • Bootstrap 5(可选)

四、项目初始化

4.1 创建 Maven 项目

使用 Spring Initializr 创建项目,或手动创建 pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
</parent>

<dependencies>
    <!-- Flowable Starter -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.8.0</version>
    </dependency>

    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
</dependencies>

4.2 数据库初始化

创建数据库和流程定义表:

CREATE DATABASE flowable_online CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE flowable_online;

CREATE TABLE flow_definition (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    process_key VARCHAR(255NOT NULL,
    process_name VARCHAR(255NOT NULL,
    version INT NOT NULL DEFAULT 1,
    bpmn_xml LONGTEXT NOT NULL,
    description TEXT,
    category VARCHAR(255),
    created_by VARCHAR(100),
    deployed TINYINT DEFAULT 0,
    deployment_id VARCHAR(64),
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0,
    UNIQUE KEY uk_process_key_version (process_key, version, deleted)
);

五、后端实现

5.1 实体类设计

创建流程定义实体类:

@Data
@TableName("flow_definition")
public class FlowDefinition {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String processKey;
    private String processName;
    private Integer version;
    private String bpmnXml;
    private String description;
    private String category;
    private String createdBy;
    private Integer deployed;
    private String deploymentId;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;

    @TableLogic
    private Integer deleted;
}

5.2 数据访问层

使用 MyBatis Plus 的 Mapper 接口:

@Mapper
public interface FlowDefinitionMapper extends BaseMapper<FlowDefinition> {
    List<FlowDefinitionselectByProcessKey(@Param("processKey") String processKey);
    List<FlowDefinitionselectByCreatedBy(@Param("createdBy") String createdBy);
}

5.3 服务层实现

流程设计核心服务:

package com.example.flowable.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.flowable.entity.FlowDefinition;
import com.example.flowable.entity.Result;
import com.example.flowable.exception.FlowableException;
import com.example.flowable.repository.FlowDefinitionMapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.List;
import java.util.zip.ZipInputStream;

/**
 * 流程设计服务实现类
 * 提供流程定义的保存、查询、删除、部署等功能
 * @version 1.0.0
 */
@Slf4j
@Service
public class FlowDesignService extends ServiceImpl<FlowDefinitionMapper, FlowDefinition> {

    @Autowired
    private FlowDefinitionMapper flowDefinitionMapper;

    @Autowired
    private RepositoryService repositoryService;

    /**
     * 保存流程定义
     *
     * @param flowDefinition 流程定义实体
     * @return 保存结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<FlowDefinition> saveFlowDefinition(FlowDefinition flowDefinition) {
        try {
            // 验证必填字段
            if (!StringUtils.hasText(flowDefinition.getProcessKey())) {
                return Result.fail("流程Key不能为空");
            }
            if (!StringUtils.hasText(flowDefinition.getProcessName())) {
                return Result.fail("流程名称不能为空");
            }
            if (!StringUtils.hasText(flowDefinition.getBpmnXml())) {
                return Result.fail("BPMN XML内容不能为空");
            }

            // 检查流程Key是否已存在(同版本)
            QueryWrapper<FlowDefinition> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("process_key", flowDefinition.getProcessKey())
                    .eq("version", flowDefinition.getVersion() != null ? flowDefinition.getVersion() : 1)
                    .eq("deleted"0);
            Long count = flowDefinitionMapper.selectCount(queryWrapper);
            if (count > 0) {
                return Result.fail("流程Key和版本号已存在,请修改版本号");
            }

            // 设置默认值
            if (flowDefinition.getVersion() == null) {
                flowDefinition.setVersion(1);
            }
            if (flowDefinition.getCreateTime() == null) {
                flowDefinition.setCreateTime(LocalDateTime.now());
            }
            flowDefinition.setUpdateTime(LocalDateTime.now());
            flowDefinition.setDeleted(0);
            flowDefinition.setDeployed(0);

            // 保存到数据库
            flowDefinitionMapper.insert(flowDefinition);

            log.info("保存流程定义成功: id={}, key={}, name={}",
                    flowDefinition.getId(), flowDefinition.getProcessKey(), flowDefinition.getProcessName());

            return Result.success("流程定义保存成功", flowDefinition);
        } catch (Exception e) {
            log.error("保存流程定义失败: {}", e.getMessage(), e);
            return Result.fail("保存流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 更新流程定义
     *
     * @param flowDefinition 流程定义实体
     * @return 更新结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<FlowDefinition> updateFlowDefinition(FlowDefinition flowDefinition) {
        try {
            if (flowDefinition.getId() == null) {
                return Result.fail("流程定义ID不能为空");
            }

            FlowDefinition existing = flowDefinitionMapper.selectById(flowDefinition.getId());
            if (existing == null) {
                return Result.fail("流程定义不存在");
            }

            // 已部署的流程不能修改
            if (existing.getDeployed() == 1) {
                return Result.fail("已部署的流程不能修改,请新建版本");
            }

            flowDefinition.setUpdateTime(LocalDateTime.now());
            int rows = flowDefinitionMapper.updateById(flowDefinition);

            if (rows > 0) {
                log.info("更新流程定义成功: id={}", flowDefinition.getId());
                return Result.success("流程定义更新成功", flowDefinition);
            } else {
                return Result.fail("更新流程定义失败");
            }
        } catch (Exception e) {
            log.error("更新流程定义失败: {}", e.getMessage(), e);
            return Result.fail("更新流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 删除流程定义
     *
     * @param id 流程定义ID
     * @return 删除结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> deleteFlowDefinition(Long id) {
        try {
            FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id);
            if (flowDefinition == null) {
                return Result.fail("流程定义不存在");
            }

            // 逻辑删除
            flowDefinition.setDeleted(1);
            flowDefinition.setUpdateTime(LocalDateTime.now());
            flowDefinitionMapper.updateById(flowDefinition);

            log.info("删除流程定义成功: id={}", id);
            return Result.success("流程定义删除成功");
        } catch (Exception e) {
            log.error("删除流程定义失败: {}", e.getMessage(), e);
            return Result.fail("删除流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 根据ID查询流程定义
     *
     * @param id 流程定义ID
     * @return 查询结果
     */
    public Result<FlowDefinition> getFlowDefinitionById(Long id) {
        try {
            FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id);
            if (flowDefinition == null) {
                return Result.fail("流程定义不存在");
            }
            return Result.success(flowDefinition);
        } catch (Exception e) {
            log.error("查询流程定义失败: {}", e.getMessage(), e);
            return Result.fail("查询流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 查询所有流程定义列表
     *
     * @return 流程定义列表
     */
    public Result<List<FlowDefinition>> getAllFlowDefinitions() {
        try {
            QueryWrapper<FlowDefinition> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("deleted"0)
                    .orderByDesc("create_time");
            List<FlowDefinition> list = flowDefinitionMapper.selectList(queryWrapper);
            return Result.success(list);
        } catch (Exception e) {
            log.error("查询流程定义列表失败: {}", e.getMessage(), e);
            return Result.fail("查询流程定义列表失败: " + e.getMessage());
        }
    }

    /**
     * 部署流程到Flowable引擎
     *
     * @param id 流程定义ID
     * @return 部署结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Deployment> deployFlowDefinition(Long id) {
        try {
            FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id);
            if (flowDefinition == null) {
                return Result.fail("流程定义不存在");
            }

            if (flowDefinition.getDeployed() == 1) {
                return Result.fail("该流程已经部署过了");
            }

            // 部署到Flowable引擎
            String deploymentName = flowDefinition.getProcessName() + ".bpmn20.xml";
            Deployment deployment = repositoryService.createDeployment()
                    .name(flowDefinition.getProcessName())
                    .category(flowDefinition.getCategory())
                    .addBytes(deploymentName, flowDefinition.getBpmnXml().getBytes(StandardCharsets.UTF_8))
                    .deploy();

            // 更新部署状态
            flowDefinitionMapper.updateDeployStatus(id, 1, deployment.getId());

            log.info("部署流程成功: id={}, deploymentId={}", id, deployment.getId());
            return Result.success("流程部署成功", deployment);
        } catch (Exception e) {
            log.error("部署流程失败: {}", e.getMessage(), e);
            return Result.fail("部署流程失败: " + e.getMessage());
        }
    }

    /**
     * 获取Flowable流程定义列表
     *
     * @return 流程定义列表
     */
    public Result<List<ProcessDefinition>> getFlowableProcessDefinitions() {
        try {
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
                    .orderByProcessDefinitionVersion()
                    .desc()
                    .list();
            return Result.success(list);
        } catch (Exception e) {
            log.error("获取Flowable流程定义列表失败: {}", e.getMessage(), e);
            return Result.fail("获取流程定义列表失败: " + e.getMessage());
        }
    }

    /**
     * 根据流程Key获取最新版本的流程定义
     *
     * @param processKey 流程Key
     * @return 流程定义
     */
    public Result<ProcessDefinition> getLatestProcessDefinitionByKey(String processKey) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionKey(processKey)
                    .latestVersion()
                    .singleResult();

            if (processDefinition == null) {
                return Result.fail("流程定义不存在");
            }
            return Result.success(processDefinition);
        } catch (Exception e) {
            log.error("获取流程定义失败: {}", e.getMessage(), e);
            return Result.fail("获取流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 导出流程定义XML
     *
     * @param id 流程定义ID
     * @return BPMN XML字符串
     */
    public Result<String> exportFlowDefinitionXml(Long id) {
        try {
            FlowDefinition flowDefinition = flowDefinitionMapper.selectById(id);
            if (flowDefinition == null) {
                return Result.fail("流程定义不存在");
            }
            return Result.success(flowDefinition.getBpmnXml());
        } catch (Exception e) {
            log.error("导出流程定义失败: {}", e.getMessage(), e);
            return Result.fail("导出流程定义失败: " + e.getMessage());
        }
    }

    /**
     * 获取流程定义的XML资源
     *
     * @param processDefinitionId 流程定义ID
     * @return XML资源内容
     */
    public Result<String> getProcessDefinitionResource(String processDefinitionId) {
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(processDefinitionId)
                    .singleResult();

            if (processDefinition == null) {
                return Result.fail("流程定义不存在");
            }

            String resourceName = processDefinition.getResourceName();
            InputStream inputStream = repositoryService.getResourceAsStream(
                    processDefinition.getDeploymentId(),
                    resourceName);

            byte[] bytes = readAllBytes(inputStream);
            return Result.success(new String(bytes, StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error("获取流程定义资源失败: {}", e.getMessage(), e);
            return Result.fail("获取流程定义资源失败: " + e.getMessage());
        }
    }

    /**
     * 读取输入流的所有字节(Java 8 兼容版本)
     *
     * @param inputStream 输入流
     * @return 字节数组
     * @throws IOException 读取异常
     */
    private byte[] readAllBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[4096];
        while ((nRead = inputStream.read(data0data.length)) != -1) {
            buffer.write(data0, nRead);
        }
        buffer.flush();
        return buffer.toByteArray();
    }
}

5.4 控制器层

RESTful API 接口:

package com.example.flowable.controller;

import com.example.flowable.entity.FlowDefinition;
import com.example.flowable.entity.Result;
import com.example.flowable.service.FlowDesignService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

/**
 * 流程设计控制器
 * 提供流程设计的REST API接口
 * @version 1.0.0
 */
@Slf4j
@RestController
@RequestMapping("/api/flow")
@CrossOrigin(origins = "*")
public class FlowDesignController {

    @Autowired
    private FlowDesignService flowDesignService;

    /**
     * 保存流程定义
     *
     * @param flowDefinition 流程定义实体
     * @return 保存结果
     */
    @PostMapping("/save")
    public Result<FlowDefinition> saveFlowDefinition(@Valid @RequestBody FlowDefinition flowDefinition) {
        log.info("保存流程定义: key={}, name={}", flowDefinition.getProcessKey(), flowDefinition.getProcessName());
        return flowDesignService.saveFlowDefinition(flowDefinition);
    }

    /**
     * 更新流程定义
     *
     * @param flowDefinition 流程定义实体
     * @return 更新结果
     */
    @PostMapping("/update")
    public Result<FlowDefinitionupdateFlowDefinition(@Valid @RequestBody FlowDefinition flowDefinition) {
        log.info("更新流程定义: id={}", flowDefinition.getId());
        return flowDesignService.updateFlowDefinition(flowDefinition);
    }

    /**
     * 删除流程定义
     *
     * @param id 流程定义ID
     * @return 删除结果
     */
    @DeleteMapping("/delete/{id}")
    public Result<VoiddeleteFlowDefinition(@PathVariable Long id) {
        log.info("删除流程定义: id={}", id);
        return flowDesignService.deleteFlowDefinition(id);
    }

    /**
     * 根据ID查询流程定义
     *
     * @param id 流程定义ID
     * @return 查询结果
     */
    @GetMapping("/get/{id}")
    public Result<FlowDefinitiongetFlowDefinitionById(@PathVariable Long id) {
        return flowDesignService.getFlowDefinitionById(id);
    }

    /**
     * 获取所有流程定义列表
     *
     * @return 流程定义列表
     */
    @GetMapping("/list")
    public Result<List<FlowDefinition>> getAllFlowDefinitions() {
        return flowDesignService.getAllFlowDefinitions();
    }

    /**
     * 部署流程到Flowable引擎
     *
     * @param id 流程定义ID
     * @return 部署结果
     */
    @PostMapping("/deploy/{id}")
    public Result<DeploymentdeployFlowDefinition(@PathVariable Long id) {
        log.info("部署流程: id={}", id);
        return flowDesignService.deployFlowDefinition(id);
    }

    /**
     * 获取Flowable流程定义列表
     *
     * @return 流程定义列表
     */
    @GetMapping("/deployed/list")
    public Result<List<ProcessDefinition>> getDeployedFlowDefinitions() {
        return flowDesignService.getFlowableProcessDefinitions();
    }

    /**
     * 根据流程Key获取最新版本的流程定义
     *
     * @param processKey 流程Key
     * @return 流程定义
     */
    @GetMapping("/getByKey/{processKey}")
    public Result<ProcessDefinitiongetProcessDefinitionByKey(@PathVariable String processKey) {
        return flowDesignService.getLatestProcessDefinitionByKey(processKey);
    }

    /**
     * 导出流程定义XML
     *
     * @param id 流程定义ID
     * @return BPMN XML字符串
     */
    @GetMapping("/export/{id}")
    public Result<StringexportFlowDefinitionXml(@PathVariable Long id) {
        log.info("导出流程定义XML: id={}", id);
        return flowDesignService.exportFlowDefinitionXml(id);
    }

    /**
     * 获取流程定义的XML资源
     *
     * @param processDefinitionId 流程定义ID
     * @return XML资源内容
     */
    @GetMapping("/resource/{processDefinitionId}")
    public Result<StringgetProcessDefinitionResource(@PathVariable String processDefinitionId) {
        return flowDesignService.getProcessDefinitionResource(processDefinitionId);
    }
}

六、前端实现

6.1 集成 bpmn-js

在 HTML 页面中引入 bpmn-js 库:

<script src="https://unpkg.com/bpmn-js@14.0.0/dist/bpmn-modeler.development.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/diagram-js.css"/>

6.2 初始化建模器

// 创建 BPMN 建模器实例
const bpmnModeler new BpmnJS({
    container'#canvas',
    height'100%',
    keyboard: { bindTo: window }
});

// 导入默认的 BPMN 模板
bpmnModeler.importXML(defaultBpmnTemplate).then(() => {
    bpmnModeler.get('canvas').zoom('fit-viewport');
});

6.3 保存流程定义

function saveFlowDefinition({
    // 获取 BPMN XML
    bpmnModeler.saveXML({ formattrue }).then(result => {
        const bpmnXml = result.xml;

        // 发送到后端保存
        $.ajax({
            url'/api/flow/save',
            method'POST',
            contentType'application/json',
            data: JSON.stringify({
                processKey: $('#processKey').val(),
                processName: $('#processName').val(),
                bpmnXml: bpmnXml
            }),
            success: function(response{
                if (response.code === 200) {
                    showToast('保存成功''success');
                }
            }
        });
    });
}

6.4 部署流程

function deployFlowDefinition(id) {
    $.ajax({
        url'/api/flow/deploy/' + id,
        method'POST',
        successfunction(response) {
            if (response.code === 200) {
                showToast('部署成功!部署ID: ' + response.data.id'success');
            }
        }
    });
}

七、流程部署完整流程

7.1 绘制阶段

  1. 用户打开流程设计器
  2. 使用 bpmn-js 拖拽组件绘制流程图
  3. 配置各元素的属性(任务名称、处理人、器等)

7.2 保存阶段

  1. 点击"保存"按钮
  2. 前端将 BPMN XML 发送到后端
  3. 后端验证数据并保存到 MySQL
  4. 返回保存结果

7.3 部署阶段

  1. 用户选择要部署的流程
  2. 点击"部署"按钮
  3. 后端调用 Flowable 的 RepositoryService
  4. Flowable 引擎解析 BPMN XML
  5. 创建部署对象并存储到数据库
  6. 更新流程定义的部署状态

八、数据库设计

8.1 核心表结构

flow_definition 表

  • 存储用户绘制的流程定义信息
  • 包含 BPMN XML、版本号、部署状态等字段

Flowable 核心表(自动创建)

  • ACT_RE_PROCDEF:流程定义表
  • ACT_RE_DEPLOYMENT:部署表
  • ACT_RU_EXECUTION:运行时执行实例表
  • ACT_RU_TASK:运行时任务表
  • ACT_HI_PROCINST:历史流程实例表
  • ACT_HI_TASKINST:历史任务表

8.2 表关系说明

ACT_RE_DEPLOYMENT (1) ───→ ACT_RE_PROCDEF (N)
    一个部署包含多个流程定义

ACT_RE_PROCDEF (1) ───→ ACT_RU_EXECUTION (N)
    一个流程定义可启动多个流程实例

ACT_RU_EXECUTION (1) ───→ ACT_RU_TASK (N)
    一个流程实例包含多个任务

九、完整业务流程

9.1 用户操作流程

  1. 登录系统:用户登录在线流程设计系统
  2. 创建流程:点击"新建流程"按钮
  3. 绘制流程图:使用 bpmn-js 设计器绘制 BPMN 2.0 流程图
  4. 配置属性:为各元素配置属性(名称、处理人等)
  5. 保存流程:将流程定义保存到 MySQL 数据库

9.2 设计师操作流程

  1. 绘制流程图:拖拽 BPMN 组件到画布
  2. 连接元素:使用顺序流连接各个元素
  3. 配置属性:设置任务名称、候选组、到期时间等
  4. 保存流程:将设计好的流程保存到数据库

9.3 系统处理流程

  1. 验证 XML:系统验证 BPMN XML 的格式和有效性
  2. 部署流程:将流程部署到 Flowable 引擎
  3. 存储数据:将流程数据持久化到数据库

十、实战案例

10.1 请假审批流程

下面以一个简单的请假审批流程为例,展示完整的实现过程。

流程需求

  1. 员工提交请假申请
  2. 部门经理审批
  3. 如果请假天数 > 3 天,需要 HR 复审
  4. 审批通过后,通知员工

BPMN 设计

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL">
  <bpmn:process id="leaveRequest" name="请假审批流程" isExecutable="true">
    <bpmn:startEvent id="start"/>
    <bpmn:userTask id="managerApproval" name="经理审批" flowable:assignee="${manager}"/>
    <bpmn:exclusiveGateway id="gateway"/>
    <bpmn:userTask id="hrApproval" name="HR审批" flowable:candidateGroups="hr"/>
    <bpmn:endEvent id="end"/>

    <!-- 流转 -->
    <bpmn:sequenceFlow sourceRef="start" targetRef="managerApproval"/>
    <bpmn:sequenceFlow sourceRef="managerApproval" targetRef="gateway">
      <bpmn:conditionExpression xsi:type="tFormalExpression">${approved != null}</bpmn:conditionExpression>
    </bpmn:sequenceFlow>
    <bpmn:sequenceFlow sourceRef="gateway" targetRef="hrApproval">
      <bpmn:conditionExpression xsi:type="tFormalExpression">${days > 3}</bpmn:conditionExpression>
    </bpmn:sequenceFlow>
    <bpmn:sequenceFlow sourceRef="gateway" targetRef="end">
      <bpmn:conditionExpression xsi:type="tFormalExpression">${days <= 3}</bpmn:conditionExpression>
    </bpmn:sequenceFlow>
    <bpmn:sequenceFlow sourceRef="hrApproval" targetRef="end"/>
  </bpmn:process>
</bpmn:definitions>

部署和使用

// 启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
    "leaveRequest",
    businessKey,
    variables
);

// 完成经理审批任务
Task task = taskService.createTaskQuery()
    .processInstanceId(processInstance.getId())
    .singleResult();

taskService.complete(task.getId(), Collections.singletonMap("approved", true));

十一、单元测试

11.1 测试服务层

使用 JUnit 5 和 Mockito 进行单元测试:

@ExtendWith(MockitoExtension.class)
class FlowDesignServiceTest {

    @Mock
    private FlowDefinitionMapper flowDefinitionMapper;

    @Mock
    private RepositoryService repositoryService;

    @InjectMocks
    private FlowDesignService flowDesignService;

    @Test
    @DisplayName("测试保存流程定义")
    void testSaveFlowDefinition() {
        // Given
        FlowDefinition flow = new FlowDefinition();
        flow.setProcessKey("test");
        flow.setProcessName("测试流程");
        flow.setBpmnXml(bpmnXml);

        // When
        Result<FlowDefinition> result = flowDesignService.saveFlowDefinition(flow);

        // Then
        assertTrue(result.isSuccess());
        verify(flowDefinitionMapper).insert(any());
    }
}

十二、部署和运行

12.1 编译打包

# 编译项目
mvn clean compile

# 运行测试
mvn test

# 打包(可选)
mvn clean package

12.2 启动应用

# 使用 Maven 启动
mvn spring-boot:run

# 或使用 java -jar 启动
java -jar target/flowable_online-demo-1.0.0.jar

12.3 访问系统

  • 首页:
  • 流程设计器:

十三、总结

本文介绍了如何基于 Flowable 6.8.0 和 bpmn-js 构建一个在线流程图绘制系统。 该系统采用前后端分离架构,易于扩展和集成。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com