springboot集成flowable工作流

1.引入依赖

<flowable.version>6.7.2</flowable.version>
<dependency>
	<groupId>org.flowable</groupId>
	<artifactId>flowable-engine</artifactId>
</dependency>

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

2. flowable核心概念解释

flowable核心概念和表解释

由于flowable-starter 内置集成了repositoryService,historyService,runtimeService等等因此直接注入到业务类上即可
springboot集成flowable工作流_第1张图片

3. Flowable实践

3.1、部署流程模型
前端上传的是xml文件字符串,转io流

/**
     * 导入流程文件
     *
     * @param name
     * @param category
     * @param in
     */
    @Override
    public void importFile(String name, String category, InputStream in) {
    	// 部署流程定义文件
	    // 1、部署信息存储
	    // 2、存储bpmn文件 和  bpmn.png
	    // 3、存储流程定义基本信息
        Deployment deploy = repositoryService.createDeployment()
			    .addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category)
			    .deploy();
        // 通过部署id,获取部署完成的流程定义
	    ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
			    .deploymentId(deploy.getId()).singleResult();
        // 设置流程定义分类
        repositoryService.setProcessDefinitionCategory(definition.getId(), category);
    }

涉及以下几张表

  • act_re_deployment:会有一条部署记录,记录此次部署的基本信息
  • act_ge_bytearray: 有两条记录,记录的是本次上传的bpmn文件和对应的图片文件,每条记录都有act_re_deployment表的外键关联
  • act_re_procdef: 有一条记录,记录的是该bpmn文件包含的基本信息,包含act_re_deployment表外键,发生更新,会生成一条新的部署记录,和新的流程模型定义记录。

3.2、流程模型挂载表单
实际上是定义一个表单form表,一个deploy_form表。form存放动态表单json,deploy_form表建立流程模型部署信息与表单信息的关系。
流程模型挂载表单实际上就是往deply_form存放模型挂载的表单信息。 目的为了当通过模型启动流程实例的时候,将流程模型(部署id)对应的动态表单json取出来。

3.3、启动流程
1、启动流程需要,提交流程定义id,和表单提供的变量map
2、申请人启动流程,那么自动完成申请任务,追加任务处理人,处理意见。通过taskService.complete完成任务
3、注意:第一,需要将流程发起者存储,以便后续取出。第二,一般情况下,任务只有最新的待完成,开在ru_task表看到。第三,ru相关表只会存储运行的流程数据,流程结束便会删除

 /**
     * 根据流程定义ID启动流程实例
     *
     * @param procDefId 流程定义Id
     * @param variables 流程变量
     * @return
     */
    @Override
    public AjaxResult startProcessInstanceById(String procDefId, Map<String, Object> variables) {
        try {
        	// 流程定义被挂起,将无法激活流程
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
                    .latestVersion().singleResult();
            if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
                return AjaxResult.error("流程已被挂起,请先激活流程");
            }

            // 设置流程发起人Id到流程中
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            // 设置流程发起者  设置前者可以在历史流程实例中查询到,设置后置可以在表单变量中获取流程发起者
            identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
            variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId().toString());

	        // 给第一步申请人节点设置任务执行人和意见,并且完成任务
            ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
            Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
            if (Objects.nonNull(task)) {
		        taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
                taskService.setAssignee(task.getId(), sysUser.getUserId().toString());
		        taskService.complete(task.getId(), variables);
	        }
            return AjaxResult.success("流程启动成功");
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("流程启动错误");
        }
    }

涉及表

  • act_ru_execution:插入一条记录,记录这个流程定义的执行实例,其中id和proc_inst_id相同都是流程执行实例id,也就是本次执行这个流程定义的id,包含流程定义的id外键
  • act_ru_task:插入一条记录,记录的是第一个任务的信息,也就是开始执行第一个任务。包括act_ru_execution表中的execution_id外键和proc_inst_id外键,也就是本次执行实例id
  • act_hi_procinst:插入一条记录,记录的是本次执行实例的历史记录
  • act_hi_taskinst:插入一条记录,记录的是本次任务的历史记录

3.4、获取当前用户待处理的任务

