Flowable的简单运用

Flowable介绍:

Flowable 是一个轻量级的业务流程管理(BPM)和工作流引擎,它允许开发者在应用程序中定义、执行和管理业务流程。简单来说,它帮助你自动化和协调一系列任务或步骤,以完成某个业务目标。

举个生活中的例子:

想象你在公司申请一个请假流程

  • 员工提交请假申请
  • 上级审批;如果超过3天,还需要更高级别的领导审批
  • 审批通过后,HR系统更新假期记录
  • 最后员工收到审批结果通知

这个过程可以使用 Flowable 来建模和自动化执行。

Flowable 类似生活中的:快递分拣系统

你可以把它想象成一个自动化的快递分拣流水线:

  • 快递到达仓库(触发流程)
  • 扫描包裹(第一个节点)
  • 根据目的地决定流向(条件分支)
  • 打包、装车、运输(多个处理节点)
  • 到达目的地网点(结束事件)

在这个过程中,Flowable 就像那个智能的“分拣系统”,根据预设规则把任务一步步流转下去。

Flowable 的核心概念类比

ProcessDefinition:分拣系统的图纸/流程图

ProcessInstance:正在运行的一个快递包裹流程

Task:某个分拣节点需要做的操作(如扫描、打包)

UserTask:需要人工参与的操作(如客服确认地址)

ServiceTask:自动执行的任务(如系统自动发送短信)

Gateway:分岔路口,根据条件选择不同路线(如包裹大小决定走哪个通道)

总结一句话:

Flowable 就像是一个“流程指挥官”,帮你把一堆杂乱的业务步骤有条不紊地组织起来,让系统知道什么时候该做什么事。

接下来的流程需要你提前了解如何通过Flowable UI画一个完整的流程图,并且配置好对应的参数变量,或者了解bpmn20.xml文件的结构。其实Flowable流程图导出就是一个bpmn20.xml

下面是我在写Demo过程中使用的流程图及bpmn20.xml

Flowable的简单运用_第1张图片

bpmn20.xml文件内容



  
    公司员工请假申请流程
    
    
    
      是否大于3天
    
    
    
      结束
    
    
    
    
      <=3天
      
    
    
      >3天
      3}]]>
    
    
      驳回
      
    
    
      通过
      
    
    
      驳回
      
    
    
    
    
      通过
      
    
  
  
    
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
      
        
        
        
      
      
        
        
      
      
        
        
      
      
        
        
      
    
  

自行将改代码保存成以bpmn20.xml为文件后缀的文件,比如【公司员工请假申请流程.bpmn20.xml】注意不可以是【公司员工请假申请流程.bpmn20(1).xml】

接下来就是上代码:

前提:需要创建好flowable数据库,具体我在上一篇Flowable UI已经讲过

创建一个SpringBoot项目

pom.xml文件引入如下依赖

  
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.projectlombok
            lombok
            1.18.34
        

        
        
            mysql
            mysql-connector-java
        

        
        
            org.flowable
            flowable-spring-boot-starter
            6.7.2
        

        
            cn.hutool
            hutool-all
            5.8.22
        

yml文件配置好数据源

Flowable的简单运用_第2张图片

操作流程定义(ProcessDefinition)(增删改查)


