SpringBoot integration Flowable workflow-2 (code integration)

Insert picture description here

Article Directory

1 Introduction

The previous blog [ SpringBoot Integration Flowable Workflow-1 (Drawing Process Definition) ] introduced a simple flow chart using Flowable-ui.

This blog will introduce the code integration part. The main contents are: [Release Process Definition], [Open Process Task], [Get User Task], [User Approval Task], [Add Approval Opinion], [Get Flow Chart], [ Get my to-do task], [Get the process initiated by me], [Process that I have approved]...

2. Code add dependencies

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

3. Create a database

Creating a database can be achieved by using the sql provided by Flowable, or by automatically creating a database through a program

3.1 SQL provided by Flowable

Download the Flowable-related resources of the file, go to https://flowable.com/open-source/downloads , and then click [Download Flowable v6.xx], the download is a compressed package, after decompression, you will see the following directory structure
└─database                                   # 数据库文件
    └─create
        └─all
            └─flowable.mysql.all.create.sql

Find the $/database/create/database/create/flowable.mysql.all.create.sql file and import the mysql database

Need to add a parameter value in the url of jdbc, nullCatalogMeansCurrent=true

as follows:

jdbc:mysql://127.0.0.1:3306/flowable?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true

4. Code section

4.1 Several commonly used Service classes

/** 运行时Service(用于运行时流程实例、流程变量、流程节点) */
@Autowired
private RuntimeService runtimeService;
/** 资源存储Service(用于模型、流程定义) */
@Autowired
private RepositoryService repositoryService;
/** 流程引擎Service */
@Qualifier("processEngine")
@Autowired
private ProcessEngine processEngine;
/** 任务Service(用于运行时的用户任务、审批日志) */
@Autowired
private TaskService taskService;
@Autowired
protected ManagementService managementService;
/** 历史Service(用于历史记录,可以找到历史的流程实例、流程节点) */
@Autowired
protected HistoryService historyService;

4.2.1 Release process definition

The previous blog [ SpringBoot Integration Flowable Workflow-1 (Drawing Process Definition) ] Draw the process, and then download it is an xml file of "leave process 1.bpmn20.xml", download it and you can publish this process through code It's in the process definition.

code show as below

 @Override
public boolean importProcessDefinition(MultipartFile file) {
    try {
        Deployment deployment = repositoryService.createDeployment()
                // .key()
                // .name(name)
                // .category(category)
                // .tenantId()
                // 通过压缩包的形式一次行多个发布
                // .addZipInputStream()
                // 通过InputStream的形式发布
                .addInputStream(file.getOriginalFilename(), file.getInputStream())
                // 通过存放在classpath目录下的文件进行发布
                // .addClasspathResource("p1.bpmn20.xml")
                // 通过xml字符串的形式
                // .addString()
                .deploy();
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
        if (processDefinition == null) {
            return false;
        }
    } catch (Exception e) {
        throw new BusinessException("导入流程定义失败:" + e.getMessage());
    }
    return true;
}

Based on the SpringBoot release process definition, there is another ingenious form that is to create a folder processes in the resources directory, and then send the corresponding process files to this folder. When starting the SpringBoot project, you can observe the log You will find that the process is automatically released. (Not recommended)

workflow-server
  └─src
     └─main
         ├─java
         └─resources
             ├─mapper
             └─processes
                 └─请假流程1.bpmn20.xml

4.2.2 Query process definition

// 创建 ProcessDefinitionQuery 
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
// 根据流程定义ID
// processDefinitionQuery.processDefinitionId(requestDTO.getId());
// 根据流程定义Key
// processDefinitionQuery.processDefinitionKeyLike("%" + requestDTO.getKey().trim() + "%");
// 根据流程定义名称
// processDefinitionQuery.processDefinitionNameLike("%" + requestDTO.getName().trim() + "%");
//
// 获取总数
long count = processDefinitionQuery.count();
// 获取单个 
ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
// 获取列表
List<ProcessDefinition> processDefinitions = processDefinitionQuery.list();
// 获取分页
List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage(0, 10)

4.2.3 Get process definition xml

public String getXmlResource(String id) {
     ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
     InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                                                                     processDefinition.getResourceName());
     try {
         return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
     } catch (Exception e) {
         throw new BusinessException("获取资源失败:" + e.getMessage());
     } finally {
         try {
             IOUtils.close(inputStream, null);
         } catch (IOException ignored) {
         }
     }
}

