SpringBoot整合Flowable【08】- 前后端如何交互

简介: 本文详细介绍了如何通过 Flowable 的 BpmnModel API 以编程方式动态构建 BPMN 流程模型,而无需依赖 XML 文件。文章从实际业务场景出发,探讨了前端传递参数实现流程创建的可行性,并通过代码示例展示了整个实现过程。主要内容包括:定义流程实体与节点结构、创建控制器处理请求、服务层实现流程模型转换及部署、递归构建任务节点和子节点、创建任务监听器和表单属性等关键方法。最后通过 curl 命令测试接口,验证流程部署成功。虽然此方法灵活强大,但复杂流程的构建需编写大量代码。建议封装常用元素(如开始/结束事件、网关等),提升开发效率和代码可维护性。

引子

在第02篇中,我通过 Flowable-UI 绘制了一个简单的绩效流程,并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而,在实际业务场景中,如果要求前端将用户绘制的流程文件发送给后端再进行解析处理,这种方式显得繁琐且不够优雅。那么,有没有更简便的方法,让前端通过常规的参数传递方式就能实现流程创建呢?

答案是肯定的。Flowable 提供了强大的 BpmnModel API,它允许我们以编程方式动态构建流程定义,无需依赖XML文件。

什么是 BpmnModel ?

BpmnModelFlowable 提供的一个核心类,它允许开发者以编程方式构建完整的 BPMN 2.0 流程模型。与通过 Flowable-UI 设计流程并导出XML文件的方式不同,BpmnModel 让我们可以直接在代码中定义流程的各个元素。

所以,我们可以通过 BpmnModel 对象来动态创建流程定义,前端只需通过接口传递必要的流程参数即可,无需传递完整的 XML 文件。这里需要明确区分两个概念:一是后端如何接收参数并构建流程模型,二是前端如何提供流程设计的交互界面。对于后者,可以基于 bpmn.js 开发自定义设计器,也可以采用现有的开源方案,这些选项在第01篇中已有详细介绍。

代码编写

接下来,我将完全抛弃 Flowable UI 可视化建模方式,转而通过纯代码方式使用 BpmnModel 对象构建先前设计的绩效流程。让我们深入编码环节,一步步实现这一转换。

一、构建传参对象

1.流程定义实体

流程定义是创建流程的传参对象,包含流程的基本信息和节点结构。

import lombok.Data;
import java.io.Serializable;

/**
 * 流程定义.
 */
@Data
public class ProcessDef implements Serializable {
   

    /**
     * 流程定义id.
     */
    private String processDefKey;

    /**
     * 流程定义名称.
     */
    private String processName;

    /**
     * 流程定义描述.
     */
    private String description;

    /**
     * 创建人id.
     */
    private Long creatorId;

    /**
     * 流程定义节点.
     */
    private ProcessNode processNode;

}

2. 流程节点实体

流程节点定义了流程中的各个环节,包括节点类型、表单属性以及子节点关系等。

import lombok.Data;
import java.io.Serializable;
import java.util.List;

/**
 * 流程节点.
 */
@Data
public class ProcessNode implements Serializable {
   

    /**
     * 节点名称.
     */
    private String name;

    /**
     * 节点类型.
     */
    private NodeCategory category;

    /**
     * 是否启用 0否 1是.
     */
    private Integer enable;

    /**
     * 办理人属性.
     */
    private AssigneeProps assigneeProps;

    /**
     * 表单列表.
     */
    private List<FormProp> formProps;

    /**
     * 子节点.
     */
    private ProcessNode children;

}

3. 相关枚举和属性类

为了更好地定义流程节点的各种属性,我们最好还是定义相关枚举和辅助类。(Ps:在实际开发中,个人建议如果类型比较多的情况下,尽量使用枚举,避免魔法字段。)

节点类型枚举

/**
 * 节点类型.
 */
public enum NodeCategory {
   

    /**
     * 自评.
     */
    SELF_EVALUATION,