@RestController
@RequestMapping("/processDefinition")
public class ProcessDefinitionController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngine processEngine;
    /**
     * 上传一个xml文件,创建一个或多个流程定义(一个xml文件可能有多个流程图)
     */
    @PostMapping("/deploy")
    public String deploy(@RequestParam("file") MultipartFile file,@RequestPart ProcessDeployAddReq dto) {
        try {
            String xmlContent = new String(file.getBytes(), StandardCharsets.UTF_8);
            Deployment deployment = repositoryService.createDeployment()
                    .addString(dto.getProcDefCode() + ".bpmn", xmlContent)
                    .key(dto.getProcDefCode())
                    .name(dto.getProcDefName())
                    .deploy();
            return deployment.getId();
        } catch (IOException e) {
            throw new RuntimeException("文件读取失败", e);
        }
    }

    /**
     * 获取所有流程定义列表
     * @return
     */
    @GetMapping("/processList")
    public List> processList() {
        return repositoryService.createProcessDefinitionQuery().list()
                .stream().map(BeanUtil::beanToMap)
                .collect(Collectors.toList());
    }

    /**
     * 通过流程定义id获取流程图
     * @param id
     * @param response
     * @return
     */
    @GetMapping("/getProcessPng")
    public void getProcess(String id, HttpServletResponse response) {
        InputStream is = repositoryService.getProcessDiagram(id);
        // 设置响应类型为 PNG 图片image
        response.setContentType("image/png");
        try {
            IOUtils.copy(is,response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @GetMapping("/getProcessInfo")
    public Map getProcessInfo(String id) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(id)
                .singleResult();


        return BeanUtil.beanToMap(processDefinition);
    }

    /**
     * 挂起一个流程定义
     * @param
     * @return
     */

    @GetMapping("/suspend")
    public String suspendProcess(String id) {
        repositoryService.suspendProcessDefinitionById(id, true, null);
        return "流程定义已挂起(不可启动新实例)";
    }
    /**
     * 激活一个流程定义
     * @param
     * @return
     */
    @GetMapping("/activateProcess")
    public String activateProcess(String id) {
        repositoryService.activateProcessDefinitionById(id, true, null);
        return "流程定义已激活(可启动新实例)";
    }
    /**
     * 删除一个流程部署
     * @param
     * @return
     */
    @GetMapping("/deleteDeployment")
    public String deleteDeployment(String deploymentId) {
        repositoryService.deleteDeployment(deploymentId, true);
        return "删除成功";
    }
    /**
     * 更新流程定义
     */

}

注意区分Deployment 和ProcessDefinition

Deployment 表示一次部署,一次部署可能会包含多个流程定义,即一个xml文件可能有多个流程图
    特点:

  •    每次调用repositoryService.createDeployment().deploy()都会产生一个新的 Deployment。
  •    可以包含多个流程定义(一个 BPMN 文件中可能定义多个 )。
  •    主要用于管理和查询流程资源的部署记录。

  ProcessDefinition 描述一个流程定义,即一个流程图 
    特点:

  •           来源于 BPMN 文件中的 定义。
  •           每个流程定义都有版本号,相同 key 的流程定义多次部署会递增版本。
  •           是流程引擎运行时的核心数据结构。

获取流程图图片时会出现中文乱码的情况,可以配置好字体,我这使用的是全局 

@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer {


    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName("宋体");
        engineConfiguration.setLabelFontName("宋体");
        engineConfiguration.setAnnotationFontName("宋体");
    }
}

操作流程实例ProcessInstance

@RestController
@RequestMapping("/processInstance")
public class ProcessInstanceController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private HistoryService historyService;
    /**
     * 获取流程实例列表
     * @return
     */
    @RequestMapping("/list")
    public List> list() {
        return runtimeService.createProcessInstanceQuery()
                .includeProcessVariables()  // ⚠️ 关键点:包含流程变量
                .list().stream().map(processInstance -> {
            Map map = new HashMap<>();
            map.put("id", processInstance.getId());
            map.put("name", processInstance.getName());
            map.put("processDefinitionId", processInstance.getProcessDefinitionId());
            map.put("businessKey", processInstance.getBusinessKey());
            map.put("startTime", processInstance.getStartTime());
            map.put("businessStatus", processInstance.getBusinessStatus());
            map.put("startUserId", processInstance.getStartUserId());
            map.put("ProcessDefinitionName",processInstance.getProcessDefinitionName());
            map.put("ProcessDefinitionKey",processInstance.getProcessDefinitionKey());
            map.put("ProcessDefinitionVersion",processInstance.getProcessDefinitionVersion());
            map.put("DeploymentId",processInstance.getDeploymentId());
            map.put("isSuspended",processInstance.isSuspended());
            map.put("ProcessVariables",processInstance.getProcessVariables());
            map.put("TenantId",processInstance.getTenantId());
            map.put("Description",processInstance.getDescription());
            map.put("LocalizedName",processInstance.getLocalizedName());
            map.put("LocalizedDescription",processInstance.getLocalizedDescription());
            map.put("CallbackId",processInstance.getCallbackId());
            map.put("CallbackType",processInstance.getCallbackType());
            return map;
        }).collect(Collectors.toList());
    }

    /**
     * 启动流程实例
     */
    @RequestMapping("/start")
    public Map start(String userId, Integer day,String key) {
        Map variables = new HashMap<>();
        variables.put("userId", userId);
        variables.put("day", day);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key, variables);
        Map result = new HashMap<>();
        result.put("id", processInstance.getId());
        result.put("processDefinitionId", processInstance.getProcessDefinitionId());
        result.put("processInstanceId", processInstance.getProcessInstanceId());
        result.put("startTime", processInstance.getStartTime()); // 可以根据实际需要获取
        result.put("tenantId", processInstance.getTenantId());
        result.put("isEnded", processInstance.isEnded());
        result.put("startUserId", processInstance.getStartUserId());
        result.put("isSuspended", processInstance.isSuspended());
        return result;
    }
    /**
     * 获取流程实例详情
     */
    @RequestMapping("/get")
    public Map get(String id) {
        ProcessInstance processInstance = runtimeService
                .createProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceId(id).singleResult();
        Map map = new HashMap<>();
        map.put("id", processInstance.getId());
        map.put("name", processInstance.getName());
        map.put("processDefinitionId", processInstance.getProcessDefinitionId());
        map.put("businessKey", processInstance.getBusinessKey());
        map.put("startTime", processInstance.getStartTime());
        map.put("businessStatus", processInstance.getBusinessStatus());
        map.put("startUserId", processInstance.getStartUserId());
        map.put("ProcessDefinitionName",processInstance.getProcessDefinitionName());
        map.put("ProcessDefinitionKey",processInstance.getProcessDefinitionKey());
        map.put("ProcessDefinitionVersion",processInstance.getProcessDefinitionVersion());
        map.put("DeploymentId",processInstance.getDeploymentId());
        map.put("isSuspended",processInstance.isSuspended());
        map.put("ProcessVariables",processInstance.getProcessVariables());
        map.put("TenantId",processInstance.getTenantId());
        map.put("Description",processInstance.getDescription());
        map.put("LocalizedName",processInstance.getLocalizedName());
        map.put("LocalizedDescription",processInstance.getLocalizedDescription());
        map.put("CallbackId",processInstance.getCallbackId());
        map.put("CallbackType",processInstance.getCallbackType());
        return map;
    }
    /**
     * 挂起流程实例
     */
    @GetMapping("/suspend")
    public String suspendProcess(String id) {
        runtimeService.suspendProcessInstanceById(id);
        return "流程实例已挂起";
    }
    /**
     * 激活流程实例
     */
    @GetMapping("/activate")
    public String activateProcess(String id) {
        runtimeService.activateProcessInstanceById(id);
        return "流程实例已激活";
    }



}

