Activiti 自带一套用户管理系统,维护了用户和用户组的对应关系,通过 identityService
可以对它们进行增删改查。当你在 UserTask
中指定了用户组时,Activiti 就会使用它维护的这个关系寻找用户了。
但是这也为 Activiti 的使用带来了困难,因为一般应用都有一套自建的用户管理,不可能去使用 Activiti 这一套,就不得不花一些成本来对接,而且对接方法都比较 hack,个人觉得这是 Activiti 设计得不太好的地方。
受理人与候选人
一个 UserTask
可以设置一个受理人(Assignee),或者多个候选人。
受理人就是负责完成该任务的人,在审批场景下就可以理解成审批人,只能有一个,如果要有多个的话则应该使用上一篇文章提到的多实例任务。
<!--kermit就是受理人的 id--><userTaskid="theTask"name="my task"activiti:assignee="kermit"/>
候选人可以有一个或者多个,他们都是受理人的“候选”,他们要“抢”任务,谁先调用了 taskService.claim(taskId, userId);
,谁就变成了任务的受理人。
<!--两个候选人 kermit与gonzo--><userTaskid="theTask"name="my task"activiti:candidateUsers="kermit,gonzo"/>
<!--management用户组的所有用户作为候选人--><userTaskid="theTask"name="my task"activiti:candidateGroups="management"/>
内置的用户管理
Activiti 中的用户模型非常简单,就是用户和用户组之间的多对多关系,所以只涉及三张表:
ACT_ID_USER
:用户信息ACT_ID_GROUP
:用户组信息ACT_ID_MEMBERSHIP
:存储用户和用户组之间的多对多关联
流程运行时用户信息
在应用中一般会调用下面的方法查询某个用户的 task:
// 查询所有候选 userId 的任务List<Task>tasks=taskService.createTaskQuery() .taskCandidateUser(userId).list(); // 查询所有 userId 受理的任务List<Task>tasks=taskService.createTaskQuery() .taskAssignee(userId).list(); // 查询所有 userId候选或者受理的任务List<Task>tasks=taskService.createTaskQuery() .taskCandidateOrAssigned(userId).list();
受理人查询
在 ACT_RU_TASK
表中本来就有一个 ASSIGNEE_
字段表示受理人,所有当你使用 taskAssignee(userId)
查询时,直接就是用的 ASSIGNEE_=userId
去 ACT_RU_TASK
表中去查的。
候选人查询
在流程运行时还会有一张 ACT_RU_IDENTITYLINK
表,用来记录流程运行时相关的人员信息,比如流程发起人,参与人,Task 的候选人等信息:
其中 TYPE_
有以下几种取值:
- starter: 流程发起人
- participant: 流程参与人,当前的受理人就会被记录在这里
- candidate: Task 对应的候选人或者候选用户组
因为这是一张运行时表,所以里面数据是会随着流程运转不断增删改的。
当你使用 taskCandidateUser(userId)
条件查询时其实就是去这张表里根据 TYPE_='candidate' AND USER_ID_=userId
找到用户候选的 Task
候选用户组
假如你将 UserTask 配置成 activiti:candidateGroups="management"
,如果 userId 属于 manangement
用户组,那么使用 taskCandidateUser(userId)
条件查询,也是能查出该 UserTask 的。
此时就需要用到上一小节提到的用户管理相关表了。Activiti 如果会去 ACT_ID_MEMBERSHIP
中找到该用户的 groupId,然后去 ACT_RU_IDENTITYLINK
中根据 GROUP_ID_
查。
Activiti 如何与已有用户管理系统对接?
从上面的分析可以看到,Activiti 只有在你配置了 candidateGroups
才可能去查询内置的用户管理信息,所以个人建议是就不要使用 candidateGroups
配置了,全部使用 candidateUsers
和 assignee
,然后在 userId 里加入一些特征区分用户组或者别的业务相关的东西,比如加个前缀 GROUP-groupId
,之后就全部用自己业务系统里的 id 去查 Task。不用担心这些ID在 Activiti 内置的用户管理系统中没有,Activiti 根本不会去校验这个。
当然也有人研究过一些更加麻烦的对接方法,列出如下,也可以参考:
- 同步或者重构Activiti Identify用户数据的多种方案比较,里面提到了三个方案
- 往 Activiti 内置的用户表中,同步自己业务的用户数据。个人感觉比较麻烦,而且也容易出各种问题
- 将 Activiti 用户相关存储类用自己的实现类换掉。听起来很复杂,但是因为 Activiti 6 所采用的可插拔的存储层设计,其实只要替换两类就可以了,后面我有空会再研究一下它的存储层设计。而且如果要搞分库分表的话,存储层本来就是要重新弄的,顺手就可以把用户管理换掉。
- 用业务用户数据的视图替换掉 Activiti 内置用户管理的这三张表。感觉不一定所有的业务用户数据都可以映射到 Activiti 的用户模型,所有通用性有限