leave.bpmn中有定义流程启动
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId"></startEvent>
menu.jsp中
<a rel="oa/leave/apply">请假申请(普通)</a>
然后对应控制层
@Controller
@RequestMapping(value = "/oa/leave")
public class LeaveController {
@RequestMapping(value = {"apply", ""})
public String createForm(Model model) {
model.addAttribute("leave", new Leave());
return "/oa/leave/leaveApply";
}
}
再到流程启动页面leaveApply.jsp
<form:form id="inputForm" action="${ctx}/oa/leave/start" method="post" class="form-horizontal">
<fieldset>
<legend><small>请假申请</small></legend>
<table border="1">
<tr>
<td>请假类型:</td>
<td>
<select id="leaveType" name="leaveType">
<option>公休</option>
<option>病假</option>
<option>调休</option>
<option>事假</option>
<option>婚假</option>
</select>
</td>
</tr>
<tr>
<td>开始时间:</td>
<td><input type="text" id="startTime" name="startTime" /></td>
</tr>
<tr>
<td>结束时间:</td>
<td><input type="text" id="endTime" name="endTime" /></td>
</tr>
<tr>
<td>请假原因:</td>
<td>
<textarea name="reason"></textarea>
</td>
</tr>
<tr>
<td> </td>
<td>
<button type="submit">申请</button>
</td>
</tr>
</table>
</fieldset>
</form:form>
输入表单值,点击申请按钮,/oa/leave/start
/**
* 启动请假流程
*
* @param leave
*/
@RequestMapping(value = "start", method = RequestMethod.POST)
public String startWorkflow(Leave leave, RedirectAttributes redirectAttributes, HttpSession session) {
try {
User user = UserUtil.getUserFromSession(session);
// 用户未登录不能操作,实际应用使用权限框架实现,例如Spring Security、Shiro等
if (user == null || StringUtils.isBlank(user.getId())) {
return "redirect:/login?timeout=true";
}
leave.setUserId(user.getId());
Map<String, Object> variables = new HashMap<String, Object>();
ProcessInstance processInstance = workflowService.startWorkflow(leave, variables);
redirectAttributes.addFlashAttribute("message", "流程已启动,流程ID:" + processInstance.getId());
} catch (ActivitiException e) {
if (e.getMessage().indexOf("no processes deployed with key") != -1) {
logger.warn("没有部署流程!", e);
redirectAttributes.addFlashAttribute("error", "没有部署流程,请在[工作流]->[流程管理]页面点击<重新部署流程>");
} else {
logger.error("启动请假流程失败:", e);
redirectAttributes.addFlashAttribute("error", "系统内部错误!");
}
} catch (Exception e) {
logger.error("启动请假流程失败:", e);
redirectAttributes.addFlashAttribute("error", "系统内部错误!");
}
return "redirect:/oa/leave/apply";
}
这里Map<String, Object> variables可以保存流程启动变量,当然我们是希望这些变量在流程各个节点间都可以共享,后面会介绍怎么取流程实例。
在WorkflowService中,有具体的流程启动方法,这个方法保存leave对象,启动流程,返回流程实例
/**
* 启动流程
*
* @param entity
*/
public ProcessInstance startWorkflow(Leave entity, Map<String, Object> variables) {
leaveManager.saveLeave(entity);
logger.debug("save entity: {}", entity);
String businessKey = entity.getId().toString();
ProcessInstance processInstance = null;
try {
// 用来设置启动流程的人员ID,引擎会自动把用户ID保存到activiti:initiator中
identityService.setAuthenticatedUserId(entity.getUserId());
processInstance = runtimeService.startProcessInstanceByKey("leave", businessKey, variables);
String processInstanceId = processInstance.getId();
entity.setProcessInstanceId(processInstanceId);
logger.debug("start process of {key={}, bkey={}, pid={}, variables={}}", new Object[]{"leave", businessKey, processInstanceId, variables});
} finally {
identityService.setAuthenticatedUserId(null);
}
return processInstance;
}
执行启动流程后,打印日志:
save entity: me.kafeitu.demo.activiti.entity.oa.Leave@1b9b6ad
--- starting StartProcessInstanceCmd --------------------------------------------------------
--- starting GetNextIdBlockCmd --------------------------------------------------------
Running command with propagation REQUIRES_NEW
Preparing: select * from ACT_GE_PROPERTY where NAME_ = ?
Parameters: next.dbid(String)
--- GetNextIdBlockCmd finished --------------------------------------------------------
insert ProcessInstance[12]
Preparing: insert into ACT_RU_EXECUTION (ID_, REV_, PROC_INST_ID_, BUSINESS_KEY_, PROC_DEF_ID_, ACT_ID_, IS_ACTIVE_, IS_CONCURRENT_, IS_SCOPE_,IS_EVENT_SCOPE_, PARENT_ID_, SUPER_EXEC_, SUSPENSION_STATE_, CACHED_ENT_STATE_, TENANT_ID_, NAME_) values ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 12(String), 12(String), 2(String), leave:1:20(String), deptLeaderAudit(String), true(Boolean), false(Boolean), true(Boolean), false(Boolean), null, null, 1(Integer), 2(Integer), (String), null
insert VariableInstanceEntity[id=13, name=applyUserId, type=string, textValue=kafeitu]
Preparing: insert into ACT_RU_VARIABLE (ID_, REV_, TYPE_, NAME_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_) values ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 13(String), string(String), applyUserId(String), 12(String), 12(String), null, null, null, null, kafeitu(String), null
insert HistoricVariableInstanceEntity[id=13, name=applyUserId, revision=0, type=string, textValue=kafeitu]
Preparing: insert into ACT_HI_VARINST (ID_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, NAME_, REV_, VAR_TYPE_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_, CREATE_TIME_, LAST_UPDATED_TIME_) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 13(String), 12(String), 12(String), null, applyUserId(String), 0(Integer), string(String), null, null, null, kafeitu(String), null, 2015-05-05 15:27:45.127(Timestamp), 2015-05-05 15:27:45.127(Timestamp)
insert IdentityLinkEntity[id=14, type=starter, userId=kafeitu, processInstanceId= 12]
Preparing: insert into ACT_RU_IDENTITYLINK (ID_, REV_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_, PROC_DEF_ID_) values (?, 1, ?, ?, ?, ?, ?, ?)
Parameters: 14(String), starter(String), kafeitu(String), null, null, 12(String), null
insert org.activiti.engine.impl.persistence.entity.HistoricIdentityLinkEntity@f2818d
Preparing: insert into ACT_HI_IDENTITYLINK (ID_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_) values (?, ?, ?, ?, ?, ?)
Parameters: 14(String), starter(String), kafeitu(String), null, null, 12(String)
insert HistoricProcessInstanceEntity[superProcessInstanceId=null]
Preparing: insert into ACT_HI_PROCINST ( ID_, PROC_INST_ID_, BUSINESS_KEY_, PROC_DEF_ID_, START_TIME_, END_TIME_, DURATION_, START_USER_ID_, START_ACT_ID_, END_ACT_ID_, SUPER_PROCESS_INSTANCE_ID_, DELETE_REASON_, TENANT_ID_, NAME_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 12(String), 12(String), 2(String), leave:1:20(String), 2015-05-05 15:27:45.206(Timestamp), null, null, kafeitu(String), startevent1(String), null, null, null, (String), null
insert HistoricActivityInstanceEntity[activityId=startevent1, activityName=Start]
Preparing: insert into ACT_HI_ACTINST ( ID_, PROC_DEF_ID_, PROC_INST_ID_, EXECUTION_ID_, ACT_ID_, TASK_ID_, CALL_PROC_INST_ID_, ACT_NAME_, ACT_TYPE_, ASSIGNEE_, START_TIME_, END_TIME_, DURATION_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 15(String), leave:1:20(String), 12(String), 12(String), startevent1(String), null, null, Start(String), startEvent(String), null, 2015-05-05 15:27:45.206(Timestamp), 2015-05-05 15:27:45.268(Timestamp), 62(Long), (String)
insert HistoricActivityInstanceEntity[activityId=deptLeaderAudit, activityName=部门领导审批]
Preparing: insert into ACT_HI_ACTINST ( ID_, PROC_DEF_ID_, PROC_INST_ID_, EXECUTION_ID_, ACT_ID_, TASK_ID_, CALL_PROC_INST_ID_, ACT_NAME_, ACT_TYPE_, ASSIGNEE_, START_TIME_, END_TIME_, DURATION_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 16(String), leave:1:20(String), 12(String), 12(String), deptLeaderAudit(String), 17(String), null, 部门领导审批(String), userTask(String), null, 2015-05-05 15:27:45.268(Timestamp), null, null, (String)
insert Task[id=17, name=部门领导审批]
Preparing: insert into ACT_RU_TASK (ID_, REV_, NAME_, PARENT_TASK_ID_, DESCRIPTION_, PRIORITY_, CREATE_TIME_, OWNER_, ASSIGNEE_, DELEGATION_, EXECUTION_ID_, PROC_INST_ID_, PROC_DEF_ID_, TASK_DEF_KEY_, DUE_DATE_, CATEGORY_, SUSPENSION_STATE_, TENANT_ID_, FORM_KEY_) values (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 17(String), 部门领导审批(String), null, null, 50(Integer), 2015-05-05 15:27:45.268(Timestamp), null, null, null, 12(String), 12(String), leave:1:20(String), deptLeaderAudit(String), null, null, 1(Integer), (String), null
insert org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity@1177565
Preparing: insert into ACT_HI_TASKINST ( ID_, PROC_DEF_ID_, PROC_INST_ID_, EXECUTION_ID_, NAME_, PARENT_TASK_ID_, DESCRIPTION_, OWNER_, ASSIGNEE_, START_TIME_, CLAIM_TIME_, END_TIME_, DURATION_, DELETE_REASON_, TASK_DEF_KEY_, FORM_KEY_, PRIORITY_, DUE_DATE_, CATEGORY_, TENANT_ID_ ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
Parameters: 17(String), leave:1:20(String), 12(String), 12(String), 部门领导审批(String), null, null, null, null, 2015-05-05 15:27:45.268(Timestamp), null, null, null, null, deptLeaderAudit(String), null, 50(Integer), null, null, (String)
insert IdentityLinkEntity[id=18, type=candidate, groupId=deptLeader, taskId=17]
Preparing: insert into ACT_RU_IDENTITYLINK (ID_, REV_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_, PROC_DEF_ID_) values (?, 1, ?, ?, ?, ?, ?, ?)
Parameters: 18(String), candidate(String), null, deptLeader(String), 17(String), null, null
insert org.activiti.engine.impl.persistence.entity.HistoricIdentityLinkEntity@11c4ef6
Preparing: insert into ACT_HI_IDENTITYLINK (ID_, TYPE_, USER_ID_, GROUP_ID_, TASK_ID_, PROC_INST_ID_) values (?, ?, ?, ?, ?, ?)
Parameters: 18(String), candidate(String), null, deptLeader(String), 17(String), null
--- StartProcessInstanceCmd finished --------------------------------------------------------
start process of {key=leave, bkey=2, pid=12, variables={}}
可以看到,
每次启动流程实例,首先会去ACT_GE_PROPERTY表取next.dbid,即流程实例ID;
启动流程的核心的部分就是往ACT_RU_*表中添加当前运行流程实例信息,往ACT_HI_*表中记录流程实例历史数据,当前流程实例和流程任务同步添加到运行表和历史表中。
ACT_RU_EXECUTION表记录运行中流程实例,ACT_RU_TASK表记录运行中的流程实例当前任务,
当流程跳转时,ACT_RU_EXECUTION表中记录是更新的(update),ACT_RU_TASK表中记录是先新增(insert)新任务后删除(delete)旧任务。
ps:
关于流程变量,流程启动时会在act_ru_variable和act_hi_varinst表中都记录变量,记录流程发起人(对应bpmn中的activiti:initiator)的NAME_为applyUserId,TEXT_为流程启动用户,比如:kafeitu;
刚才也提到我们代码中可以设置其它流程变量,修改代码
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("testKey", "testValue");
然后测试启动一个新的流程,发现act_ru_variable和act_hi_varinst表中确实各多了一条记录
然后编写测试用例
/**
* 测试获取流程变量
* @throws Exception
*/
@Test
public void testGetVariable(){
String pid = "2524";
Map<String, Object> variables = taskService.getVariables(pid);
Set<String> keySet = variables.keySet();
for(String key : keySet){
System.out.println(key);
}
}
打印日志如:
--- starting GetTaskVariablesCmd --------------------------------------------------------
Running command with propagation REQUIRED
Preparing: select * from ACT_RU_TASK where ID_ = ?
Parameters: 2524(String)
Total: 1
Preparing: select * from ACT_RU_VARIABLE where TASK_ID_ = ?
Parameters: 2524(String)
Total: 0
Preparing: select * from ACT_RU_EXECUTION where ID_ = ?
Parameters: 2512(String)
Total: 1
[
Preparing: select * from ACT_RU_VARIABLE where EXECUTION_ID_ = ? and TASK_ID_ is null
Parameters: 2512(String)
Total: 3
--- GetTaskVariablesCmd finished --------------------------------------------------------
输出:
applyUserId
testKey
deptLeaderPass
可见这些变量都不是作为某个任务的变量存储在数据库中的,而是和整个流程实例关联。