Flowable工作流

1 什么是工作流

工作流(Workflow)是一种自动化处理业务过程的方法,用于定义、执行和管理业务任务的顺序。

1.1 常见的工作流引擎

  1. Activiti

  2. 一个流行的开源工作流引擎,支持 BPMN 2.0 标准,适用于企业级应用。

  3. Flowable

  4. 一个开源的工作流引擎,支持 BPMN 2.0 标准,广泛用于 Java 应用。Flowable 是 Activiti 的一个分支,后来发展成为独立的项目,它在很多方面进行了改进和优化。

  5. Camunda

  6. 一个开源的业务流程管理平台,支持 BPMN 2.0 和 CMMN 标准,提供强大的社区支持和商业版本。

  7. jBPM

  8. 一个开源的业务流程管理框架,支持 BPMN 2.0 和其他标准,适用于复杂的业务流程。

1.2 应用场景

工作流广泛应用于各种业务领域,包括但不限于:

  1. 业务审批:如请假申请、费用报销、合同审批等。

  2. 生产制造:如订单处理、生产调度、质量控制等。

  3. 客户服务:如客户投诉处理、服务请求跟踪等。

  4. 项目管理:如项目计划、任务分配、进度跟踪等。

  5. 供应链管理:如采购管理、库存管理、物流配送等。

2 flowable-ui

flowable-ui 是理 Flowable 项目的一部分,提供了一套基于 Web 的用户界面,用于管理和监控工作流。它包括多个模块,每个模块负责不同的功能,如任务管、模型设计、管理控制台等。

我们用它主要就是用它的ui界面,绘画流程图,生成xml文件。

2.1 添加依赖

<dependencies>
    <!--Springboot项目自带 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--Springboot Web项目 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <!-- idm依赖提供身份认证 -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
        <version>6.7.1</version>
    </dependency>

    <!-- modeler绘制流程图 -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
        <version>6.7.1</version>
    </dependency>

    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
        <version>4.3.0</version>
    </dependency>

    <!-- jpa -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- mysql驱动版本必须指定为8.0.18,不然报错。 -->
    <dependency>
        <groupId>mysql</groupId> 
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.18</version>
    </dependency>

    <!-- flowable -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.7.1</version>
    </dependency>

</dependencies>

2.2 配置文件

server:
  port: 8088

flowable:
  database-schema-update: true # true表示每次启动都会重新创建表,false表示使用已经存在的表
  async-executor-activate: false # true表示启动时启动定时任务,false表示不启动
  idm:
    app:
      admin:
        # 登录的用户名
        user-id: admin
        # 登录的密码
        password: admin
        # 用户的名字
        first-name: zhang
        last-name: san
spring:
  # mysql连接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
    username: root
    password: root
  liquibase:
    enabled: false
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    open-in-view: true

2.3 数据库建库

在数据库建立指定的数据库flowable即可,当第一次启动时,会自动创建flowable所需要的表,大概79个左右。

2.4 启动启动类

启动类第一次启动会时间比较长,因为在创建表。

启动完成后,在本地浏览器输入http://localhost:8088/即可访问ui界面

密码就是配置文件里面的账号密码 admin

2.5 使用ui创建库存审批图

2.5.1 创建流程

2.5.2 创建模型

记住这个key,后面要用到

2.5.3 绘画流程图

这里我直接画了,具体怎么画的可以参考这个文章

https://blog.csdn.net/zhipengfang/article/details/134690012?spm=1001.2014.3001.5506

主要就是实现了对采购单是审批流程。

2.6 导出xml文件

库存审批流程.bpm

3 flowable

使用flowable模拟库存审批流程

3.1 新建项目 导入依赖

<dependencies>
    <!--Springboot项目自带 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--Springboot Web项目 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.18</version>
    </dependency>

    <!-- flowable -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.7.1</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

</dependencies>

3.2 配置文件

server:
  port: 8081

spring:
  # mysql连接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/flowable?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT&nullCatalogMeansCurrent=true
    username: root
    password: root
flowable:
  database-schema-update: true
  async-executor-activate: false
  process-definition-location-prefix: classpath:/processes/ # 流程定义文件路径
logging:
  level:
    com:
      by: debug
    root: error

3.3 导入流程文件

把刚刚下载的xml文件放到resource目录下的processes(没有自己创建一个)里面,一定要和配置文件里面指定目录一致

这里我们可以下一个插件 Flowable BPMN visualizer 有了他就不需要ui界面一直打开了

使用也很简单,右键xml文件,选择打开即可

3.4 Controller

这里我写了一个controller模拟库存审核流程

