likes
comments
collection
share

Camunda实战教程之员工请假流程

作者站长头像
站长
· 阅读数 13

本文中使用的Camunda版本为7.17。

本文内容以员工请假流程为例子对Camunda的整合使用进行说明,涉及基础组件,但并不完全,不然篇幅就过大了。

目的是为了让对Camunda工作流感兴趣却又无从下手的同志,花1小时的时间了解其工作方式,快速判断是否适合自己。

环境准备

官网:camunda.com/

中文站点:camunda-cn.shaochenfeng.com/

流程绘制器下载:camunda.com/download/mo…

流程管理平台下载:camunda.com/download/pl…

重点说明一下流程绘制器流程管理平台

流程绘制器

是一个可视化的流程图绘制工具,Camunda提供了支持多平台的软件下载链接,按需下载,启动后如下所示:

Camunda实战教程之员工请假流程

实战中我们选择的是Camunda Platform 7的BPMN diagram,这表示Camunda版本为7的BPMN绘图。至于Camunda8他有些组件是商业授权,所以先不考虑这个了。

在流程绘制器里面作图,仍然需要一些学习成本,因为绘制器包含的组件太多,所以本文仅对场景设计所涉及的组件进行说明,有需要可以自行查看这方面的教程。

流程管理平台

我们在流程绘制器作好图之后,就可以把他部署到Camunda引擎中,这一步叫:部署流程,部署流程就是将流程推送到流程管理平台上面,这种推送有很多种方式,最简单的我们可以通过流程绘制器下面的按钮部署:

Camunda实战教程之员工请假流程

这里要提醒一下,Camunda多数是英文界面,有需要可以打中文补丁。

可以看到上图部署的路径是:http://localhost:8888/engine-rest,这就是流程管理平台,流程管理平台的启动有三种方式:

方式一:下载到本地后执行启动脚本

在上面的流程管理平台下载链接下载后,下载解压后,执行.bat或.sh启动,

访问:http://localhost:8080/

默认账密是:demo/demo

方式二:Docker

# 拉取镜像
docker pull camunda/camunda-bpm-platform:7.17.0

# 启动容器
docker run -d --name camunda -p 8080:8080 ccamunda/camunda-bpm-platform:7.17.0

方式三:Spring Boot

pom.xml:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.3.4.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.camunda.bpm</groupId>
            <artifactId>camunda-bom</artifactId>
            <version>7.17.0</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!--springboot启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.camunda.bpm.springboot</groupId>
        <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
    </dependency>

    <dependency>
        <groupId>org.camunda.bpm.springboot</groupId>
        <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
    </dependency>

    <dependency>
        <groupId>org.camunda.bpm</groupId>
        <artifactId>camunda-engine-plugin-spin</artifactId>
    </dependency>

    <dependency>
        <groupId>org.camunda.spin</groupId>
        <artifactId>camunda-spin-dataformat-all</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- Spring-data-jpa依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!--MySQL数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

<!--使用阿里云的Maven源-->
<repositories>
    <repository>
        <id>aliyunmaven</id>
        <name>aliyun</name>
        <url>https://maven.aliyun.com/repository/public</url>
    </repository>
</repositories>

application.yml:

spring.datasource.url: jdbc:h2:file:./camunda-h2-database

camunda.bpm.admin-user:
  id: demo
  password: demo

server:
  port: 8888

改用MySQL:

spring:
  datasource:
    url: jdbc:mysql://mylocalhost:3306/camunda?createDatabaseIfNotExist=true&character_set_server=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root

一般不会使用h2数据库,所以直接配置MySQL了,记得提前创建好数据库:camunda

启动项目后,会自动在数据库中建表,然后同样访问:http://localhost:8888 即可。

结论

不管用什么方式部署,只要流程管理平台起来了,就能访问链接,通过默认账密:demo/demo进入:

Camunda实战教程之员工请假流程

上图中Cockpit是流程信息,部署的流程、流程实例等都在这里查看;