注意:

1、流程实例参数的获取

      在 Flowable 中,ProcessInstance 接口的 getProcessVariables() 方法默认不会返回流程变量,除非你在查询时显式指定需要加载变量。这是出于性能考虑:流程变量可能较大,因此默认不主动加载

        需要在查询的时候

runtimeService.createProcessInstanceQuery()       
        .includeProcessVariables()  // ⚠️ 关键点:包含流程变量
        .list()

2、ProcessInstance对象不能直接响应给前端

因为ProcessInstance没有序列化

3、ProcessInstance对象不能通过BeanUtil.beanToMap()

ProcessInstance 对象,它是一个代理对象。当你使用 Hutool 的 BeanUtil.beanToMap() 方法进行转换时,会尝试访问所有属性,包括一些延迟加载或需要 CommandContext 支持的属性(比如当前节点、流程模型等),从而触发如下异常:

Flowable的简单运用_第3张图片

任务操作

用户任务操作

这里操作的用户任务是流程实例还未结束的任务,已经结束的在下面介绍的历史里面

@RestController
@RequestMapping("/task")
public class TaskController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    /**
    *获取任务列表,注意这里获取到的只是还未结束的流程实例的任务列表
    *
    */
    @RequestMapping("/list")
    public List> list() {
        return taskService.createTaskQuery()
                .includeProcessVariables()
                .includeTaskLocalVariables()
                .includeCaseVariables()
                .list().stream().map(task -> {
            Map map = new HashMap<>();
            map.put("id", task.getId());
            map.put("name", task.getName());
            map.put("assignee", task.getAssignee());
            map.put("createTime", task.getCreateTime());
            map.put("dueDate", task.getDueDate());
            map.put("processInstanceId", task.getProcessInstanceId());
            map.put("taskDefinitionKey", task.getTaskDefinitionKey());
            map.put("processDefinitionId", task.getProcessDefinitionId());
            map.put("category", task.getCategory());
            map.put("parentTaskId", task.getParentTaskId());
            map.put("tenantId", task.getTenantId());
            map.put("formKey", task.getFormKey());
            map.put("taskLocalVariables", task.getTaskLocalVariables());
            map.put("ProcessVariables",task.getProcessVariables());
            map.put("CaseVariables",task.getCaseVariables());
            return map;
        }).collect(Collectors.toList());
    }

    /**
     * 获取任务详情
     * @param taskId
     * @return
     */
    @RequestMapping("/get")
    public Map get(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return (Map) new HashMap<>().put("msg","任务不存在");
        }
        Map map = new HashMap<>();
        map.put("id", task.getId());
        map.put("name", task.getName());
        map.put("assignee", task.getAssignee());
        map.put("createTime", task.getCreateTime());
        map.put("dueDate", task.getDueDate());
        map.put("processInstanceId", task.getProcessInstanceId());
        map.put("taskDefinitionKey", task.getTaskDefinitionKey());
        map.put("processDefinitionId", task.getProcessDefinitionId());
        map.put("category", task.getCategory());
        map.put("parentTaskId", task.getParentTaskId());
        map.put("tenantId", task.getTenantId());
        map.put("formKey", task.getFormKey());
        map.put("taskLocalVariables", task.getTaskLocalVariables());
        map.put("processVariables", task.getProcessVariables());
        map.put("identityLinks", task.getIdentityLinks());
        map.put("claimTime", task.getClaimTime());
        map.put("delegationState", task.getDelegationState());
        map.put("suspended", task.isSuspended());
        map.put("scopeId", task.getScopeId());
        map.put("subScopeId", task.getSubScopeId());
        map.put("scopeType", task.getScopeType());
        map.put("scopeDefinitionId", task.getScopeDefinitionId());
        map.put("propagatedStageInstanceId", task.getPropagatedStageInstanceId());
        map.put("isSuspended", task.isSuspended());
        return map;

    }

    /**
     * 用户任务审批,该接口仅仅只是简单的模拟审批,实际开发中,应该根据业务逻辑进行审批
     * 比如:审批角色,审批人,审批原因,审批结果等等
     * @param taskId
     * @param isApproval
     * @return
     */
    @RequestMapping("/complete")
    public String complete(String taskId, Integer isApproval) {
        Map teacherMap = new HashMap<>();
        teacherMap.put("isApproval", isApproval);
        taskService.complete(taskId, teacherMap);
        return "审批成功:" + taskId;
    }
}
服务任务