4.2.4 Get process definition picture

public String getDiagramImageResource(String id) {
    // 理论上我用这种形式也是行的,但是我获取出来会有乱码,我也比较奇怪,所以换了通过 bpmnModel 的方式
    // ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
    // InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
    //                                                                 processDefinition.getDiagramResourceName());
    // 获取bpmnModel对象
    BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
    // 生成图片流
    ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
    ProcessDiagramGenerator diagramGenerator = configuration.getProcessDiagramGenerator();
    InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", Collections.emptyList(),
                                                               Collections.emptyList(), "宋体", "宋体", "宋体",
                                                               this.getClass().getClassLoader(), 1.0, true);
    try {
        return "data:image/png;base64," + Base64.encode(inputStream);
    } catch (Exception e) {
        throw new BusinessException("获取资源失败:" + e.getMessage());
    } finally {
        try {
            IOUtils.close(inputStream, null);
        } catch (IOException ignored) {
        }
    }
}

4.2.5 Delete process definition

public void deleteByIds(List<String> ids) {
     List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().processDefinitionIds(new HashSet<>(ids)).list();
     // repositoryService.deleteDeployment(String deploymentId, boolean cascade)
     // 删除给定的部署和级联删除流程实例、历史流程实例和作业。
     processDefinitions.forEach(v -> repositoryService.deleteDeployment(v.getDeploymentId(), true));
 }

4.3.1 Adding process instance approval comments

Play the role of operation record similar to the recording process. Here I encapsulate a layer by myself. I encapsulate my business user ID, user name, execution type, opinion content...
@Data
@ApiModel("流程实例-审批意见请求参数")
public class ProcessInstanceCommentRequestDTO implements Serializable {

    @ApiModelProperty(value = "流程定义Key")
    private String processInstanceId;
    @ApiModelProperty(value = "任务ID(缺省)")
    private String taskId;
    @ApiModelProperty(value = "类型 CommentEntity: event/comment")
    private String type;

    @ApiModelProperty(value = "用户ID")
    private String userId;
    @ApiModelProperty(value = "用户昵称")
    private String nickname;
    @ApiModelProperty(value = "执行类型")
    private String executeType;
    @ApiModelProperty(value = "执行类型(参考ExecuteTypeEnum)SUBMIT-提交;YES-同意;NO-拒绝;STOP-流程终止;DELETE-流程删除")
    private String executeTypeValue;
    @ApiModelProperty(value = "内容")
    private String content;
    @ApiModelProperty(value = "额外携带的内容")
    private String ext;
}

Add process instance approval comments

// 添加流程实例审批记录
public void addProcessInstanceComment(ProcessInstanceCommentRequestDTO requestDTO) {
    CommentWrapper wrapper = new CommentWrapper();
    wrapper.setUserId(requestDTO.getUserId());
    wrapper.setNickname(requestDTO.getNickname());
    wrapper.setExecuteType(requestDTO.getExecuteType());
    wrapper.setExecuteTypeValue(requestDTO.getExecuteTypeValue());
    wrapper.setContent(requestDTO.getContent());
    wrapper.setExt(requestDTO.getExt());
    String message = JSON.toJSONString(wrapper);
    // 使用 taskService 添加一条审批意见
    taskService.addComment(requestDTO.getTaskId(), requestDTO.getProcessInstanceId(), requestDTO.getType(), message);
}

4.3.2 Start process instance

@Data
@ApiModel("流程实例-启动请求参数")
public class ProcessInstanceStartRequestDTO implements Serializable {

    @ApiModelProperty(value = "流程定义Key")
    @NotEmpty(message = "流程定义Key 不可以为空")
    private String processDefinitionKey;
    @ApiModelProperty(value = "流程实例名称")
    @NotEmpty(message = "流程实例名称 不可以为空")
    private String name;
    @ApiModelProperty(value = "项目ID")
    @NotEmpty(message = "项目ID 不可以为空")
    private String communityId;
    @ApiModelProperty(value = "全局变量")
    private Map<String, Object> variables;

}

Start process instance