Tasklist的任务列表,顾名思义与当前用户有关的流程任务在这里显示;

Admin是用户、组、租户等这些授权相关,具体后面再说。

本文采用方式三的方式部署流程管理平台,更灵活,也比较实际一些,因为项目开发时肯定要和系统业务作关联嘛。

实战场景:员工请假流程

**场景说明:**员工发起请假申请,天数<=1天的由组长审批,天数>1天并且<=3天的由经理审批,大于3天的由总经理审批,并且审批完成后抄送给人事做记录。

现在打开绘图工具,作图。

创建Camunda7的BPMN diagram图:

Camunda实战教程之员工请假流程

先给出完整的流程图做参考:

Camunda实战教程之员工请假流程

流程图信息

在空白处点击一下,然后右侧的框框中把流程图的名字填好:

Camunda实战教程之员工请假流程

  • Name:表示该流程显示的名称,可以用中文加强可读性。
  • ID:该流程的唯一标识,默认会生成一个随机字符串,我们改成leave_flow即请假流程,这个ID等会要用来部署流程的。

发起人

点击开始圆圈,在下图箭头处填写发起人名称,这里为:initiator,可以随意修改,这个变量会通过流程路径传递,注意这时候的initiator是一个变量名,不是值,我们在后面创建流程实例的时候会把值传进这个变量。

这个输入框是Initiator,表示发起人

Camunda实战教程之员工请假流程

员工发起请假申请

在下图框框中填写${initiator},这是EL表达式,表示获取该变量的值,结合上面发起人可以明白这里是通过表达式获取该变量的值。

这个框是Assignee,表示分配到任务的人,假如我们传进来的值是xiaoming,那么这个任务就是分配给xiaoming这个单一用户的。

Camunda实战教程之员工请假流程

任务类型说明

这里需要再说明一下任务这个组件,这算是一些小细节,需要注意,看下图:

Camunda实战教程之员工请假流程

两个红色框框,一个包含任务类型:

  • User Task:用户任务,最常见的任务类型,用于分配给特定用户或用户组去完成的人工任务。通常要求用户提供输入、执行某些操作或决策,并将结果反馈到流程引擎。
  • Service Task:服务任务,是一个自动执行的任务,通常与外部系统或服务进行集成。可以执行与业务逻辑相关的操作,例如调用REST API、发送电子邮件、生成报告等。可以通过编写适当的代码或调用外部服务来实现服务任务。

员工发起请假申请、领导审批这些都是用户任务,要人工操作;最后抄送人事做记录这个是服务任务,由系统自动执行。

一个是多实例类型:

  • 三条竖线:并行任务,当这个任务分配给三个人执行时,这三个人可以并行执行任务,互不影响
  • 三条横线:串行任务,当这个任务分配给三个人执行时,这三个人需要顺序执行任务,等上一个人执行完才能接着执行

分配给单一用户的任务不需要设置实例类型,但是当任务分配给多人的时候就需要设置。

请假申请表单

发起申请当然要填写请假信息啦,点击发起申请的任务,选择箭头指的这项,这样就会出现一个Form fields的表单配置:

Camunda实战教程之员工请假流程

然后填写表单内容,记得ID和Label不要搞混了,一个是变量名,一个是描述文本,然后是变量类型,一个是long,一个是string:

Camunda实战教程之员工请假流程

排他网关

我们创建的这两个表单变量会传递到后面的网关,由网关判断流程路径的走向,在这里我们使用排他网关,就是一个大大的X的菱形组件,排他网关的意思是只会选择一条路径。

我们点击网关连接任务的三条路径线,在右侧的条件中选择Type为Expression的选项,表示执行条件为表达式,然后使用EL表达式填写条件内容,顺便为了流程图好看一点,给路径线加上了一些描述文本,如下图所示:

Camunda实战教程之员工请假流程

领导审批

领导审批和员工发起请假申请相似,不同之处在于:

  1. 多实例:这样可以动态分配给多个领导审批,而不是固定某一个用户,为了动态,我们需要写一个类来处理
  2. 表单内容:是否批准和评论

