此案例是demo。功能有创建流程、完成审批、生成流程图。适合有java基础的人员看。
resources资源包下,新建processes包,新建一个文件,我命名他apply-rest.bpmmn20.xml。bpmn20.xml后缀文件是流程图配置文件。idea的右下角的流程图画板打开。
安装插件Flowable BPMN visualizer
,菜单Settings->Plugins->Marketplace里搜索下载插件。
简单画个流程图:
我的流程图配置文件展示下:
approved}]]> !approved}]]>
在这里插入代码片
4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.8.RELEASE com.cherry demo 0.0.1-SNAPSHOT flowable_demo Demo project for Spring Boot 1.8 com.alibaba fastjson 2.0.10 io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.flowable flowable-spring-boot-starter 6.5.0 org.flowable flowable-json-converter 6.5.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java 8.0.22 runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
工作流的拿出来格外展示下
org.flowable flowable-spring-boot-starter 6.5.0 org.flowable flowable-json-converter 6.5.0
server:#设置端口port: 9999spring:datasource:#驱动mysql8以上driver-class-name: com.mysql.cj.jdbc.Driver#连接flowable自己创建的数据库url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&charcterEncoding=UTF-8&useSSL=false#mysql数据库用户名username: root#mysql数据库密码password: 12345678flowable:#关闭定时任务JOBasync-executor-activate: false#将databaseSchemaUpdate设置为true。当flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。database-schema-update: true
启动类展示下:
package com.cherry.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@EnableSwagger2
@SpringBootApplication
public class FlowableDemoApplication {public static void main(String[] args) {SpringApplication.run(FlowableDemoApplication.class, args);}}
swagger的配置类展示一下:
这里设置了一个token,controller测试时随便填写
package com.cherry.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;
import java.util.List;@Configuration
@EnableSwagger2
public class SwaggerConfig {@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.cherry.demo.controller")).paths(PathSelectors.any()).build().globalOperationParameters(setHeaderToken());}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("action-swagger").description("swagger实战").termsOfServiceUrl("").version("1.0").build();}/*** @Description: 设置swagger文档中全局参数* @param* @return: java.util.List*/private List setHeaderToken() {List pars = new ArrayList<>();ParameterBuilder userId = new ParameterBuilder();userId.name("token").description("用户TOKEN").modelRef(new ModelRef("string")).parameterType("header").required(true).build();pars.add(userId.build());return pars;}
}
三个测试类,封装历史任务HistanceTaskEntity
、封装流程图状态StateEnum
、封装任务常用信息TaskInstanceEntity
历史任务类展示下:
package com.cherry.demo.entity;import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 历史任务实例*/
@Data
public class HistanceTaskEntity implements Serializable {private static final long serialVersionUID = 5805783585536248176L;// 流程实例idprivate String processInstanceId;// 任务idprivate String taskId;// 开始时间private Date startTime;// 结束时间private Date endTime;
}
流程图状态展示下:
package com.cherry.demo.entity;/*** 流程状态枚举类*/
public enum StateEnum {LEADER_NOLOOK("0","组长未审批"),LEADER_LOOK("1","组长审批");private String state;private String content;StateEnum(String state,String content){this.state = state;this.content = content;}StateEnum() {}public String getState() {return state;}public void setState(String state) {this.state = state;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}
任务常用信息实体类:
package com.cherry.demo.entity;import lombok.Data;import java.io.Serializable;/*** 封装任务常用信息*/
@Data
public class TaskInstanceEntity implements Serializable {private static final long serialVersionUID = 4321159119155954142L;// 任务idprivate String taskId;// 流程实例idprivate String processInstanceId;
}
package com.cherry.demo.config;import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;/*** desc: flowable配置----为放置生成的流程图中中文乱码*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer {@Overridepublic void configure(SpringProcessEngineConfiguration engineConfiguration) {engineConfiguration.setActivityFontName("微软雅黑");engineConfiguration.setLabelFontName("微软雅黑");engineConfiguration.setAnnotationFontName("微软雅黑");}
}
package com.cherry.demo.controller;import com.cherry.demo.entity.HistanceTaskEntity;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cmd.DeleteProcessInstanceCmd;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/**** 测试演示* token随便填*/
@Slf4j
@Api(value = "测试接口", tags = {"测试接口"})
@RestController
@RequestMapping("test")
public class DemoController {@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate HistoryService historyService;@Autowiredprivate ProcessEngine processEngine;@Resourceprivate ManagementService managementService;/*** 创建流程开启审批* @param processId bpmn20.xml的process的id。案例是"apply-rest"* @param param 流程的第一个userTask的flowable:assignee="${param}"绑定参数。* 此参数自定义,可以是指定处理的人id* @return 流程实例唯一id*/@PostMapping(value = "/start")public String startProcess(@RequestParam(required = false,defaultValue = "apply-rest") String processId,String param) {HashMap map = new HashMap(1){{ put("param", param);}};ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processId, map);log.info("创建请假流程 processId:{}", processInstance.getId());return processInstance.getId();}/*** 完成审批第二步任务 组长审批* @param processInstanceId 流程实例唯一id,开启流程是生成* @param approved 是否通过,true是通过,false是拒绝,bpmn20.xml会根据值决定流哪个流程* @return 任务信息包括任务id和任务名称*/@GetMapping("/changeStatus")public String changeStatus(String processInstanceId,Boolean approved){// 根据流程实例id获得准备进行的任务Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).active().singleResult();// 任务不存在if (task == null){ return "流程不存在"; }// 任务存入approvedMap v = new HashMap<>(1);v.put("approved",approved);// 根据任务id和参数,选择分支完成任务taskService.complete(task.getId(),v);return task.toString();}/*** 完成最后一步到结束节点* @param processInstanceId 流程实例* @return 任务信息*/@GetMapping("/finish")public String changeStatus1(String processInstanceId){// 根据流程id获得准备进行的任务Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).active().singleResult();if (task == null){ return "流程不存在"; }// 根据任务id,参数存在HashMap里taskService.complete(task.getId(),new HashMap<>());return task.toString();}/*** 生成流程图* @param httpServletResponse* @param processInstanceId* @throws Exception*/@GetMapping(value = "/image")public void genProcessDiagram(HttpServletResponse httpServletResponse, String processInstanceId) throws Exception {// 产生流程实例ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();// 产生历史流程实例HistoricProcessInstance his = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();// 实例idString instanceId;// 如果实例不是正在进行的就是历史的if (pi == null) { instanceId = his.getProcessDefinitionId(); } else { instanceId = pi.getProcessDefinitionId(); }// 完全不存在的实例无法生成流程图if (StringUtils.isBlank(instanceId)){ return;}//流程走完的不显示图/*if (pi == null) {return;}*/// 历史活动实例List historyProcess = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();// 活动idList activityIds = new ArrayList<>();// 流程idList flows = new ArrayList<>();// 获取流程图BpmnModel bpmnModel = repositoryService.getBpmnModel(instanceId);for (HistoricActivityInstance hi : historyProcess) {// 获得xml中标签String activityType = hi.getActivityType();// 流程线或者互斥网关存入流程id中,用户任务、开始任务、结束事件存入活动id中if (StringUtils.equalsAny(activityType,"sequenceFlow", "exclusiveGateway")) {flows.add(hi.getActivityId());} else if (StringUtils.equalsAny(activityType,"userTask", "startEvent", "endEvent")) {activityIds.add(hi.getActivityId());}}// 获得所有任务,根据流程实例idList tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();// 补全活动id,添加任务id到活动id集合中for (Task task : tasks) {activityIds.add(task.getTaskDefinitionKey());}// 设置响应的类型格式为图片格式httpServletResponse.setContentType("image/png");// 禁止图像缓存httpServletResponse.setHeader("Pragma", "no-cache");// 信息不被存储httpServletResponse.setHeader("Cache-Control", "no-cache");// 防止缓存代理服务httpServletResponse.setDateHeader("Expires", 0);// 流程引擎配置ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();// 流程图生成器ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();// 生成流程图输入流try (InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0,false)) {OutputStream out = null;// 1kb缓存大小byte[] buf = new byte[1024];int length = 0;try {out = httpServletResponse.getOutputStream();while ((length = in.read(buf)) != -1) {out.write(buf, 0, length);}} finally {if (in != null) {in.close();}if (out != null) {out.close();}}}}/*** 生成流程图** @param processInstanceId 任务ID*/@GetMapping(value = "/imageBase")public String genProcessDiagramBase(HttpServletResponse httpServletResponse, String processInstanceId) throws Exception {ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();HistoricProcessInstance his = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();String pdi;if (pi == null) {pdi = his.getProcessDefinitionId();} else {pdi = pi.getProcessDefinitionId();}if (StringUtils.isBlank(pdi)) {return null;}//获得活动的节点List historyProcess = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();List activityIds = new ArrayList<>();List flows = new ArrayList<>();//获取流程图BpmnModel bpmnModel = repositoryService.getBpmnModel(pdi);for (HistoricActivityInstance hi : historyProcess) {String activityType = hi.getActivityType();if (StringUtils.equalsAny(activityType,"sequenceFlow", "exclusiveGateway")) {flows.add(hi.getActivityId());} else if (StringUtils.equalsAny(activityType,"userTask", "startEvent", "endEvent")) {activityIds.add(hi.getActivityId());}}List tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();for (Task task : tasks) {activityIds.add(task.getTaskDefinitionKey());}ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();//定义流程画布生成器ProcessDiagramGenerator processDiagramGenerator = engConf.getProcessDiagramGenerator();InputStream in = processDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engConf.getActivityFontName(), engConf.getLabelFontName(), engConf.getAnnotationFontName(), engConf.getClassLoader(), 1.0, true);// 设置响应的类型格式为图片格式httpServletResponse.setContentType("image/png");// 禁止图像缓存httpServletResponse.setHeader("Pragma", "no-cache");httpServletResponse.setHeader("Cache-Control", "no-cache");httpServletResponse.setDateHeader("Expires", 0);String base64Img = null;try {// in.available()返回文件的字节长度byte[] buf = new byte[in.available()];// 将文件中的内容读入到数组中in.read(buf);// 进行Base64编码处理base64Img = new String(Base64Utils.encode(buf), StandardCharsets.UTF_8);} catch (IOException e) {e.printStackTrace();} finally {if (in != null) {in.close();}}return base64Img;}/*** 查看创建流程实例时登记的变量信息* @param processInstanceId 工程实例id* @return 变量名和值的键值对*/@GetMapping(value = "/queryProcessVariables")public Map queryProcessVariables(String processInstanceId){// 查询历史变量实例List historicVariableInstances = historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();// 没有历史变量实例返回空或抛异常if (historicVariableInstances == null) {return new HashMap<>();}// 存变量和值Map ret = Maps.newHashMap();for (HistoricVariableInstance var : historicVariableInstances) {ret.put(var.getVariableName(), var.getValue());}return ret;}/*** 获取某人的历史审批数据* @param processInstanceId 流程实例id* @param assignee 代理信息* @return*/@GetMapping(value = "/queryHistoryProcessWithAssignee")public List queryHistoryProcess(String processInstanceId, String assignee){// 查询历史活动实例,根据流程id和代理参数List activities = historyService.createHistoricActivityInstanceQuery().processDefinitionId(processInstanceId).taskAssignee(assignee).finished().orderByHistoricActivityInstanceEndTime().desc().list();// 搜集历史任务实例List result = new ArrayList<>();for (HistoricActivityInstance h : activities) {HistanceTaskEntity d = new HistanceTaskEntity();d.setProcessInstanceId(h.getProcessInstanceId());d.setTaskId(h.getTaskId());d.setStartTime(h.getStartTime());d.setEndTime(h.getEndTime());result.add(d);}return result;}/*** 查询是否存在历史数据的流程实例* @param processInstanceId 流程实例id* @return*/@GetMapping(value = "/queryHistoryProcess")public Boolean isExistHistoricProcessInstance(String processInstanceId){// 获得历史流程实例HistoricProcessInstance historicProcessInstance =historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();// 判断是否历史流程实例if (historicProcessInstance == null) {return false;}return true;}/*** 将指定的流程挂起* @param processInstanceId 流程实例id*/@PutMapping("/suspendProcessInstance")public void suspendProcessInstance(String processInstanceId){runtimeService.suspendProcessInstanceById(processInstanceId);}/*** 将指定的流程激活* @param processInstanceId 流程实例id*/@PutMapping("/activateProcessInstance")public void activateProcessInstance(String processInstanceId){runtimeService.activateProcessInstanceById(processInstanceId);}/*** 删除流程实例* @param processInstanceId 流程实例id* @param deleteReason 删除原因*/@PostMapping("/deleteProcessInstance")public void deleteProcessInstance(String processInstanceId, String deleteReason){// 流程的数量long count = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).count();// 判断是否需要删除流程if (count > 0) {// 需要删除时,增加删除原因DeleteProcessInstanceCmd cmd = new DeleteProcessInstanceCmd(processInstanceId, deleteReason);// 执行命令managementService.executeCommand(cmd);} else {// 删除历史数据流程实体historyService.deleteHistoricProcessInstance(processInstanceId);}}/*** 查询流程实例是否完成* @param processInstanceId 流程实例id* @return 判断结果*/@GetMapping("/isProcessFinished")public Boolean isProcessFinished(String processInstanceId){// 判断历史中流程的个数是否>0return historyService.createHistoricProcessInstanceQuery().finished().processInstanceId(processInstanceId).count() > 0;}/*** 删除给定的部署并级联删除到流程实例、历史流程实例和作业* @param deployId 部署的id* @return*/@GetMapping("/deleteDeploy")public Boolean deleteDeploy(String deployId){this.repositoryService.deleteDeployment(deployId,true);return true;}/*** 获得即将进行的任务* @param processInstanceId 流程实例id* @return 任务*/@GetMapping("/activeTask")public String getActiveTaskByProcessInstanceId(String processInstanceId){if(StringUtils.isBlank(processInstanceId)){return null;}Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).active().singleResult();if (task!=null){return task.toString();}return "任务不存在";}}