@Transactional(rollbackFor = Exception.class)
public ProcessInstanceStartResponseDTO startProcessInstance(ProcessInstanceStartRequestDTO requestDTO) {
    ValidatorUtils.validate(requestDTO);
    //
    ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
    processDefinitionQuery.processDefinitionKey(requestDTO.getProcessDefinitionKey());
    ProcessDefinition processDefinition = processDefinitionQuery.latestVersion().singleResult();
    AssertUtils.notEmpty(processDefinition, "找不到流程定义");
    // 启动流程
    ProcessInstanceBuilder builder = runtimeService.createProcessInstanceBuilder();
    builder.processDefinitionKey(requestDTO.getProcessDefinitionKey());
    builder.name(requestDTO.getName());
    // variables("name", "value") 
    // 可以添加流程过程的变量,比如这里我添加了我不少业务变量进来,方面流程流转的时候处理业务
    builder.variables(requestDTO.getVariables());
    builder.variable(VAR_COMMUNITY_ID, VAR_COMMUNITY_ID_EQ + requestDTO.getCommunityId());
    builder.variable(VAR_PROCESS_INSTANCE_NAME, VAR_PROCESS_INSTANCE_NAME_EQ + requestDTO.getName());
    builder.variable(VAR_CREATE_USER_ID, VAR_CREATE_USER_ID_EQ + SecurityUser.getUserId());
    builder.variable(VAR_CREATE_USER_NICKNAME, VAR_CREATE_USER_NICKNAME_EQ + SecurityUser.get().getName());
    builder.variable(VAR_PROCESS_DEFINITION_NAME, VAR_PROCESS_DEFINITION_NAME_EQ + processDefinition.getName());
    builder.transientVariables(requestDTO.getTransientVariables());
    // builder.tenantId("101");
    ProcessInstance processInstance = builder.start();
    // 添加审批意见
    ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
    commentRequestDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
    commentRequestDTO.setTaskId(null);
    commentRequestDTO.setUserId(SecurityUser.getUserId());
    commentRequestDTO.setNickname(SecurityUser.get().getName());
    commentRequestDTO.setExecuteType(ExecuteTypeEnum.SUBMIT.name());
    commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.SUBMIT.getValue());
    commentRequestDTO.setContent(requestDTO.getName() + " 提交流程");
    commentRequestDTO.setExt("");
    this.addProcessInstanceComment(commentRequestDTO);
    // 构建 ResponseDTO
    ProcessInstanceStartResponseDTO responseDTO = new ProcessInstanceStartResponseDTO();
    responseDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
    responseDTO.setProcessDefinitionId(processInstance.getProcessDefinitionId());
    responseDTO.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
    responseDTO.setProcessDefinitionName(processInstance.getProcessDefinitionName());
    responseDTO.setName(processInstance.getName());
    responseDTO.setBusinessKey(processInstance.getBusinessKey());
    responseDTO.setDescription(processInstance.getDescription());
    //
    return responseDTO;
}

4.3.3 Get process progress picture

public String getProcessImage(String processInstanceId) {
    // 1.获取当前的流程实例
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    String processDefinitionId;
    List<String> activeActivityIds = new ArrayList<>();
    List<String> highLightedFlows = new ArrayList<>();
    // 2.获取所有的历史轨迹线对象
    List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(processInstanceId).activityType(BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW).list();
    historicActivityInstances.forEach(historicActivityInstance -> highLightedFlows.add(historicActivityInstance.getActivityId()));
    // 3. 获取流程定义id和高亮的节点id
    if (processInstance != null) {
        // 3.1 正在运行的流程实例
        processDefinitionId = processInstance.getProcessDefinitionId();
        activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
    } else {
        // 3.2 已经结束的流程实例
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        processDefinitionId = historicProcessInstance.getProcessDefinitionId();
        // 3.3 获取结束节点列表
        List<HistoricActivityInstance> historicEnds = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId).activityType(BpmnXMLConstants.ELEMENT_EVENT_END).list();
        List<String> finalActiveActivityIds = activeActivityIds;
        historicEnds.forEach(historicActivityInstance -> finalActiveActivityIds.add(historicActivityInstance.getActivityId()));
    }
    // 4. 获取bpmnModel对象
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
    // 5. 生成图片流
    ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration();
    ProcessDiagramGenerator diagramGenerator = configuration.getProcessDiagramGenerator();
    InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", activeActivityIds,
                                                               highLightedFlows, "宋体", "宋体", "宋体",
                                                               this.getClass().getClassLoader(), 1.0, true);
    // 6. 转化成Base64网络传输
    return "data:image/png;base64," + Base64.encode(inputStream);
}

