SpringBoot整合Flowable工作流-2(代码整合)


1. 前言

上一篇博客【SpringBoot整合Flowable工作流-1(画流程定义) 】介绍用 Flowable-ui 画了一个简单的流程图。


这篇博客将介绍代码整合部分,主要内容有:【发布流程定义】、【开启流程任务】、【获取用户任务】、【用户审批任务】、【添加审批意见】、【获取流程图】、【获取我的待办任务】、【获取我发起的流程】、【我审批过的流程】…

2. 代码添加依赖

  1. <dependency>
  2. <groupId>org.flowable</groupId>
  3. <artifactId>flowable-spring-boot-starter</artifactId>
  4. <version>6.4.2</version>
  5. </dependency>

3. 创建数据库

创建数据库可以使用通过 Flowable 提供的 sql 实现,也可以通过程序自动创建数据库实现

3.1 Flowable 提供的 sql

下载文件 Flowable 相关的资源,进入 https://flowable.com/open-source/downloads,然后点击 【Download Flowable v6.x.x】,下载下来是一个压缩包,解压后会看到如下目录结构

  1. └─database # 数据库文件
  2. └─create
  3. └─all
  4. └─flowable.mysql.all.create.sql

找到 $/database/create/database/create/flowable.mysql.all.create.sql 文件,导入mysql数据库即可

3.2 应用程序自动创建数据库(推荐)

需要在 jdbc 的 url 中添加一个参数值,nullCatalogMeansCurrent=true

如下:

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

4. 代码部分

4.1 常用的几个Service类

  1. /** 运行时Service(用于运行时流程实例、流程变量、流程节点) */
  2. @Autowired
  3. private RuntimeService runtimeService;
  4. /** 资源存储Service(用于模型、流程定义) */
  5. @Autowired
  6. private RepositoryService repositoryService;
  7. /** 流程引擎Service */
  8. @Qualifier("processEngine")
  9. @Autowired
  10. private ProcessEngine processEngine;
  11. /** 任务Service(用于运行时的用户任务、审批日志) */
  12. @Autowired
  13. private TaskService taskService;
  14. @Autowired
  15. protected ManagementService managementService;
  16. /** 历史Service(用于历史记录,可以找到历史的流程实例、流程节点) */
  17. @Autowired
  18. protected HistoryService historyService;

4.2 流程定义相关代码

4.2.1 发布流程定义

上一篇博客【SpringBoot整合Flowable工作流-1(画流程定义) 】画好了流程,然后下载下来是一个 “请假流程1.bpmn20.xml” 的xml文件,下载就可以通过代码把这个流程发布到流程定义中了。

代码如下

  1. @Override
  2. public boolean importProcessDefinition(MultipartFile file) {
  3. try {
  4. Deployment deployment = repositoryService.createDeployment()
  5. // .key()
  6. // .name(name)
  7. // .category(category)
  8. // .tenantId()
  9. // 通过压缩包的形式一次行多个发布
  10. // .addZipInputStream()
  11. // 通过InputStream的形式发布
  12. .addInputStream(file.getOriginalFilename(), file.getInputStream())
  13. // 通过存放在classpath目录下的文件进行发布
  14. // .addClasspathResource("p1.bpmn20.xml")
  15. // 通过xml字符串的形式
  16. // .addString()
  17. .deploy();
  18. ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
  19. if (processDefinition == null) {
  20. return false;
  21. }
  22. } catch (Exception e) {
  23. throw new BusinessException("导入流程定义失败:" + e.getMessage());
  24. }
  25. return true;
  26. }

基于 SpringBoot 发布流程定义,还有一种巧妙的形式,那就是在 resources 目录下建立一个文件夹 processes ,然后把对应的流程文件发到这个文件夹下即可,启动 SpringBoot 项目的时候,通过观察日志就会发现该流程就自动发布了。(不推荐)

  1. workflow-server
  2. └─src
  3. └─main
  4. ├─java
  5. └─resources
  6. ├─mapper
  7. └─processes
  8. └─请假流程1.bpmn20.xml

4.2.2 查询流程定义

  1. // 创建 ProcessDefinitionQuery
  2. ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
  3. // 根据流程定义ID
  4. // processDefinitionQuery.processDefinitionId(requestDTO.getId());
  5. // 根据流程定义Key
  6. // processDefinitionQuery.processDefinitionKeyLike("%" + requestDTO.getKey().trim() + "%");
  7. // 根据流程定义名称
  8. // processDefinitionQuery.processDefinitionNameLike("%" + requestDTO.getName().trim() + "%");
  9. //
  10. // 获取总数
  11. long count = processDefinitionQuery.count();
  12. // 获取单个
  13. ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
  14. // 获取列表
  15. List<ProcessDefinition> processDefinitions = processDefinitionQuery.list();
  16. // 获取分页
  17. List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage(0, 10)

