shell复制代码# 拉取镜像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 Bootpom.xml: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:yml复制代码spring.datasource.url: jdbc:h2:file:./camunda-h2-databasecamunda.bpm.admin-user: id: demo password: demoserver: port: 8888
改用MySQL:yml复制代码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进入:上图中Cockpit是流程信息,部署的流程、流程实例等都在这里查看;Tasklist的任务列表,顾名思义与当前用户有关的流程任务在这里显示;Admin是用户、组、租户等这些授权相关,具体后面再说本文采用方式三的方式部署流程管理平台,更灵活,也比较实际一些,因为项目开发时肯定要和系统业务作关联嘛实战场景:员工请假流程场景说明:员工发起请假申请,天数<=1天的由组长审批,天数>1天并且<=3天的由经理审批,大于3天的由总经理审批,并且审批完成后抄送给人事做记录现在打开绘图工具,作图创建Camunda7的BPMN diagram图:先给出完整的流程图做参考:流程图信息在空白处点击一下,然后右侧的框框中把流程图的名字填好:Name:表示该流程显示的名称,可以用中文加强可读性ID:该流程的唯一标识,默认会生成一个随机字符串,我们改成leave_flow即请假流程,这个ID等会要用来部署流程的发起人点击开始圆圈,在下图箭头处填写发起人名称,这里为:initiator,可以随意修改,这个变量会通过流程路径传递,注意这时候的initiator是一个变量名,不是值,我们在后面创建流程实例的时候会把值传进这个变量这个输入框是Initiator,表示发起人员工发起请假申请在下图框框中填写${initiator},这是EL表达式,表示获取该变量的值,结合上面发起人可以明白这里是通过表达式获取该变量的值这个框是Assignee,表示分配到任务的人,假如我们传进来的值是xiaoming,那么这个任务就是分配给xiaoming这个单一用户的任务类型说明这里需要再说明一下任务这个组件,这算是一些小细节,需要注意,看下图:两个红色框框,一个包含任务类型:User Task:用户任务,最常见的任务类型,用于分配给特定用户或用户组去完成的人工任务通常要求用户提供输入、执行某些操作或决策,并将结果反馈到流程引擎Service Task:服务任务,是一个自动执行的任务,通常与外部系统或服务进行集成可以执行与业务逻辑相关的操作,例如调用REST API、发送电子邮件、生成报告等可以通过编写适当的代码或调用外部服务来实现服务任务员工发起请假申请、领导审批这些都是用户任务,要人工操作;最后抄送人事做记录这个是服务任务,由系统自动执行一个是多实例类型:三条竖线:并行任务,当这个任务分配给三个人执行时,这三个人可以并行执行任务,互不影响三条横线:串行任务,当这个任务分配给三个人执行时,这三个人需要顺序执行任务,等上一个人执行完才能接着执行分配给单一用户的任务不需要设置实例类型,但是当任务分配给多人的时候就需要设置请假申请表单发起申请当然要填写请假信息啦,点击发起申请的任务,选择箭头指的这项,这样就会出现一个Form fields的表单配置:然后填写表单内容,记得ID和Label不要搞混了,一个是变量名,一个是描述文本,然后是变量类型,一个是long,一个是string:排他网关我们创建的这两个表单变量会传递到后面的网关,由网关判断流程路径的走向,在这里我们使用排他网关,就是一个大大的X的菱形组件,排他网关的意思是只会选择一条路径我们点击网关连接任务的三条路径线,在右侧的条件中选择Type为Expression的选项,表示执行条件为表达式,然后使用EL表达式填写条件内容,顺便为了流程图好看一点,给路径线加上了一些描述文本,如下图所示:领导审批领导审批和员工发起请假申请相似,不同之处在于:多实例:这样可以动态分配给多个领导审批,而不是固定某一个用户,为了动态,我们需要写一个类来处理表单内容:是否批准和评论多实例对于三个领导审核的任务,我们首先需要处理多实例部分,如下图:这里介绍一下每一栏的作用:Loop cardinality:循环条件,为什么是循环?因为是多实例,给多个用户操作的,意思为循环分配任务Completion condition:跳出条件,当需要循环跳出时,配置这里上面两个我们没有用到,所以重点关注下面的:Collection:循环集合,我们用EL表达式写成:${leaders},我们需要传入这个leaders用户数组对象,但是前面的流程没有这个leaders变量,所以我们要新建一个类来处理Element variable:循环元素,每次循环出的对象叫什么变量名Assignee:分配到任务的人,这个刚刚写过了,很好理解,用的是传进来的:${leader}上面这样写,是因为我们希望把领导用户做成动态的,其实我们可以不写Collection,直接在Assignee处赋予用户名称,但这样固定并不是一个好的业务流程如图所示,我们在员工发起请假申请到网关的这条路径上,添加一个监听器,监听器的代码为:java复制代码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工作流引擎是如何与业务系统做关联的领导审批表单在说明下一步用户模块之前,我们最好先把领导审批的表单做完,步骤同样,增加是否批准和评论:记得三个任务都要加上,别漏了也别写错了虽然给三个任务加上重复的表单很蠢,但是绘制器支持引入外部表单,即写一份表单给多个任务使用,不过本文不扯这么复杂了,先这样顶上重点:三个领导审批任务都检查一下,别漏填了,不然后面无法部署流程用户和组我们虽然有超级管理员demo,但是还缺少几个用户:cc:员工/发起人ZuZhang:组长JingLing:经理ZongJingLi:总经理添加用户有两种方式:在流程管理平台的Admin中添加通过Camunda引擎的API添加第一种方式很简单,大家摸索一下就能创建出来,为了体现实战,我们当然是用API啦除了用户和组之外,还有一个租户的概念,租户不在本文范畴API的使用也不难,这里直接贴出接口代码和接口请求示例:接口代码java复制代码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;@RestControllerpublic 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; }}
用户请求参数类:java复制代码public class UserParam { // 账号,唯一 private String id; private String firstName; private String lastName; private String email; // 密码 private String password;}
组请求参数类:java复制代码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/createjson复制代码{ "id": "cc", "firstName": "c", "lastName": "c", "password": "1"}
更新用户信息:http://localhost:8888/user/updatejson复制代码{ "id": "cc", "firstName": "c31232", "lastName": "c"}
更新用户密码:http://localhost:8888/user/updatePasswordjson复制代码{ "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/createjson复制代码{ "id": "td", "name": "技术部", "type": "dept"}
更新组信息:http://localhost:8888/group/updatejson复制代码{ "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即业务任务:别忘了审核不通过的条件:然后是抄送人事的业务任务代理类:贴出代码:java复制代码@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); }}
部署流程现在开始干正事,把绘制好的流程图部署到流程管理平台上面,部署流程有两种方式:在流程绘制器的下面有个部署按钮,点击deploy即可把流程图的XML内容拷贝出来,放到Spring Boot项目的/resource/BPMN路径下:如:bash复制代码/resources /BPMN leave_flow.bpmn然后启动程序就会自动部署了下图所示为流程绘制器的底部:创建流程实例虽然我们可以用流程绘制器或者流程管理平台创建一个流程实例,但是照旧使用API的方式去进行,为此我们要写一个接口:java复制代码@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表单可以填写:但是我们不我们要用API来做
java复制代码@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类不能直接返回,所以我们要做一个转换类:java复制代码public class TaskDto { private String id; private String name; private String assignee; private Date createTime;}
调用接口:http://localhost:8888/task/list/cc返回结果:json复制代码[ { "id": "fb83cd42-2c62-11ee-aaa4-3c7c3fd838f3", "name": "发起请假申请", "assignee": "cc", "createTime": "2023-07-27T09:50:13.000+00:00" }]
员工填写请假表单java复制代码@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循环遍历赋值请求数据:json复制代码{ "id": "fb83cd42-2c62-11ee-aaa4-3c7c3fd838f3", "leaveDays": 1, "reason": "我要请假
"}
id是从待办任务列表里面拿来的填写成功后,这时候再获取待办任务列表就是空的了监听器设置审批领导员工请假表单提交之后,流程路径到达网管前会触发我们之前写的监听类:AddLeaderListener从监听类的代码逻辑来看可知,因为请求天数是1天,所以审批人是:ZuZhang网关根据条件进行流转这里走流程图的条件,不走代码领导审批请求接口和员工一样,请求体不同:json复制代码{ "id": "7ff706f9-2d16-11ee-a86a-3c7c3fd838f3", "approve": true, "comment": "准了"}
抄送人事因为领导审批通过,所以会经过类:NotifyHRService这个类会输出一些log,就当抄送人事了流程结束,查看历史任务至此流程结束,我们查看一下用户已完成的历史任务:java复制代码@Autowiredprivate 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丰富易懂,提供的组件也很完整,最重要的是流程可视化,所以有工作流需要的同志可以考虑作者:失败的面链接:https://juejin.cn/post/7260763321480380453(图片来源网络,侵删)
0 评论