4.3.4 Obtaining process approval opinions

@Override
public List<ProcessInstanceCommentResponseDTO> findProcessInstanceCommentList(String processInstanceId) {
    List<Comment> processInstanceComments = taskService.getProcessInstanceComments(processInstanceId);
    List<ProcessInstanceCommentResponseDTO> list = processInstanceComments.stream()
            .map(this::convertComment)
            .filter(Objects::nonNull).collect(Collectors.toList());
    return list;
}

private ProcessInstanceCommentResponseDTO convertComment(Comment v) {
    String fullMessage = v.getFullMessage();
    if (StringUtils.startsWith(fullMessage, "{")) {
        CommentWrapper wrapper = JSON.parseObject(fullMessage, CommentWrapper.class);
        ProcessInstanceCommentResponseDTO responseDTO = new ProcessInstanceCommentResponseDTO();
        responseDTO.setProcessInstanceId(v.getProcessInstanceId());
        responseDTO.setType(v.getType());
        responseDTO.setTaskId(v.getTaskId());
        responseDTO.setTime(v.getTime());
        responseDTO.setUserId(wrapper.getUserId());
        responseDTO.setNickname(wrapper.getNickname());
        responseDTO.setExecuteType(wrapper.getExecuteType());
        responseDTO.setExecuteTypeValue(wrapper.getExecuteTypeValue());
        responseDTO.setContent(wrapper.getContent());
        responseDTO.setExt(wrapper.getExt());
        return responseDTO;
    }
    return null;
}

4.3.5 Process instance executes the next step

@Data
@ApiModel("流程实例-执行下一步请求参数")
public class ProcessInstanceExecuteNextStepRequestDTO implements Serializable {

    @ApiModelProperty(value = "流程实例ID")
    @NotEmpty(message = "流程实例ID 不可以为空")
    private String processInstanceId;
    @ApiModelProperty(value = "任务ID")
    private String taskId;
    @ApiModelProperty(value = "ExecuteTypeEnum 执行类型")
    @NotEmpty(message = "执行类型 不可以为空")
    private String executeType;
    @ApiModelProperty(value = "审批意见")
    @NotEmpty(message = "审批意见 不可以为空")
    private String commentContent;
    @ApiModelProperty(value = "变量参数")
    private HashMap<String, Object> variables;

}
@Transactional(rollbackFor = Exception.class)
public void executeNextStep(ProcessInstanceExecuteNextStepRequestDTO requestDTO) {
    ValidatorUtils.validate(requestDTO);
    //
    if (ExecuteTypeEnum.of(requestDTO.getExecuteType()).isNone()) {
        throw new BusinessException("未知执行状态");
    }
    TaskQuery taskQuery = taskService.createTaskQuery();
    taskQuery.taskId(requestDTO.getTaskId());
    Task task = taskQuery.singleResult();
    AssertUtils.notEmpty(task, "找不到任务");
    //
    // 添加审批意见
    ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
    commentRequestDTO.setProcessInstanceId(task.getProcessInstanceId());
    commentRequestDTO.setTaskId(task.getId());
    // commentRequestDTO.setType("event");
    commentRequestDTO.setUserId(SecurityUser.getUserId());
    commentRequestDTO.setNickname(SecurityUser.get().getName());
    commentRequestDTO.setExecuteType(requestDTO.getExecuteType());
    commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.of(requestDTO.getExecuteType()).getValue());
    commentRequestDTO.setContent(task.getName() + ":" + requestDTO.getCommentContent());
    commentRequestDTO.setExt("");
    //
    this.addProcessInstanceComment(commentRequestDTO);
    // 处理流程审批
    HashMap<String, Object> variables = requestDTO.getVariables();
    if (variables == null) {
        variables = new HashMap<>(8);
    }
    // 这里会put一个执行变量executeType,也就是流程定义xml中的那个变量名称,这个变量决定了流程再走向
    variables.put(WorkflowConstants.EXECUTE_TYPE, requestDTO.getExecuteType());
    // 添加执行人
    variables.put("_execute_user_id=" + SecurityUser.getUserId(), "_execute_user_id=" + SecurityUser.getUserId());
    taskService.complete(task.getId(), variables);
}

4.3.6 Terminate process instance

@Data
@ApiModel("流程实例-分页请求参数")
public class ProcessInstanceStopRequestDTO implements Serializable {

