焦點速看:Springboot整合Camunda工作流引擎實現(xiàn)審批流程實例
環(huán)境:Spingboot2.6.14+camunda-spring-boot-starter7.18.0環(huán)境配置依賴配置7.18.0org.camunda
環(huán)境:Spingboot2.6.14 +camunda-spring-boot-starter7.18.0
環(huán)境配置依賴配置
(資料圖片僅供參考)
7.18.0 org.camunda.bpm.springboot camunda-bpm-spring-boot-starter-webapp ${camunda.version} org.camunda.bpm.springboot camunda-bpm-spring-boot-starter-rest ${camunda.version}
應(yīng)用程序配置
camunda.bpm: webapp: # 設(shè)置管理控制臺的訪問上下文 application-path: /workflow auto-deployment-enabled: true admin-user: # 配置登錄管理控制臺的用戶 id: admin password: admin firstName: admin filter: create: All tasks database: #數(shù)據(jù)庫類型 type: mysql #是否自動更新表信息 schema-update: truelogging: level: #配置日志,這樣在開發(fā)過程中就能看到每步執(zhí)行的SQL語句了 "[org.camunda.bpm.engine.impl.persistence.entity]": debug---spring: jersey: application-path: /api-flow type: servlet servlet: load-on-startup: 0
通過上面的配置后訪問控制臺:http://localhost:8100/workflow/
默認(rèn)是沒有上面的tasks中的內(nèi)容,這里是我之前測試數(shù)據(jù)
環(huán)境準(zhǔn)備好后,接下來就可以設(shè)計工作流程。
上面的camunda-bpm-spring-boot-starter-rest依賴中定義了一系列操作camunda的 rest api 這api的實現(xiàn)是通過jersey實現(xiàn),我們可以通過/api-flow前綴來訪問這些接口,具體有哪些接口,我們可以通過官方提供的camunda-bpm-run-7.18.0.zip
設(shè)計流程http://localhost:8080/swaggerui/#/
這里設(shè)計兩個節(jié)點的審批流程,經(jīng)理審批---》人事審批 流程。
經(jīng)理審批節(jié)點
人事審批節(jié)點
上面配置了2個用戶任務(wù)節(jié)點,并且為每個任務(wù)節(jié)點都設(shè)置了表達(dá)式,指定節(jié)點的審批人。
最終生成的流程XML內(nèi)容如下:
Flow_18pxcpx Flow_18pxcpx Flow_0n014x3 Flow_0n014x3 Flow_0dsfy6s Flow_0dsfy6s
部署流程這里我不通過上面的rest api 進(jìn)行部署,而是通過自定義的接口然后調(diào)用camunda的相關(guān)api來實現(xiàn)流程部署。
上面的流程設(shè)計我是通過vue整合的camunda進(jìn)行設(shè)計,并沒有使用官方提供的設(shè)計器。設(shè)計完成后直接上傳到服務(wù)端。
接口@RestController@RequestMapping("/camunda")public class BpmnController { // 上傳路徑 @Value("${gx.camunda.upload}") private String path ; // 通用的工作流操作api服務(wù)類 @Resource private ProcessService processService ; @PostMapping("/bpmn/upload") public AjaxResult uploadFile(MultipartFile file, String fileName, String name) throws Exception { try { // 上傳并返回新文件名稱 InputStream is = file.getInputStream() ; File storageFile = new File(path + File.separator + fileName) ; FileOutputStream fos = new FileOutputStream(new File(path + File.separator + fileName)) ; byte[] buf = new byte[10 * 1024] ; int len = -1 ; while((len = is.read(buf)) > -1) { fos.write(buf, 0, len) ; } fos.close() ; is.close() ; // 創(chuàng)建部署流程 processService.createDeploy(fileName, name, new FileSystemResource(storageFile)) ; return AjaxResult.success(); } catch (Exception e) { return AjaxResult.error(e.getMessage()); } }}
部署流程Service// 這個是camunda spring boot starter 自動配置@Resourceprivate RepositoryService repositoryService ;public void createDeploy(String resourceName, String name, org.springframework.core.io.Resource resource) { try { Deployment deployment = repositoryService.createDeployment() .addInputStream(resourceName, resource.getInputStream()) .name(name) .deploy(); logger.info("流程部署id: {}", deployment.getId()); logger.info("流程部署名稱: {}", deployment.getName()); } catch (IOException e) { throw new RuntimeException(e) ; }}
執(zhí)行上面的接口就能將上面設(shè)計的流程部署到camunda中(其實就是將流程文件保存到了數(shù)據(jù)庫中,對應(yīng)的數(shù)據(jù)表是:act_ge_bytearray)。
啟動流程啟動流程還是一樣,通過我們自己的接口來實現(xiàn)。
接口@RestController@RequestMapping("/process")public class ProcessController { @Resource private ProcessService processService ; // 根據(jù)流程定義id,啟動流程;整個流程需要動態(tài)傳2個參數(shù)(審批人),如果不傳將會報錯 @GetMapping("/start/{processDefinitionId}") public AjaxResult startProcess(@PathVariable("processDefinitionId") String processDefinitionId) { Map variables = new HashMap<>() ; variables.put("uid", "1") ; variables.put("mid", "1000") ; processService.startProcessInstanceAssignVariables(processDefinitionId, "AKF", variables) ; return AjaxResult.success("流程啟動成功") ; }}
服務(wù)Service接口@Resourceprivate RuntimeService runtimeService ;public ProcessInstance startProcessInstanceAssignVariables(String processDefinitionId, String businessKey, Map variables) { ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables); logger.info("流程定義ID: {}", processInstance.getProcessDefinitionId()); logger.info("流程實例ID: {}", processInstance.getId()); logger.info("BussinessKey: {}", processInstance.getBusinessKey()) ; return processInstance ;}
流程啟動后就可以查看當(dāng)前需要自己審批的所有審批單
接口實現(xiàn)@Resourceprivate TaskService taskService ;@Resourceprivate ManagementService managementService ;// 根據(jù)時間段查詢public List queryTasksByBusinessAndCreateTime(String assignee, String businessKey, String startTime, String endTime) { NativeTaskQuery nativeQuery = taskService.createNativeTaskQuery() ; nativeQuery.sql("select distinct RES.* from " + managementService.getTableName(TaskEntity.class) + " RES " + " left join " + managementService.getTableName(IdentityLinkEntity.class) + " I on I.TASK_ID_ = RES.ID_ " + " WHERE (RES.ASSIGNEE_ = #{assignee} or " + " (RES.ASSIGNEE_ is null and I.TYPE_ = "candidate" " + " and (I.USER_ID_ = #{assignee} or I.GROUP_ID_ IN ( #{assignee} ) ))) " + " and RES.CREATE_TIME_ between #{startTime} and #{endTime} " + " order by RES.CREATE_TIME_ asc LIMIT #{size} OFFSET 0") ; nativeQuery.parameter("assignee", assignee) ; nativeQuery.parameter("startTime", startTime) ; nativeQuery.parameter("endTime", endTime) ; nativeQuery.parameter("size", Integer.MAX_VALUE) ; return nativeQuery.list() ;}
審批流程流程啟動后,接下來就是各個用戶任務(wù)節(jié)點配置的用戶進(jìn)行審批
接口@GetMapping("/approve/{id}")public AjaxResult approve(@PathVariable("id") String instanceId) { if (StringUtils.isEmpty(instanceId)) { return AjaxResult.error("未知審批任務(wù)") ; } // 下面的參數(shù)信息應(yīng)該自行保存管理(與發(fā)起審批設(shè)置的指派人要一致) Map variables = new HashMap<>() ; // 第一個節(jié)點所要提供的遍歷信息(這里就是依次類推,mid等) variables.put("uid", "1") ; processService.executionTask(variables, instanceId, task -> {}, null) ; return AjaxResult.success() ; }
服務(wù)Service接口@Resourceprivate TaskService taskService ;@Resourceprivate RuntimeService runtimeService ;@Transactionalpublic void executionTask(Map variables, String instanceId, Consumer consumer, String type) { Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult() ; if (task == null) { logger.error("任務(wù)【{}】不存在", instanceId) ; throw new RuntimeException("任務(wù)【" + instanceId + "】不存在") ; } taskService.setVariables(task.getId(), variables); taskService.complete(task.getId(), variables) ; long count = runtimeService.createExecutionQuery().processInstanceId(instanceId).count(); if (count == 0) { consumer.accept(task) ; }}
以上就完成了從整個流程的生命周期:
設(shè)計流程---》部署流程---》啟動流程---》審批流程
完畢!!!