SpringBoot+flowable快速实现工作流-动态选择审批领导

前言

OA系统中的工作流不仅是企业日常运营的重要组成部分,也是实现企业数字化转型、提高工作效率和执行力的重要工具。 在国内大部分的工作流系统使用Activiti框架实现。 其实flowable也可以轻松实现工作流业务。在线体验JeecgFlow

flowable简介

Flowable是一个使用Java编写的轻量级业务流程引擎。它主要用于部署BPMN 2.0流程定义(一种用于定义流程的行业XML标准),创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等。Flowable流程引擎的特点包括灵活性和可扩展性,提供了图形化的流程设计器,可以与规则引擎集成,支持多种执行环境,并且可以轻易地加入任何Java环境。

版本信息

Flowable 6.7.2

SpringBoot 2.7.12

SpringBoot整合flowable

maven依赖

xml 复制代码
<flowable.spring-boot.version>6.7.2</flowable.spring-boot.version

<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter</artifactId>
    <version>${flowable.spring-boot.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

配置

yml 复制代码
flowable:
  #关闭定时任务JOB
  async-executor-activate: false
  #将databaseSchemaUpdate设置为true。当flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
  database-schema-update: true
  # 自动部署验证设置:true-开启(默认)、false-关闭
  check-process-definitions: false
  #保存历史数据级别设置为full最高级别,便于历史数据的追溯
  history-level: full

简单两步,就可以启动boot应用。初次运行时flowable会将自动执行flowable中的初始化脚本完成工作流所需要的数据表的建立,如果指定的数据库中还未创建过flowable的相关数据表的话。

流程定义

假设现在有一个请假需求,用户提交申请表单后, 直属领导进行审批操作。 可以是通过或拒绝。并且要求不同部门的人员绑定所属的领导。

申请节点,定义用户提交数据的字段

申请节点, 设置了任务完成后的监听器。用于动态设置审批领导的名称。

直属领导申请的节点属性设置。

xml内容

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
  <bpmn:process id="Process_choose_leader" name="动态审批" isExecutable="true">
    <bpmn:startEvent id="StartEvent_1">
      <bpmn:outgoing>Flow_09w75kg</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:sequenceFlow id="Flow_09w75kg" sourceRef="StartEvent_1" targetRef="Activity_1xn21s1" />
    <bpmn:userTask id="Activity_1xn21s1" name="申请" camunda:assignee="${general}">
      <bpmn:extensionElements>
        <camunda:formData>
          <camunda:formField id="days" label="请假天数" type="long" defaultValue="1" />
          <camunda:formField id="reason" label="请假理由" type="string" />
          <camunda:formField id="type" label="请假类型" type="string" />
        </camunda:formData>
        <camunda:taskListener delegateExpression="${chooseLeaderListener}" event="complete" />
      </bpmn:extensionElements>
      <bpmn:incoming>Flow_09w75kg</bpmn:incoming>
      <bpmn:outgoing>Flow_1n7z9g2</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:sequenceFlow id="Flow_1n7z9g2" sourceRef="Activity_1xn21s1" targetRef="Activity_0474tsq" />
    <bpmn:userTask id="Activity_0474tsq" name="直属领导" camunda:assignee="${leader}">
      <bpmn:extensionElements>
        <camunda:formData>
          <camunda:formField id="comment" label="评论" type="string" />
          <camunda:formField id="imageUrl" label="图片" type="string" />
        </camunda:formData>
      </bpmn:extensionElements>
      <bpmn:incoming>Flow_1n7z9g2</bpmn:incoming>
      <bpmn:outgoing>Flow_1lyuw6k</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:endEvent id="Event_10twye6">
      <bpmn:incoming>Flow_1lyuw6k</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="Flow_1lyuw6k" sourceRef="Activity_0474tsq" targetRef="Event_10twye6" />
  </bpmn:process>

</bpmn:definitions>

代码编写

开始流程

java 复制代码
@ApiOperation(value = "发起请假", notes = "提交请假的数据")
@PostMapping("start")
public Result<?> start(@RequestBody LeaveStartReq leaveStartReq) {
    log.info("start.leaveStartReq:{}", leaveStartReq);
    ValidatorUtils.validateEntity(leaveStartReq);
    //查询登陆用户
    String id = SecurityContextHolder.getContext().getAuthentication().getName();
    User loginUser = userService.getById(id);
    //发起流程并初始化各个用户任务节点的指定人
    Authentication.setAuthenticatedUserId(Optional.ofNullable(loginUser).map(m -> m.getUsername()).orElse("xxx"));
    Map<String, Object> variables = new HashMap<>(4);
    variables.put("general", Optional.ofNullable(loginUser).map(m -> m.getUsername()).orElse("mark"));
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(FlowConstant.MODEL_KEY_LEAVE, variables);
    //执行一个申请节点的动作
    userTaskService.completeApplyUserTask(processInstance.getId(), loginUser, leaveStartReq);
    return Results.newSuccessResult(processInstance.getId());
}

以上逻辑就是发起流程, 核心步骤就是获取登陆用户信息,并且设置流程启动参数。 在流程启动后完成第一个用户任务节点。此时流程便可以往下面流转。

完成申请任务的监听事件

java 复制代码
@Component("chooseLeaderListener")
@Slf4j
public class ChooseLeaderListener implements TaskListener {

    @Autowired
    private ISysBaseAPI sysBaseAPI;

    @Override
    public void notify(DelegateTask delegateTask) {
        log.info("动态选择下一个用户任务节点审批人员监听器");
        //查询发起用户所属的部门Id集合
        String startUser = String.valueOf(delegateTask.getVariable("general"));
        List<String> myDeptIds = sysBaseAPI.getDepartIdsByUsername(startUser);
        //根据部门id查询负责人
        List<String> leaderList = new ArrayList<>();
        for (String myDeptId : myDeptIds) {
            leaderList.addAll(sysBaseAPI.getDeptHeadByDepId(myDeptId));
        }
        log.info("直属部门领导人员:{}", leaderList);
        //设置下一个审批节点的用户名称,注意如何是有多个领导需要同时审批,要把审批节点设置为多实例。
        if(CollectionUtil.isNotEmpty(leaderList)){
            delegateTask.setVariable("leader", leaderList.get(0));
        }
    }
}

在上一步申请节点完成后,将执行任务监听起逻辑,查询到下一个节点的审批用户名称。并且绑定到leader流程变量。此时便可以实现不同部门有不同领导。

审批通过

java 复制代码
public void pass(PassReq passReq, User loginUser) {
    Task task = taskService.createTaskQuery().processInstanceId(passReq.getProcessInstanceId()).taskAssignee(loginUser.getUsername()).singleResult();
  
    //完成用户任务
    Map<String, Object> map = new HashMap<>(12);
    map.put("comment", passReq.getContent());
    map.put("imageUrl", imageUrl);
    taskService.complete(task.getId(), map);
}

审批通过操作的核心逻辑。 驳回就留给大家自己研究了。

运行效果的图

通过Springboot+flowable就是这么简单, 在线体验JeecgFlow

相关推荐
无相求码1 分钟前
为什么你的第二个 scanf 还没输入就直接跳过了?C语言输入缓冲区陷阱
c语言·后端
叼馒女友郭芙蓉1 分钟前
Function Calling理解
后端
玉宇夕落3 分钟前
LangChain RAG Retriever 的学习
后端
用户298698530148 分钟前
Python 一键导出 Excel 中的图表和形状为图片
后端·python·excel
用户81300778163710 分钟前
PageHelper分页查询步骤
后端
是小李呀12 分钟前
MyBatis、MyBatis-Plus 与全自动 ORM 的本质区别
后端
小堂子这厢有礼了25 分钟前
Chet.Admin 权限模型:菜单级 + 按钮级 + 行级三层防护
前端·后端·信息可视化·前端框架·.net
SimonKing26 分钟前
Spring Boot 4.0 + gRPC 极简集成:4步搞定,比REST还丝滑
java·后端·程序员
IT_陈寒1 小时前
Vue的这个响应式陷阱,让我加班到凌晨三点
前端·人工智能·后端
Wang's Blog1 小时前
Go-Zero基础入门5: 探究go-zero如何基于gRPC扩展客户端
开发语言·后端·golang·go-zero