Flowable 快速入门教程:Flowable 入门开发案例,结合流程设计器详细讲解

  • 前言
  • 流程设计器集成
  • 整体流程图
  • 流程节点说明
  • 第一审核人节点:实际设置审核人
  • 配置信息
  • 说明
  • 第二审核人:参数设置审核人
  • 配置信息
  • 说明
  • 第三审核人:参数分支判断与实际组配置
  • 配置信息
  • 说明
  • 会签:多人并审与参数设置用户组
  • 配置信息
  • 说明
  • 监听器会签:结合监听器实现多人并审
  • 配置信息
  • 说明
  • 示例代码
  • 项目结构
  • 流程部署
  • 启动流程
  • 业务数据列表查询
  • 页面效果
  • 功能描述
  • 审核列表数据查询
  • 效果图
  • 功能描述
  • 业务系统代码
  • 流程引擎功能封装
  • 审核功能
  • 功能描述
  • 业务系统代码
  • 流程引擎功能封装
  • 审核历史查询
  • 效果图
  • 功能描述
  • 业务系统代码
  • 流程引擎功能封装
  • 流程图查看流程进度
  • 效果图
  • 功能描述
  • 流程引擎功能封装
  • 参考代码


前言

本文以一个简答的 Demo 为案例,按节点讲解,目的是为了让刚接触流程引擎的人能更快的熟悉流程引擎开发,了解业务系统与流程引擎结合的思路。

由于是 Demo,接口存在不完善之处,需要自己补充添加。

流程设计器集成

文章:Flowable 快速入门教程:SpringBoot 集成 Flowable + Flowable Modeler 流程配置可视化(超详细)

整体流程图

起始与结束节点,就配置了名称方便查看,没做额外配置

flowable绑定businessKey flowable教程视频_Flowable Modeler

流程节点说明

第一审核人节点:实际设置审核人

flowable绑定businessKey flowable教程视频_Flowable Modeler_02

配置信息

flowable绑定businessKey flowable教程视频_Flowable_03


flowable绑定businessKey flowable教程视频_流程引擎_04

说明

这里的分配人为固定的人,用的使用户的编号

任务完成时,直接 complete 即可

/**
 * 任务处理
 *
 * @param taskId   任务 Id,来自 ACT_RU_TASK
 * @param assignee 设置审核人,替换
 * @param map      完成任务需要的条件参数
 * @return
 */
@RequestMapping(value = "/task", method = RequestMethod.POST)
public void taskByAssignee(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "assignee") String assignee, @RequestBody Map<String, Object> map) {
    // 设置审核人
    taskService.setAssignee(taskId, assignee);

    // 设置任务参数,也可不设置:key value,只是示例
    // 带 Local 为局部参数,只适用于本任务,不带 Local 为全局任务,可在其他任务调用参数
    taskService.setVariableLocal(taskId, "status", true);

    // 完成任务
    taskService.complete(taskId, map);
    logger.info("任务完成:" + taskId + " " + new Date());
}

第二审核人:参数设置审核人

flowable绑定businessKey flowable教程视频_Flowable Modeler_05

配置信息

flowable绑定businessKey flowable教程视频_Modeler_06


flowable绑定businessKey flowable教程视频_Modeler_07

说明

变量:reviewer 节点审核人

说明:通过参数的形式设定分配人,因此在需要在进入任务节点之前把对应参数注入。否则会提示表达式错误。

疑问说明:如果我在审核时候才设置审核人,那我表单展示时如何知道审核人是谁?

  1. 首先我这里是第一次审核时候就放进去的,因为是 Demo 所有逻辑的严谨性上没考虑那么全
  2. 关于怎么知道表单每条数据的审核人有谁,个人建议自己建表来存关系最为方便与准确。之后审核时候审核人可以在流程开始阶段通过监听器设置进变量或者直接审核动作之前再设置都可以。

业务系统代码

// 设置审核人
feignClientService.setVariable(taskId, "reviewer", "bbb");
// 完成任务,taskId 任务节点 ID
feignClientService.taskByAssignee(taskId, user.getUsername(), map);