    /**
     * 上级评.
     */
    LEADER_EVALUATION,

    /**
     * 隔级评.
     */
    AUDIT,

    /**
     * 绩效确认.
     */
    CONFIRMATION;
}

办理人属性

import java.io.Serializable;
import lombok.Data;

/**
 * 办理人Props.
 */
@Data
public class AssigneeProps implements Serializable {
   
    /**
     * 办理人类型.
     */
    private AssigneeType assigneeType;

    /**
     * 候选办理人类型.
     */
    private AssigneeType candidateType;
}

办理人类型枚举

/**
 * 办理人类型.
 */
public enum AssigneeType {
   

    /**
     * 节点处理人类型, 用户本人,直接上级,上级部门管理员,隔级上级,隔级部门管理员.
     */
    USER_ASSESSED, LEADER, DEPT_MANAGER, SUPERIOR_LEADER, SUPERIOR_DEPT_MANAGER
}

表单属性

import java.io.Serializable;
import lombok.Data;

/**
 * FormProps 表单属性.
 */
@Data
public class FormProp implements Serializable {
   

    /**
     * id.
     */
    private String id;

    /**
     * 属性名称.
     */
    private String name;

    /**
     * 属性变量.
     */
    private String variable;

    /**
     * 变量类型.
     */
    private String type;

    /**
     * 值.
     */
    private String value;

    /**
     * 表单配置.
     */
    private FormConfig config;

    public FormConfig getConfig() {
   
        return config;
    }
}

表单配置

import java.io.Serializable;
import lombok.Data;

/**
 * FormConfig 表单配置.
 */
@Data
public class FormConfig implements Serializable {
   

    /**
     * 分组名称.
     */
    private String group;

    /**
     * 分组权重.
     */
    private Double weight;

    /**
     * 分类:评分SCORE、评语COMMENT.
     */
    private FormCategory category;

}

表单类别枚举

/**
 * 表单类别.
 */
public enum FormCategory {
   
    /**
     * 评分.
     */
    SCORE,

    /**
     * 评语.
     */
    COMMENT;
}

二、创建控制器

import com.pitayafruit.base.BaseResponse;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.service.ProcessDefService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 流程Controller.
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/process-def/v1")
public class ProcessDefControllerV1 {
   

    private final ProcessDefService processDefService;

    /**
     * 创建流程模型并部署.
     *
     * @param processDef 流程定义
     * @return 流程key
     */
    @PostMapping
    public ResponseEntity<Object> createProcessDef(@RequestBody ProcessDef processDef) {
   
        //1,创建并部署流程模型
        String processDefKey = processDefService.createProcessDefApi(processDef);
        //2.返回流程模型key
        return new ResponseEntity<>(new BaseResponse<>(processDefKey), HttpStatus.OK);
    }


}

三、实现服务层主方法

这个负责将前端传递的数据结构转换为 Flowable 的 BpmnModel 模型,并进行部署。

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.pitayafruit.rest.vo.AssigneeProps;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.rest.vo.ProcessNode;
import com.pitayafruit.service.ProcessDefService;
import com.pitayafruit.utils.UUIDUtil;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.validation.ValidationError;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 流程定义ServiceImpl.
 */
@Service
@RequiredArgsConstructor
public class ProcessDefServiceImpl implements ProcessDefService {
   

    private final RepositoryService repositoryService;