4.2.3 获取流程定义xml

  1. public String getXmlResource(String id) {
  2. ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
  3. InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
  4. processDefinition.getResourceName());
  5. try {
  6. return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
  7. } catch (Exception e) {
  8. throw new BusinessException("获取资源失败:" + e.getMessage());
  9. } finally {
  10. try {
  11. IOUtils.close(inputStream, null);
  12. } catch (IOException ignored) {
  13. }
  14. }
  15. }

4.2.4 获取流程定义图片

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

4.2.5 删除流程定义

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

4.3 流程实例相关代码

4.3.1 添加流程实例审批意见

起到类似于记录流程的操作记录的作用,我这里是自己封装了一层,我封装了自己的业务用户ID、用户名、执行类型、意见内容…

  1. @Data
  2. @ApiModel("流程实例-审批意见请求参数")
  3. public class ProcessInstanceCommentRequestDTO implements Serializable {
  4. @ApiModelProperty(value = "流程定义Key")
  5. private String processInstanceId;
  6. @ApiModelProperty(value = "任务ID(缺省)")
  7. private String taskId;
  8. @ApiModelProperty(value = "类型 CommentEntity: event/comment")
  9. private String type;
  10. @ApiModelProperty(value = "用户ID")
  11. private String userId;
  12. @ApiModelProperty(value = "用户昵称")
  13. private String nickname;
  14. @ApiModelProperty(value = "执行类型")
  15. private String executeType;
  16. @ApiModelProperty(value = "执行类型(参考ExecuteTypeEnum)SUBMIT-提交;YES-同意;NO-拒绝;STOP-流程终止;DELETE-流程删除")
  17. private String executeTypeValue;
  18. @ApiModelProperty(value = "内容")
  19. private String content;
  20. @ApiModelProperty(value = "额外携带的内容")
  21. private String ext;
  22. }

添加流程实例审批意见

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

4.3.2 启动流程实例

  1. @Data
  2. @ApiModel("流程实例-启动请求参数")
  3. public class ProcessInstanceStartRequestDTO implements Serializable {
  4. @ApiModelProperty(value = "流程定义Key")
  5. @NotEmpty(message = "流程定义Key 不可以为空")
  6. private String processDefinitionKey;
  7. @ApiModelProperty(value = "流程实例名称")
  8. @NotEmpty(message = "流程实例名称 不可以为空")
  9. private String name;
  10. @ApiModelProperty(value = "项目ID")
  11. @NotEmpty(message = "项目ID 不可以为空")
  12. private String communityId;
  13. @ApiModelProperty(value = "全局变量")
  14. private Map<String, Object> variables;
  15. }

启动流程实例

  1. @Transactional(rollbackFor = Exception.class)
  2. public ProcessInstanceStartResponseDTO startProcessInstance(ProcessInstanceStartRequestDTO requestDTO) {
  3. ValidatorUtils.validate(requestDTO);
  4. //
  5. ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
  6. processDefinitionQuery.processDefinitionKey(requestDTO.getProcessDefinitionKey());
  7. ProcessDefinition processDefinition = processDefinitionQuery.latestVersion().singleResult();
  8. AssertUtils.notEmpty(processDefinition, "找不到流程定义");
  9. // 启动流程
  10. ProcessInstanceBuilder builder = runtimeService.createProcessInstanceBuilder();
  11. builder.processDefinitionKey(requestDTO.getProcessDefinitionKey());
  12. builder.name(requestDTO.getName());
  13. // variables("name", "value")
  14. // 可以添加流程过程的变量,比如这里我添加了我不少业务变量进来,方面流程流转的时候处理业务
  15. builder.variables(requestDTO.getVariables());
  16. builder.variable(VAR_COMMUNITY_ID, VAR_COMMUNITY_ID_EQ + requestDTO.getCommunityId());
  17. builder.variable(VAR_PROCESS_INSTANCE_NAME, VAR_PROCESS_INSTANCE_NAME_EQ + requestDTO.getName());
  18. builder.variable(VAR_CREATE_USER_ID, VAR_CREATE_USER_ID_EQ + SecurityUser.getUserId());
  19. builder.variable(VAR_CREATE_USER_NICKNAME, VAR_CREATE_USER_NICKNAME_EQ + SecurityUser.get().getName());
  20. builder.variable(VAR_PROCESS_DEFINITION_NAME, VAR_PROCESS_DEFINITION_NAME_EQ + processDefinition.getName());
  21. builder.transientVariables(requestDTO.getTransientVariables());
  22. // builder.tenantId("101");
  23. ProcessInstance processInstance = builder.start();
  24. // 添加审批意见
  25. ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
  26. commentRequestDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
  27. commentRequestDTO.setTaskId(null);
  28. commentRequestDTO.setUserId(SecurityUser.getUserId());
  29. commentRequestDTO.setNickname(SecurityUser.get().getName());
  30. commentRequestDTO.setExecuteType(ExecuteTypeEnum.SUBMIT.name());
  31. commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.SUBMIT.getValue());
  32. commentRequestDTO.setContent(requestDTO.getName() + " 提交流程");
  33. commentRequestDTO.setExt("");
  34. this.addProcessInstanceComment(commentRequestDTO);
  35. // 构建 ResponseDTO
  36. ProcessInstanceStartResponseDTO responseDTO = new ProcessInstanceStartResponseDTO();
  37. responseDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
  38. responseDTO.setProcessDefinitionId(processInstance.getProcessDefinitionId());
  39. responseDTO.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
  40. responseDTO.setProcessDefinitionName(processInstance.getProcessDefinitionName());
  41. responseDTO.setName(processInstance.getName());
  42. responseDTO.setBusinessKey(processInstance.getBusinessKey());
  43. responseDTO.setDescription(processInstance.getDescription());
  44. //
  45. return responseDTO;
  46. }