流程引擎系统代码,同上

/**
 * 任务处理
 *
 * @param taskId   任务 Id,来自 ACT_RU_TASK
 * @param assignee 设置审核人,替换
 * @param map      完成任务需要的条件参数
 * @return
 */
@RequestMapping(value = "/task", method = RequestMethod.POST)
public void taskByAssignee(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "assignee") String assignee, @RequestBody Map<String, Object> map) {
    // 设置审核人
    taskService.setAssignee(taskId, assignee);

    // 设置任务参数,也可不设置:key value,只是示例
    // 带 Local 为局部参数,只适用于本任务,不带 Local 为全局任务,可在其他任务调用参数
    taskService.setVariableLocal(taskId, "status", true);

    // 完成任务
    taskService.complete(taskId, map);
    logger.info("任务完成:" + taskId + " " + new Date());
}

第三审核人:参数分支判断与实际组配置

flowable绑定businessKey flowable教程视频_流程引擎_08

配置信息

flowable绑定businessKey flowable教程视频_流程引擎_09


flowable绑定businessKey flowable教程视频_Modeler_10


flowable绑定businessKey flowable教程视频_入门教程_11


flowable绑定businessKey flowable教程视频_Flowable_12


flowable绑定businessKey flowable教程视频_Flowable_13


flowable绑定businessKey flowable教程视频_Flowable Modeler_14

说明

变量:assignee 节点审核人

说明:分支判断,如果是 admin 审核的进入 分支审核第四审核人2号 节点,其他情况走 分支审核第四审核人1号 节点,默认走 分支审核第四审核人1号

组的配置不管是实际还是参数形式其实差不多,因为都无法让流程直接拿到审核人。区别在于实际值方便那些不懂流程的人配置以及我们可以直接获取组然后查找,而参数我们可以通过变量注入。还是建议自己建表存关系,降低开发难度。

业务系统代码

// 流程完成所需的条件参数
Map<String, Object> map = new HashMap<>();
map.put("assignee", user.getUsername());
// 完成任务,taskId 任务节点 ID
feignClientService.taskByAssignee(taskId, user.getUsername(), map);

流程引擎系统代码,同上。之后流程引擎 complete 后会自动根据你的参数进行分支判断

/**
 * 任务处理
 *
 * @param taskId   任务 Id,来自 ACT_RU_TASK
 * @param assignee 设置审核人,替换
 * @param map      完成任务需要的条件参数
 * @return
 */
@RequestMapping(value = "/task", method = RequestMethod.POST)
public void taskByAssignee(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "assignee") String assignee, @RequestBody Map<String, Object> map) {
    // 设置审核人
    taskService.setAssignee(taskId, assignee);

    // 设置任务参数,也可不设置:key value,只是示例
    // 带 Local 为局部参数,只适用于本任务,不带 Local 为全局任务,可在其他任务调用参数
    taskService.setVariableLocal(taskId, "status", true);

    // 完成任务
    taskService.complete(taskId, map);
    logger.info("任务完成:" + taskId + " " + new Date());
}

会签:多人并审与参数设置用户组

flowable绑定businessKey flowable教程视频_Flowable Modeler_15

配置信息

flowable绑定businessKey flowable教程视频_流程引擎_16

说明

变量:

  1. assigneeList:审核人列表,注意这里不能用 ${ } 包裹,否则系统拿到的就是对应的 value 值
  2. nrOfInstances:会签环节实例总数,系统参数,可直接使用
  3. nrOfActiveInstances:还没有完成的实例数量,系统参数,可直接使用
  4. nrOfCompletedInstances:已经完成的实例的数量,系统参数,可直接使用

多实例类型:

  1. Parallel:并行模式,多人同事审核
  2. Sequential:串行模式,按顺序审核,顺序为集合顺序(暂时没试验过)

说明:

assigneeList 这个参数必须在进入会签任务之前注入,原因是任务在进入会签任务时就需要根据 assigneeList 来生成 task 任务,因此这个参数必须存在。这里可以利用监听器在任务生成之前注入,或者在之前节点就注入。