    /**
     * 创建流程模型并部署.
     *
     * @param processDef 流程定义
     * @return 流程key
     */
    @Override
    public String createProcessDefApi(ProcessDef processDef) {
   
        //1.设置流程定义Key
        processDef.setProcessDefKey("processDef" + UUIDUtil.getUUID());
        //2.设置流程定义创建人id
        processDef.setCreatorId(1L);
        //3.设置流程定义名称
        processDef.setProcessName("绩效流程");
        //4.创建流程模型
        BpmnModel bpmnModel = toBpmn(processDef);
        //5.部署流程模型
        repositoryService.createDeployment()
                //5-1.流程定义key
                .key(processDef.getProcessDefKey())
                //5-2.流程定义名称
                .name(processDef.getProcessName())
                //5-3.添加流程模型
                .addBpmnModel(processDef.getProcessDefKey() + ".bpmn", bpmnModel)
                //5-4.部署
                .deploy();
        //6.返回流程定义key
        return processDef.getProcessDefKey();
    }

四、流程模型转换方法

类型转换涉及到的逻辑比较多,所以把这个方法单独抽取出来。首先创建一个流程对象,设置流程的基本属性,添加开始事件,然后通过调用 buildTask 方法递归构建流程中的各个节点,最后生成并验证 BpmnModel。

/**
 * 将流程定义转换为BpmnModel.
 *
 * @param processDef 流程定义
 * @return Bpmn模型
 */
private BpmnModel toBpmn(ProcessDef processDef) {
   
    //1.创建流程
    //1-1.声明流程对象
    Process process = new Process();
    //1-2.设置流程id
    process.setId(processDef.getProcessDefKey());
    //1-3.设置流程名称
    process.setName(processDef.getProcessName());
    //2.创建开始事件
    //2-1.声明开始事件对象
    StartEvent startEvent = new StartEvent();
    //2-2.设置开始事件id
    startEvent.setId("startEvent" + UUIDUtil.getUUID());
    //2-3.设置开始事件名称
    startEvent.setName("开始");
    //2-4.将开始事件添加到流程中
    process.addFlowElement(startEvent);
    //创建用户节点
    ProcessNode processNode = processDef.getProcessNode();
    buildTask(startEvent, processNode, process);
    //创建流程模型
    BpmnModel bpmnModel = new BpmnModel();
    bpmnModel.addProcess(process);
    // 验证BPMN模型
    List<ValidationError> validationErrors = repositoryService.validateProcess(bpmnModel);
    if (ObjectUtil.isNotEmpty(validationErrors)) {
   
        //打印失败日志
        validationErrors.forEach(validationError -> System.out.println(validationError.toString()));
        throw new IllegalArgumentException("验证失败");
    }
    return bpmnModel;
}

五、任务节点构建方法

这个方法负责创建用户任务节点。根据传入的流程节点数据,创建一个用户任务对象,设置其属性,添加自定义属性和任务监听器,然后将任务添加到流程中,并构建与前一个节点的连线。如果当前节点未启用,则直接处理下一个节点。

/**
 * 创建用户任务节点.
 *
 * @param parentTask  父节点
 * @param processNode 流程节点
 * @param process     流程定义
 */
private void buildTask(FlowNode parentTask, ProcessNode processNode, Process process) {
   
    //如果节点启用,处理当前节点
    if (ObjectUtil.isNotNull(processNode.getEnable()) && processNode.getEnable() == 1) {
   
        UserTask userTask = new UserTask();
        userTask.setId("userTask" + UUIDUtil.getUUID());
        userTask.setName(processNode.getName());
        //设置节点类型
        userTask.setCategory(processNode.getCategory().toString());
        List<CustomProperty> customProperties = new ArrayList<>();
        //办理人属性
        AssigneeProps assigneeProps = processNode.getAssigneeProps();
        //设置办理人类型
        customProperties.add(
                buildCustomProperty("assigneeType", assigneeProps.getAssigneeType().toString()));
        //设置候选办理人类型
        if (ObjectUtil.isNotNull(assigneeProps.getCandidateType())) {
   
            customProperties.add(buildCustomProperty("candidateType", assigneeProps.getCandidateType().toString()));
        }
        //绑定表单
        userTask.setFormProperties(buildFormProperty(processNode));
        //表单列表添加到节点扩展属性
        customProperties.add(buildCustomProperty("formProps", JSON.toJSONString(processNode.getFormProps())));
        //设置自定义属性,包括办理人类型和表单分组和权重配置
        userTask.setCustomProperties(customProperties);
        //监听器列表
        List<FlowableListener> taskListeners = new ArrayList<>();
        //任务创建时添加监听器,用于动态指定任务的办理人和设置变量
        taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_CREATE, "taskCreateListener"));
        //任务完成后添加监听器,用于计算分数和保存表单
        //如果是手动填写流程,添加手动填写的监听器
        taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_COMPLETE, "taskCompleteListener"));
        //设置任务监听器
        userTask.setTaskListeners(taskListeners);
        // 将用户任务节点添加到流程定义
        process.addFlowElement(userTask);
        //添加流程连线
        process.addFlowElement(new SequenceFlow(parentTask.getId(), userTask.getId()));
        //解析下一个节点,参数为当前任务,当前流程节点,流程定义,流程类型
        buildChildren(userTask, processNode, process);
    } else {
   
        //当前节点关闭的情况下,直接解析下一个节点,参数为父任务,当前流程节点,流程定义,流程类型
        buildChildren(parentTask, processNode, process);
    }
}