@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
    // RuntimeService用于启动流程定义的新流程实例。
    @Autowired
    private RuntimeService runtimeService;
    // TaskService用于查询和完成任务。
    @Autowired
    private TaskService taskService;
    // RepositoryService用于查询流程定义。
    @Autowired
    private RepositoryService repositoryService;
    // ProcessEngine用于获取RepositoryService、RuntimeService、TaskService等
    @Autowired
    private ProcessEngine processEngine;
    // HistoryService用于查询历史数据。

    // 库存申请流程的id
    private final static String inventoryId = "inventoryApproval";

    /**
     * 添加库存申请
     * @param inventory 库存申请
     * @return 流程id
     */
    @PostMapping("/add")
    public String add(@RequestBody Inventory inventory) {
        Map<String, Object> map = new HashMap<>();
        map.put("requester", inventory.getRequester());
        map.put("qty", inventory.getQty());
        map.put("manager", "经理");

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(inventoryId, map);
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        // 判断库存数量是否小于1000
        if (inventory.getQty()<=1000){
            // 直接通过 无需审批
            taskService.complete(task.getId());
         、}
        System.out.println("流程启动成功,流程Id: " + processInstance.getId());

        return "提交成功.流程Id为:" + processInstance.getId();
    }

    /**
     * 获取任务列表
     * @param user 用户名(可以是 requester 或 manager)
     * @return 任务列表
     */
    @GetMapping("/list")
    public String list(String user) {
        List<Task> tasks = taskService.createTaskQuery()
                .taskAssignee(user)
                .active()  // 只查询未完成的任务
                .orderByTaskCreateTime()
                .desc()
                .list();
-
        System.out.println("查询任务列表,用户: " + user + ", 任务数量: " + tasks.size());

        for (Task task : tasks) {
            System.out.println(task.toString());
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            System.out.println("流程实例状态: " + (processInstance != null ? "活跃" : "已结束"));
        }
        return tasks.toString();
    }

    /**
     * 审批通过
     * @param taskId 任务id
     * @return 审批结果
     */
    @GetMapping("/apply")
    public String apply(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new RuntimeException("任务不存在");
        }
        // 通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        taskService.complete(taskId, map);
        return "processed ok!";
    }

    /**
     * 驳回
     * @param taskId 任务id
     * @return 驳回结果
     */
    @GetMapping("/reject")
    public String reject(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new RuntimeException("任务不存在");
        }
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "驳回");
        taskService.complete(taskId, map);
        return "reject";
    }

    /**
     * 生成流程图
     * @param httpServletResponse 响应对象
     * @param processId 流程id
     * @throws Exception 异常
     */
    @GetMapping("/genProcessDiagram")
    public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

        // 流程走完的不显示图
        if (pi == null) {
            httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }

        // 使用流程实例ID,查询正在执行的任务
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        if (task == null) {
            httpServletResponse.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }

        // 使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
        String instanceId = task.getProcessInstanceId();
        List<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(instanceId)
                .list();

        // 得到正在执行的Activity的Id
        List<String> activityIds = new ArrayList<>();
        for (Execution exe : executions) {
            List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
            activityIds.addAll(ids);
        }

        // 获取流程图
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
        ProcessDiagramGenerator diagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
        InputStream in = diagramGenerator.generateDiagram(
                bpmnModel,
                "png",
                activityIds,
                new ArrayList<>(), // flows
                1.0, // 缩放比例
                true // 是否水平布局
        );

        httpServletResponse.setContentType("image/png");
        OutputStream out = null;
        byte[] buf = new byte[1024];
        int length = 0;
        try {
            out = httpServletResponse.getOutputStream();
            while ((length = in.read(buf)) != -1) {
                out.write(buf, 0, length);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

3.5 演示结果

注意:流程id和任务id不是同一个

3.5.1 添加任务

3.5.2 获取任务列表

因为qty小于1000的任务直接完成 不需要审批 所以列表只有一个任务

3.5.3 审核通过

填入任务id

通过后再查询任务列表就没有了 因为已经审核通过了

3.5.4 审核驳回

再次查询也是没有

3.6 获取流程图(乱码未解决)

会显示现在已经到那个步骤了 会高亮显示。

3.7 测试查看全流程

3.7.1 qty大于1000

3.7.2 小于1000

3.7.3 具体测试代码

@SpringBootTest
@Slf4j
class InventoryTests {
    @Autowired
    private RuntimeService runtimeService;
 
    @Autowired
    private TaskService taskService;
 
    @Autowired
    private HistoryService historyService;

    @Test
    void Test() {
        // 发送库存请求

        // 收集的数据作为一个Map实例传递,其中的键就是之后用于获取变量的标识符
        Map<String, Object> map = new HashMap<>();
        map.put("requester", "小明");
        map.put("qty", 500);
        map.put("manager", "经理");
        // runtimeService根据xml中流程的id启动流程实例
        ProcessInstance inventoryManagement = runtimeService.startProcessInstanceByKey("inventoryApproval", map);
        // 获取任务 该查询基于刚刚创建的流程实例ID来查找相关的任务。singleResult方法返回查询结果中的第一个任务(
        Task task = taskService.createTaskQuery().processInstanceId(inventoryManagement.getId()).singleResult();
        // 完成任务
        taskService.complete(task.getId());

        // 库存审批
        List<Task> principalTaskList = taskService.createTaskQuery().taskAssignee("经理").list();
        Map<String, Object> principalMap = new HashMap<>();

        principalMap.put("outcome", "通过");
        for (Task principalTask : principalTaskList) {
            taskService.complete(principalTask.getId(), principalMap);
        }

        // 查看历史
        List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(inventoryManagement.getId())
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc() // 按照活动实例结束时间正序排列
                .list();
        for (HistoricActivityInstance activity : activities) {
            System.out.println(activity.getActivityName());
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值