flowable绑定businessKey flowable教程视频_流程引擎_17


会签判断条件:完成率达到 50% 进入下个节点

业务系统代码,会签节点之前
PS:注意集合参数在请求后,流程引擎那不能用 Object 类型来接收,会导致解析时无法转化为集合,因此我这里集合单独写了个接口来设置参数

// 设置 assigneeList 参数
// 注意,这个参数是在进入会签节点之前就设置了
feignClientService.setListVariable(taskId, "assigneeList", assigneeList);

业务系统代码,会签节点

// 完成任务,taskId 任务节点 ID
feignClientService.taskByAssignee(taskId, user.getUsername(), map);

流程引擎系统代码,同上。之后流程引擎 complete 后会自动根据配置的表达式判断节点是否完成。

/**
 * 任务处理
 *
 * @param taskId   任务 Id,来自 ACT_RU_TASK
 * @param assignee 设置审核人,替换
 * @param map      完成任务需要的条件参数
 * @return
 */
@RequestMapping(value = "/task", method = RequestMethod.POST)
public void taskByAssignee(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "assignee") String assignee, @RequestBody Map<String, Object> map) {
    // 设置审核人
    taskService.setAssignee(taskId, assignee);

    // 设置任务参数,也可不设置:key value,只是示例
    // 带 Local 为局部参数,只适用于本任务,不带 Local 为全局任务,可在其他任务调用参数
    taskService.setVariableLocal(taskId, "status", true);

    // 完成任务
    taskService.complete(taskId, map);
    logger.info("任务完成:" + taskId + " " + new Date());
}

监听器会签:结合监听器实现多人并审

flowable绑定businessKey flowable教程视频_入门教程_18

配置信息

flowable绑定businessKey flowable教程视频_Modeler_19


flowable绑定businessKey flowable教程视频_入门教程_20

说明

变量:okNum 已审核的次数

说明:ExecutionListener 任务监听器,每次有人审核执行任务就把数字 +1,达到 3 个完成节点任务,
监听器会在 complete 之前执行。

流程引擎系统代码

/**
 * 会签监听器
 * @author: linjinp
 * @create: 2019-11-18 11:43
 **/
@Component
public class MyListener implements ExecutionListener {

    // 页面配置参数注入
    private FixedValue num;

    @Override
    public void notify(DelegateExecution delegateExecution) {
        // 获取页面配置参数的值
        System.out.println(num.getExpressionText());

        // 校验 okNum 是否已经存在
        if (!delegateExecution.hasVariable("okNum")) {
            delegateExecution.setVariable("okNum", 0);
        }
        // 已审核次数,审核一次 +1
        int okNum = (int) delegateExecution.getVariable("okNum") + 1;
        delegateExecution.setVariable("okNum", okNum);
    }
}

示例代码

项目结构

我这里业务系统与流程引擎系统是分开的,不处于一个项目,有各自的数据库

业务系统:用户数据,表单,权限等,这里业务系统通过 feign 调用流程引擎的接口

流程引擎系统:流程引擎根据功能对接口进行封装,通过 Restful 形式的接口对外开放

Demo 一共 5 个功能:业务数据列表查询(这个不涉及流程引擎),审核列表数据查询,审核功能,审核历史查询,流程图查看流程进度

代码仅用作入门学习与逻辑参考

流程部署

ACT_DE_MODEL:保存后的流程模型信息

flowable绑定businessKey flowable教程视频_入门教程_21

/**
 * 流程部署
 *
 * @param modelId 流程ID,来自 ACT_DE_MODEL
 * @return
 */
@RequestMapping(value = "/deploy/{modelId}", method = RequestMethod.GET)
public void deploy(@PathVariable(value = "modelId") String modelId) {
    // 根据模型 ID 获取模型
    Model modelData = modelService.getModel(modelId);

    byte[] bytes = modelService.getBpmnXML(modelData);
    if (bytes == null) {
        logger.error("模型数据为空,请先设计流程并成功保存,再进行发布");
    }

    BpmnModel model = modelService.getBpmnModel(modelData);
    if (model.getProcesses().size() == 0) {
        logger.error("数据模型不符要求,请至少设计一条主线流程");
    }
    byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
    String processName = modelData.getName() + ".bpmn20.xml";

    // 部署流程
    repositoryService.createDeployment()
            .name(modelData.getName())
            .addBytes(processName, bpmnBytes)
            .deploy();

    logger.info("流程部署成功:" + modelId + " " + new Date());
}