    @ApiModelProperty(value = "流程实例ID")
    @NotEmpty(message = "流程实例ID 不可以为空")
    private String processInstanceId;
    @ApiModelProperty(value = "审批意见")
    @NotEmpty(message = "审批意见 不可以为空")
    private String commentContent;

}
public void stopProcessInstance(ProcessInstanceStopRequestDTO requestDTO) {
    ValidatorUtils.validate(requestDTO);
    // 修改流转执行状态
    runtimeService.setVariable(requestDTO.getProcessInstanceId(), WorkflowConstants.EXECUTE_TYPE, ExecuteTypeEnum.STOP.name());
    //
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
            .processInstanceId(requestDTO.getProcessInstanceId()).singleResult();
    // 添加一条审批记录
    ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
    commentRequestDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
    commentRequestDTO.setTaskId(null);
    commentRequestDTO.setUserId(SecurityUser.getUserId());
    commentRequestDTO.setNickname(SecurityUser.get().getName());
    commentRequestDTO.setExecuteType(ExecuteTypeEnum.STOP.name());
    commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.STOP.getValue());
    commentRequestDTO.setContent(StringUtils.defaultString(requestDTO.getCommentContent(), "终止流程"));
    commentRequestDTO.setExt("");
    this.addProcessInstanceComment(commentRequestDTO);
    /// 执行终止
    List<Execution> executions = runtimeService.createExecutionQuery().parentId(requestDTO.getProcessInstanceId()).list();
    List<String> executionIds = executions.stream().map(v -> v.getId()).collect(Collectors.toList());
    // 获取流程结束点
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
    Process process = bpmnModel.getMainProcess();
    List<EndEvent> endNodes = process.findFlowElementsOfType(EndEvent.class);
    String endId = endNodes.get(endNodes.size() - 1).getId();
    // 执行跳转
    runtimeService.createChangeActivityStateBuilder()
            .moveExecutionsToSingleActivityId(executionIds, endId)
            .changeState();
}

4.3.7 Get the list of process instance variables in batches

This method is a custom implementation, because Flowable does not have a corresponding batch of process variables obtained according to the list of process instance IDs
/**
 * 实例映射 Flowable 的 act_hi_varinst 表
 */
@Data
public class HistoryVariable implements Serializable {

    public static final HistoryVariable EMPTY = new HistoryVariable();

    private String id;
    private String rev;
    private String processInstanceId;
    private String executionId;
    private String taskId;
    private String name;
    private String varType;
    private String scopeId;
    private String subScopeId;
    private String scopeType;
    private String bytearrayId;
    private Double doubleValue;
    private Long longValue;
    private String text;
    private String text2;
    private Date createTime;
    private Date lastUpdatedTime;

    /*
    act_hi_varinst 字段列表:
    ID_
    REV_
    PROC_INST_ID_
    EXECUTION_ID_
    TASK_ID_
    NAME_
    VAR_TYPE_
    SCOPE_ID_
    SUB_SCOPE_ID_
    SCOPE_TYPE_
    BYTEARRAY_ID_
    DOUBLE_
    LONG_
    TEXT_
    TEXT2_
    CREATE_TIME_
    LAST_UPDATED_TIME_
     */
}

Service

@Override
public List<HistoryVariable> findHistoryVariableList(Collection<String> processInstanceIds) {
    if (CollectionUtils.isEmpty(processInstanceIds)) {
        return Collections.emptyList();
    }
    QueryWrapper<HistoryVariable> ew = new QueryWrapper<>();
    ew.in("t.PROC_INST_ID_", processInstanceIds);
    return this.baseMapper.findHistoryVariableList(ew);
}

Dao

List<HistoryVariable> findHistoryVariableList(@Param("ew") QueryWrapper<HistoryVariable> ew);

xml

<select id="findHistoryVariableList" resultType="cn.leadersheep.xz.workflow.server.entity.flowable.HistoryVariable">
    SELECT
    t.ID_ AS id,
    t.REV_ AS rev,
    t.PROC_INST_ID_ AS process_instance_id,
    t.EXECUTION_ID_ AS execution_id,
    t.TASK_ID_ AS task_id,
    t.NAME_ AS name,
    t.VAR_TYPE_ AS var_type,
    t.SCOPE_ID_ AS scope_id,
    t.SUB_SCOPE_ID_ AS sub_scope_id,
    t.SCOPE_TYPE_ AS scope_type,
    t.BYTEARRAY_ID_ AS bytearray_id,
    t.DOUBLE_ AS double_value,
    t.LONG_ AS long_value,
    t.TEXT_ AS text,
    t.TEXT2_ AS text2,
    t.CREATE_TIME_ AS create_time,
    t.LAST_UPDATED_TIME_ AS last_update_time

    FROM act_hi_varinst t
    <where>
        ${ew.sqlSegment}
    </where>
