提示:以下是本篇文章正文内容,下面案例可供参考
Flowable下载
Flowable源码GitHub下载
Flowable用户使用手册
Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。
具体介绍请看Flowable介绍
参考知乎Flowable工作流引擎
对于业务建模,我们需要一种通用的语言来描绘,这样在沟通上和实现上会降低难度,就像中文、英文一样,BPMN2.0便是一种国际通用的建模语言,他能让自然人轻松阅读,更能被计算机所解析。
协议中元素的主要分类为,事件-任务-连线-网关
一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。
BPMN2.0协议
不同的图标代表不同的含义
互斥网关(Exclusive Gateway) 又称排他网关,他有且仅有一个有效出口,可以理解为if…else if… else if…else,就和我们平时写代码的一样。
并行网关(Parallel Gateway) 他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。
包容性网关(Inclusive Gateway)
只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…)
do,所有的条件判断都是同级别的。
人工任务(User Task)
它是使用得做多的一种任务类型,他自带有一些人工任务的变量,例如签收人(Assignee),签收人就代表该任务交由谁处理,我们也可以通过某个特定或一系列特定的签收人来查找待办任务。利用上面的行为解释便是,当到达User
Task节点的时候,节点设置Assignee变量或等待设置Assignee变量,当任务被完成的时候,我们使用Trigger来要求流程引擎退出该任务,继续流转。
服务任务(Service Task)
该任务会在到达的时候执行一段自动的逻辑并自动流转。从“到达自动执行一段逻辑”这里我们就可以发现,服务任务的想象空间就可以非常大,我们可以执行一段计算,执行发送邮件,执行RPC调用,而使用最广泛的则为HTTP调用,因为HTTP是使用最广泛的协议之一,它可以解决大部分第三方调用问题,在我们的使用中,HTTP服务任务也被我们单独剥离出来作为一个特殊任务节点。
接受任务(Receive Task) 该任务的名字让人费解,但它又是最简单的一种任务,当该任务到达的时候,它不做任何逻辑,而是被动地等待Trigger,它的适用场景往往是一些不明确的阻塞,比如:一个复杂的计算需要等待很多条件,这些条件是需要人为来判断是否可以执行,而不是直接执行,这个时候,工作人员如果判断可以继续了,那么就Trigger一下使其流转。
调用活动(Call Activity)
调用活动可以理解为函数调用,它会引用另外一个流程使之作为子流程运行,调用活动跟函数调用的功能一样,使流程模块化,增加复用的可能性。
Flowable在项目启动的时候会自动创建表,以下是主要几张表介绍
File-->settings
搜索:flowable
安装完成,重启idea
/** * 引入数据库依赖,Flowable在项目启动的时候会自动创建表 */ org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java runtime /** * lombok */ org.projectlombok lombok 1.16.16 compile /** * 添加flowable工作流依赖 */ org.flowable flowable-spring-boot-starter 6.7.2 /** * 添加flowable工作流前端页面依赖,如果项目没有集成页面,无需添加此依赖 */ org.flowable flowable-ui-modeler-rest 6.7.2 /** * 添加flowable-ui-modeler配置依赖项 */ org.flowable flowable-ui-modeler-conf 6.7.2
server: port: 8080 #tomcat端口 servlet: context-path: / spring: #数据库链接配置 datasource: url: jdbc:mysql://127.0.0.1:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root flowable: #异步执行 async-executor-activate: true #自动更新数据库 database-schema-update: true #校验流程文件,默认校验resources下的processes文件夹里的流程文件 process-definition-location-prefix: classpath*:/processes/ process-definition-location-suffixes: "**.bpmn20.xml, **.bpmn" #该配置只是防止页面报错,没有实际意义 common: app: idm-admin: password: test user: test #没有实际意义 idm-url: http://localhost:8080/flowable-demo
员工提交申请 ${outcome=='驳回'} ${outcome=='通过'} ${outcome=='驳回'} ${outcome=='通过'}
import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; import org.springframework.context.annotation.Configuration; @Configuration public class FlowableConfig implements EngineConfigurationConfigurer { /** * 防止生成的流程图中文乱码 * @param springProcessEngineConfiguration */ @Override public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) { springProcessEngineConfiguration.setActivityFontName("宋体"); springProcessEngineConfiguration.setLabelFontName("宋体"); springProcessEngineConfiguration.setAnnotationFontName("宋体"); } }
如果醒目没有集成页面,【SecurityConfiguration.java】无需配置
参考简书springboot集成flowable
import org.flowable.ui.common.security.SecurityConstants; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * 绕过flowable的登录验证 */ @Configuration public class SecurityConfiguration { @Configuration(proxyBeanMethods = false) //Order配置说明 // 这个地方相同会报错 //这个地方如果大于则该配置在FlowableUiSecurityAutoConfiguratio中对应项后加载,不能起到绕过授权作用 //所以这个地方-1让该配置项在FlowableUiSecurityAutoConfiguratio中对应配置项前加载,以跳过授权 @Order(SecurityConstants.FORM_LOGIN_SECURITY_ORDER - 1) public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable(); http //必须要将csrf设置为disable,不然后面发送POST请求时会报403错误 .csrf().disable() //为了简单起见,简单粗暴方式直接放行modeler下面所有请求 .authorizeRequests().antMatchers("/modeler/**").permitAll(); } } }
import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.*; import org.flowable.engine.runtime.Execution; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.image.ProcessDiagramGenerator; import org.flowable.task.api.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.io.OutputStream; import java.util.*; @Slf4j @RestController @RequestMapping("askForLeave") public class AskForLeaveFlowableController { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private RepositoryService repositoryService; @Autowired private ProcessEngine processEngine; /** * 员工提交请假申请 * * @param employeeNo 员工工号 * @param name 姓名 * @param reason 原因 * @param days 天数 * @return */ @GetMapping("employeeSubmit") public String employeeSubmitAskForLeave( @RequestParam(value = "employeeNo") String employeeNo, @RequestParam(value = "name") String name, @RequestParam(value = "reason") String reason, @RequestParam(value = "days") Integer days) { HashMap map = new HashMap<>(); /** * 员工编号字段来自于配置文件 */ map.put("employeeNo", employeeNo); map.put("name", name); map.put("reason", reason); map.put("days", days); /** * key:配置文件中的下个处理流程id * value:默认领导工号为002 */ map.put("leaderNo", "002"); /** * askForLeave:为开启流程的id 与配置文件中的一致 */ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("askForLeave", map); log.info("{},提交请假申请,流程id:{}", name, processInstance.getId()); return "提交成功,流程id:"+processInstance.getId(); } /** * 领导审核通过 * @param employeeNo 员工工号 * @return */ @GetMapping("leaderExaminePass") public String leaderExamine(@RequestParam(value = "employeeNo") String employeeNo) { List taskList = taskService.createTaskQuery().taskAssignee(employeeNo).orderByTaskId().desc().list(); if (null == taskList) { throw new RuntimeException("当前员工没有任何申请"); } for (Task task : taskList) { if (task == null) { log.info("任务不存在 ID:{};", task.getId()); continue; } log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended()); Map map = new HashMap<>(); /** * key:配置文件中的下个处理流程id * value:默认老板工号为001 */ map.put("bossNo", "001"); /** * key:指定配置文件中的条件判断id * value:指定配置文件中的审核条件 */ map.put("outcome", "通过"); taskService.complete(task.getId(), map); } return "领导审核通过"; } /** * 老板审核通过 * @param leaderNo 领导工号 * @return */ @GetMapping("bossExaminePass") public String bossExamine(@RequestParam(value = "leaderNo") String leaderNo) { List taskList = taskService.createTaskQuery().taskAssignee(leaderNo).orderByTaskId().desc().list(); if (null == taskList) { throw new RuntimeException("当前员工没有任何申请"); } for (Task task : taskList) { if (task == null) { log.info("任务不存在 ID:{};", task.getId()); continue; } log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended()); Map map = new HashMap<>(); /** * 老板是最后的审批人 无需指定下个流程 */ // map.put("boss", "001"); /** * key:指定配置文件中的条件判断id * value:指定配置文件中的审核条件 */ map.put("outcome", "通过"); taskService.complete(task.getId(), map); } return "领导审核通过"; } /** * 驳回 * * @param employeeNo 员工工号 * @return */ @GetMapping("reject") public String reject(@RequestParam(value = "employeeNo") String employeeNo) { List taskList = taskService.createTaskQuery().taskAssignee(employeeNo).orderByTaskId().desc().list(); if (null == taskList) { throw new RuntimeException("当前员工没有任何申请"); } for (Task task : taskList) { if (task == null) { log.info("任务不存在 ID:{};", task.getId()); continue; } log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended()); Map map = new HashMap<>(); /** * key:指定配置文件中的领导id * value:指定配置文件中的审核条件 */ map.put("outcome", "驳回"); taskService.complete(task.getId(), map); } return "申请被驳回"; } /** * 生成流程图 * * @param processId 任务ID */ @GetMapping(value = "processDiagram") public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception { ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult(); //流程走完的不显示图 if (pi == null) { return; } Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult(); //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象 String InstanceId = task.getProcessInstanceId(); List executions = runtimeService .createExecutionQuery() .processInstanceId(InstanceId) .list(); //得到正在执行的Activity的Id List activityIds = new ArrayList<>(); List flows = new ArrayList<>(); for (Execution exe : executions) { List ids = runtimeService.getActiveActivityIds(exe.getId()); activityIds.addAll(ids); } //获取流程图 BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId()); ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration(); ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator(); InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, true); OutputStream out = null; byte[] buf = new byte[1024]; int legth = 0; try { out = httpServletResponse.getOutputStream(); while ((legth = in.read(buf)) != -1) { out.write(buf, 0, legth); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } }
员工提交请假申请,如下图:
http://localhost:8080/askForLeave/employeeSubmit?name=Jerry&reason=去旅游&days=30&employeeNo=006
查看员工提交申请的流程图
http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
领导进行审批
http://localhost:8080/askForLeave/leaderExaminePass?employeeNo=006
查看领导审批后的流程图
http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
老板审批
http://localhost:8080/askForLeave/bossExaminePass?leaderNo=002
查看老板审批后的流程图
http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
参考简书毛于晏
下载源码包,此处使用的是6.7.2版本的
源码下载地址: https://github.com/flowable/flowable-engine/tree/flowable-6.7.2
或
链接:https://pan.baidu.com/s/1w-iuynskMuREBlYcpbDFAg
提取码:9gs7
将以下路径的代码复制到自己工程
/flowable-6.7.2/modules/flowable-ui/flowable-ui-modeler-frontend/src/main/resources/static/modeler
跳过验证
import org.flowable.ui.common.security.SecurityConstants; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * 绕过flowable的登录验证 */ @Configuration public class SecurityConfiguration { @Configuration(proxyBeanMethods = false) //Order配置说明 // 这个地方相同会报错 //这个地方如果大于则该配置在FlowableUiSecurityAutoConfiguratio中对应项后加载,不能起到绕过授权作用 //所以这个地方-1让该配置项在FlowableUiSecurityAutoConfiguratio中对应配置项前加载,以跳过授权 @Order(SecurityConstants.FORM_LOGIN_SECURITY_ORDER - 1) public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable(); http //必须要将csrf设置为disable,不然后面发送POST请求时会报403错误 .csrf().disable() //为了简单起见,简单粗暴方式直接放行modeler下面所有请求 .authorizeRequests().antMatchers("/modeler/**").permitAll(); } } }
确保已在idea中安装Flowable插件
在resources下创建processes文件夹,右键,创建一个【*.bpmn20.xml】文件
选中创建好的【*.bpmn20.xml】文件,右键;
开始画流程图 任意位置右键–>启动一个流程
设置员工提交申请
设置领导
设置网关,用来判断领导是否同意
网关条件判断条件–驳回
网关条件判断条件–通过
设置老板
设置网关,用来判断老板是否同意
网关条件判断条件–驳回
网关条件判断条件–通过
当我们画好图后,【*.bpmn20.xml】流程文件也已经创建成功了
可以书写针对流程的逻辑了