启动流程

ACT_RE_PROCDEF:流程定义相关信息,流程多次部署后的历史信息也可以在这张表查看

flowable绑定businessKey flowable教程视频_流程引擎_22

/**
 * 启动流程
 *
 * @param deployId 部署的流程 Id,来自 ACT_RE_PROCDEF
 * @param userId   用户 Id
 * @param dataKey  数据 Key,业务键,一般为表单数据的 ID,仅作为表单数据与流程实例关联的依据
 * @return
 */
@RequestMapping(value = "/start/{deployId}/{userId}/{dataKey}", method = RequestMethod.GET)
public void start(@PathVariable(value = "deployId") String deployId, @PathVariable(value = "userId") String userId, @PathVariable(value = "dataKey") String dataKey) {
    // 设置发起人
    identityService.setAuthenticatedUserId(userId);
    // 根据流程 ID 启动流程
    runtimeService.startProcessInstanceById(deployId, dataKey);
    logger.info("流程启动成功:" + deployId + " " + new Date());
}

业务数据列表查询

页面效果

flowable绑定businessKey flowable教程视频_Modeler_23

功能描述

由于不涉及流程引擎,因此就不上代码了。

由于表单与流程引擎本身是不存在关系的,因此这里的状态储存是直接在业务数据表里储存状态,查询起来也简单。

如果需要用到流程引擎中的参数,那就自己查询就可以了。

审核列表数据查询

效果图

flowable绑定businessKey flowable教程视频_流程引擎_24

功能描述

查询当前用户需要审核的数据列表

业务系统代码

整体逻辑:获取流程中所有审核人为当前用户的流程数据,返回保存在流程中的业务键以及其他需要的数据,然后对数据进行组合业务键也就是数据 ID 是在流程启动时候设置到流程中的,详细看启动接口。

/**
 * 获取审核列表
 * @return
 */
@GetMapping(value = "/getApproveList")
public ErrorMsg getApproveList(HttpServletRequest request) {
    ErrorMsg errorMsg = new ErrorMsg();
    String token = request.getHeader("X-Token");
    User user = (User) redisTemplate.opsForValue().get(token);

    // 获取用户拥有的用户组,admin,aaa,test
    List<Group> hasGroup = userPermissionDao.getUserGroup(user.getUsername());
    // 取出组 ID
    List<String> groups = new ArrayList<>();
    hasGroup.forEach(group -> {
        groups.add(group.getId());
    });

    // 调用流程引擎封装的接口
    // 根据用户组获取需要审核的数据对应的流程信息
    // 主要为了满足我设置的是实际组的情况
    List<Map<String, Object>> idListByGroupMapList = feignClientService.getRuntimeBusinessKeyByGroup(groups);

    // 调用流程引擎封装的接口
    // 获取自己发起的正在进行的审核数据对应的流程信息
    List<Map<String, Object>> idListByUserMapList = feignClientService.getRuntimeBusinessKeyByUser(user.getUsername());
    // 整合两个 list
    idListByUserMapList.addAll(idListByGroupMapList);

    // 这里开始对数据进行组合,方便前端展示
    List<String> idList = new ArrayList<>();
    idListByUserMapList.forEach(idListByUserMap -> {
        // businessKey 为业务键,我用来存数据的 ID
        idList.add((String) idListByUserMap.get("businessKey"));
    });
    // 获取正在审核的数据
    List<Map<String, Object>> roles = demoService.getRoleListByIdsDemo(idList);
    // 数据整合,将需要在前端展示的流程信息与业务数据信息组合到一起
    roles.forEach(role -> {
        idListByUserMapList.forEach(idListByUserMap -> {
            if (role.get("id").toString().equals(idListByUserMap.get("businessKey").toString())) {
                role.put("taskId", idListByUserMap.get("taskId"));
                role.put("processInstanceName", idListByUserMap.get("processInstanceName"));
                role.put("startTime", idListByUserMap.get("startTime"));
            }
        });
    });
    // 统一返回
    errorMsg.setErrorCode(ErrorCode.SUCCESS);
    errorMsg.setErrorMsg("SUCCESS");
    errorMsg.setRetData(roles);
    return errorMsg;
}