</select>

4.3.8 Get Process Instance Pagination

Acquisition process instance paging, because involved here 数据权限过滤, as well as 待办, 已办, 历史记录, 我发起and so on, the logic was quite complex, so a simple API by Flowable's probably not a good choice, so here custom implementation, by looking up the relevant database Flowable Table to find out matching records

1. If the process instance is still in progress data is stored in act_ru_*these few tables, if the end of the process instance data is stored in act_hi_*these few tables, so when queries need to do a scene without a query tables;
2. process definition defined in Fig. the assigned user group stored in act_ru_identitylinkthe table;

Process instance-paging request parameters

@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("流程实例-分页请求参数")
public class ProcessInstancePageRequestDTO extends PageParamsEntity {

    @ApiModelProperty(value = "项目ID")
    private String communityId;
    @ApiModelProperty(value = "查找范围:MY_TODO-我的待办;MY_DONE-我的已办;MY_SCOPE-我的范围;MY_CREATE-我的创建;")
    private String searchScope;
    @ApiModelProperty(value = "任务名称")
    private String processInstanceName;

}

Process definition-paging response results

@Data
@ApiModel("流程定义-分页响应结果")
public class ProcessInstancePageResponseDTO implements Serializable {

    @ApiModelProperty(value = "项目ID")
    private String communityId;
    @ApiModelProperty(value = "项目名称")
    private String communityName;
    @ApiModelProperty(value = "流程实例ID")
    private String processInstanceId;
    @ApiModelProperty(value = "流程实例名称")
    private String processInstanceName;
    @ApiModelProperty(value = "流程定义ID")
    private String processDefinitionId;
    @ApiModelProperty(value = "流程定义名称")
    private String processDefinitionName;
    @ApiModelProperty(value = "任务ID")
    private String taskId;
    @ApiModelProperty(value = "任务名称")
    private String taskName;
    @ApiModelProperty(value = "开始时间")
    private Date startTime;
    @ApiModelProperty(value = "结束时间")
    private Date endTime;
    @ApiModelProperty(value = "持续时间")
    private String duration;
    @ApiModelProperty(value = "创建人ID")
    private String createId;
    @ApiModelProperty(value = "创建人名称")
    private String createName;

}

Service

