C
amunda平台是一個輕量級、開源的業務流程管理平台。在性能、功能上優於其它開源流程框架,非常適合引入項目做二次開發。要在項目中引入Camunda流程引擎,那么我們就應該對流程引擎了解,不便能避免很多未知的問題,還能更好地發揮其作用。
一、Camunda 流程引擎版本介紹
Camunda 是基於JAVA 語言開發,從7.0到目前最新版本 7.16 ,每一次版本的更新,流程引擎會增加一些新功能,也會優化現有性能。

二、SpringBoot 集成 Camunda版本對照
Camunda 與
Spring Boot 的集成,主要是因為每個版本的 Camunda
Boot Starter 基於特定版本的 Spring Boot 開發,並
綁定了特定版本的 Camunda BPM。
注意:
從7.13.0版開始,Camunda BPM及其兼容的Spring Boot Starter始終共享同一版本。 另外,Spring Boot Starter中使用的Camunda BPM版本不再需要被覆蓋。 只需選擇與您要使用的Camunda BPM版本類似的Starter版本。
Spring Boot Starter version
|
Camunda BPM version
|
Spring Boot version
|
1.0.0*
|
7.3.0
|
1.2.5.RELEASE
|
1.1.0*
|
7.4.0
|
1.3.1.RELEASE
|
1.2.0*
|
7.5.0
|
1.3.5.RELEASE
|
1.2.1*
|
7.5.0
|
1.3.6.RELEASE
|
1.3.0*
|
7.5.0
|
1.3.7.RELEASE
|
2.0.0**
|
7.6.0
|
1.4.2.RELEASE
|
2.1.x**
|
7.6.0
|
1.5.3.RELEASE
|
2.2.x**
|
7.7.0
|
1.5.6.RELEASE
|
2.3.x
|
7.8.0
|
1.5.8.RELEASE
|
3.0.x
|
7.9.0
|
2.0.x.RELEASE
|
3.1.x
|
7.10.0
|
2.0.x.RELEASE
|
3.2.x
|
7.10.0
|
2.1.x.RELEASE
|
3.3.1+
|
7.11.0
|
2.1.x.RELEASE
|
3.4.x
|
7.12.0
|
2.2.x.RELEASE
|
7.13.x
7.13.3+***
|
7.13.x
7.13.3+
|
2.2.x.RELEASE
|
三、
SpringBoot 集成Camunda 示例
網上已經有很多關於
Spring Boot 集成的示例 Camunda,但很多引入太直接,並不是很理想,不但容易出現版本沖突問題,而且不便於POM文件的管理,我這里推薦大家使用官網推薦大家基於BOM的方式引入Camunda。
Camunda 現在最新版本是7.16.0,在我的項目中使用的是7.15.0
項目版本准備:
Spring Boot: 2.3.7.RELEASE
Camunda: 7.15.0
在我的項目是父子結構,父POM文件中定義了 Spring Boot 的版本,下面是POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hx</groupId> <artifactId>hx-fixSystem</artifactId> <packaging>pom</packaging> <version>2.0</version> <name>hx-fixSystem 管理系統</name> <modules> <!— Camunda 流程引擎模塊 --> <module>hx-bpm</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.7.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>11</java.version> <jedis.version>2.9.0</jedis.version> <log4jdbc.version>1.16</log4jdbc.version> <swagger.version>2.7.0</swagger.version> <fastjson.version>1.2.54</fastjson.version> <druid.version>1.1.22</druid.version> <commons-pool2.version>2.5.0</commons-pool2.version> <mapstruct.version>1.3.1.Final</mapstruct.version> </properties> <dependencies> <!--Spring boot start--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- spring cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--Spring boot end--> <!--spring2.0集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>${commons-pool2.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!--監控sql日志--> <dependency> <groupId>org.bgee.log4jdbc-log4j2</groupId> <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId> <version>${log4jdbc.version}</version> </dependency> <!-- RESTful APIs swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> <!--Mysql依賴包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- druid數據源驅動 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> <!--lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <optional>true</optional> </dependency> </dependencies> <repositories> <repository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>
下面是Camunda 流程引擎模塊的POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>hx-fixSystem</artifactId> <groupId>com.hx</groupId> <version>2.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hx-bpm</artifactId> <name>流程引擎</name> <description>流程引擎相關操作</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-bom</artifactId> <version>7.15.0</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--Spring boot 集成camunda--> <dependency> <groupId>org.camunda.bpm.springboot</groupId> <artifactId>camunda-bpm-spring-boot-starter</artifactId> </dependency> <!--camunda Rest 接口--> <dependency> <groupId>org.camunda.bpm.springboot</groupId> <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId> </dependency> <!--camunda webapp 頁面操作流程--> <dependency> <groupId>org.camunda.bpm.springboot</groupId> <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId> </dependency> <!--外部任務客戶端--> <dependency> <groupId>org.camunda.bpm.springboot</groupId> <artifactId>camunda-bpm-spring-boot-starter-external-task-client</artifactId> </dependency> <!--連接器--> <dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-engine-plugin-connect</artifactId> </dependency> <!--Spin和Json Rest Http中解析; parse http response variable with camunda-spin--> <dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-engine-plugin-spin</artifactId> </dependency> <dependency> <groupId>org.camunda.spin</groupId> <artifactId>camunda-spin-dataformat-json-jackson</artifactId> </dependency> <dependency> <groupId>org.camunda.spin</groupId> <artifactId>camunda-spin-core</artifactId> </dependency> <dependency> <groupId>org.camunda.spin</groupId> <artifactId>camunda-spin-dataformat-all</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>org.simpleframework</groupId> <artifactId>simple-xml</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>qrcode-utils</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>com.cronutils</groupId> <artifactId>cron-utils</artifactId> <version>9.0.2</version> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.13</version> </dependency> <!-- 服務注冊--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> <version>2.2.5.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>10.2.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> </plugin> </plugins> </build> </project>
在POM文件中引入 Camunda 依賴,按上面紅色字體的方式引入,不但固定了版本,而且在下面的具體功能模塊引入時,就可以不用加版本號,它們會自動按上面定好的版本引入對應依賴。
就這樣一個 流程引擎項目就可以正常運轉了。
四:Camunda 引擎自帶的 組件式服務
Camunda 流程引擎功能很強大,做了很多封裝工作,提供了很多’開箱’ 即用的組件式服務,直接調用現存的API,不用寫什么代碼就能把一般的流程跑起來。
下面就介紹幾個重要組件:
組件名稱
|
功能
|
RuntimeService
|
運行時服務。 提供了流程的發起、刪除、掛起、激活、獲取參數、刪除參數等方法。
發起流程實例有 20個方法
最常用的根據流程定義ID(),流程定義KEY方法,啟動成功后返回流程示例。
ProcessInstance startProcessInstanceById(String processDefinitionId)
ProcessInstance startProcessInstanceByKey(String processDefinitionKey);
刪除流程實例 15個方法
其中刪除有 6個方法 (有返回);其余的為直接刪除,沒有返回
獲取活動實例 節點 ,活動節點實例各 1個
向在給定執行中等待的活動實例發送外部觸發器
3個
獲取參數 17個方法
添加參數及值 8個方法
刪除參數 4個方法
掛起流程實例 3個方法
激活流程實例 3個方法
還有其它一些方法沒使用,占時還不明白用途
|
TaskService
|
任務服務。 提供了流程中任務的產生、刪除、完成、委托、認領、設置參數、獲取參數方法
其中常用的方法有:
Task newTask();
void deleteTask(String taskId);
void claim(String taskId, String userId);
void complete(String taskId);
void delegateTask(String taskId, String userId);
void setAssignee(String taskId, String userId);
void setOwner(String taskId, String userId);
void setPriority(String taskId, int priority);
void setVariable(String taskId, String variableName, Object value);
Object getVariable(String taskId, String variableName);
void removeVariable(String taskId, String variableName);
Comment createComment(String taskId, String processInstanceId, String message);
List<Comment> getProcessInstanceComments(String processInstanceId);
|
HistoryService
|
歷史服務。 對流程中已經完成的節點任務、參數的查詢服務,歷史流程。
其中常用的方法有:
HistoricProcessInstanceQuery createHistoricProcessInstanceQuery();
NativeHistoricActivityInstanceQuery createNativeHistoricActivityInstanceQuery();
|
IdentityService
|
權限服務。 想着的權限設置 |
對於以上的組件,在項目中直接引用即可使用,示例如下。
@Resource private RuntimeService runtimeService; @Resource private TaskService taskService; @Resource private FormService formService; @Resource private HistoryService historyService;
如何部署一個流程代碼:
/** * file 為上傳流程圖的文件, * * name 設定流程的名字 */ @Override public void bpmnModelDeployFile(MultipartFile file, String name) { if(file == null){ throw new ApiException("流程文件不能為空!"); } try { repositoryService.createDeployment() //.tenantId(String.valueOf(tenantId)) .name(name) // 流程名字 .source("公司的流程") // 來源 .addInputStream(file.getOriginalFilename(), file.getInputStream()) .deploy(); } catch (Exception e) { throw new ApiException("流程部署失敗".concat(e.getMessage())); } }
啟動流程:
/** * key 為定義流程的KEY * * WorkflowVariableDto 為參數對象 * 返回 流程實例ID */ @Override public String startProcess(String key, WorkflowVariableDto dto) { List<String> tenantIds = new ArrayList<>(Arrays.asList(dto.getTenantIds().split(","))); // 設置流程發起人 if (StringUtils.isNotBlank(dto.getStartUser())) { identityService.setAuthentication(dto.getStartUser(), new ArrayList<>(), tenantIds); } // 啟動流程實例 ProcessInstance instance = runtimeService.startProcessInstanceByKey(key, dto.getBusinessId()); try { // 獲取當前任務,會簽可能會有多個 List<Task> tasks = taskService.createTaskQuery().processInstanceId(instance.getProcessInstanceId()).list(); if (CollectionUtils.isNotEmpty(tasks)) { tasks.forEach(t -> { // 為當前任務分派用戶 if (StringUtils.isNotBlank(dto.getAssignee())) { taskService.setAssignee(t.getId(), dto.getAssignee()); } // 為當前任務添加候選用戶 if (StringUtils.isNotBlank(dto.getCandidateUsers())) { List<String> users = WorkflowVariableDto.getIdListByStr(dto.getCandidateUsers()); users.forEach(u -> taskService.addCandidateUser(t.getId(), u)); } // 為當前任務添加候選組 if (StringUtils.isNotBlank(dto.getCandidateGroups())) { List<String> groups = WorkflowVariableDto.getIdListByStr(dto.getCandidateGroups()); groups.forEach(g -> taskService.addCandidateGroup(t.getId(), g)); } }); } } catch (Exception e) { throw new ApiException("發起流程失敗:".concat(e.getMessage())); } return instance.getProcessInstanceId(); }
完成任務:
/** * taskId 為任務ID * WorkflowVariableDto 為參數對象 * */ @Override public void completeTask(String taskId, WorkflowVariableDto dto) { try { // 獲取完成前任務 Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); if(ObjectUtils.isEmpty(task)){ log.info("沒有查找到當前任務:{}", taskId); return ; } // 完成任務填寫的備注 if(StringUtils.isNotBlank(dto.getReason()) && StringUtils.isNotBlank(task.getProcessInstanceId())){ taskService.createComment(task.getId(), task.getProcessInstanceId(), dto.getReason()); } // 完成當前任務 taskService.complete(taskId, dto.getVariables()); } catch (Exception e) { throw new ApiException("完成任務失敗:".concat(e.getMessage())); } }
駁回任務:
/** * 駁回並返回到起點 * @param processInstanceId 流程實例ID * @param taskId 任務ID * @param reason 原因 */ @Override public boolean reject(String processInstanceId, String taskId, String reason, int step){ Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).taskId(taskId).singleResult(); if(task != null){ ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstanceId); if(activityInstance != null){ List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .executionId(task.getExecutionId()) .activityType("userTask") .finished() .orderByHistoricActivityInstanceEndTime() .desc().list(); historicActivityInstanceList = historicActivityInstanceList.stream().filter(it -> !it.getActivityId().equals(task.getTaskDefinitionKey())).collect(Collectors.toList()); if(historicActivityInstanceList.size() > 0){ HistoricActivityInstance first = historicActivityInstanceList.get(step); String toActId = first.getActivityId(); String assignee = first.getAssignee(); Map<String, Object> taskVariable = new HashMap<>(); //設置當前處理人 taskVariable.put("assignee", assignee) // 填寫拒絕的原因 taskService.createComment(task.getId(), processInstanceId, reason); runtimeService.createProcessInstanceModification(processInstanceId) //關閉相關任務 .cancelActivityInstance(getInstanceIdForActivity(activityInstance, task.getTaskDefinitionKey())) .setAnnotation("進行了駁回到"+ step + "任務節點操作") //啟動目標活動節點 .startBeforeActivity(toActId) //流程的可變參數賦值 .setVariables(taskVariable) .execute(); List<Task> _taskList = taskService.createTaskQuery().processInstanceId(processInstanceId).list(); if(ObjectUtils.isNotEmpty(_taskList)){ for(Task item: _taskList){ if(item.getTaskDefinitionKey().equals(toActId)){ taskService.setAssignee(item.getId(), assignee); return true; } } } } } } return false; }