流程引擎功能封装

根据组获取任务数据

/**
 * 获取组,获取需要审核的业务键 business_key 列表
 *
 * @param groupIds 组 Id
 * @return
 */
@RequestMapping(value = "/getRuntimeBusinessKeyByGroup", method = RequestMethod.POST)
public List<Map<String, Object>> getRuntimeBusinessKeyByGroup(@RequestBody List<String> groupIds) {
    List<Map<String, Object>> idList = new ArrayList<>();
    // 判断是否有组信息
    if (groupIds != null && groupIds.size() > 0) {
        // 根据发起人获取正在执行的任务列表
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroupIn(groupIds).list();
        tasks.forEach(task -> {
            Map<String, Object> data = new HashMap<>();
            // 根据任务获取流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
            // 获取流程实例中的业务键
            data.put("businessKey", processInstance.getBusinessKey());
            // 获取任务 Id
            data.put("taskId", task.getId());
            // 流程定义名称
            data.put("processInstanceName", processInstance.getProcessDefinitionName());
            // 流程开始时间
            data.put("startTime", processInstance.getStartTime());
            idList.add(data);
        });
    }
    return idList;
}

根据审核人获取任务数据

/**
 * 根据用户,获取需要审核的业务键 business_key 列表
 *
 * @param userId 用户 Id
 * @return
 */
@RequestMapping(value = "/getRuntimeBusinessKeyByUser/{userId}", method = RequestMethod.GET)
public List<Map<String, Object>> getRuntimeBusinessKeyByUser(@PathVariable(value = "userId") String userId) {
    List<Map<String, Object>> idList = new ArrayList<>();
    // 根据用户获取正在进行的任务
    List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).list();
    tasks.forEach(task -> {
        Map<String, Object> data = new HashMap<>();
        // 根据任务获取流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        // 获取流程实例中的业务键
        data.put("businessKey", processInstance.getBusinessKey());
        // 获取任务 Id
        data.put("taskId", task.getId());
        // 流程定义名称
        data.put("processInstanceName", processInstance.getProcessDefinitionName());
        // 流程开始时间
        data.put("startTime", processInstance.getStartTime());
        idList.add(data);
    });
    return idList;
}

审核功能

功能描述

点击按钮,进行审核

业务系统代码

整体逻辑:获取任务 Id,完成任务,对需要的参数进行设置
PS:关于用户组,审核人的参数设置建议用监听器之类的来实现,这里为了方便直接在审核里设置了

/**
* 进行审核
 * @param request
 * @param taskId 任务节点 Id
 * @return
 */