public PageDTO<ProcessInstancePageResponseDTO> findPage(ProcessInstancePageRequestDTO requestDTO) {
    // 在这里实现数据过滤
    String filterSql = StringUtils.defaultString(processFilterSQL("TEXT_", requestDTO.getCommunityId(), true), "");
    filterSql = filterSql.replace("('", "('" + WorkflowConstants.VAR_COMMUNITY_ID_EQ);
    filterSql = filterSql.replace(", '", ", '" + WorkflowConstants.VAR_COMMUNITY_ID_EQ);
    // TEXT_ IN ('_community_id=1545645315843546', '_community_id=15456453158436521')
    // System.out.println("filterSQL = " + filterSQL);
    //
    PageDTO<ProcessInstancePageResponseDTO> page = this.buildPage(requestDTO);
    if ("MY_TODO".equals(requestDTO.getSearchScope())) {
        // 我的待办
        QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
        ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t4.NAME_", requestDTO.getProcessInstanceName());
        ew.eq("1", 1);
        ew.apply(CoreUtil.isNotEmpty(filterSql), "t3." + filterSql);
        ew.orderByDesc("t.CREATE_TIME_");
        ew.groupBy("t.PROC_INST_ID_");
        //
        if (!AuthHelper.isRoleSuperAdmin(SecurityUser.current().getRoleCodeSet())) {
            // 不是超级管理员,需要过滤
            ew.and(and -> and.in("t2.GROUP_ID_", SecurityUser.current().getRoleCodeSet()).or(or -> {
                or.eq("t2.USER_ID_", SecurityUser.getUserId());
            }));
        }
        page = this.baseMapper.findMyTodo(page, ew);
    } else if ("MY_DONE".equals(requestDTO.getSearchScope())) {
        // 我的已办
        QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
        ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
        ew.eq("t2.TEXT_", "_execute_user_id=" + SecurityUser.getUserId());
        ew.apply(filterSql.length() > 0, "t3." + filterSql);
        ew.orderByDesc("t.START_TIME_");
        ew.groupBy("t.ID_");
        //
        page = this.baseMapper.findMyDonePage(page, ew);
    } else if ("MY_SCOPE".equals(requestDTO.getSearchScope())) {
        // 我的范围
        QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
        ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
        ew.eq("1", 1);
        ew.apply(filterSql.length() > 0, "t4." + filterSql);
        ew.orderByDesc("t.START_TIME_");
        ew.groupBy("t.ID_");
        //
        if (!AuthHelper.isRoleSuperAdmin(SecurityUser.current().getRoleCodeSet())) {
            // 不是超级管理员,需要过滤
            ew.and(and -> and.in("t3.GROUP_ID_", SecurityUser.current().getRoleCodeSet()).or(or -> {
                or.eq("t3.USER_ID_", SecurityUser.getUserId());
            }));
        }
        page = this.baseMapper.findMyScopePage(page, ew);
    } else if ("MY_CREATE".equals(requestDTO.getSearchScope())) {
        // 我发起的
        QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
        ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
        ew.eq("t2.TEXT_", "_create_user_id=" + SecurityUser.getUserId());
        ew.apply(filterSql.length() > 0, "t3." + filterSql);
        ew.orderByDesc("t.START_TIME_");
        ew.groupBy("t.ID_");
        //
        page = this.baseMapper.findMyDonePage(page, ew);
    }
    //
    if (CollectionUtils.isNotEmpty(page.getRecords())) {
	    // 填充其他属性
        List<String> processInstanceIds = page.getRecords().stream().map(v -> v.getProcessInstanceId()).distinct().collect(Collectors.toList());
        List<HistoryVariable> variableList = this.findHistoryVariableList(processInstanceIds);
        Map<String, HistoryVariable> variableMap = variableList.stream()
                .collect(Collectors.toMap(v -> v.getProcessInstanceId() + "_" + v.getName(), v -> v));
        page.getRecords().forEach(v -> {
            String prefix = v.getProcessInstanceId() + "_";
            //
            HistoryVariable variable = variableMap.getOrDefault(prefix + VAR_COMMUNITY_ID, HistoryVariable.EMPTY);
            String text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_COMMUNITY_ID_EQ, "")).orElse(null);
            v.setCommunityId(text);
            //
            // variable = variableMap.getOrDefault(prefix + VAR_PROCESS_INSTANCE_NAME, HistoryVariable.EMPTY);
            // text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_PROCESS_INSTANCE_NAME_EQ, "")).orElse(null);
            // v.setProcessInstanceName(text);
            //
            variable = variableMap.getOrDefault(prefix + VAR_CREATE_USER_ID, HistoryVariable.EMPTY);
            text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_CREATE_USER_ID_EQ, "")).orElse(null);
            v.setCreateId(text);
            //
            variable = variableMap.getOrDefault(prefix + VAR_CREATE_USER_NICKNAME, HistoryVariable.EMPTY);
            text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_CREATE_USER_NICKNAME_EQ, "")).orElse(null);
            v.setCreateName(text);
            //
            variable = variableMap.getOrDefault(prefix + VAR_PROCESS_DEFINITION_NAME, HistoryVariable.EMPTY);
            text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_PROCESS_DEFINITION_NAME_EQ, "")).orElse(null);
            v.setProcessDefinitionName(text);
        });
        sysOrganizationRemote.fillOrganization(page.getRecords(), v -> v.getCommunityId(), (v, c) -> v.setCommunityName(c.getName()));
    }
    return page;
}

Dao

/**
 * 获取我的待办任务
 *
 * @author houyu [email protected] <br>
 * @param page 分页
 * @param ew 参数包装器
 * @return PageDTO<ProcessInstancePageResponseDTO>
 */
PageDTO<ProcessInstancePageResponseDTO> findMyTodo(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);

/**
 * 获取我的范围内的任务
 *
 * @author houyu [email protected] <br>
 * @param page 分页
 * @param ew 参数包装器
 * @return PageDTO<ProcessInstancePageResponseDTO>
 */
PageDTO<ProcessInstancePageResponseDTO> findMyScopePage(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);