4.3.3 获取流程进度图片

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

4.3.4 获取流程审批意见

  1. @Override
  2. public List<ProcessInstanceCommentResponseDTO> findProcessInstanceCommentList(String processInstanceId) {
  3. List<Comment> processInstanceComments = taskService.getProcessInstanceComments(processInstanceId);
  4. List<ProcessInstanceCommentResponseDTO> list = processInstanceComments.stream()
  5. .map(this::convertComment)
  6. .filter(Objects::nonNull).collect(Collectors.toList());
  7. return list;
  8. }
  9. private ProcessInstanceCommentResponseDTO convertComment(Comment v) {
  10. String fullMessage = v.getFullMessage();
  11. if (StringUtils.startsWith(fullMessage, "{")) {
  12. CommentWrapper wrapper = JSON.parseObject(fullMessage, CommentWrapper.class);
  13. ProcessInstanceCommentResponseDTO responseDTO = new ProcessInstanceCommentResponseDTO();
  14. responseDTO.setProcessInstanceId(v.getProcessInstanceId());
  15. responseDTO.setType(v.getType());
  16. responseDTO.setTaskId(v.getTaskId());
  17. responseDTO.setTime(v.getTime());
  18. responseDTO.setUserId(wrapper.getUserId());
  19. responseDTO.setNickname(wrapper.getNickname());
  20. responseDTO.setExecuteType(wrapper.getExecuteType());
  21. responseDTO.setExecuteTypeValue(wrapper.getExecuteTypeValue());
  22. responseDTO.setContent(wrapper.getContent());
  23. responseDTO.setExt(wrapper.getExt());
  24. return responseDTO;
  25. }
  26. return null;
  27. }