服务任务是自动执行的,在xml文件里面通过class配置了对应的执行类

 

执行类需要实现JavaDelegate重写execute方法,在execute方法里面编写业务逻辑代码

public class YesJavaDelegateImpl implements JavaDelegate {
   
    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println(delegateExecution.getVariables());
        System.out.println(delegateExecution.getProcessInstanceId());
        System.out.println("通过申请");
        // 修改申请状态为通过
        //给申请人发送消息
        System.out.println("给申请人发送消息");

    }
}

历史节点信息

下面只是几个简单的,其他自行扩展

/**
 * 流程实例已经结束的节点信息
 * @author yzz
 */
@RestController
@RequestMapping("/history")
public class HistoryController {
    @Autowired
    private HistoryService historyService;
    /**
    *
    *获取历史流程实例,已经结束的
    */
    @RequestMapping("/list")
    public List> list() {
        return historyService.createHistoricProcessInstanceQuery()
                .includeProcessVariables()  
                .list().stream().map(processInstance -> {
                    Map map = new HashMap<>();
                    map.put("id", processInstance.getId());
                    map.put("name", processInstance.getName());
                    map.put("processDefinitionId", processInstance.getProcessDefinitionId());
                    map.put("businessKey", processInstance.getBusinessKey());
                    map.put("startTime", processInstance.getStartTime());
                    map.put("businessStatus", processInstance.getBusinessStatus());
                    map.put("startUserId", processInstance.getStartUserId());
                    map.put("ProcessDefinitionName",processInstance.getProcessDefinitionName());
                    map.put("ProcessDefinitionKey",processInstance.getProcessDefinitionKey());
                    map.put("ProcessDefinitionVersion",processInstance.getProcessDefinitionVersion());
                    map.put("DeploymentId",processInstance.getDeploymentId());
                    map.put("ProcessVariables",processInstance.getProcessVariables());
                    map.put("TenantId",processInstance.getTenantId());
                    map.put("Description",processInstance.getDescription());
                    map.put("CallbackId",processInstance.getCallbackId());
                    map.put("CallbackType",processInstance.getCallbackType());
                    return map;
                }).collect(Collectors.toList());
    }
    /**
     * 获取某一个流程实例的流程轨迹
     */
    @RequestMapping("/getHisActivity")
    public List> getHisActivity(String processInstanceId) {
        List list = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
        return list.stream().map(activityInstance -> {
            Map map = new HashMap<>();
            map.put("Id", activityInstance.getId());
            map.put("ProcessInstanceId", activityInstance.getProcessInstanceId());
            map.put("ActivityId", activityInstance.getActivityId());
            map.put("ActivityName", activityInstance.getActivityName());
            return map;
        }).collect(Collectors.toList());
    }
}

你可能感兴趣的:(java,maven,spring,boot)