/**
	 * 代办任务列表
	 *
	 * @param pageNum  当前页码
	 * @param pageSize 每页条数
	 * @return
	 */
	@Override
	public AjaxResult todoList(Integer pageNum, Integer pageSize) {
		Page<FlowTaskDto> page = new Page<>();
		Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
		// 获取当前用户的待处理的任务
		TaskQuery taskQuery = taskService.createTaskQuery()
				.active().includeProcessVariables()
                .taskAssignee(userId.toString())
				.orderByTaskCreateTime().desc();
		page.setTotal(taskQuery.count());
		List<Task> taskList = taskQuery.listPage(pageSize * (pageNum - 1), pageSize);
		
		List<FlowTaskDto> flowList = new ArrayList<>();
		for (Task task : taskList) {
			FlowTaskDto flowTask = new FlowTaskDto();
			// 当前流程信息
			flowTask.setTaskId(task.getId());
			flowTask.setTaskDefKey(task.getTaskDefinitionKey());
			flowTask.setCreateTime(task.getCreateTime());
			flowTask.setProcDefId(task.getProcessDefinitionId());
			flowTask.setExecutionId(task.getExecutionId());
			flowTask.setTaskName(task.getName());
			
			// 流程定义信息
			ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
					.processDefinitionId(task.getProcessDefinitionId()).singleResult();
			flowTask.setDeployId(pd.getDeploymentId());
			flowTask.setProcDefName(pd.getName());
			flowTask.setProcDefVersion(pd.getVersion());
			flowTask.setProcInsId(task.getProcessInstanceId());

			// 流程发起人信息
			HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
					.processInstanceId(task.getProcessInstanceId()).singleResult();
			SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
			
			flowTask.setStartUserId(startUser.getNickName());
			flowTask.setStartUserName(startUser.getNickName());
			flowTask.setStartDeptName(startUser.getDept().getDeptName());
			flowList.add(flowTask);
		}

		page.setRecords(flowList);
		return AjaxResult.success(page);
	}

3.5、审批待处理的任务并设置审批意见
taskId:待处理任务节点

/**
	 * 完成任务
	 *
	 * @param taskVo 请求实体参数
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public AjaxResult complete(FlowTaskVo taskVo) {
		Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult();
		if (Objects.isNull(task)) {
			return AjaxResult.error("任务不存在");
		}
		if (DelegationState.PENDING.equals(task.getDelegationState())) {
			// 完成审批
			taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), taskVo.getComment());
			taskService.resolveTask(taskVo.getTaskId(), taskVo.getValues());
		} else {
			// 完成审批
			taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment());
			Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
			taskService.setAssignee(taskVo.getTaskId(), userId.toString());
			taskService.complete(taskVo.getTaskId(), taskVo.getValues());
		}
		return AjaxResult.success();
	}

涉及表

  • act_ru_variable:插入变量信息,包含本次流程执行实例的两个id外键,但不包括任务的id,由于setVariable方法设置的是全局变量,也就是整个流程都会有效的变量
  • act_ru_task:表中审批人的记录被删除,新插入审批人乙的任务记录
  • act_ru_execution:活动记录并无删除,而是将正在执行的任务变成审批人乙
  • act_hi_var_inst:插入流程实例的历史记录
  • act_hi_taskinst:插入任务的历史记录

3.6、前端显示当前正在执行的流程图
1、读取流程模型xml

/**
     * 读取xml
     *
     * @param deployId
     * @return
     */
    @Override
    public AjaxResult readXml(String deployId) throws IOException {
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 通过部署id,指定名称,读取指定的资源为流
        InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
        // 将xml文件流,转字符串
        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
        return AjaxResult.success("", result);
    }

2、获取流程实例的历史活动节点(未执行到的将不会被取到)

/**
	 * 获取流程执行过程
	 *
	 * @param procInsId 流程实例id
	 * @return
	 */
	@Override
	public AjaxResult getFlowViewer(String procInsId, String executionId) {
		List<FlowViewerDto> flowViewerList = new ArrayList<>();

		// 获取流程实例全部的历史活动节点
		List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInsId).list();

		for (HistoricActivityInstance finishedActivityInstance : historicActivityInstances) {
			// 将sequenceFlow节点不交由前端渲染
			if (!"sequenceFlow".equals(finishedActivityInstance.getActivityType())) {
				FlowViewerDto flowViewerDto = new FlowViewerDto();
				flowViewerDto.setKey(finishedActivityInstance.getActivityId());
				// 根据流程节点处理时间校验改节点是否已完成
				flowViewerDto.setCompleted(!Objects.isNull(finishedActivityInstance.getEndTime()));
				flowViewerList.add(flowViewerDto);
			}
		}
		return AjaxResult.success(flowViewerList);
	}