4.3.5 流程实例执行下一步

  1. @Data
  2. @ApiModel("流程实例-执行下一步请求参数")
  3. public class ProcessInstanceExecuteNextStepRequestDTO implements Serializable {
  4. @ApiModelProperty(value = "流程实例ID")
  5. @NotEmpty(message = "流程实例ID 不可以为空")
  6. private String processInstanceId;
  7. @ApiModelProperty(value = "任务ID")
  8. private String taskId;
  9. @ApiModelProperty(value = "ExecuteTypeEnum 执行类型")
  10. @NotEmpty(message = "执行类型 不可以为空")
  11. private String executeType;
  12. @ApiModelProperty(value = "审批意见")
  13. @NotEmpty(message = "审批意见 不可以为空")
  14. private String commentContent;
  15. @ApiModelProperty(value = "变量参数")
  16. private HashMap<String, Object> variables;
  17. }
  1. @Transactional(rollbackFor = Exception.class)
  2. public void executeNextStep(ProcessInstanceExecuteNextStepRequestDTO requestDTO) {
  3. ValidatorUtils.validate(requestDTO);
  4. //
  5. if (ExecuteTypeEnum.of(requestDTO.getExecuteType()).isNone()) {
  6. throw new BusinessException("未知执行状态");
  7. }
  8. TaskQuery taskQuery = taskService.createTaskQuery();
  9. taskQuery.taskId(requestDTO.getTaskId());
  10. Task task = taskQuery.singleResult();
  11. AssertUtils.notEmpty(task, "找不到任务");
  12. //
  13. // 添加审批意见
  14. ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
  15. commentRequestDTO.setProcessInstanceId(task.getProcessInstanceId());
  16. commentRequestDTO.setTaskId(task.getId());
  17. // commentRequestDTO.setType("event");
  18. commentRequestDTO.setUserId(SecurityUser.getUserId());
  19. commentRequestDTO.setNickname(SecurityUser.get().getName());
  20. commentRequestDTO.setExecuteType(requestDTO.getExecuteType());
  21. commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.of(requestDTO.getExecuteType()).getValue());
  22. commentRequestDTO.setContent(task.getName() + ":" + requestDTO.getCommentContent());
  23. commentRequestDTO.setExt("");
  24. //
  25. this.addProcessInstanceComment(commentRequestDTO);
  26. // 处理流程审批
  27. HashMap<String, Object> variables = requestDTO.getVariables();
  28. if (variables == null) {
  29. variables = new HashMap<>(8);
  30. }
  31. // 这里会put一个执行变量executeType,也就是流程定义xml中的那个变量名称,这个变量决定了流程再走向
  32. variables.put(WorkflowConstants.EXECUTE_TYPE, requestDTO.getExecuteType());
  33. // 添加执行人
  34. variables.put("_execute_user_id=" + SecurityUser.getUserId(), "_execute_user_id=" + SecurityUser.getUserId());
  35. taskService.complete(task.getId(), variables);
  36. }

4.3.6 终止流程实例

  1. @Data
  2. @ApiModel("流程实例-分页请求参数")
  3. public class ProcessInstanceStopRequestDTO implements Serializable {
  4. @ApiModelProperty(value = "流程实例ID")
  5. @NotEmpty(message = "流程实例ID 不可以为空")
  6. private String processInstanceId;
  7. @ApiModelProperty(value = "审批意见")
  8. @NotEmpty(message = "审批意见 不可以为空")
  9. private String commentContent;
  10. }
  1. public void stopProcessInstance(ProcessInstanceStopRequestDTO requestDTO) {
  2. ValidatorUtils.validate(requestDTO);
  3. // 修改流转执行状态
  4. runtimeService.setVariable(requestDTO.getProcessInstanceId(), WorkflowConstants.EXECUTE_TYPE, ExecuteTypeEnum.STOP.name());
  5. //
  6. ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
  7. .processInstanceId(requestDTO.getProcessInstanceId()).singleResult();
  8. // 添加一条审批记录
  9. ProcessInstanceCommentRequestDTO commentRequestDTO = new ProcessInstanceCommentRequestDTO();
  10. commentRequestDTO.setProcessInstanceId(processInstance.getProcessInstanceId());
  11. commentRequestDTO.setTaskId(null);
  12. commentRequestDTO.setUserId(SecurityUser.getUserId());
  13. commentRequestDTO.setNickname(SecurityUser.get().getName());
  14. commentRequestDTO.setExecuteType(ExecuteTypeEnum.STOP.name());
  15. commentRequestDTO.setExecuteTypeValue(ExecuteTypeEnum.STOP.getValue());
  16. commentRequestDTO.setContent(StringUtils.defaultString(requestDTO.getCommentContent(), "终止流程"));
  17. commentRequestDTO.setExt("");
  18. this.addProcessInstanceComment(commentRequestDTO);
  19. /// 执行终止
  20. List<Execution> executions = runtimeService.createExecutionQuery().parentId(requestDTO.getProcessInstanceId()).list();
  21. List<String> executionIds = executions.stream().map(v -> v.getId()).collect(Collectors.toList());
  22. // 获取流程结束点
  23. BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
  24. Process process = bpmnModel.getMainProcess();
  25. List<EndEvent> endNodes = process.findFlowElementsOfType(EndEvent.class);
  26. String endId = endNodes.get(endNodes.size() - 1).getId();
  27. // 执行跳转
  28. runtimeService.createChangeActivityStateBuilder()
  29. .moveExecutionsToSingleActivityId(executionIds, endId)
  30. .changeState();
  31. }

4.3.7 批量获取流程实例变量列表

