文章目录
资料来源:B站云尚办公
审批篇
审批流程利用Activiti
实现,将业务流程模板化及自动化。
工作流引擎简单介绍:
Activiti
为工作流引擎:
工作流:
工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。
以专业的方式来实现工作流的管理,并且可以做到业务流程变化之后,我们的程序可以不用改变,如果可以实现这样的效果,那么我们的业务系统的适应能力就得到了极大提升。在这样的背景下,就出现了工作流引擎。
为什么使用工作流引擎,能实现业务流程改变,不用修改代码,流程还能自动推进?
(1)我们先来说说为什么流程改变,不用修改代码:我们的工作流引擎都实现了一个规范,这个规范要求我们的流程管理与状态字段无关,始终都是读取业务流程图的下一个节点。当业务更新的时候我们只需要更新业务流程图就行了。这就实现了业务流程改变,不用修改代码。
(2)再来说说流程自动推进,这个原理就更简单了,就拿上面的请假模型来说,工作流引擎会用一张表来记录当前处在的节点。当填写完请假单后肯定是要轮到部门经理来审批了,所以我们一旦完成了请假单填写那么这条记录将会被从这张表删除掉,并且会把下一个节点部门经理的信息插入到这张表中,当我们用部门经理的信息去这张表中查询的时候就能查出部门经理相关的审批的信息了,以此类推,这样层层递进,就实现了流程的自动递交了。
后端所有接口
一、实现审批前的工作:审批模版和审批类型
申请需要审批模版。只有在审批模版内填写申请信息,然后才能点击申请。
**审批模版**
-
审批模版使用form-create由前端进行实现。后端所需要的就是容纳模版形成JSON数据。同时审批模版由类型分类,如人事,办公等类型。后端需要两张表,一张是审批模板,另一张是审批模板类型。
-
模版创造过程
- 以下是
前端界面创造模版过程
点击添加审批设置(添加模版)
- 以下是
(模版创造过程)
- 前端模版信息填写:
- form-create 创造模版,也就是1.中的表单设置。
- 上传Activiti中的流程模型。(模型定义的key就是上zip文件名,制作模型的时候记住,后端接口根据文件名部署),至此一个模版上传成功。
后台人员前两步是填写模版基础信息,第三步是上传Activiti中的模型。前两步创造了模板,后一步是上传Activiti流程模型到后台。第三步具体实现看下面接口。前两布创造模版的后端接口省略,不是重点。这里只展示了前端界面过程,为了更好理解审批的场景,为后面开始审批做铺垫。
- 后端接口位于ProcessTemplateController中
//模版保存
//@PreAuthorize("hasAuthority('bnt.processTemplate.templateSet')")
@ApiOperation(value = "新增")
@PostMapping("save")
public Result save(@RequestBody ProcessTemplate processTemplate) {
processTemplateService.save(processTemplate);
return Result.ok();
}
//Activiti中模型保存
@ApiOperation("上传流程定义")
@PostMapping("uploadProcessDefinition")
public Result uploadProcessDefinition(@RequestParam("file") MultipartFile multipartFile) throws FileNotFoundException {
System.out.println("multipart: "+multipartFile.toString());
//得到存储路径
String absolutePath = new File(ResourceUtils.getURL("").getPath()).getAbsolutePath();
//上传目录
File tempFile = new File(absolutePath + "/service-oa/target/classes/processes/");
if (!tempFile.exists()) tempFile.mkdirs();
System.out.println("tempFile.getAbsolutePath(): " + tempFile.getAbsolutePath());
System.out.println("multipartFile.getOriginalFilename(): "+multipartFile.getOriginalFilename());
//创建空文件用于写入文件
String finalFileName=multipartFile.getOriginalFilename();
File finalFile = new File(tempFile.getAbsolutePath() + "/" + multipartFile.getOriginalFilename());
try {
//将文件复制到指定文件
multipartFile.transferTo(finalFile);
} catch (IOException e) {
e.printStackTrace();
return Result.fail().message("上传失败");
}
Map<String, Object> map = new HashMap<>();
//根据上传地址后续部署流程定义,文件名称为流程定义的默认key
map.put("processDefinitionPath", "processes/" + finalFileName);
map.put("processDefinitionKey", finalFileName.substring(0, finalFileName.lastIndexOf(".")));
return Result.ok(map);
}
- 到这里审批模板制作完成,但是接下来即将开始审批,需要
Activiti
实现。所以需要将刚才上传到后台的Activiti审批流程模型
部署到Activiti中。
ps:审批的定义就是审批流程的实现,这里审批流程的业务逻辑由
Activiti
实现。而审批流程就是刚上传的模型,需要在Activiti
进行流程部署。
PS: 前端将部署过程定义为 发布
后端接口ProcesstemplateController
//@PreAuthorize("hasAuthority('bnt.processTemplate.publish')")
@ApiOperation(value = "发布")
@GetMapping("/publish/{id}")
public Result publish(@PathVariable Long id) {
processTemplateService.publish(id);
return Result.ok();
}
ProcesstemplateServiceImpl
@Transactional
@Override
public void publish(Long id) {
ProcessTemplate processTemplate = this.getById(id);
processTemplate.setStatus(1);
int update = processTemplateMapper.updateById(processTemplate);
//优先发布在线流程设计
if (!StringUtils.isEmpty(processTemplate.getProcessDefinitionPath())) {
processService.deployByZip(processTemplate.getProcessDefinitionPath());
}
}
ProcessServiceImpl
@Override
public void deployByZip(String deployPath) {
//通过部署地址找到zip文件,转为二进制文件
//this.getClass().getClassLoader().getResourceAsStream(deployPath);
/*
String prePath=null;
try {
prePath= ResourceUtils.getURL("").getPath()+"/service-oa/src/main/java/processes/";
} catch (FileNotFoundException e) {
e.printStackTrace();
}*/
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(deployPath);
ZipInputStream zipInputStream
= new ZipInputStream(inputStream);
// 部署文件
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream).deploy();
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());
}
二、申请和审批流程
前端申请
- 申请界面:
- 点击加班
- 提交后,我们来到审批人界面
分别是待处理,已处理,已发起。
- 点击待处理中的任务进行审批
- 审批后已处理界面出现刚才任务
- 已发起任务界面
后端接口处理
- 点击提交审批后: Activiti
开启一次任务。
后端ProcessServiceImpl
类调用这个方法(controller和service类省略。。。):
@Override
//ProcessFormVo后面有图
public Process startUp(ProcessFormVo processFormVo) {
//申请人
SysUser sysUser = sysUserService.getById(LoginInfoHelper.getUserId());
System.out.println("存在threadLocal中的用户id"+LoginInfoHelper.getUserId());
//申请模版
ProcessTemplate processTemplate = processTemplateService.getById(processFormVo.getProcessTemplateId());
//创建业务,保存到数据库中
//数据库表Process:存储每一次申请任务(后面有图)
Process process = new Process();
BeanUtils.copyProperties(processFormVo, process);
String workNo = System.currentTimeMillis() + "";
process.setProcessCode(workNo);
process.setUserId(LoginInfoHelper.getUserId());
process.setFormValues(processFormVo.getFormValues());//当申请人填写表单后提交,表单化作JSON字符串formValue,
process.setTitle(sysUser.getName() + "发起" + processTemplate.getName() + "申请");
process.setStatus(1);
processMapper.insert(process);
//运行流程,并且关联业务
HashMap<String, Object> variablesMap = new HashMap<>();//参数
//process.getFormValues(),是动态表单的数据拆分为json字符串。分为formData和formShowData(后面有图)
JSONObject jsonObject = JSON.parseObject(process.getFormValues());
System.out.println("JOSN将字符串反序列化成为Object:" + jsonObject);
JSONObject formData = jsonObject.getJSONObject("formData");
Map<String, Object> map = new HashMap<>();//formData的数据,转换为map
for (Map.Entry<String, Object> entry : formData.entrySet()) {
map.put(entry.getKey(), entry.getValue());
}
variablesMap.put("data", map);
String businessKey = String.valueOf(process.getId());
//使用Activiti启动流程,开启一次任务
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processTemplate.getProcessDefinitionKey()
, businessKey, variablesMap);//启动实例需要流程定义Key,这里流程就是模版,定义Key在模版中。关联业务(BusinessKey),这里是全局变量(variableMap)
process.setProcessInstanceId(processInstance.getId());//关联业务
//计算下一个审批人,可能有多个(并行审批)
List<Task> taskList = this.getCurrentTaskList(String.valueOf(process.getId()));
if (!CollectionUtils.isEmpty(taskList)) {
List<String> assigneeList = new ArrayList<>();
for(Task task : taskList) {
SysUser user = sysUserService.getOne(
new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, task.getAssignee()));
assigneeList.add(user.getName());
//未完待续(微信公众号)
//推送消息给下一个审批人
// messageService.pushPendingMessage(process.getId(), sysUser.getId(), task.getId());
}
process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
}
//记录操作行为
processRecordService.record(process.getId(), 1, "发起申请");
processMapper.updateById(process);
return process;
}
/**
* 获取当前任务列表
* @param processInstanceId
* @return
*/
private List<Task> getCurrentTaskList(String processInstanceId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
return tasks;
}
- 申请人提交申请后,在审批人界面也就有了待处理
任务
后端ProcessServiceImpl
类调用这个方法(controller和service类省略。。。):
/**
* 查询待处理的任务,待处理的任务根据用户名查询,查询一个人有什么待处理任务。
* ps:查询待处理任务就是查询Activiti中assignee的任务,我们让assignee为用户的用户名
* 查出来为Task集合,而前端需要ProcessVo集合,因此先Task-》process—》processVo
* @param processPageParam
* @return
*/
@Override
public IPage<ProcessVo> findPending(Page<Process> processPageParam) {
System.out.println("用户名; "+LoginInfoHelper.getUsername());
//1.根据用户名查询其任务(任务就是流程实例的某一个节点)
TaskQuery query = taskService.createTaskQuery()
.taskAssignee(LoginInfoHelper.getUsername())
.orderByTaskCreateTime()
.desc();
long size = processPageParam.getSize();
long currentStart=(processPageParam.getCurrent()-1)*processPageParam.getSize();
//对于TaskService的分页查询,(当前条数开始,最大限制数)
List<Task> taskList = query.listPage((int)currentStart,(int)size);
System.out.println("待处理任务Task: "+taskList.toString());
ArrayList<ProcessVo> processVoList = new ArrayList<>();
long count = query.count();//任务总数
//2.转换类型Task-》process->processVo(后面此类)
for(Task item: taskList){
//通过Task找到Process
//通过Task找到流程实例,流程实例processInstance有processid(业务id)
//找流程实例
String processInstanceId = item.getProcessInstanceId();
ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId);
ProcessInstance processInstance = processInstanceQuery.singleResult();
if(processInstance==null) continue;
//找业务id
String businessKey = processInstance.getBusinessKey();
if(businessKey==null) continue;
Process process = this.getById(Long.parseLong(businessKey) );
if(process==null ) continue;
ProcessVo processVo = new ProcessVo();
BeanUtils.copyProperties(process,processVo );
processVo.setTaskId(item.getId());
processVoList.add(processVo);
}
IPage<ProcessVo> processVoPage = new Page<>(processPageParam.getCurrent(), size, count);
processVoPage.setRecords(processVoList);
System.out.println("待处理任务: "+processVoPage.getRecords());
return processVoPage;
}
ProcessVo类
package com.wu.vo.process;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel(description = "Process")
public class ProcessVo {
private Long id;
private Date createTime;
@ApiModelProperty(value = "审批code")
private String processCode;
@ApiModelProperty(value = "用户id")
private Long userId;
private String name;
@TableField("process_template_id")
private Long processTemplateId;
private String processTemplateName;
@ApiModelProperty(value = "审批类型id")
private Long processTypeId;
private String processTypeName;
@ApiModelProperty(value = "标题")
private String title;
@ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "表单属性")
private String formProps;
@ApiModelProperty(value = "表单选项")
private String formOptions;
@ApiModelProperty(value = "表单属性值")
private String formValues;
@ApiModelProperty(value = "流程实例id")
private String processInstanceId;
@ApiModelProperty(value = "当前审批人")
private String currentAuditor;
@ApiModelProperty(value = "状态(0:默认 1:审批中 2:审批通过 -1:驳回)")
private Integer status;
private String taskId;
}
- 审批人点击待处理任务,进行审批
,通过和否决
后端ProcessServiceImpl
类调用这个方法(controller和service类省略。。。):
/**
* 对于申请内容的审批,通过status判断是否同意审批内容
* 过程:1为同意,记录审批结果到数据库,更新流程,传送审批内容到下一位审批者手中
* 结束:通过是否还有下一位审批人和status判断整个审批是否结束(结果为:同意结束还是不同意结束)
* @param approvalVo
*/
@Override
public void approve(ApprovalVo approvalVo) {
//通过approve类的status判断是否通过审批,来推进审批任务的完成或结束
Map<String, Object> variables1 = taskService.getVariables(approvalVo.getTaskId());
for( Map.Entry<String,Object> entry:variables1.entrySet()){
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
String taskId = approvalVo.getTaskId();
System.out.println("任务id:"+taskId);
if(approvalVo.getStatus()==1) {//同意
//已通过
Map<String, Object> variables = new HashMap<String, Object>();//这里表明可以添加全局变量
taskService.complete(taskId, variables);
}else {
//驳回
this.endTask(taskId);
}
//记录本次审批的结果
String description = approvalVo.getStatus().intValue() == 1 ? "已通过" : "驳回";
processRecordService.record(approvalVo.getProcessId(), approvalVo.getStatus(), description);
Process process = this.getById(approvalVo.getProcessId());
//推送给下一个审批人并且更新流程
List<Task> currentTaskList = getCurrentTaskList(process.getProcessInstanceId()); //方法在上一步:已处理任务那里
if(!CollectionUtils.isEmpty(currentTaskList)){
List<String> assigneeList = new ArrayList<>();
for (Task task:currentTaskList){
String assignee = task.getAssignee();
SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, assignee));
assigneeList.add(sysUser.getName());
//推送消息给下一个审批人
process.setDescription("等待" + StringUtils.join(assigneeList.toArray(), ",") + "审批");
process.setStatus(1);
}
}else {
if(approvalVo.getStatus().intValue() == 1) {
process.setDescription("审批完成(同意)");
process.setStatus(2);
} else {
process.setDescription("审批完成(拒绝)");
process.setStatus(-1);
}
}
//推送消息给申请人
//messageService.pushProcessedMessage(process.getId(), process.getUserId(), approvalVo.getStatus());
this.updateById(process);
}
/**
* 申请被拒绝
* 在activiti中拒绝没有直接处理方法,我们对这个流程实例采取的方法是
* 通过改变实例流向,直接将该节点通向下一个节点流向直接断掉,然后通向事件结束节点
* @param taskId
*/
private void endTask(String taskId) {
//首先获得全部节点模型用来获取流程节点
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());//通过流程定义id
//获取节点可以用定义id或者定义key
//结束节点
//EndEvent->Event-> FlowNode->FlowElement-(继承关系)
List<EndEvent> endEventFlowElementsOfType = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
EndEvent endEvent = endEventFlowElementsOfType.get(0);
FlowNode endFlowNode=(FlowNode)endEvent;
//当前节点
FlowElement currentFlowElement = bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
FlowNode currentFlowNode=(FlowNode) currentFlowElement;
// ?为什么要改变类型
// 因为这个类型才有获取流向这个方法,其他类型没有gg。 currentFlowNode.getOutgoingFlows();这个方法,
//当前节点流向
List<SequenceFlow> outgoingFlows = currentFlowNode.getOutgoingFlows();
// 清理活动方向
currentFlowNode.getOutgoingFlows().clear();
//建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(endFlowNode);
List newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 当前节点指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成当前任务
taskService.complete(task.getId());
}
- 完成审批后,审批人已审批界面后会出现刚才的任务
后端ProcessServiceImpl
类调用这个方法(controller和service类省略。。。):
@Override
public IPage<ProcessVo> findProcessed(Page<Process> pageParam) {
System.out.println(LoginInfoHelper.getUsername());
// 查询历史任务实例,根据Assignee,这里处理人设置为用户的用户名
HistoricTaskInstanceQuery historicTaskInstanceQuery =
historyService.createHistoricTaskInstanceQuery()
.taskAssignee(LoginInfoHelper.getUsername())
.finished()
.orderByTaskCreateTime()
.desc();
System.out.println("historicTaskInstanceQuery的实例:======="+historicTaskInstanceQuery.count());
//分页
long totalCount = historicTaskInstanceQuery.count();
long currentMany=(pageParam.getCurrent()-1)*pageParam.getSize();
List<HistoricTaskInstance> historicTaskInstanceList =
historicTaskInstanceQuery.
listPage((int) currentMany, (int) pageParam.getSize());
System.out.println("已处理任务分页:"+historicTaskInstanceList.toString());
//将HistoricTaskInstance==》ProcessVo
// 其实前端展示的已处理任务都是我们要处理的业务,
// 所以找到历史任务的流程实例,流程实例绑定着业务id(ProcessId)
ArrayList<ProcessVo> processArrayList = new ArrayList<>();
if(!CollectionUtils.isEmpty(historicTaskInstanceList)){
for(HistoricTaskInstance historicTaskInstance: historicTaskInstanceList){
String processInstanceId = historicTaskInstance.getProcessInstanceId();
Process process = this.getOne(new LambdaQueryWrapper<Process>()
.eq(Process::getProcessInstanceId, processInstanceId));
ProcessVo processVo = new ProcessVo();
BeanUtils.copyProperties(process,processVo );
processVo.setTaskId("0");//为什么为0
processArrayList.add(processVo);
}
}
IPage<ProcessVo> page = new Page<ProcessVo>(pageParam.getCurrent(), pageParam.getSize(), totalCount);
page.setRecords(processArrayList);
return page;
}
- 还有个接口,发起任务的界面(谁申请的)
在数据库Process表中记录了每次申请的流程(看图)。
后端ProcessServiceImpl
类调用这个方法(controller和service类省略。。。):
@Override
public IPage<ProcessVo> findStarted(Page<ProcessVo> pageParam) {
ProcessQueryVo processQueryVo = new ProcessQueryVo();
processQueryVo.setUserId(LoginInfoHelper.getUserId());
//查询Process表
IPage<ProcessVo> page = processMapper.selectPage1(pageParam, processQueryVo);
for (ProcessVo item : page.getRecords()) {
item.setTaskId("0");
}
return page;
}