身份服务
在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人或者候选人组,可以从候选人中选择参与者来完成任务。
身份服务是对各种用户/组库的API抽象。其基本实体是:
- User: 使用不同ID区分的不同用户
- Group: 使用不同ID区分的不同组
- Membership: 组与用户之间的关系
- Tenant: 使用不同ID区分的不同租户
- Tenant Membership: 租户与 用户/组 之间的关系
1.候选人
1.1 绘制流程图
首先绘制一个如下的基本流程图。然后我们分别来指派处理人。
人事审批这块我们可以直接来指定多个候选人来处理。demo,zhang,lisi
在总经理审批的位置我们在设计的时候不太清楚会是谁来审批,所以通过值表达式来处理。
设计完成后对应的xml中的数据为:
1.2 部署和启动流程
创建了流程图后我们就可以直接来部署该流程。
代码语言:javascript复制 /**
* 完成流程的部署操作
*/
@Test
public void deploy(){
Deployment deploy = repositoryService.createDeployment()
.name("候选人案例")
.addClasspathResource("flow/候选人案例.bpmn")
.deploy();
System.out.println("deploy.getId() = " deploy.getId());
}
接着就可以直接来启动该流程了。
代码语言:javascript复制 /**
* 启动流程实例
*/
@Test
public void startFlow(){
String processInstanceId = "Process_05vjqic:1:cca1e181-362e-11ed-b8fc-c03c59ad2248";
runtimeService.startProcessInstanceById(processInstanceId);
}
启动完成流程后我们进入到act_ru_task
中可以发现我们创建的流程任务信息,但是处理人
字段还是空的。
注意:相关的候选人的信息存储在了act_ru_identitylink
表中。
对应的查询操作如下:
代码语言:javascript复制 @Test
public void getTaskCandidate(){
List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask("023f0279-362f-11ed-8d8a-c03c59ad2248");
for (IdentityLink identityLink : identityLinksForTask) {
System.out.println(identityLink.getTaskId());
System.out.println(identityLink.getProcessDefId());
System.out.println(identityLink.getUserId());
}
}
1.3 任务的拾取
候选要操作我们需要通过拾取
的行为把候选人
转换为处理人
.那么候选人登录后需要能查询出来他可以拾取
的任务。在camunda的web应用中我们可以看到这样的操作。demo
账号登录。
在代码上的实现,先来看查询操作。
代码语言:javascript复制 /**
* 根据登录的用户查询对应的可以拾取的任务
*/
@Test
public void queryTaskCandidate(){
List<Task> list = taskService.createTaskQuery()
.processInstanceId("023da2e6-362f-11ed-8d8a-c03c59ad2248")
.taskCandidateUser("demo")
.list();
for (Task task : list) {
System.out.println("task.getId() = " task.getId());
System.out.println("task.getName() = " task.getName());
}
}
/**
* 查询当前任务所有的候选人
*/
@Test
public void getTaskCandidate(){
String taskId = "52b2642a-36fa-11ed-bde4-c03c59ad2248";
List<IdentityLink> linksForTask = taskService.getIdentityLinksForTask(taskId);
if(linksForTask != null && linksForTask.size() > 0){
for (IdentityLink identityLink : linksForTask) {
System.out.println(identityLink.getUserId());
}
}
}
然后我们就可以在上面的基础上来做拾取
的操作了。
/**
* 根据登录的用户查询对应的可以拾取的任务
*/
@Test
public void claimTaskCandidate(){
List<Task> list = taskService.createTaskQuery()
.processInstanceId("023da2e6-362f-11ed-8d8a-c03c59ad2248")
.taskCandidateUser("demo")
.list();
for (Task task : list) {
taskService.claim(task.getId(),"demo");
}
}
进入到表结构中你会发现这条Task记录的处理人被指派为了demo
,而且在Web端可以看到可以审批了。
注意:这时demo
拾取了任务之后其他的用户就不能再拾取了,查询的时候也查询不到了。
1.4.任务的归还
拾取任务后如果不想操作那么可以归还任务。
代码语言:javascript复制 /**
* 退还任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void unclaimTaskCandidate(){
Task task = taskService.createTaskQuery()
.processInstanceId("023da2e6-362f-11ed-8d8a-c03c59ad2248")
.taskAssignee("demo")
.singleResult();
if(task != null){
// 归还相关的任务 置空即可
taskService.setAssignee(task.getId(),null);
System.out.println("归还拾取成功");
}
}
1.5 任务的交接
拾取任务后如果不想操作也不想归还可以直接交接给另外一个人来处理.
代码语言:javascript复制 @Test
public void taskCandidate(){
Task task = taskService.createTaskQuery()
.processInstanceId("023da2e6-362f-11ed-8d8a-c03c59ad2248")
.taskAssignee("demo")
.singleResult();
if(task != null){
// 任务交接
taskService.setAssignee(task.getId(),"zhang");
System.out.println("任务交接给了zhang");
}
}
1.6 任务完成
正常的任务处理
代码语言:javascript复制 @Test
public void completeTask1(){
String taskId = "023f0279-362f-11ed-8d8a-c03c59ad2248";
// 但是下一个节点的 处理人是值表达式 我们需要赋值
Map<String,Object> map = new HashMap<>();
map.put("user1","demo");
map.put("user2","zhangsan");
map.put("user3","wangwu");
taskService.complete(taskId,map);
}
当然我们通过值表达式来处理的候选人操作。在act_ru_identitylink
表中同样有相关记录,我们需要结合流程变量表来处理了。但是处理的API和上面是一样的。
2. 候选人组
当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。
2.1 管理用户和组
2.1.1 用户管理
我们需要先单独维护用户信息。后台对应的表结构是ACT_ID_USER
.
/**
* 维护用户
*/
@Test
public void createUser(){
User user = identityService.newUser("zhang");
user.setFirstName("张");
user.setLastName("三");
user.setEmail("zhangsan@qq.com");
user.setPassword("123456");
identityService.saveUser(user);
}
要更新或者删除用户的话。通过相关API即可完成
代码语言:javascript复制 @Test
public void updateUser(){
User user = identityService.createUserQuery().userId("zhang").singleResult();
user.setPassword("123");
identityService.saveUser(user);
}
@Test
public void deleteUser(){
identityService.deleteUser("zhang");
}
2.1.2 Group管理
维护对应的Group信息,后台对应的表结构是ACT_ID_GROUP
/**
* 创建用户组
*/
@Test
public void createGroup(){
// 创建Group对象并指定相关的信息
Group group = identityService.newGroup("group1");
group.setName("开发部");
group.setType("type1");
// 创建Group对应的表结构数据
identityService.saveGroup(group);
}
更新和删除参考上面的用户管理
2.1.3 用户分配组
用户和组是一个多对多的关联关联,我们需要做相关的分配,后台对应的表结构是ACT_ID_MEMBERSHIP
/**
* 将用户分配给对应的Group
*/
@Test
public void userGroup(){
// 根据组的编号找到对应的Group对象
Group group = identityService.createGroupQuery().groupId("group1").singleResult();
// 创建 MemberShip 建立用户和组的关系
identityService.createMembership("zhang",group.getId());
}
2.2 候选人组应用
搞清楚了用户和用户组的关系后我们就可以来使用候选人组的应用了
2.2.1 创建流程图
创建一个简单的请假流程,处理人通过候选人组的方式来处理。
2.2.2 流程的部署运行
然后我们把流程部署和运行。
代码语言:javascript复制 /**
* 完成流程的部署操作
*/
@Test
public void deploy(){
Deployment deploy = repositoryService.createDeployment()
.name("候选人组案例")
.addClasspathResource("flow/候选人组.bpmn")
.deploy();
System.out.println("deploy.getId() = " deploy.getId());
}
/**
* 启动流程实例
*/
@Test
public void startFlow(){
String processInstanceId = "Process_1gvo8so:1:3e253edb-3682-11ed-a1ff-c03c59ad2248";
runtimeService.startProcessInstanceById(processInstanceId);
}
部署成功后我们可以在act_ru_identitylink
中看到对应的记录。
2.2.3 任务的拾取和完成
然后完成任务的查询拾取和处理操作。逻辑是根据当前的登录用户
找到对应的组
,然后根据组
找到对应的任务信息。
/**
* 根据登录的用户查询对应的可以拾取的任务
*
*/
@Test
public void queryTaskCandidateGroup(){
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember("zhang").singleResult();
List<Task> list = taskService.createTaskQuery()
.processInstanceId("711d5726-3682-11ed-8b9b-c03c59ad2248")
.taskCandidateGroup(group.getId())
.list();
for (Task task : list) {
System.out.println("task.getId() = " task.getId());
System.out.println("task.getName() = " task.getName());
}
}
/**
* 拾取任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void claimTaskCandidate1(){
String userId = "zhang";
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
Task task = taskService.createTaskQuery()
.processInstanceId("711d5726-3682-11ed-8b9b-c03c59ad2248")
.taskCandidateGroup(group.getId())
.singleResult();
if(task != null) {
// 任务拾取
taskService.claim(task.getId(),userId);
System.out.println("任务拾取成功");
}
}
拾取后的操作和前面是一样的,就没必要赘述。当然我们在定义流程的时候也可以通过值表达式来处理,我们需要注意赋值即可。
3.租户
多租户 是指一个单一的Camunda应用需要为多个的租户服务的情况。对于每个租户来说,应该有某些隔离的保证。例如,一个租户的流程实例不应干扰另一个租户的流程实例。
多租户可以通过两种不同的方式实现。一种是使用每个租户一个流程引擎。另一种方式是只使用一个流程引擎,并将数据与租户标识符相关联。这两种方式在数据隔离程度、维护工作和可扩展性方面各有不同。两种方式的组合也是可能的。
多租户可以使用租户标识符(即tenant-ids)的流程引擎来实现。所有租户的数据都存储在一个表中(同一数据库和表结构)。通过存储在列中的租户标识符来提供隔离。
3.1 租户管理
租户对应于act_id.tenant
表结构中的数据。我们可以先来维护租户的相关信息。创建长沙分公司
的租户信息
/**
* 创建租户
*/
@Test
public void createTenant(){
Tenant tenant = identityService.newTenant("cs");
tenant.setName("长沙分公司");
identityService.saveTenant(tenant);
}
当然参考前面的删除和更新操作也可以非常轻松的完成租户
相关信息的处理。然后我们来看下租户和用户和组的关系。
/**
* 建立 租户 和 组的关系
* 当然也可以建立 租户和用户的关系。只是这种比较少用
*/
@Test
public void createTenantAndGroupShip(){
identityService.createTenantGroupMembership("cs","group1");
}
3.2 部署操作
我们在部署流程的时候可以指定对应的租户编号。如果不指定租户编号,说明该流程是属于所有租户的。
代码语言:javascript复制 /**
* 完成流程的部署操作
*/
@Test
public void deploy(){
Deployment deploy = repositoryService.createDeployment()
.name("候选人案例-租户")
.tenantId("tenant1")
.addClasspathResource("flow/候选人案例.bpmn")
.deploy();
System.out.println("deploy.getId() = " deploy.getId());
}
3.3 查看部署流程
设置了租户编号后,我们要做相关的查询,可以通过如下的API来实现
代码语言:javascript复制 /**
* 基于租户 查询相关的部署流程
*/
@Test
public void getByTenantId(){
List<Deployment> list = repositoryService.createDeploymentQuery()
.tenantIdIn("cs")
.list();
for (Deployment deployment : list) {
System.out.println("deployment.getId() = " deployment.getId());
System.out.println("deployment.getName() = " deployment.getName());
System.out.println("------------");
}
}
通过调用withoutTenantId()
来查询不属于任何租户的部署。
@Test
public void getByWithoutTenantId(){
List<Deployment> list = repositoryService.createDeploymentQuery()
.withoutTenantId() // 查询出所有不属于任何tenantId的记录
.list();
for (Deployment deployment : list) {
System.out.println("deployment.getId() = " deployment.getId());
System.out.println("deployment.getName() = " deployment.getName());
System.out.println("------------");
}
}
也可以通过调用includeDeploymentsWithoutTenantId()
来查询属于特定租户或不属于租户的部署。
@Test
public void getByIncludTenantId(){
List<Deployment> list = repositoryService.createDeploymentQuery()
.tenantIdIn("cs")
.includeDeploymentsWithoutTenantId() // 查询出 tenant1 和 不属于 租户的记录
.list();
for (Deployment deployment : list) {
System.out.println("deployment.getId() = " deployment.getId());
System.out.println("deployment.getName() = " deployment.getName());
System.out.println("------------");
}
}
与 “部署查询” 类似,定义查询允许通过一个或多个租户和不属于任何租户的定义进行过滤。
代码语言:javascript复制List<ProcessDefinition> processDefinitions = repositoryService
.createProcessDefinitionQuery()
.tenantIdIn("cs")
.includeProcessDefinitionsWithoutTenantId();
.list();
3.4 启动流程实例
通过key创建一个为多租户部署的流程定义的实例,必须在ProcessInstantiationBuilder
中传递租户标识符 。
/**
* 租户 启动一个流程实例
*/
@Test
public void startFlow(){
String processKey = "Process_05vjqic";
ProcessInstance processInstance = runtimeService
.createProcessInstanceByKey(processKey)
.processDefinitionTenantId("cs")
.execute();
System.out.println("processInstance.getId() = " processInstance.getId());
}
@Test
public void startFlow1(){
String processId = "Process_05vjqic:1:a6b23794-3767-11ed-a4df-c03c59ad2248";
ProcessInstance processInstance = runtimeService
.createProcessInstanceById(processId)
.execute();
System.out.println("processInstance.getId() = " processInstance.getId());
}
启动后流程后,在创建的Task记录中我们可以看到对应的租户
信息
因为我们在流程设计的时候就指定了第一个节点的候选人是group1
,所以在act_ru_identitylink
表中可以看到相关的记录。
3.5 任务拾取
接下来就可以对任务做拾取
的操作了
/**
* 根据当前登录用户 查询到需要拾取的任务
*/
@Test
public void claimTask(){
// 根据登录用户查询到对应的Group
List<Group> groups = identityService.createGroupQuery().groupMember("demo").list();
if(groups != null && groups.size() > 0){
for (Group group : groups) {
// 根据 group 找到对应的 租户
List<Tenant> tenants = identityService.createTenantQuery().groupMember(group.getId()).list();
List<String> tenantStrings = new ArrayList<>();
if(tenants != null && tenants.size() > 0){
tenantStrings = tenants.stream().map((item)->{
return item.getId();
}).collect(Collectors.toList());
String[] ss = new String[tenantStrings.size()];
tenantStrings.toArray(ss);
List<Task> list = taskService.createTaskQuery()
.tenantIdIn(ss)
.list();
if(list != null && list.size() > 0){
for (Task task : list) {
System.out.println("task.getId() = " task.getId());
}
}
}
}
}
}
可以查询到对应的拾取就比较简单了
代码语言:javascript复制 @Test
public void claimTask(){
// 根据登录用户查询到对应的Group
List<Group> groups = identityService.createGroupQuery().groupMember("demo").list();
if(groups != null && groups.size() > 0){
for (Group group : groups) {
// 根据 group 找到对应的 租户
List<Tenant> tenants = identityService.createTenantQuery().groupMember(group.getId()).list();
List<String> tenantStrings = new ArrayList<>();
if(tenants != null && tenants.size() > 0){
tenantStrings = tenants.stream().map((item)->{
return item.getId();
}).collect(Collectors.toList());
String[] ss = new String[tenantStrings.size()];
tenantStrings.toArray(ss);
List<Task> list = taskService.createTaskQuery()
.tenantIdIn(ss)
.list();
if(list != null && list.size() > 0){
for (Task task : list) {
taskService.claim(task.getId(),"demo");
}
}
}
}
}
}
能够拾取成功,那么后面的操作就是任务的审批。接下来的操作就和前面是一样的了。不再赘述~