这个方法是自定义实现的,因为 Flowable 没有对应的根据流程实例ID列表获取批量的流程变量

  1. /**
  2. * 实例映射 Flowable 的 act_hi_varinst 表
  3. */
  4. @Data
  5. public class HistoryVariable implements Serializable {
  6. public static final HistoryVariable EMPTY = new HistoryVariable();
  7. private String id;
  8. private String rev;
  9. private String processInstanceId;
  10. private String executionId;
  11. private String taskId;
  12. private String name;
  13. private String varType;
  14. private String scopeId;
  15. private String subScopeId;
  16. private String scopeType;
  17. private String bytearrayId;
  18. private Double doubleValue;
  19. private Long longValue;
  20. private String text;
  21. private String text2;
  22. private Date createTime;
  23. private Date lastUpdatedTime;
  24. /*
  25. act_hi_varinst 字段列表:
  26. ID_
  27. REV_
  28. PROC_INST_ID_
  29. EXECUTION_ID_
  30. TASK_ID_
  31. NAME_
  32. VAR_TYPE_
  33. SCOPE_ID_
  34. SUB_SCOPE_ID_
  35. SCOPE_TYPE_
  36. BYTEARRAY_ID_
  37. DOUBLE_
  38. LONG_
  39. TEXT_
  40. TEXT2_
  41. CREATE_TIME_
  42. LAST_UPDATED_TIME_
  43. */
  44. }

Service

  1. @Override
  2. public List<HistoryVariable> findHistoryVariableList(Collection<String> processInstanceIds) {
  3. if (CollectionUtils.isEmpty(processInstanceIds)) {
  4. return Collections.emptyList();
  5. }
  6. QueryWrapper<HistoryVariable> ew = new QueryWrapper<>();
  7. ew.in("t.PROC_INST_ID_", processInstanceIds);
  8. return this.baseMapper.findHistoryVariableList(ew);
  9. }

Dao

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

xml

  1. <select id="findHistoryVariableList" resultType="cn.leadersheep.xz.workflow.server.entity.flowable.HistoryVariable">
  2. SELECT
  3. t.ID_ AS id,
  4. t.REV_ AS rev,
  5. t.PROC_INST_ID_ AS process_instance_id,
  6. t.EXECUTION_ID_ AS execution_id,
  7. t.TASK_ID_ AS task_id,
  8. t.NAME_ AS name,
  9. t.VAR_TYPE_ AS var_type,
  10. t.SCOPE_ID_ AS scope_id,
  11. t.SUB_SCOPE_ID_ AS sub_scope_id,
  12. t.SCOPE_TYPE_ AS scope_type,
  13. t.BYTEARRAY_ID_ AS bytearray_id,
  14. t.DOUBLE_ AS double_value,
  15. t.LONG_ AS long_value,
  16. t.TEXT_ AS text,
  17. t.TEXT2_ AS text2,
  18. t.CREATE_TIME_ AS create_time,
  19. t.LAST_UPDATED_TIME_ AS last_update_time
  20. FROM act_hi_varinst t
  21. <where>
  22. ${ew.sqlSegment}
  23. </where>
  24. </select>

4.3.8 获取流程实例分页

获取流程实例分页,因为这里涉及到数据权限过滤、以及待办已办历史记录我发起等等,逻辑还是挺复杂的,因此简单通过Flowable的API可能不是一个很好的选择了,因此这里自定义实现,通过查找Flowable相关的数据库表找出符合记录


1.如果流程实例还在进行中数据是保存在 act_ru_* 这几张表中,如果流程实例结束了数据是保存在 act_hi_* 这几张表中,因此查询的时候需要根据不用场景查询不用的表;
2.流程定义图中定义的分配的用户组,保存在 act_ru_identitylink 表中;

流程实例-分页请求参数

  1. @Data
  2. @EqualsAndHashCode(callSuper = true)
  3. @ApiModel("流程实例-分页请求参数")
  4. public class ProcessInstancePageRequestDTO extends PageParamsEntity {
  5. @ApiModelProperty(value = "项目ID")
  6. private String communityId;
  7. @ApiModelProperty(value = "查找范围:MY_TODO-我的待办;MY_DONE-我的已办;MY_SCOPE-我的范围;MY_CREATE-我的创建;")
  8. private String searchScope;
  9. @ApiModelProperty(value = "任务名称")
  10. private String processInstanceName;
  11. }