/**
 * 获取我的已办(我的发起 / 我审批的)
 *
 * @author houyu [email protected] <br>
 * @param page 分页
 * @param ew 参数包装器
 * @return PageDTO<ProcessInstancePageResponseDTO>
 */
PageDTO<ProcessInstancePageResponseDTO> findMyDonePage(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);

Xml

<select id="findMyTodo" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
    SELECT
    t.ID_ AS task_id,
    t.PROC_INST_ID_ AS process_instance_id,
    t4.NAME_ AS process_instance_name,
    t.NAME_ AS task_name,
    t.PROC_DEF_ID_ as process_definition_id,
    t.CREATE_TIME_ as start_time,
    NULL as end_time

    FROM act_ru_task t
    LEFT JOIN act_ru_identitylink t2 ON t2.TASK_ID_ = t.ID_
    LEFT JOIN act_ru_variable t3 ON t3.PROC_INST_ID_ = t.PROC_INST_ID_
    LEFT JOIN act_hi_procinst t4 ON t4.PROC_INST_ID_ = t.PROC_INST_ID_
    <where>
        ${ew.sqlSegment}
    </where>
</select>

<select id="findMyScopePage" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
    SELECT
    t.PROC_INST_ID_ AS process_instance_id,
    t.NAME_ AS process_instance_name,
    t.PROC_DEF_ID_ as process_definition_id,
    t.START_TIME_ as start_time,
    t.END_TIME_ as end_time

    FROM act_hi_procinst t
    LEFT JOIN act_hi_taskinst t2 ON t2.PROC_INST_ID_ = t.ID_
    LEFT JOIN act_hi_identitylink t3 ON t3.TASK_ID_ = t2.ID_
    LEFT JOIN act_hi_varinst t4 ON t4.PROC_INST_ID_ = t.ID_
    <where>
        ${ew.sqlSegment}
    </where>
</select>

<select id="findMyDonePage" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
    SELECT
    t.PROC_INST_ID_ AS process_instance_id,
    t.NAME_ AS process_instance_name,
    t.PROC_DEF_ID_ as process_definition_id,
    t.START_TIME_ as start_time,
    t.END_TIME_ as end_time

    FROM act_hi_procinst t
    LEFT JOIN act_hi_varinst t2 ON t2.PROC_INST_ID_ = t.ID_
    LEFT JOIN act_hi_varinst t3 ON t3.PROC_INST_ID_ = t.ID_
    <where>
        ${ew.sqlSegment}
    </where>
</select>

5. Linux deployment flowchart text is garbled

Briefly describe the situation I encountered. I was developing on Windows at the time. I set the font of the flow chart to Times New Roman, and there were no garbled characters. However, when I deployed to the Linux server to view the flow chart, the text appeared garbled, and then I can probably guess that it is because of the lack of fonts (because I have learned that garbled in the activiti flowchart is the problem of lack of fonts), so this time I followed the same routine to add Arial font to Linux and solved it.

5.1 Copy fonts on Windows

Enter the directory: C:\Windows\Fonts Copy "Song Ti regular" to the desktop for use

Insert picture description here

5.2 Linux find out the location of java

Excuting an order

whereis java

Mine is in /var/lib/jdk/jdk1.8.0_211/bin/java

5.3 Copy font files to jre/lib/fonts

Find out the location of java, the location of jre is in the first few levels of java directory

java:
/var/lib/jdk/jdk1.8.0_211/bin/java

jre 字体目录(复制到这里):
/var/lib/jdk/jdk1.8.0_211/jre/lib/fonts

5.4 Copy font files to the system font directory (/usr/share/fonts)

This directory may not exist, if it does not exist, create it yourself

/usr/share/fonts

5.5 Restart the system

reboot

After restarting the system, start the application, and there will be no garbled characters. Good luck~


Previous blog: SpringBoot integrates Flowable workflow-1 (drawing process definition)

The code based on the flowable-spring-boot-starter integration is basically completed, but it still feels like something is missing. The process has been executed step by step. When will the execution be completed? Where is it now? It seems that we are not very clear about how to operate the business after executing the business. This is to introduce the flowable global event listener. The next blog will introduce the flowable global event listener, which combines the listener to implement business notification services.


  • Official account: IT loading (it_loading)
  • CSDN: https://blog.csdn.net/JinglongSource
  • Blog: https://ihouyu.cn/
  • Email: [email protected]