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; }