六、子节点处理方法

这个方法用于处理流程节点的子节点。它检查当前节点是否有子节点,如果有则递归调用 buildTask 方法构建子节点,如果没有则创建结束事件,表示流程的结束。

/**
 * 解析子节点.
 *
 * @param parentTask  下一个节点的父任务
 * @param processNode 流程节点
 * @param process     流程定义
 */
private void buildChildren(FlowNode parentTask, ProcessNode processNode, Process process) {
   
    ProcessNode childrenNode = processNode.getChildren();
    if (ObjectUtil.isNotNull(childrenNode)) {
   
        //创建子节点
        buildTask(parentTask, childrenNode, process);
    } else {
   
        //创建结束事件
        EndEvent endEvent = new EndEvent();
        endEvent.setId("endEvent" + UUIDUtil.getUUID());
        endEvent.setName("结束");
        process.addFlowElement(endEvent);
        //添加流程连线
        process.addFlowElement(new SequenceFlow(parentTask.getId(), endEvent.getId()));
    }
}

七、任务监听器创建方法

这个方法用于创建任务监听器。在Flowable中,任务监听器可以在任务的不同生命周期阶段触发特定逻辑,例如在任务创建时动态分配任务办理人,或在任务完成时处理表单数据。这里使用了委托表达式方式,将监听器的实现委托给Spring容器中的Bean。

/**
 * 创建任务监听器,用于动态分配任务的办理人.
 *
 * @param event    事件
 * @param beanName 类名
 * @return 任务监听器
 */
private FlowableListener buildTaskListener(String event, String beanName) {
   
    FlowableListener flowableListener = new FlowableListener();
    flowableListener.setEvent(event);
    flowableListener.setImplementationType("delegateExpression");
    flowableListener.setImplementation("${" + beanName + "}");
    return flowableListener;
}

八、表单属性创建方法

这个方法用于创建表单属性。它遍历流程节点中的表单属性列表,为每个表单属性创建一个Flowable的FormProperty对象,设置其ID、名称、变量名等属性,并将原始表单属性的ID和变量名更新为实际使用的值,以便在后续处理中使用。

/**
 * 创建表单属性.
 *
 * @param processNode 流程节点
 */
private List<FormProperty> buildFormProperty(ProcessNode processNode) {
   
    List<FormProperty> formProperties = new ArrayList<>();
    if (ObjectUtil.isNull(processNode.getFormProps())) {
   
        return formProperties;
    }
    processNode.getFormProps().forEach(prop -> {
   
        //新建表单属性对象
        FormProperty formProperty = new FormProperty();
        //设置表单属性id
        String id = "formProperty" + UUIDUtil.getUUID();
        formProperty.setId(id);
        //设置表单名称
        formProperty.setName(prop.getName());
        //设置表单变量名为表单id
        formProperty.setVariable(id);
        //设置表单变量类型
        formProperty.setType(prop.getType());
        //设置表单是否必填
        formProperty.setRequired(true);
        formProperties.add(formProperty);
        //设置表单属性id
        prop.setId(id);
        prop.setVariable(id);
    });
    return formProperties;
}