流程定义-分页响应结果

  1. @Data
  2. @ApiModel("流程定义-分页响应结果")
  3. public class ProcessInstancePageResponseDTO implements Serializable {
  4. @ApiModelProperty(value = "项目ID")
  5. private String communityId;
  6. @ApiModelProperty(value = "项目名称")
  7. private String communityName;
  8. @ApiModelProperty(value = "流程实例ID")
  9. private String processInstanceId;
  10. @ApiModelProperty(value = "流程实例名称")
  11. private String processInstanceName;
  12. @ApiModelProperty(value = "流程定义ID")
  13. private String processDefinitionId;
  14. @ApiModelProperty(value = "流程定义名称")
  15. private String processDefinitionName;
  16. @ApiModelProperty(value = "任务ID")
  17. private String taskId;
  18. @ApiModelProperty(value = "任务名称")
  19. private String taskName;
  20. @ApiModelProperty(value = "开始时间")
  21. private Date startTime;
  22. @ApiModelProperty(value = "结束时间")
  23. private Date endTime;
  24. @ApiModelProperty(value = "持续时间")
  25. private String duration;
  26. @ApiModelProperty(value = "创建人ID")
  27. private String createId;
  28. @ApiModelProperty(value = "创建人名称")
  29. private String createName;
  30. }

Service

  1. public PageDTO<ProcessInstancePageResponseDTO> findPage(ProcessInstancePageRequestDTO requestDTO) {
  2. // 在这里实现数据过滤
  3. String filterSql = StringUtils.defaultString(processFilterSQL("TEXT_", requestDTO.getCommunityId(), true), "");
  4. filterSql = filterSql.replace("('", "('" + WorkflowConstants.VAR_COMMUNITY_ID_EQ);
  5. filterSql = filterSql.replace(", '", ", '" + WorkflowConstants.VAR_COMMUNITY_ID_EQ);
  6. // TEXT_ IN ('_community_id=1545645315843546', '_community_id=15456453158436521')
  7. // System.out.println("filterSQL = " + filterSQL);
  8. //
  9. PageDTO<ProcessInstancePageResponseDTO> page = this.buildPage(requestDTO);
  10. if ("MY_TODO".equals(requestDTO.getSearchScope())) {
  11. // 我的待办
  12. QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
  13. ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t4.NAME_", requestDTO.getProcessInstanceName());
  14. ew.eq("1", 1);
  15. ew.apply(CoreUtil.isNotEmpty(filterSql), "t3." + filterSql);
  16. ew.orderByDesc("t.CREATE_TIME_");
  17. ew.groupBy("t.PROC_INST_ID_");
  18. //
  19. if (!AuthHelper.isRoleSuperAdmin(SecurityUser.current().getRoleCodeSet())) {
  20. // 不是超级管理员,需要过滤
  21. ew.and(and -> and.in("t2.GROUP_ID_", SecurityUser.current().getRoleCodeSet()).or(or -> {
  22. or.eq("t2.USER_ID_", SecurityUser.getUserId());
  23. }));
  24. }
  25. page = this.baseMapper.findMyTodo(page, ew);
  26. } else if ("MY_DONE".equals(requestDTO.getSearchScope())) {
  27. // 我的已办
  28. QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
  29. ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
  30. ew.eq("t2.TEXT_", "_execute_user_id=" + SecurityUser.getUserId());
  31. ew.apply(filterSql.length() > 0, "t3." + filterSql);
  32. ew.orderByDesc("t.START_TIME_");
  33. ew.groupBy("t.ID_");
  34. //
  35. page = this.baseMapper.findMyDonePage(page, ew);
  36. } else if ("MY_SCOPE".equals(requestDTO.getSearchScope())) {
  37. // 我的范围
  38. QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
  39. ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
  40. ew.eq("1", 1);
  41. ew.apply(filterSql.length() > 0, "t4." + filterSql);
  42. ew.orderByDesc("t.START_TIME_");
  43. ew.groupBy("t.ID_");
  44. //
  45. if (!AuthHelper.isRoleSuperAdmin(SecurityUser.current().getRoleCodeSet())) {
  46. // 不是超级管理员,需要过滤
  47. ew.and(and -> and.in("t3.GROUP_ID_", SecurityUser.current().getRoleCodeSet()).or(or -> {
  48. or.eq("t3.USER_ID_", SecurityUser.getUserId());
  49. }));
  50. }
  51. page = this.baseMapper.findMyScopePage(page, ew);
  52. } else if ("MY_CREATE".equals(requestDTO.getSearchScope())) {
  53. // 我发起的
  54. QueryWrapper<ProcessInstancePageResponseDTO> ew = buildEmptyQueryWrapper();
  55. ew.like(StringUtils.isNotEmpty(requestDTO.getProcessInstanceName()), "t.NAME_", requestDTO.getProcessInstanceName());
  56. ew.eq("t2.TEXT_", "_create_user_id=" + SecurityUser.getUserId());
  57. ew.apply(filterSql.length() > 0, "t3." + filterSql);
  58. ew.orderByDesc("t.START_TIME_");
  59. ew.groupBy("t.ID_");
  60. //
  61. page = this.baseMapper.findMyDonePage(page, ew);
  62. }
  63. //
  64. if (CollectionUtils.isNotEmpty(page.getRecords())) {
  65. // 填充其他属性
  66. List<String> processInstanceIds = page.getRecords().stream().map(v -> v.getProcessInstanceId()).distinct().collect(Collectors.toList());
  67. List<HistoryVariable> variableList = this.findHistoryVariableList(processInstanceIds);
  68. Map<String, HistoryVariable> variableMap = variableList.stream()
  69. .collect(Collectors.toMap(v -> v.getProcessInstanceId() + "_" + v.getName(), v -> v));
  70. page.getRecords().forEach(v -> {
  71. String prefix = v.getProcessInstanceId() + "_";
  72. //
  73. HistoryVariable variable = variableMap.getOrDefault(prefix + VAR_COMMUNITY_ID, HistoryVariable.EMPTY);
  74. String text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_COMMUNITY_ID_EQ, "")).orElse(null);
  75. v.setCommunityId(text);
  76. //
  77. // variable = variableMap.getOrDefault(prefix + VAR_PROCESS_INSTANCE_NAME, HistoryVariable.EMPTY);
  78. // text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_PROCESS_INSTANCE_NAME_EQ, "")).orElse(null);
  79. // v.setProcessInstanceName(text);
  80. //
  81. variable = variableMap.getOrDefault(prefix + VAR_CREATE_USER_ID, HistoryVariable.EMPTY);
  82. text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_CREATE_USER_ID_EQ, "")).orElse(null);
  83. v.setCreateId(text);
  84. //
  85. variable = variableMap.getOrDefault(prefix + VAR_CREATE_USER_NICKNAME, HistoryVariable.EMPTY);
  86. text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_CREATE_USER_NICKNAME_EQ, "")).orElse(null);
  87. v.setCreateName(text);
  88. //
  89. variable = variableMap.getOrDefault(prefix + VAR_PROCESS_DEFINITION_NAME, HistoryVariable.EMPTY);
  90. text = Optional.ofNullable(variable.getText()).map(t -> t.replace(VAR_PROCESS_DEFINITION_NAME_EQ, "")).orElse(null);
  91. v.setProcessDefinitionName(text);
  92. });
  93. sysOrganizationRemote.fillOrganization(page.getRecords(), v -> v.getCommunityId(), (v, c) -> v.setCommunityName(c.getName()));
  94. }
  95. return page;
  96. }

