Camunda平台是一个轻量级、开源的业务流程管理平台。在性能、功能上优于其它开源流程框架,非常适合引入项目做二次开发。要在项目中引入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;
}