1.引入依赖
<flowable.version>6.7.2</flowable.version>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-basic</artifactId>
</dependency>
2. flowable核心概念解释
flowable核心概念和表解释
由于flowable-starter 内置集成了repositoryService,historyService,runtimeService等等因此直接注入到业务类上即可
3. Flowable实践
3.1、部署流程模型
前端上传的是xml文件字符串,转io流
/**
* 导入流程文件
*
* @param name
* @param category
* @param in
*/
@Override
public void importFile(String name, String category, InputStream in) {
// 部署流程定义文件
// 1、部署信息存储
// 2、存储bpmn文件 和 bpmn.png
// 3、存储流程定义基本信息
Deployment deploy = repositoryService.createDeployment()
.addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category)
.deploy();
// 通过部署id,获取部署完成的流程定义
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploy.getId()).singleResult();
// 设置流程定义分类
repositoryService.setProcessDefinitionCategory(definition.getId(), category);
}
涉及以下几张表
3.2、流程模型挂载表单
实际上是定义一个表单form表,一个deploy_form表。form存放动态表单json,deploy_form表建立流程模型部署信息与表单信息的关系。
流程模型挂载表单实际上就是往deply_form存放模型挂载的表单信息。 目的为了当通过模型启动流程实例的时候,将流程模型(部署id)对应的动态表单json取出来。
3.3、启动流程
1、启动流程需要,提交流程定义id,和表单提供的变量map
2、申请人启动流程,那么自动完成申请任务,追加任务处理人,处理意见。通过taskService.complete完成任务
3、注意:第一,需要将流程发起者存储,以便后续取出。第二,一般情况下,任务只有最新的待完成,开在ru_task表看到。第三,ru相关表只会存储运行的流程数据,流程结束便会删除
/**
* 根据流程定义ID启动流程实例
*
* @param procDefId 流程定义Id
* @param variables 流程变量
* @return
*/
@Override
public AjaxResult startProcessInstanceById(String procDefId, Map<String, Object> variables) {
try {
// 流程定义被挂起,将无法激活流程
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
.latestVersion().singleResult();
if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
return AjaxResult.error("流程已被挂起,请先激活流程");
}
// 设置流程发起人Id到流程中
SysUser sysUser = SecurityUtils.getLoginUser().getUser();
// 设置流程发起者 设置前者可以在历史流程实例中查询到,设置后置可以在表单变量中获取流程发起者
identityService.setAuthenticatedUserId(sysUser.getUserId().toString());
variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId().toString());
// 给第一步申请人节点设置任务执行人和意见,并且完成任务
ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables);
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult();
if (Objects.nonNull(task)) {
taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请");
taskService.setAssignee(task.getId(), sysUser.getUserId().toString());
taskService.complete(task.getId(), variables);
}
return AjaxResult.success("流程启动成功");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("流程启动错误");
}
}
涉及表
3.4、获取当前用户待处理的任务
/**
* 代办任务列表
*
* @param pageNum 当前页码
* @param pageSize 每页条数
* @return
*/
@Override
public AjaxResult todoList(Integer pageNum, Integer pageSize) {
Page<FlowTaskDto> page = new Page<>();
Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
// 获取当前用户的待处理的任务
TaskQuery taskQuery = taskService.createTaskQuery()
.active().includeProcessVariables()
.taskAssignee(userId.toString())
.orderByTaskCreateTime().desc();
page.setTotal(taskQuery.count());
List<Task> taskList = taskQuery.listPage(pageSize * (pageNum - 1), pageSize);
List<FlowTaskDto> flowList = new ArrayList<>();
for (Task task : taskList) {
FlowTaskDto flowTask = new FlowTaskDto();
// 当前流程信息
flowTask.setTaskId(task.getId());
flowTask.setTaskDefKey(task.getTaskDefinitionKey());
flowTask.setCreateTime(task.getCreateTime());
flowTask.setProcDefId(task.getProcessDefinitionId());
flowTask.setExecutionId(task.getExecutionId());
flowTask.setTaskName(task.getName());
// 流程定义信息
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(task.getProcessDefinitionId()).singleResult();
flowTask.setDeployId(pd.getDeploymentId());
flowTask.setProcDefName(pd.getName());
flowTask.setProcDefVersion(pd.getVersion());
flowTask.setProcInsId(task.getProcessInstanceId());
// 流程发起人信息
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId()).singleResult();
SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId()));
flowTask.setStartUserId(startUser.getNickName());
flowTask.setStartUserName(startUser.getNickName());
flowTask.setStartDeptName(startUser.getDept().getDeptName());
flowList.add(flowTask);
}
page.setRecords(flowList);
return AjaxResult.success(page);
}
3.5、审批待处理的任务并设置审批意见
taskId:待处理任务节点
/**
* 完成任务
*
* @param taskVo 请求实体参数
*/
@Transactional(rollbackFor = Exception.class)
@Override
public AjaxResult complete(FlowTaskVo taskVo) {
Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult();
if (Objects.isNull(task)) {
return AjaxResult.error("任务不存在");
}
if (DelegationState.PENDING.equals(task.getDelegationState())) {
// 完成审批
taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), taskVo.getComment());
taskService.resolveTask(taskVo.getTaskId(), taskVo.getValues());
} else {
// 完成审批
taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), taskVo.getComment());
Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
taskService.setAssignee(taskVo.getTaskId(), userId.toString());
taskService.complete(taskVo.getTaskId(), taskVo.getValues());
}
return AjaxResult.success();
}
涉及表
3.6、前端显示当前正在执行的流程图
1、读取流程模型xml
/**
* 读取xml
*
* @param deployId
* @return
*/
@Override
public AjaxResult readXml(String deployId) throws IOException {
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
// 通过部署id,指定名称,读取指定的资源为流
InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName());
// 将xml文件流,转字符串
String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
return AjaxResult.success("", result);
}
2、获取流程实例的历史活动节点(未执行到的将不会被取到)
/**
* 获取流程执行过程
*
* @param procInsId 流程实例id
* @return
*/
@Override
public AjaxResult getFlowViewer(String procInsId, String executionId) {
List<FlowViewerDto> flowViewerList = new ArrayList<>();
// 获取流程实例全部的历史活动节点
List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInsId).list();
for (HistoricActivityInstance finishedActivityInstance : historicActivityInstances) {
// 将sequenceFlow节点不交由前端渲染
if (!"sequenceFlow".equals(finishedActivityInstance.getActivityType())) {
FlowViewerDto flowViewerDto = new FlowViewerDto();
flowViewerDto.setKey(finishedActivityInstance.getActivityId());
// 根据流程节点处理时间校验改节点是否已完成
flowViewerDto.setCompleted(!Objects.isNull(finishedActivityInstance.getEndTime()));
flowViewerList.add(flowViewerDto);
}
}
return AjaxResult.success(flowViewerList);
}
前端bpmn-modeler 读取xml,遍历流程节点,将当前历史活动节点全部高亮即可。
对应前端代码,data:xml
// 让图能自适应屏幕
fitViewport() {
this.zoom = this.modeler.get('canvas').zoom('fit-viewport')
const bbox = document.querySelector('.flow-containers .viewport').getBBox()
const currentViewbox = this.modeler.get('canvas').viewbox()
const elementMid = {
x: bbox.x + bbox.width / 2 - 65,
y: bbox.y + bbox.height / 2
}
this.modeler.get('canvas').viewbox({
x: elementMid.x - currentViewbox.width / 2,
y: elementMid.y - currentViewbox.height / 2,
width: currentViewbox.width,
height: currentViewbox.height
})
this.zoom = bbox.width / currentViewbox.width * 1.8
},
// 放大缩小
zoomViewport(zoomIn = true) {
this.zoom = this.modeler.get('canvas').zoom()
this.zoom += (zoomIn ? 0.1 : -0.1)
this.modeler.get('canvas').zoom(this.zoom)
},
async createNewDiagram(data) {
// 将字符串转换成图显示出来
// data = data.replace(//g, '<![CDATA[$1]]>')
data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function(match, str) {
return str.replace(/</g, '<')
})
try {
await this.modeler.importXML(data)
this.adjustPalette()
this.fitViewport()
if (this.taskList !==undefined && this.taskList.length > 0 ) {
this.fillColor()
}
} catch (err) {
console.error(err.message, err.warnings)
}
},
fillColor() {
const canvas = this.modeler.get('canvas')
this.modeler._definitions.rootElements[0].flowElements.forEach(n => {
const completeTask = this.taskList.find(m => m.key === n.id)
const todoTask = this.taskList.find(m => !m.completed)
const endTask = this.taskList[this.taskList.length - 1]
if (n.$type === 'bpmn:UserTask') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
if (todoTask && completeTask.key === todoTask.key && !todoTask.completed){
canvas.addMarker(nn.id, todoTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, todoTask.completed ? 'highlight' : 'highlight-todo')
}else {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
}
})
}
}
// 排他网关
else if (n.$type === 'bpmn:ExclusiveGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
// 并行网关
else if (n.$type === 'bpmn:ParallelGateway') {
if (completeTask) {
canvas.addMarker(n.id, completeTask.completed ? 'highlight' : 'highlight-todo')
n.outgoing?.forEach(nn => {
debugger
const targetTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (targetTask) {
canvas.addMarker(nn.id, targetTask.completed ? 'highlight' : 'highlight-todo')
canvas.addMarker(nn.targetRef.id, targetTask.completed ? 'highlight' : 'highlight-todo')
}
})
}
}
else if (n.$type === 'bpmn:StartEvent') {
n.outgoing.forEach(nn => {
const completeTask = this.taskList.find(m => m.key === nn.targetRef.id)
if (completeTask) {
canvas.addMarker(nn.id, 'highlight')
canvas.addMarker(n.id, 'highlight')
return
}
})
}
else if (n.$type === 'bpmn:EndEvent') {
if (endTask.key === n.id && endTask.completed) {
canvas.addMarker(n.id, 'highlight')
return
}
}
})
},
3.7、挂载流程定义
一旦流程定义被挂载,那么就不能通过该定义启动流程实例。
挂载本质上就是修改suspend为2
/**
* 流程定义id 激活或挂起流程定义
* 注意:流程定义一旦被挂起,那么就不应该通过该流程定义启动流程实例
* @param state 状态
* @param deployId 流程部署ID
*/
@Override
public void updateState(Integer state, String deployId) {
// 通过流程定义id挂起或激活流程定义
ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
// 激活
if (state == 1) {
repositoryService.activateProcessDefinitionById(procDef.getId(), true, null);
}
// 挂起
if (state == 2) {
repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null);
}
}
3.8、挂载流程实例
/**
* 激活或挂起流程实例
*
* @param state 状态
* @param instanceId 流程实例ID
*/
@Override
public void updateState(Integer state, String instanceId) {
// 激活
if (state == 1) {
runtimeService.activateProcessInstanceById(instanceId);
}
// 挂起
if (state == 2) {
runtimeService.suspendProcessInstanceById(instanceId);
}
}
3.9、删除流程模型
/**
* 删除流程部署 连带删除procdef,bytearray
*
* @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值
*/
@Override
public void delete(String deployId) {
// true 允许级联删除 ,不设置会导致数据库外键关联异常
repositoryService.deleteDeployment(deployId, true);
}