Dao

  1. /**
  2. * 获取我的待办任务
  3. *
  4. * @author houyu for.houyu@qq.com <br>
  5. * @param page 分页
  6. * @param ew 参数包装器
  7. * @return PageDTO<ProcessInstancePageResponseDTO>
  8. */
  9. PageDTO<ProcessInstancePageResponseDTO> findMyTodo(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);
  10. /**
  11. * 获取我的范围内的任务
  12. *
  13. * @author houyu for.houyu@qq.com <br>
  14. * @param page 分页
  15. * @param ew 参数包装器
  16. * @return PageDTO<ProcessInstancePageResponseDTO>
  17. */
  18. PageDTO<ProcessInstancePageResponseDTO> findMyScopePage(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);
  19. /**
  20. * 获取我的已办(我的发起 / 我审批的)
  21. *
  22. * @author houyu for.houyu@qq.com <br>
  23. * @param page 分页
  24. * @param ew 参数包装器
  25. * @return PageDTO<ProcessInstancePageResponseDTO>
  26. */
  27. PageDTO<ProcessInstancePageResponseDTO> findMyDonePage(@Param("page") PageDTO<ProcessInstancePageResponseDTO> page, @Param("ew") QueryWrapper<ProcessInstancePageResponseDTO> ew);

Xml

  1. <select id="findMyTodo" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
  2. SELECT
  3. t.ID_ AS task_id,
  4. t.PROC_INST_ID_ AS process_instance_id,
  5. t4.NAME_ AS process_instance_name,
  6. t.NAME_ AS task_name,
  7. t.PROC_DEF_ID_ as process_definition_id,
  8. t.CREATE_TIME_ as start_time,
  9. NULL as end_time
  10. FROM act_ru_task t
  11. LEFT JOIN act_ru_identitylink t2 ON t2.TASK_ID_ = t.ID_
  12. LEFT JOIN act_ru_variable t3 ON t3.PROC_INST_ID_ = t.PROC_INST_ID_
  13. LEFT JOIN act_hi_procinst t4 ON t4.PROC_INST_ID_ = t.PROC_INST_ID_
  14. <where>
  15. ${ew.sqlSegment}
  16. </where>
  17. </select>
  18. <select id="findMyScopePage" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
  19. SELECT
  20. t.PROC_INST_ID_ AS process_instance_id,
  21. t.NAME_ AS process_instance_name,
  22. t.PROC_DEF_ID_ as process_definition_id,
  23. t.START_TIME_ as start_time,
  24. t.END_TIME_ as end_time
  25. FROM act_hi_procinst t
  26. LEFT JOIN act_hi_taskinst t2 ON t2.PROC_INST_ID_ = t.ID_
  27. LEFT JOIN act_hi_identitylink t3 ON t3.TASK_ID_ = t2.ID_
  28. LEFT JOIN act_hi_varinst t4 ON t4.PROC_INST_ID_ = t.ID_
  29. <where>
  30. ${ew.sqlSegment}
  31. </where>
  32. </select>
  33. <select id="findMyDonePage" resultType="cn.leadersheep.xz.workflow.client.dto.flowable.response.ProcessInstancePageResponseDTO">
  34. SELECT
  35. t.PROC_INST_ID_ AS process_instance_id,
  36. t.NAME_ AS process_instance_name,
  37. t.PROC_DEF_ID_ as process_definition_id,
  38. t.START_TIME_ as start_time,
  39. t.END_TIME_ as end_time
  40. FROM act_hi_procinst t
  41. LEFT JOIN act_hi_varinst t2 ON t2.PROC_INST_ID_ = t.ID_
  42. LEFT JOIN act_hi_varinst t3 ON t3.PROC_INST_ID_ = t.ID_
  43. <where>
  44. ${ew.sqlSegment}
  45. </where>
  46. </select>

