学会一个框架前,第一步就是安装依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.activiti.dependencies</groupId>
<artifactId>activiti-dependencies</artifactId>
<version>7.1.0.M6</version>
<type>pom</type>
</dependency>
工作流的组成部分是:流程定义(图的XML),流程实例(相当于是一个图纸使用多次)
那么第一步,获取流程定义列表
流程定义
首先在activiti 7的数据库中,是这个表ACT_RE_PROCDEF存放流程定义数据的。大家可以自行查看,我这里说一些重要的字段。
// 需要注入
private final RepositoryService repositoryService;
// 起始页
int firstResult = (processDefinitionDto.getCurrent() - 1) * processDefinitionDto.getPageSize();
// 用于执行查询流程定义
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
// 分页获取流程定义数据
List<ProcessDefinition> list = processDefinitionQuery.listPage(firstResult, processDefinitionDto.getPageSize());
// 获取流程定义的总数
long count = processDefinitionQuery.count();
// 那么不分页获取数据怎么实现呢?
// List<ProcessDefinition> list2 = processDefinitionQuery.list();
流程定义实体,这里我说一下经常使用的。
public interface ProcessDefinition {
/** 流程定义名称 */
String getName();
/** 唯一Key */
String getKey();
/** 版本号 */
int getVersion();
/** 文件名 */
String getResourceName();
/** 流程定义ID */
String getDeploymentId();
}
功能一:实现上传 BPMN 文件
什么是 BPMN呢,它的作用是描述和表示业务流程,而 Activiti7 加载的业务图就是 BPMN 文件,一个流程定义其实就是一个表示业务工作图的文件。
上传方式呢:一般是,zip压缩包或者直接是 BPMN 文件。
// 需要注入
private final RepositoryService repositoryService;
public boolean uploadBpmn(MultipartFile file) {
// 获取文件名
String filename = file.getOriginalFilename();
// 这里加上时间
String name = filename + "---" + System.currentTimeMillis();
// 获取文件扩展名
String extension = FilenameUtils.getExtension(filename);
// 获取文件流
try (InputStream inputStream = file.getInputStream()) {
if ("zip".equalsIgnoreCase(extension)) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
repositoryService.createDeployment().addZipInputStream(zipInputStream).name(name).deploy();
}
} else {
repositoryService.createDeployment().addInputStream(filename, inputStream).name(name).deploy();
}
} catch (IOException e) {
throw new RuntimeException("文件上传失败:" + e);
}
}
功能二:在线绘制流程定义
这里前端部分,可以看我其他文章,这里不解释。
前端呢,需要传递一个 XML 和一个 name,XML呢,其实就是 BPMN 文件的内容了。程序其实相对简单哈。
// 需要注入
private final RepositoryService repositoryService;
public boolean saveDefinition(String xml, String name) {
repositoryService.createDeployment().addString("createWithBpmnJs-" + name + ".bpmn", xml)
.name(name).deploy();
return true;
}
功能三:用户点击查看按钮,查看流程定义图
前端部分已经省略,用户请求该流程定义的 XML数据,将XML传递给前端 bpmnjs 进行渲染。
这里之所以选择请求,而不是将xml放到咱们获取流程定义的请求里,是因为 xml 的数据量很大,这样会有很多冗余的数据,并且性能也有受损。
这里需要传递两个参数,一个是流程定义的ID,另一个是文件名
// 需要注入
private final RepositoryService repositoryService;
public String getXml(String deploymentId, String resourceName) {
// 获取流程定义的资源文件,返回的是一个 InputStream 对象,该流中的数据是XML数据
try (InputStream resourceAsStream = repositoryService.getResourceAsStream(deploymentId, resourceName)) {
// 将 InputStream 转换为字符串,编码方式是 UTF-8
return IOUtils.toString(resourceAsStream, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("获取流程定义失败", e);
}
}
功能四:启动流程实例 / 删除流程定义
首先我们说启动流程实例吧,需要两个参数,这里的 key 不是流程定义的ID,注意下,名称可以随便写。
// 需要注入
private final ProcessRuntime processRuntime;
public ProcessInstance startInstance(String processDefinitionKey, String name) {
// 启动流程实例
StartProcessPayload build = ProcessPayloadBuilder.start()
// 流程key
.withProcessDefinitionKey(processDefinitionKey)
// 启动名称
.withName(name)
// .withVariable("key", "value") // 不需要可以去除
// .withBusinessKey("自定义的参数‘") // 不需要可以去除
.build();
return processRuntime.start(build);
}
删除流程定义,根据流程定义ID
// 需要注入
private final RepositoryService repositoryService;
public boolean deleteDefinitionById(String processDefinitionId) {
// true 表示所有的历史都删掉
repositoryService.deleteDeployment(processDefinitionId, true);
return true;
}
说到现在,流程定义基本上是讲完了。
流程实例
先来看一下实体类,说了一些关键的字段。
public interface ProcessInstance extends ApplicationElement {
// 流程实例的ID
String getId();
// 名称
String getName();
// 启动时间
Date getStartDate();
// 当前状态
ProcessInstanceStatus getStatus();
// 流程定义ID
String getProcessDefinitionId();
// 流程定义Key
String getProcessDefinitionKey();
// 流程定义版本
Integer getProcessDefinitionVersion();
}
功能一:分页查询流程实例列表
// 需要注入
private final ProcessRuntime processRuntime;
// 传入分页参数,第 1 页,每页 10 条
Pageable pageable = Pageable.of(0, 10);
// 仅返回正在进行的或已暂停的流程实例
Page<ProcessInstance> processInstancePage = processRuntime.processInstances(pageable);
// 获取内容
List<ProcessInstance> content = processInstancePage.getContent();
// 获取流程实例的总数
long totalItems = processInstancePage.getTotalItems();
功能二:挂起 / 激活流程实例
业务我来说一下,前端传递流程实例的ID,通过 processInstance 方法查询该流程实例,判断当前的状态,如果是运行中,执行挂起,如果是暂停状态,执行激活。
// 需要注入
private final ProcessRuntime processRuntime;
public boolean updateStatus(String id) {
ProcessInstance processInstance = processRuntime.processInstance(id);
// 根据Id查询出来流程实例
if (processInstance.getStatus().equals(ProcessInstance.ProcessInstanceStatus.RUNNING)) {
// 挂起
SuspendProcessPayload build = ProcessPayloadBuilder.suspend().withProcessInstanceId(id).build();
processRuntime.suspend(build);
} else if (processInstance.getStatus().equals(ProcessInstance.ProcessInstanceStatus.SUSPENDED)) {
// 激活
ResumeProcessPayload build = ProcessPayloadBuilder.resume().withProcessInstanceId(id).build();
processRuntime.resume(build);
}
return true;
}
功能三: 删除流程实例
// 需要注入
private final ProcessRuntime processRuntime;
public boolean deleteById(String instanceId) {
// 删除流程实例
DeleteProcessPayload build = ProcessPayloadBuilder.delete()
.withProcessInstanceId(instanceId).build();
processRuntime.delete(build);
return true;
}
功能四:获取全部流程实例
// 需要注入
private final RuntimeService runtimeService;
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().list();
功能五:查看当前实例执行进度
这里还说比较复杂的,有一些业务逻辑需要梳理。
首先,用户传递 实例ID,查询该实例的进度。我们需要做的是什么?
- 用灰色显示已经执行过的,节点标注灰色
- 如果该节点是自己执行的,连接线标注绿色
- 如果该节点是正在执行的,节点标注黄色
理解了需求,现在看一些 API
根据实例ID查询流程实例ID历史记录
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
// 根据流程实例ID查询
.processInstanceId(processInstanceId)
// 唯一
.singleResult();
根据流程实例获取活动列表
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
// 根据实例ID查询
.processInstanceId(processInstanceId)
// 根据执行人查询
.taskAssignee("执行人")
.list();
整体执行逻辑:
- 根据流程实例ID,获取流程定义KEY
- 根据流程定义KEY获取 BPMN xml 结构
- 根据流程实例ID获取活动节点
- 查询出自己的活动节点
- 遍历节点忽略掉 开始和结束节点
- 判断是否是连接线,如果是,在自己的活动节点中查询,存在则记录。
- 判断是节点,如果是,判断结束时间,第一个结束时间是null的表示是当前活动,存在的标注为已执行,其他的标注为未执行。
public List<InstanceHistory> highlightProcessHistory(String processInstanceId) {
// 查询流程实例
HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
// 根据流程实例ID查询
.processInstanceId(processInstanceId)
// 唯一
.singleResult();
// 获取bpmnModel对象
BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
// 因为我们这里只定义了一个Process 所以获取集合中的第一个即可
Process process = bpmnModel.getProcesses().get(0);
//获取所有的FlowElement信息
ArrayList<FlowElement> flowElements = (ArrayList<FlowElement>) process.getFlowElements();
//获取流程实例 历史节点(全部)
Map<String, HistoricActivityInstance> collect = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).list().stream()
.collect(Collectors.toMap(HistoricActivityInstance::getActivityId, Function.identity()));
// 查询执行人是我自己的,历史任务
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).taskAssignee(SecurityUtils.getUsername()).list();
boolean flag = false;
ArrayList<InstanceHistory> instanceHistories = new ArrayList<>();
// 执行过的(灰色),我执行的,当前执行的
for (FlowElement flowElement : flowElements) {
// 排除起始节点和结束节点
if (flowElement instanceof StartEvent || flowElement instanceof EndEvent) {
continue;
}
String id = flowElement.getId();
String name = flowElement.getName();
if (flowElement instanceof SequenceFlow) {
String sourceRef = ((SequenceFlow) flowElement).getSourceRef();
HistoricActivityInstance historicActivityInstance = list.stream().filter(taskInstance ->
taskInstance.getActivityId().equals(sourceRef)).findFirst().orElse(null);
// 表示是我执行的并且已经执行过
if (historicActivityInstance != null && historicActivityInstance.getEndTime() != null) {
InstanceHistory e = new InstanceHistory(id, name, 0, true, 0);
instanceHistories.add(e);
}
continue;
}
HistoricActivityInstance activiti = collect.get(flowElement.getId());
// 第一个没有结束时间的是正在执行的 否则未执行
int isExecuted = 0;
if (activiti.getEndTime() == null) {
isExecuted = flag ? 1 : 2;
flag = true;
}
InstanceHistory e = new InstanceHistory(id, name, isExecuted,
Objects.equals(activiti.getAssignee(), SecurityUtils.getUsername()), 1);
instanceHistories.add(e);
}
return instanceHistories;
}
最终呢,结果就是这样的
public class InstanceHistory {
private String id;
private String name;
// 是否已经执行过了. 0 已经执行了,1 未执行,2 当前执行
private Integer isExecuted;
// 是否是当前用户执行的
private Boolean isMyTask;
// 类型:线、节点 , 0 线 1 节点
private Integer type;
}
任务
实体类,一些关键的字段。
public interface Task extends ApplicationElement {
// 任务ID
String getId();
// 待办人,如果为空表示,候选人
String getAssignee();
// 名称
String getName();
// 描述
String getDescription();
// 创建时间
Date getCreatedDate();
// 流程定义ID
String getProcessDefinitionId();
// 流程实例ID
String getProcessInstanceId();
// 状态
TaskStatus getStatus();
// 完成时间
Date getCompletedDate();
}
功能一:获取我的任务
默认是分页的,没有提供获取全部的任务方法。
// 获取任务,第1页的10条数据
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10));
// 获取任务
List<Task> content = tasks.getContent();
// 总条数
int totalItems = tasks.getTotalItems();
功能二:完成任务 / 拾取任务
这里我就做到一块了,因为有些情况下,拾取任务在进行完成,其实没必要的。
前面我们说过了,如果待办人是空,其实是候选人。
// 需要注入
private final TaskRuntime taskRuntime;
public Task completeTask(String taskId) {
// 查询任务
Task task = taskRuntime.task(taskId);
if (task.getAssignee() == null) {
// 不需要先拾取任务,再执行(表示是候选人)
ClaimTaskPayload build = TaskPayloadBuilder.claim().withTaskId(taskId).build();
// 拾取任务
taskRuntime.claim(build);
}
// 完成任务
CompleteTaskPayload build = TaskPayloadBuilder.complete().withTaskId(taskId)
// 可以完成时加参数
// .withVariables()
.build();
// 执行完成
return taskRuntime.complete(build);
}
项目暂时到这里啦,后续继续补充
后续会补充监听器、超时提醒等