九、自定义属性创建方法

这个方法用于创建自定义属性。在 Flowable 中,自定义属性可以用于存储不属于标准BPMN规范但业务上需要的信息,通常用来传递我们的业务数据。

/**
 * 创建自定义属性.
 *
 * @param key  键
 * @param value 值
 * @return 自定义属性
 */
private CustomProperty buildCustomProperty(String key, String value) {
   
    CustomProperty customProperty = new CustomProperty();
    //自定义属性名称
    customProperty.setName(key);
    //自定义属性值
    customProperty.setSimpleValue(value);
    return customProperty;
}

接口测试

我把请求转换成了curl命令,方便直接在命令行中测试。创建的这个流程与前面篇章中介绍的结构一致,包含三个核心节点:员工自评、上级评价和隔级评价,每个节点都包含分数、评语等表单项,并且每个节点都设置了不同的权重。

为什么要加权重?这是因为在实际业务场景中,不同评价环节的重要性往往不同。

curl -X POST 'http://localhost:8080/api/process-def/v1' \
-H 'Content-Type: application/json' \
-d '{
  "processName": "通过bpmn model创建的绩效流程",
  "description": "用于员工季度绩效评估",
  "processNode": {
    "name": "员工自评",
    "category": "SELF_EVALUATION",
    "enable": 1,
    "assigneeProps": {
      "assigneeType": "USER_ASSESSED"
    },
    "formProps": [
      {
        "name": "自评分数",
        "type": "string",
        "value": "0",
        "config": {
          "group": "绩效评分",
          "weight": 0.3,
          "category": "SCORE"
        }
      },
      {
        "name": "自我评价",
        "type": "string",
        "config": {
          "group": "评语",
          "weight": 0.3,
          "category": "COMMENT"
        }
      }
    ],
    "children": {
      "name": "上级评价",
      "category": "LEADER_EVALUATION",
      "enable": 1,
      "assigneeProps": {
        "assigneeType": "LEADER"
      },
      "formProps": [
        {
          "name": "上级评分",
          "type": "string",
          "value": "0",
          "config": {
            "group": "绩效评分",
            "weight": 0.5,
            "category": "SCORE"
          }
        },
        {
          "name": "上级评语",
          "type": "string",
          "config": {
            "group": "评语",
            "weight": 0.5,
            "category": "COMMENT"
          }
        }
      ],
      "children": {
        "name": "隔级评价",
        "category": "AUDIT",
        "enable": 1,
        "assigneeProps": {
          "assigneeType": "SUPERIOR_LEADER"
        },
        "formProps": [
          {
            "name": "隔级评分",
            "type": "string",
            "value": "0",
            "config": {
              "group": "绩效评分",
              "weight": 0.2,
              "category": "SCORE"
            }
          },
          {
            "name": "隔级评语",
            "type": "string",
            "config": {
              "group": "评语",
              "weight": 0.2,
              "category": "COMMENT"
            }
          }
        ],
        "children": null
      }
    }
  }
}'

按照接口的定义,部署成功后会返回流程模型key。

1.png

同时查看部署表和流程运行表,可以看到这个流程已经启动了。

2.png

3.png

小结

通过本章的实践,我们可以明显看到利用 BpmnModel API 构建流程虽然灵活强大,但即使是构建一个相对简单的线性流程,也需要编写大量代码来处理各种细节。这种方法的复杂性在面对更复杂的流程结构(如并行网关、排他网关或子流程等)时会进一步增加。

因此,在实际项目中,我建议大家可以花点时间封装,比如可以将创建开始事件、任务节点、结束事件、网关等常见元素的代码封装成独立方法。通过这些封装,在写业务的时候就能像搭积木一样轻松组合各种流程元素,提高开发效率和代码可维护性。