多实例

对于三个领导审核的任务,我们首先需要处理多实例部分,如下图:

Camunda实战教程之员工请假流程

这里介绍一下每一栏的作用:

  • Loop cardinality:循环条件,为什么是循环?因为是多实例,给多个用户操作的,意思为循环分配任务
  • Completion condition:跳出条件,当需要循环跳出时,配置这里

上面两个我们没有用到,所以重点关注下面的:

  • Collection:循环集合,我们用EL表达式写成:${leaders},我们需要传入这个leaders用户数组对象,但是前面的流程没有这个leaders变量,所以我们要新建一个类来处理
  • Element variable:循环元素,每次循环出的对象叫什么变量名
  • Assignee:分配到任务的人,这个刚刚写过了,很好理解,用的是传进来的:${leader}

上面这样写,是因为我们希望把领导用户做成动态的,其实我们可以不写Collection,直接在Assignee处赋予用户名称,但这样固定并不是一个好的业务流程。

Camunda实战教程之员工请假流程

如图所示,我们在员工发起请假申请到网关的这条路径上,添加一个监听器,监听器的代码为:

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.ExecutionListener;

@Component("AddLeaderListener")
public class AddLeaderListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) throws Exception {
        long leaveDay = (long) execution.getVariable("leaveDays");
        if (leaveDay < 0) {
            throw new RuntimeException("请假天数异常");
        }

        System.out.println("进入增加领导集合类,员工请假天数:" + leaveDay);
        List<String> leaders = new ArrayList<>();
        if (leaveDay <= 1) {
            leaders.add("ZuZhang");
        } else if (leaveDay > 3 && leaveDay <= 5) {
            leaders.add("JingLi");
        } else if (leaveDay > 5) {
            leaders.add("JingLi");
            leaders.add("ZongJingLi");
        }

        // 将leaders数组设置到变量中
        execution.setVariable("leaders", leaders);
    }
}

监听器的内容为:${AddLeaderListener},表示去找出AddLeaderListener这个类,所以要在Bean注解上面配置正确。

通过监听器之后,Camunda引擎就能流程的知道下一步走向,给到哪些用户,我想通过结合代码的情况,更能体现出Camunda工作流引擎是如何与业务系统做关联的。

领导审批表单

在说明下一步用户模块之前,我们最好先把领导审批的表单做完,步骤同样,增加是否批准和评论:

Camunda实战教程之员工请假流程

记得三个任务都要加上,别漏了也别写错了。

虽然给三个任务加上重复的表单很蠢,但是绘制器支持引入外部表单,即写一份表单给多个任务使用,不过本文不扯这么复杂了,先这样顶上。

重点:三个领导审批任务都检查一下,别漏填了,不然后面无法部署流程

用户和组

我们虽然有超级管理员demo,但是还缺少几个用户:

  • cc:员工/发起人
  • ZuZhang:组长
  • JingLing:经理
  • ZongJingLi:总经理

添加用户有两种方式:

  1. 在流程管理平台的Admin中添加
  2. 通过Camunda引擎的API添加

第一种方式很简单,大家摸索一下就能创建出来,为了体现实战,我们当然是用API啦。

除了用户和组之外,还有一个租户的概念,租户不在本文范畴。

API的使用也不难,这里直接贴出接口代码接口请求示例

接口代码

