在前面章节已经涉及到了任务分配,只是为了方便,将任务分配到个人。比如activiti:assignee="admin"
,但是这种方式在实际业务当中是由限制的,其实activiti5支持多种任务分配方式,除了分配到个人,还有指定参与人和指定参与组两种方式。修改前面的流程定义文件
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.zioer.com/reimbursement-5">
<process id="reimbursement-5" name="费用报销-5" isExecutable="true">
<startEvent id="startevent1" name="Start" activiti:initiator="startUserId" activiti:formKey="start.form"></startEvent>
<userTask id="usertask1" name="部门领导审批" activiti:candidateGroups="leadergroup" activiti:formKey="conform1.form"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="财务部门审批" activiti:candidateUsers="lobby,kitty,admin" activiti:formKey="conform2.form"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="申请人确认" activiti:assignee="${startUserId}" activiti:formKey="conform3.form"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_reimbursement-5">
<bpmndi:BPMNPlane bpmnElement="reimbursement-5" id="BPMNPlane_reimbursement-5">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="180.0" y="190.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="260.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="410.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="560.0" y="180.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="710.0" y="190.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="215.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="260.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="365.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="410.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="515.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="665.0" y="207.0"></omgdi:waypoint>
<omgdi:waypoint x="710.0" y="207.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
这里usertask1中配置了activiti:candidateGroups="leadergroup"
,这样只要是leadergroup这个组中的用户都可以接收到对应的任务了。而在usertask2中,则配置为activiti:candidateUsers="lobby,kitty,admin"
,这样,lobby,kitty,admin三个用户都可以接收到对应的任务了。与activiti:assignee的区别还有一条,就是由于现在的任务是分配给组或多个人,也就是说同时可能被多个人接收了,所以首先有了一个签收的流程,然后再是办理的流程。
而签收的流程为
taskService.claim(taskId, userId)
与办理是有区别的,办理如下所示
formService.submitTaskFormData(taskId, formProperties);
签收之后,对应的任务才有办理人,接下来才能办理完成当前任务了。其他关于自动部署、外置表单、用户和组的概念在前面已经涉及了,此处不再详述。对应的控制层代码为
package com.xquant.platform.test.activiti.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.impl.persistence.entity.HistoricProcessInstanceEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 任务分配
*/
@Controller
@RequestMapping(value = "/candiform")
public class CandiformController {
@Autowired
private RepositoryService repositoryService;
@Autowired
private FormService formService;
@Autowired
private TaskService taskService;
@Autowired
private IdentityService identityService;
@Autowired
private HistoryService historyService;
@Autowired
private RuntimeService runtimeService;
@RequestMapping(value = "/add")
public String add(Model model,HttpSession session) {
if (session.getAttribute("userId") == null){
return "redirect:/login/";
}
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("reimbursement-5")
.latestVersion().singleResult();
Object startForm = formService.getRenderedStartForm(processDefinition.getId());
model.addAttribute("formData", startForm);
return "reimbursement-5_start";
}
/**
* 提交启动流程
*/
@RequestMapping(value = "/start/save")
public String saveStartForm(Model model,HttpServletRequest request,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Map formProperties = PageData(request);
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionKey("reimbursement-5")
.latestVersion().singleResult();
String processDefinitionId = processDefinition.getId();
try {
identityService.setAuthenticatedUserId(userId);
formService.submitStartFormData(processDefinitionId, formProperties);
} finally {
identityService.setAuthenticatedUserId(null);
}
return "redirect:/candiform/list";
}
@RequestMapping(value = "/list")
public String list(Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<Task> tasks = new ArrayList<Task>();
//获得当前用户的任务
tasks = taskService.createTaskQuery().processDefinitionKey("reimbursement-5")
.taskCandidateOrAssigned(userId)
.active()
.orderByTaskId().desc().list();
model.addAttribute("list", tasks);
return "reimbursement-5_list";
}
/**
* 任务签收
*/
@RequestMapping(value = "/claim/{taskId}")
public String claim(@PathVariable("taskId") String taskId, HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
taskService.claim(taskId, userId);
return "redirect:/candiform/list";
}
/**
* 初始化启动流程,读取启动流程的表单字段来渲染start form
*/
@RequestMapping(value = "/startform/{taskId}")
public String StartTaskForm(@PathVariable("taskId") String taskId,Model model,HttpSession session) throws Exception {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Object taskForm = formService.getRenderedTaskForm(taskId);
String startUserId = (String) taskService.getVariable(taskId, "startUserId");
model.addAttribute("formData", taskForm);
model.addAttribute("taskId", taskId);
model.addAttribute("startUserId", startUserId);
return "reimbursement-5_edit";
}
/**
* 提交启动流程
*/
@RequestMapping(value = "/startform/save/{taskId}")
public String saveTaskForm(@PathVariable("taskId") String taskId,HttpSession session,HttpServletRequest request) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
Map formProperties = PageData(request);
try {
identityService.setAuthenticatedUserId(userId);
formService.submitTaskFormData(taskId, formProperties);
} finally {
identityService.setAuthenticatedUserId(null);
}
return "redirect:/candiform/list";
}
@RequestMapping(value = "/hlist")
public String historylist(Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<Map> hlist = new ArrayList<Map>();
List historylist = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey("reimbursement-5")
.startedBy(userId).list();
for (int i=0;i<historylist.size();i++){
Map<String, Object> map = new HashMap<String, Object>();
HistoricProcessInstanceEntity hpe = (HistoricProcessInstanceEntity) historylist.get(i);
map.put("id", hpe.getId());
map.put("startUserId", hpe.getStartUserId());
map.put("processInstanceId", hpe.getProcessInstanceId());
map.put("endTime", hpe.getEndTime());
map.put("startTime", hpe.getStartTime());
if (hpe.getEndTime() == null){
Task task = taskService.createTaskQuery().processInstanceId(hpe.getProcessInstanceId()).active().singleResult();
if (task != null){
map.put("name", task.getName());
}
}else{
map.put("name", "已完成");
}
hlist.add(map);
}
//获得当前用户的任务
model.addAttribute("list", hlist);
return "reimbursement_hlist";
}
@RequestMapping(value = "/hview/{pId}")
public String historyView(@PathVariable("pId") String pId,Model model,HttpSession session) {
String userId = session.getAttribute("userId") == null ? null : session.getAttribute("userId").toString();
if (userId == null){
return "redirect:/login/";
}
List<HistoricDetail> details = historyService
.createHistoricDetailQuery()
.processInstanceId(pId)
.orderByTime().asc()
.list();
model.addAttribute("list", details);
return "reimbursement_hview";
}
public Map PageData(HttpServletRequest request){
Map properties = request.getParameterMap();
Map returnMap = new HashMap();
Iterator entries = properties.entrySet().iterator();
Map.Entry entry;
String name = "";
String value = "";
while (entries.hasNext()) {
entry = (Map.Entry) entries.next();
name = (String) entry.getKey();
Object valueObj = entry.getValue();
if(null == valueObj){
value = "";
}else if(valueObj instanceof String[]){
String[] values = (String[])valueObj;
for(int i=0;i<values.length;i++){
value = values[i] + ",";
}
value = value.substring(0, value.length()-1);
}else{
value = valueObj.toString();
}
returnMap.put(name, value);
}
return returnMap;
}
}
这里的逻辑与前面不同的,所以对应的表单有调整。
reimbursement-5_list.jsp
<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<title>Zioer-Activiti示例</title>
<link rel="stylesheet" rev="stylesheet" href="<%=basePath%>css/style.css" type="text/css" media="all" />
<script language=JavaScript>
</script>
</head>
<body class="ContentBody">
<form action="add" method="post" name="fom" id="fom">
<div class="MainDiv">
<table width="90%" border="0" cellpadding="0" cellspacing="0" class="CContent">
<tr>
<th class="tablestyle_title" >费用报销管理-待办工作</th>
</tr>
<tr>
<td class="CPanel">
<table id="subtree1" style="DISPLAY: " width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td><table width="95%" border="0" align="center" cellpadding="0" cellspacing="0">
<tr>
<td height="40" class="font42">
<table width="100%" border="0" cellpadding="4" cellspacing="1" bgcolor="#FFFFEE" class="newfont03">
<tr class="CTitle" >
<td height="22" colspan="5" align="center" style="font-size:16px">当前用户办理工作列表</td>
</tr>
<tr bgcolor="#EEEEEE">
<td width="10%" height="30">任务ID</td>
<td width="20%">当前节点</td>
<td width="20%">办理人</td>
<td width="36%">创建时间</td>
<td width="17%">操作</td>
</tr>
<c:forEach items="${list}" var="var" varStatus="vs">
<tr <c:if test="${vs.count%2==0}">bgcolor="#AAAABB"</c:if> align="left" >
<td >${var.id}</td>
<td height="30">${var.name}</td>
<td >${var.assignee}</td>
<td ><fmt:formatDate value="${var.createTime}" type="both"/></td>
<td >
<c:choose>
<c:when test="${empty var.assignee}">
<a href="./claim/${var.id}">签收</a>
</c:when>
<c:otherwise>
<a href="./startform/${var.id}">办理</a>
</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</table></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
其他的jsp与前面是一致的。点击办理
保存之后,由于当前admin用户是候选人,所以还能看到对应的流程实例
签收并办理
最后进入申请人确认
在此处我们的配置为activiti:assignee="${startUserId}"
,而这个startUserId变量是从哪里来的呢?
在以下的片段当中
<startEvent id="startevent1" name="Start" activiti:initiator="startUserId" activiti:formKey="start.form"></startEvent>
activiti:initiator
流程的发起人会作为变量的startUserId的值,而流程的发起人在调用如下方法的时候就会设置了
// 设置流程发起人
identityService.setAuthenticatedUserId(userId);
当然了在activiti5中变量的值只要在该任务之前通过以下方法的第二个参数都可以设置值的,这个参数的类型为Map<String, String> properties
。
formService.submitTaskFormData(taskId, formProperties);
在业务处理过程总也可以获取到变量值,对应的方式如下所示
String startUserId = (String) taskService.getVariable(taskId, "startUserId")
除了以上的方式,还可以通过监听器的方式。
package com.xquant.platform.test.activiti.listener;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
public class AssigneeListner implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
if (delegateTask.getTaskDefinitionKey().equals("usertask1")) {
delegateTask.setVariable("user1", "admin");
}
}
}
设置监听器
<userTask id="usertask1" name="部门领导审批" activiti:candidateGroups="leadergroup" activiti:formKey="conform1.form">
<extensionElements>
<activiti:taskListener event="complete" class="com.xquant.platform.test.activiti.listener.AssigneeListner"></activiti:taskListener>
</extensionElements>
</userTask>
在部门领导审批之后,就会将user1变量值设置为admin,后续节点就可以使用这个变量了。
其实也就是说,在任务分配的过程中是完全可以实现动态的任务分配的。具体看业务需求,这里不详细探讨了。