目录
相关文章
|
消息中间件 存储 Java
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统
本文详细介绍了如何使用Spring Boot 3结合RabbitMQ构建高效可靠的聊天消息存储系统。通过引入消息队列,实现了聊天功能与消息存储的解耦,解决了高并发场景下直接写入数据库带来的性能瓶颈问题。文章首先分析了不同MQ产品的特点及适用场景,最终选择RabbitMQ作为解决方案,因其成熟稳定、灵活路由和易于集成等优势。接着,通过Docker快速部署RabbitMQ,并完成Spring Boot项目的配置与代码实现,包括生产者发送消息、消费者接收并处理消息等功能。最后,通过异步存储机制,既保证了消息的即时性,又实现了可靠持久化。
137 0
📨 Spring Boot 3 整合 MQ 构建聊天消息存储系统
|
6月前
|
存储 Java API
SpringBoot整合Flowable【02】- 整合初体验
本文介绍了如何基于Flowable 6.8.1版本搭建工作流项目。首先,根据JDK和Spring Boot版本选择合适的Flowable版本(7.0以下)。接着,通过创建Spring Boot项目并配置依赖,包括Flowable核心依赖、数据库连接等。然后,建立数据库并配置数据源,确保Flowable能自动生成所需的表结构。最后,启动项目测试,确认Flowable成功创建了79张表。文中还简要介绍了这些表的分类和常用表的作用,帮助初学者理解Flowable的工作原理。
1021 0
SpringBoot整合Flowable【02】- 整合初体验
|
供应链 芯片
电商黑话之 spu sku
SPU = Standard Product Unit (标准化产品单元),SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的基本特性。因此在电商类产品库建立时,通常会根据SPU来建立。
电商黑话之 spu sku
|
1月前
|
存储 人工智能 前端开发
🔓AI赋能开源:如何借助MCP快速解锁开源项目并提交你的首个PR
本篇文章介绍了如何利用AI工具降低开源项目贡献门槛,加速从项目理解到代码提交的过程。通过GitDiagram工具,可将GitHub仓库转化为交互式架构图,清晰展示项目结构,如Dify项目的部署、前端、核心服务及外部集成等层次。接着,借助GitMCP工具,将项目转化为AI助手的知识库,实现精准代码导航与开发辅助。例如,在Cursor中配置MCP服务后,AI能快速定位文件并分析接口结构,大幅提升开发效率。尽管MCP存在token消耗等问题,但其生态发展已展现出AI在实际开发中的巨大潜力。
58 5
|
9月前
|
人工智能 Java API
阿里云开源 AI 应用开发框架:Spring AI Alibaba
近期,阿里云重磅发布了首款面向 Java 开发者的开源 AI 应用开发框架:Spring AI Alibaba(项目 Github 仓库地址:alibaba/spring-ai-alibaba),Spring AI Alibaba 项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。本文将详细介绍 Spring AI Alibaba 的核心特性,并通过「智能机票助手」的示例直观的展示 Spring AI Alibaba 开发 AI 应用的便利性。示例源
7082 96
|
10月前
|
自然语言处理 算法 云栖大会
通义万相发布视频生成模型,更懂中国风、中国话
通义万相发布视频生成模型,可一键创作影视级高清视频
997 13
|
9月前
|
人工智能 自然语言处理 机器人
对话阿里云 CIO 蒋林泉:AI 时代,企业如何做好智能化系统建设?
10 月 18 日, InfoQ《C 位面对面》栏目邀请到阿里云 CIO 及 aliyun.com 负责人蒋林泉(花名:雁杨),就 AI 时代企业 CIO 的角色转变、企业智能化转型路径、AI 落地实践与人才培养等主题展开了讨论。
8235 69
对话阿里云 CIO 蒋林泉:AI 时代,企业如何做好智能化系统建设?
|
10月前
|
自然语言处理 IDE 开发工具
通义灵码编程智能体上线,支持Qwen3模型
通义灵码最全使用指南,一键收藏。
126955 29
通义灵码编程智能体上线,支持Qwen3模型