前端bpmn-modeler 读取xml,遍历流程节点,将当前历史活动节点全部高亮即可。

对应前端代码,data:xml

// 让图能自适应屏幕
    fitViewport() {
      this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
      const bbox = document.querySelector('.flow-containers .viewport').getBBox()
      const currentViewbox = this.modeler.get('canvas').viewbox()
      const elementMid = {
        x: bbox.x + bbox.width / 2 - 65,
        y: bbox.y + bbox.height / 2
      }
      this.modeler.get('canvas').viewbox({
        x: elementMid.x - currentViewbox.width / 2,
        y: elementMid.y - currentViewbox.height / 2,
        width: currentViewbox.width,
        height: currentViewbox.height
      })
      this.zoom = bbox.width / currentViewbox.width * 1.8
    },
    // 放大缩小
    zoomViewport(zoomIn = true) {
      this.zoom = this.modeler.get('canvas').zoom()
      this.zoom += (zoomIn ? 0.1 : -0.1)
      this.modeler.get('canvas').zoom(this.zoom)
    },
    async createNewDiagram(data) {
      // 将字符串转换成图显示出来
      // data = data.replace(//g, '<![CDATA[$1]]>')
      data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
        return str.replace(/</g, '<')
      })
      try {
        await this.modeler.importXML(data)
        this.adjustPalette()
        this.fitViewport()
        if (this.taskList !==undefined && this.taskList.length > 0 ) {
          this.fillColor()
        }
      } catch (err) {
        console.error(err.message, err.warnings)
      }
    },
    fillColor() {
      const canvas = this.modeler.get('canvas')
      this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
        const completeTask = this.taskList.find(m => m.key === n.id)
        const todoTask = this.taskList.find(m => !m.completed)
        const endTask = this.taskList[this.taskList.length - 1]
        if (n.$type === 'bpmn:UserTask') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {
                if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
                  canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
                  canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
                }else {
                  canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                  canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                }
              }
            })
          }
        }
        // 排他网关
        else if (n.$type === 'bpmn:ExclusiveGateway') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {

                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
              }

            })
          }

        }
        // 并行网关
        else if (n.$type === 'bpmn:ParallelGateway') {
          if (completeTask) {
            canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
            n.outgoing?.forEach(nn => {
              debugger
              const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
              if (targetTask) {
                canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
                canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
              }
            })
          }
        }
        else if (n.$type === 'bpmn:StartEvent') {
          n.outgoing.forEach(nn => {
            const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
            if (completeTask) {
              canvas.addMarker(nn.id, 'highlight')
              canvas.addMarker(n.id, 'highlight')
              return
            }
          })
        }
        else if (n.$type === 'bpmn:EndEvent') {
          if (endTask.key === n.id && endTask.completed) {
            canvas.addMarker(n.id, 'highlight')
            return
          }
        }
      })
    },

3.7、挂载流程定义
一旦流程定义被挂载,那么就不能通过该定义启动流程实例。
挂载本质上就是修改suspend为2

/**
     * 流程定义id  激活或挂起流程定义
     * 注意:流程定义一旦被挂起,那么就不应该通过该流程定义启动流程实例
     * @param state    状态
     * @param deployId 流程部署ID
     */
    @Override
    public void updateState(Integer state, String deployId) {
    	// 通过流程定义id挂起或激活流程定义
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        // 激活
        if (state == 1) {
            repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);
        }
        // 挂起
        if (state == 2) {
            repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
        }
    }

3.8、挂载流程实例

/**
     * 激活或挂起流程实例
     *
     * @param state      状态
     * @param instanceId 流程实例ID
     */
    @Override
    public void updateState(Integer state, String instanceId) {

        // 激活
        if (state == 1) {
            runtimeService.activateProcessInstanceById(instanceId);
        }
        // 挂起
        if (state == 2) {
            runtimeService.suspendProcessInstanceById(instanceId);
        }
    }

3.9、删除流程模型

/**
     * 删除流程部署  连带删除procdef,bytearray
     *
     * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值
     */
    @Override
    public void delete(String deployId) {
        // true 允许级联删除 ,不设置会导致数据库外键关联异常
        repositoryService.deleteDeployment(deployId, true);
    }

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