import com.cc.model.GroupParam;
import com.cc.model.UserParam;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.identity.User;
import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity;
import org.camunda.bpm.engine.impl.persistence.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class CamundaController {
    @Autowired
    private IdentityService identityService;

    @Autowired
    private RuntimeService runtimeService;

    // 用户列表
    @PostMapping("/user/list")
    public List<User> userList() {
        return identityService.createUserQuery().list();
    }

    // 查询用户
    @PostMapping("/user/detail/{id}")
    public User userDetail(@PathVariable("id") String id) {
        return identityService.createUserQuery().userId(id).singleResult();
    }

    // 添加用户
    @PostMapping("/user/create")
    public String create(@RequestBody UserParam param) {
        final User exist = identityService.createUserQuery().userId(param.getId()).singleResult();
        if (exist != null) {
            return "该用户已存在:" + param.getId();
        }

        UserEntity entity = new UserEntity();
        entity.setId(param.getId());
        entity.setFirstName(param.getFirstName());
        entity.setLastName(param.getLastName());
        entity.setEmail(param.getEmail());
        entity.setPassword(param.getPassword());
        identityService.saveUser(entity);
        return param.getId();
    }

    // 修改用户信息
    @PostMapping("/user/update")
    public String update(@RequestBody UserParam param) {
        final User user = identityService.createUserQuery().userId(param.getId()).singleResult();
        if (user == null) {
            return "该用户不存在:" + param.getId();
        }
        user.setFirstName(param.getFirstName());
        user.setLastName(param.getLastName());
        user.setEmail(param.getEmail());
        identityService.saveUser(user);
        return user.getId();
    }

    // 修改用户密码
    @PostMapping("/user/updatePassword")
    public String updatePassword(@RequestBody UserParam param) {
        final User user = identityService.createUserQuery().userId(param.getId()).singleResult();
        if (user == null) {
            return "该用户不存在:" + param.getId();
        }
        user.setPassword(param.getPassword());
        identityService.saveUser(user);
        return user.getId();
    }

    // 删除用户
    @PostMapping("/user/delete/{id}")
    public String delete(@PathVariable("id") String id) {
        identityService.deleteUser(id);
        return id;
    }

    // 组列表
    @PostMapping("/group/list")
    public List<Group> groupList() {
        return identityService.createGroupQuery().list();
    }

    // 查询组
    @PostMapping("/group/detail/{id}")
    public Group groupDetail(@PathVariable("id") String id) {
        return identityService.createGroupQuery().groupId(id).singleResult();
    }

    // 添加组
    @PostMapping("/group/create")
    public String groupCreate(@RequestBody GroupParam param) {
        final Group exist = identityService.createGroupQuery().groupId(param.getId()).singleResult();
        if (exist != null) {
            return "该组已存在:" + param.getId();
        }

        GroupEntity entity = new GroupEntity();
        entity.setId(param.getId());
        entity.setName(param.getName());
        entity.setType(param.getType());
        identityService.saveGroup(entity);

        return param.getId();
    }

    // 修改组信息
    @PostMapping("/group/update")
    public String groupUpdate(@RequestBody GroupParam param) {
        final Group group = identityService.createGroupQuery().groupId(param.getId()).singleResult();
        if (group == null) {
            return "该组不存在:" + param.getId();
        }
        group.setName(param.getName());
        group.setType(param.getType());
        identityService.saveGroup(group);
        return group.getId();
    }

    // 删除组
    @PostMapping("/group/delete/{id}")
    public String groupDelete(@PathVariable("id") String id) {
        identityService.deleteGroup(id);
        return id;
    }

    // 将用户添加到组中
    @PostMapping("/user/group/relation/{userId}/{groupId}")
    public String userGroupRelation(@PathVariable("userId") String userId,
                                    @PathVariable("groupId") String groupId) {
        String error = checkUserGroupExist(userId, groupId);
        if (error != null) {
            return error;
        }

        final User exist = identityService.createUserQuery().memberOfGroup(groupId).userId(userId).singleResult();
        if (exist != null) {
            return "该用户与组已关联";
        }

        identityService.createMembership(userId, groupId);
        return "请求成功";
    }

    // 从组中删除用户
    @PostMapping("/user/group/delete/{userId}/{groupId}")
    public String userGroupDelete(@PathVariable("userId") String userId,
                                  @PathVariable("groupId") String groupId) {
        String error = checkUserGroupExist(userId, groupId);
        if (error != null) {
            return error;
        }
        identityService.deleteMembership(userId, groupId);
        return "请求成功";
    }

    private String checkUserGroupExist(String userId, String groupId) {
        final User user = identityService.createUserQuery().userId(userId).singleResult();
        final Group group = identityService.createGroupQuery().groupId(groupId).singleResult();

        if (userId != null && user == null) {
            return "该用户不存在:" + userId;
        }

        if (groupId != null && group == null) {
            return "该组不存在:" + groupId;
        }

        return null;
    }
}