@GetMapping(value = "/task/{taskId}/{dataId}")
public ErrorMsg taskByAssignee(HttpServletRequest request, @PathVariable(value = "taskId") String taskId, @PathVariable(value = "dataId") String dataId) {
    ErrorMsg errorMsg = new ErrorMsg();
    String token = request.getHeader("X-Token");

    User user = (User) redisTemplate.opsForValue().get(token);

    // 会签情况需要用户列表数据
    List<String> assigneeList = userGroupPermissionDao.getIdByGroup("ce537a73-dbc2-4d2f-ac5f-2cdc208a20e0");
    
    // 设置会签所需的用户列表数据
    // 会签与监听器会签节点用户组参数
    // 注意这里单独封装了个方法,集合不能用 Object 接收,否则流程引擎解析会失败
    feignClientService.setListVariable(taskId, "assigneeList", assigneeList);
    // 多实例基数,没用到
    feignClientService.setVariable(taskId, "cycleNum", assigneeList.size());
    // 第二审核人节点,审核人参数
    feignClientService.setVariable(taskId, "reviewer", "bbb");

    // 流程完成所需的条件参数
    // 主要用于第三审核人节点,根据审核人进行分支判断依据
    Map<String, Object> map = new HashMap<>();
    map.put("assignee", user.getUsername());

    // 根据任务节点 Id,获取流程实例 Id
    String processInstanceId = feignClientService.getTaskInfo(taskId);
    // 完成任务,taskId 任务节点 ID
    feignClientService.taskByAssignee(taskId, user.getUsername(), map);
    // 通过流程实例 Id,判断流程是否结束
    boolean isFinish = feignClientService.checkProcessInstanceFinish(processInstanceId);
    if (isFinish) {
        // 更新审核状态
        Role role = new Role();
        role.setId(Integer.valueOf(dataId));
        role.setStatus(2);
        demoService.updateRoleDemo(role);
    }
    errorMsg.setErrorCode(ErrorCode.SUCCESS);
    errorMsg.setErrorMsg("SUCCESS");
    return errorMsg;
}

流程引擎功能封装

参数设置接口

/**
 * 设置任务参数
 *
 * @param taskId 任务ID
 * @param key 键
 * @param value 值
 * @return
 */
@RequestMapping(value = "/setVariable", method = RequestMethod.POST)
public void setVariable(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "key") String key, @RequestParam(value = "value") Object value) {
    String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
    runtimeService.setVariable(processInstanceId, key, value);
}

/**
 * 设置任务参数,List 使用
 *
 * @param taskId 任务ID
 * @param key 键
 * @param value 值
 * @return
 */
@RequestMapping(value = "/setListVariable", method = RequestMethod.POST)
public void setListVariable(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "key") String key, @RequestParam(value = "value") List<String> value) {
    String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
    runtimeService.setVariable(processInstanceId, key, value);
}

根据任务节点获取流程实例 Id

/**
 * 根据任务节点获取流程实例 Id
 *
 * @param taskId 任务节点 Id
 * @return
 */
@RequestMapping(value = "/getTaskInfo/{taskId}", method = RequestMethod.GET)
public String getTaskInfo(@PathVariable(value = "taskId") String taskId) {
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    return task.getProcessInstanceId();
}

通过流程实例 Id,判断流程是否结束

/**
 * 通过流程实例 Id,判断流程是否结束
 *
 * @param processInstanceId 流程实例 Id
 * @return true 结束,false 未结束
 */
@RequestMapping(value = "/checkProcessInstanceFinish/{processInstanceId}", method = RequestMethod.GET)
public boolean checkProcessInstanceFinish(@PathVariable(value = "processInstanceId") String processInstanceId) {
    boolean isFinish = false;
    // 根据流程 ID 获取未完成的流程中是否存在此流程
    long count = historyService.createHistoricProcessInstanceQuery().unfinished().processInstanceId(processInstanceId).count();
    // 不存在说明没有结束
    if (count == 0) {
        isFinish = true;
    }
    return isFinish;
}

审核历史查询

效果图

flowable绑定businessKey flowable教程视频_流程引擎_25

功能描述

根据审核人字段,获取已完成的任务中,审核人为当前用户的数据

PS:组审核情况,在审核时,都直接设置了审核人,因此可以直接获取到

业务系统代码

/**
 * 获取审核历史记录
 * @return
 */
@GetMapping(value = "/getApproveHistory")
public ErrorMsg getApproveHistory(HttpServletRequest request) {
    ErrorMsg errorMsg = new ErrorMsg();
    String token = request.getHeader("X-Token");
    User user = (User) redisTemplate.opsForValue().get(token);

    // 调用流程引擎接口,获取审核人为当前用户的已完成的任务
    List<Map<String, Object>> historys = feignClientService.getHistoryByUser(user.getUsername());
    errorMsg.setErrorCode(ErrorCode.SUCCESS);
    errorMsg.setErrorMsg("SUCCESS");
    errorMsg.setRetData(historys);
    return errorMsg;
}

流程引擎功能封装

/**
 * 获取用户审核历史
 *
 * @param userId 发起人 Id
 * @return
 */
