菜单里有
<a rel="oa/leave/list/task">请假办理(普通)</a>
一看就是任务列表嘛,在列表里选择可办理任务,这一节我们只研究任务列表。
上一节“流程启动”中,我们有一个问题没有讨论,就是流程实例、TASK和IDENTITY之间的关系
这里是流程启动之后,数据库中ACT_RU_TASK表的数据
可以看到流程实例当前任务进行到了部门领导审批,这是紧跟着开始节点的下一个用户任务。
TASK_DEF_KEY_和NAME_是在bpmn中指定的。
<userTask id="deptLeaderAudit" name="部门领导审批" activiti:candidateGroups="deptLeader"></userTask>
ACT_RU_IDENTITYLINK表中的数据
所谓IDENTITY在activiti中其实就是用户或者组,对应user_id和group_id,ACT_RU_IDENTITYLINK表记录对象和任务或流程实例的关系。
ACT_RU_IDENTITYLINK表有一个字段"TYPE_",这个字段用来标明关系的类型。
第一条数据对象类型是starter(发起人),用户ID是kafeitu,发起人是和流程实例有关系的,不属于某个用户任务。
然后是当前任务有关的一个对象,对象类型是candidate(候选人),说明这个任务还没有被签收(claim),指定的是候选组而不是某个用户,这个组ID是deptLeader,这个也是在bpmn中指定的。
签收后对象类型应该是participant,然后USER_ID_字段被更新为签收用户ID。
基本上我们从ACT_RU_IDENTITYLINK表就能知道流程中每个节点和用户权限之间的关系,这也进入了我们任务列表的讨论。
一、控制层
@Controller
@RequestMapping(value = "/oa/leave")
public class LeaveController {
/**
* 任务列表
*
* @param leave
*/
@RequestMapping(value = "list/task")
public ModelAndView taskList(HttpSession session, HttpServletRequest request) {
ModelAndView mav = new ModelAndView("/oa/leave/taskList");
Page<Leave> page = new Page<Leave>(PageUtil.PAGE_SIZE);
int[] pageParams = PageUtil.init(page, request);
String userId = UserUtil.getUserFromSession(session).getId();
workflowService.findTodoTasks(userId, page, pageParams);
mav.addObject("page", page);
return mav;
}
}
二、服务层
/**
* 查询待办任务
*
* @param userId 用户ID
* @return
*/
@Transactional(readOnly = true)
public List<Leave> findTodoTasks(String userId, Page<Leave> page, int[] pageParams) {
List<Leave> results = new ArrayList<Leave>();
// 根据当前人的ID查询
TaskQuery taskQuery = taskService.createTaskQuery().taskCandidateOrAssigned(userId);
List<Task> tasks = taskQuery.list();
// 根据流程的业务ID查询实体并关联
for (Task task : tasks) {
String processInstanceId = task.getProcessInstanceId();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).active().singleResult();
String businessKey = processInstance.getBusinessKey();
if (businessKey == null) {
continue;
}
Leave leave = leaveManager.getLeave(new Long(businessKey));
leave.setTask(task);
leave.setProcessInstance(processInstance);
leave.setProcessDefinition(getProcessDefinition(processInstance.getProcessDefinitionId()));
results.add(leave);
}
page.setTotalCount(taskQuery.count());
page.setResult(results);
return results;
}
/**
* 查询流程定义对象
*
* @param processDefinitionId 流程定义ID
* @return
*/
protected ProcessDefinition getProcessDefinition(String processDefinitionId) {
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
return processDefinition;
}
--- starting TaskQueryImpl --------------------------------------------------------
Preparing: select distinct RES.* from ACT_RU_TASK RES left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_ WHERE (RES.ASSIGNEE_ = ? or (RES.ASSIGNEE_ is null and (I.USER_ID_ = ? or I.GROUP_ID_ IN (select g.GROUP_ID_ from ACT_ID_MEMBERSHIP g where g.USER_ID_ = ? ) ) ) ) order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: leaderuser(String), leaderuser(String), leaderuser(String), 2147483647(Integer), 0(Integer)
--- TaskQueryImpl finished --------------------------------------------------------
--- starting ProcessInstanceQueryImpl --------------------------------------------------------
Preparing: select distinct RES.* , P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId from ACT_RU_EXECUTION RES inner join ACT_RE_PROCDEF P on RES.PROC_DEF_ID_ = P.ID_ WHERE RES.PARENT_ID_ is null and RES.ID_ = ? and RES.PROC_INST_ID_ = ? and (RES.SUSPENSION_STATE_ = 1) order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: 12(String), 12(String), 2147483647(Integer), 0(Integer)
--- ProcessInstanceQueryImpl finished --------------------------------------------------------
--- starting ProcessDefinitionQueryImpl --------------------------------------------------------
Preparing: select distinct RES.* from ACT_RE_PROCDEF RES WHERE RES.ID_ = ? order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: leave:1:20(String), 2147483647(Integer), 0(Integer)
--- ProcessDefinitionQueryImpl finished --------------------------------------------------------
--- starting ProcessInstanceQueryImpl --------------------------------------------------------
Preparing: select distinct RES.* , P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId from ACT_RU_EXECUTION RES inner join ACT_RE_PROCDEF P on RES.PROC_DEF_ID_ = P.ID_ WHERE RES.PARENT_ID_ is null and RES.ID_ = ? and RES.PROC_INST_ID_ = ? and (RES.SUSPENSION_STATE_ = 1) order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: 25(String), 25(String), 2147483647(Integer), 0(Integer)
--- ProcessInstanceQueryImpl finished --------------------------------------------------------
--- starting ProcessDefinitionQueryImpl --------------------------------------------------------
Preparing: select distinct RES.* from ACT_RE_PROCDEF RES WHERE RES.ID_ = ? order by RES.ID_ asc LIMIT ? OFFSET ?
Parameters: leave:1:20(String), 2147483647(Integer), 0(Integer)
--- ProcessDefinitionQueryImpl finished --------------------------------------------------------
--- starting TaskQueryImpl --------------------------------------------------------
Preparing: select count(distinct RES.ID_) from ACT_RU_TASK RES left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_ WHERE (RES.ASSIGNEE_ = ? or (RES.ASSIGNEE_ is null and (I.USER_ID_ = ? or I.GROUP_ID_ IN (select g.GROUP_ID_ from ACT_ID_MEMBERSHIP g where g.USER_ID_ = ? ) ) ) )
Parameters: leaderuser(String), leaderuser(String), leaderuser(String)
--- TaskQueryImpl finished --------------------------------------------------------
可以看到,taskService.createTaskQuery().taskCandidateOrAssigned(userId)实际执行的SQL为
SELECT DISTINCT
RES.*
FROM
ACT_RU_TASK RES
LEFT JOIN ACT_RU_IDENTITYLINK I
ON I.TASK_ID_ = RES.ID_
WHERE (
RES.ASSIGNEE_ = 'leaderuseror'
OR (
RES.ASSIGNEE_ IS NULL
AND (
I.USER_ID_ = 'leaderuser '
OR I.GROUP_ID_ IN
(SELECT
g.GROUP_ID_
FROM
ACT_ID_MEMBERSHIP g
WHERE g.USER_ID_ = 'leaderuser')
)
)
)
ORDER BY RES.ID_ ASC
LIMIT 2147483647 OFFSET 0
activiti本身还实现了一些别的查询API,如:
taskService.createTaskQuery().taskCandidateGroup(groupId); // 查询候选组的任务
三、页面
<table width="100%" class="need-border">
<thead>
<tr>
<th>假种</th>
<th>申请人</th>
<th>申请时间</th>
<th>开始时间</th>
<th>结束时间</th>
<th>当前节点</th>
<th>任务创建时间</th>
<th>流程状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach items="${page.result }" var="leave">
<c:set var="task" value="${leave.task }" />
<c:set var="pi" value="${leave.processInstance }" />
<tr id="${leave.id }" tid="${task.id }">
<td>${leave.leaveType }</td>
<td>${leave.userId }</td>
<td>${leave.applyTime }</td>
<td>${leave.startTime }</td>
<td>${leave.endTime }</td>
<td>
<a class="trace" href='#' pid="${pi.id }" pdid="${pi.processDefinitionId}" title="点击查看流程图">${task.name }</a>
</td>
<%--<td><a target="_blank" href='${ctx }/workflow/resource/process-instance?pid=${pi.id }&type=xml'>${task.name }</a></td> --%>
<td>${task.createTime }</td>
<td>${pi.suspended ? "已挂起" : "正常" };<b title='流程版本号'>V: ${leave.processDefinition.version }</b></td>
<td>
<c:if test="${empty task.assignee }">
<a class="claim" href="${ctx }/oa/leave/task/claim/${task.id}">签收</a>
</c:if>
<c:if test="${not empty task.assignee }">
<%-- 此处用tkey记录当前节点的名称 --%>
<a class="handle" tkey='${task.taskDefinitionKey }' tname='${task.name }' href="#">办理</a>
</c:if>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<tags:pagination page="${page}" paginationSize="${page.pageSize}"/>