用户请求参数类:

public class UserParam {
    // 账号,唯一
    private String id;

    private String firstName;

    private String lastName;

    private String email;

    // 密码
    private String password;
}

组请求参数类:

public class GroupParam {
    // 唯一
    private String id;

    // 组名
    private String name;

    // 类型,用于角色、部门等分类
    private String type;
}

接口请求示例

用户列表:http://localhost:8888/user/list

用户详情:http://localhost:8888/user/detail/cc

添加用户:http://localhost:8888/user/create

{
    "id": "cc",
    "firstName": "c",
    "lastName": "c",
    "password": "1"
}

更新用户信息:http://localhost:8888/user/update

{
    "id": "cc",
    "firstName": "c31232",
    "lastName": "c"
}

更新用户密码:http://localhost:8888/user/updatePassword

{
    "id": "cc",
    "password": "1"
}

删除用户:http://localhost:8888/user/delete/cc

组列表:http://localhost:8888/group/list

组详情:http://localhost:8888/group/detail/td

添加组:http://localhost:8888/group/create

{
    "id": "td",
    "name": "技术部",
    "type": "dept"
}

更新组信息:http://localhost:8888/group/update

{
    "id": "td",
    "name": "技术部",
    "type": "dept"
}

删除组:http://localhost:8888/group/delete/td

将用户添加到组中:http://localhost:8888/user/group/relation/cc/td

将用户从组中删除:http://localhost:8888/user/group/delete/cc/td

创建需要的用户和组

有了上面的基础,我们把用户:cc、ZuZhang、JingLi、ZongJingLi创建好,并添加到组TD中。

抄送人事

当领导审批通过后,抄送给人事告知,这一步不需要人事这个用户操作,所以使用Service Task即业务任务:

Camunda实战教程之员工请假流程

别忘了审核不通过的条件:

Camunda实战教程之员工请假流程

然后是抄送人事的业务任务代理类:

Camunda实战教程之员工请假流程

贴出代码:

@Service("NotifyHRService")
public class NotifyHRService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
        final String initiator = String.valueOf(execution.getVariable("initiator"));
        final long leaveDays = (long) execution.getVariable("leaveDays");
        final boolean approve = (boolean) execution.getVariable("approve");
        final String comment = String.valueOf(execution.getVariable("comment"));

        System.out.println("员工发起请假申请,申请人:" + initiator + ",请假天数:" + leaveDays);
        System.out.println("申请是否通过:" + (approve ? "是" : "否"));
        System.out.println("上级审批意见:" + comment);
    }
}

部署流程

现在开始干正事,把绘制好的流程图部署到流程管理平台上面,部署流程有两种方式:

  1. 在流程绘制器的下面有个部署按钮,点击deploy即可。

  2. 把流程图的XML内容拷贝出来,放到Spring Boot项目的/resource/BPMN路径下:如:

    /resources
    	/BPMN
    		leave_flow.bpmn
    

    然后启动程序就会自动部署了。

下图所示为流程绘制器的底部:

Camunda实战教程之员工请假流程

创建流程实例

虽然我们可以用流程绘制器或者流程管理平台创建一个流程实例,但是照旧使用API的方式去进行,为此我们要写一个接口:

@PostMapping("/start/{processKey}")
public void start(@PathVariable(value = "processKey") String processKey) {
    identityService.setAuthenticatedUserId("cc");

    final VariableMap variables = Variables.createVariables();
    variables.putValue("initiator", "cc");

    runtimeService.startProcessInstanceByKey(processKey, variables);
}
  • processKey:是我们流程图的唯一标识
  • setAuthenticatedUserId:表示流程的启动者、发起人,这可以是管理员或其他用户,因为请假申请人和流程发起人相同,所以这里一样传cc这个用户
  • VariableMap:表示流程开始就传入的参数,现在我们把流程图中的initiator设置为cc用户,根据我们绘制的流程图,这会将“发起请假申请”任务分配给这个cc用户
  • startProcessInstanceByKey:开始这个流程