@RequestMapping(value = "/getHistoryByUser/{userId}", method = RequestMethod.GET)
public List<Map<String, Object>> getHistoryByUser(@PathVariable(value = "userId") String userId) {
    List<Map<String, Object>> historyList = new ArrayList<>();
    // 根据用户,查询任务实例历史
    List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).finished().orderByHistoricTaskInstanceEndTime().desc().list();
    list.forEach(historicTaskInstance -> {
        // 历史流程实例
        HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(historicTaskInstance.getProcessInstanceId()).singleResult();
        // 获取需要的历史数据
        Map<String, Object> historyInfo = new HashMap<>();
        historyInfo.put("assignee", historicTaskInstance.getAssignee());
        // 节点名称
        historyInfo.put("nodeName", historicTaskInstance.getName());
        // 流程开始时间
        historyInfo.put("startTime", historicTaskInstance.getCreateTime());
        // 节点操作时间(本流程节点结束时间)
        historyInfo.put("endTime", historicTaskInstance.getEndTime());
        // 流程定义名称
        historyInfo.put("processName", hpi.getProcessDefinitionName());
        // 流程实例 ID
        historyInfo.put("processInstanceId", historicTaskInstance.getProcessInstanceId());
        // 业务键
        historyInfo.put("businessKey", hpi.getBusinessKey());
        historyList.add(historyInfo);
    });
    return historyList;
}

流程图查看流程进度

效果图

flowable绑定businessKey flowable教程视频_Flowable_26

功能描述

这里直接采用流输出直接在页面显示,因此只能前端直接调用流程引擎的接口

如果想要更完善的显示流程,建议自己解析 Json,自己前端用组件生成流程图

流程引擎功能封装

根据任务 ID 获取任务进度流程图

/**
 * 根据任务 ID 获取任务进度流程图
 *
 * @param taskId 任务节点 Id
 * @return
 */
@RequestMapping(value = "/getTaskProcessDiagram/{taskId}", method = RequestMethod.GET)
public void getTaskProcessDiagram(@PathVariable(value = "taskId") String taskId, HttpServletResponse httpServletResponse) {

    // 根据任务 ID 获取流程实例 ID
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    String processInstanceId = task.getProcessInstanceId();

    // 根据流程实例获取流程图
    // 流程定义 ID
    String processDefinitionId;

    // 查看完成的进程中是否存在此进程
    long count = historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count();
    if (count > 0) {
        // 如果流程已经结束,则得到结束节点
        HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

        processDefinitionId = pi.getProcessDefinitionId();
    } else {// 如果流程没有结束,则取当前活动节点
        // 根据流程实例ID获得当前处于活动状态的ActivityId合集
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        processDefinitionId = pi.getProcessDefinitionId();
    }
    List<String> highLightedActivitis = new ArrayList<>();

    // 获得活动的节点
    List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();

    for (HistoricActivityInstance tempActivity : highLightedActivitList) {
        String activityId = tempActivity.getActivityId();
        highLightedActivitis.add(activityId);
    }

    List<String> flows = new ArrayList<>();
    //获取流程图
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
    ProcessEngineConfiguration processEngineConfig = processEngine.getProcessEngineConfiguration();

    ProcessDiagramGenerator diagramGenerator = processEngineConfig.getProcessDiagramGenerator();
    InputStream in = diagramGenerator.generateDiagram(bpmnModel, "bmp", highLightedActivitis, flows, processEngineConfig.getActivityFontName(),
            processEngineConfig.getLabelFontName(), processEngineConfig.getAnnotationFontName(), processEngineConfig.getClassLoader(), 1.0, true);

    OutputStream out = null;
    byte[] buf = new byte[1024];
    int legth;
    try {
        out = httpServletResponse.getOutputStream();
        while ((legth = in.read(buf)) != -1) {
            out.write(buf, 0, legth);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        IOUtils.closeQuietly(out);
        IOUtils.closeQuietly(in);
    }
}

参考代码

Flowable 测试用接口部分的代码

Flowable-Demo 模块, API 包下