5. Linux部署流程图文字乱码

简单描述一下我遇到的情况,我当时是在Windows上开发的,设置流程图的字体为宋体,没有出现乱码的情况,但是我部署到Linux服务器上查看流程图的时候文字出现了乱码,然后我大概能猜到是因为缺少字体的问题(因为之前有了解过activiti流程图乱码是缺少字体的问题),所以我这次也是按照相同套路给Linux添加宋体字体就解决了。

5.1 复制Windows上的字体

进入目录:C:\Windows\Fonts 复制 “宋体 常规”到桌面上备用

5.2 Linux找出 java 位置

执行命令

  1. whereis java

我的是在 /var/lib/jdk/jdk1.8.0_211/bin/java

5.3 复制字体文件到 jre/lib/fonts

找出了java的位置,jre 的位置在 java 的前几级目录下

  1. java
  2. /var/lib/jdk/jdk1.8.0_211/bin/java
  3. jre 字体目录(复制到这里):
  4. /var/lib/jdk/jdk1.8.0_211/jre/lib/fonts

5.4 复制字体文件到系统字体目录中(/usr/share/fonts)

这个目录可能不存在,如果不存在则自己创建出来

  1. /usr/share/fonts

5.5 重启系统

  1. reboot

重启系统之后,启动应用程序,即不会出现乱码的情况了,祝你好运~


上一篇博客:SpringBoot整合Flowable工作流-1(画流程定义)

基于 flowable-spring-boot-starter 整合的代码基本完成,但是感觉还是少了一点东西,流程一步一步执行下去了,什么时候执行完?现在到什么环节了?貌似我们都不太清楚,执行完了业务要怎么操作,这就要介绍一下 flowable 全局事件监听器了,下一篇博客将介绍 flowable 全局事件监听器,结合监听器实现业务的通知业务。


程序员[ 后宇 ],是一个关注编程,热爱技术的Java后端开发者,热衷于 [ Java后端 ],[ 数据爬虫领域 ]。不定期分享 I T 技能和干货!!欢迎关注 “IT加载中”,一个只出 干货 和 实战 的公众号。

关注公众号

  标签   Flowable、  工作流、  
mail csdn

作者  :  houyu [后宇]

程序员[ 后宇 ],是一个关注编程,热爱技术的开发者,热衷于 【Java后端】,【数据爬虫】,【大数据】领域。

在这里会一直记录着我成长的点点滴滴,毕竟好记性不如烂笔头,如果你在博客中有所收获,这也将是我毕生的荣幸。如有差池,还望指出更正!



评论


暂时没有评论哦~,你来评论个吧!!