调用接口:http://localhost:8888/start/Process_leave_flow

由此即可创建一个流程实例。

待办任务列表

登录cc用户,我们可以在流程管理平台看到Tasklist里面有自己的待办任务,并且很贴心的有一个UI表单可以填写:

Camunda实战教程之员工请假流程

但是我们不!我们要用API来做。

@PostMapping("/task/list/{userId}")
public List<TaskDto> taskList(@PathVariable("userId") String userId) {
    final List<Task> list = taskService.createTaskQuery()
        .taskAssignee(userId).list();

    List<TaskDto> dtoList = new ArrayList<>();
    for (Task task : list) {
        TaskDto dto = new TaskDto();
        dto.setId(task.getId());
        dto.setName(task.getName());
        dto.setAssignee(task.getAssignee());
        dto.setCreateTime(task.getCreateTime());
        dtoList.add(dto);
    }
    return dtoList;
}

Camunda的Task类不能直接返回,所以我们要做一个转换类:

public class TaskDto {
    private String id;
    private String name;
    private String assignee;
    private Date createTime;
}

调用接口:http://localhost:8888/task/list/cc

返回结果:

[
    {
        "id": "fb83cd42-2c62-11ee-aaa4-3c7c3fd838f3",
        "name": "发起请假申请",
        "assignee": "cc",
        "createTime": "2023-07-27T09:50:13.000+00:00"
    }
]

员工填写请假表单

@PostMapping("/task/complete")
public String complete(@RequestBody HashMap<String, Object> params) {
    final String id = String.valueOf(params.get("id"));
    final Task task = taskService.createTaskQuery().taskId(id).singleResult();
    if (task == null) {
        return "该任务不存在";
    }

    VariableMap variables = Variables.createVariables();

    for (String key : params.keySet()) {
        Object value = params.get(key);
        if (value instanceof Integer) {
            variables.putValue(key, Long.parseLong(params.get(key).toString()));
        } else {
            variables.putValue(key, params.get(key));
        }
    }

    taskService.complete(id, variables);
    return "请求成功";
}

因为希望一个接口兼容员工表单、领导审批,所以用HashMap来接收,for循环遍历赋值。

请求数据:

{
    "id": "fb83cd42-2c62-11ee-aaa4-3c7c3fd838f3",
    "leaveDays": 1,
    "reason": "我要请假!!!"
}

id是从待办任务列表里面拿来的

填写成功后,这时候再获取待办任务列表就是空的了。

监听器设置审批领导

员工请假表单提交之后,流程路径到达网管前会触发我们之前写的监听类:AddLeaderListener

从监听类的代码逻辑来看可知,因为请求天数是1天,所以审批人是:ZuZhang

网关根据条件进行流转

这里走流程图的条件,不走代码。

领导审批

请求接口和员工一样,请求体不同:

{
    "id": "7ff706f9-2d16-11ee-a86a-3c7c3fd838f3",
    "approve": true,
    "comment": "准了"
}

抄送人事

因为领导审批通过,所以会经过类:NotifyHRService

这个类会输出一些log,就当抄送人事了。

流程结束,查看历史任务

至此流程结束,我们查看一下用户已完成的历史任务:

@Autowired
private HistoryService historyService;

@PostMapping("/history/list/{userId}")
public List<HistoricTaskInstance> taskList(@PathVariable("userId") String userId) {
    return historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).finished().list();
}

调用接口:http://localhost:8888/history/list/cc

可以看到一条。

总结

以上是使用Camunda实现一个员工请假流程的过程,实践完后可以看出其使用难度不高,API丰富易懂,提供的组件也很完整,最重要的是流程可视化,所以有工作流需要的同志可以考虑。