學習資料:《Activiti實戰》
第七章 Spring容器集成應用實例(五)普通表單
第六章中介紹了動態表單、外置表單。這里講解第三種表單:普通表單。
普通表單的特點:
1 把表單內容寫在表現層(JSP、JSF、HTML)文件中 2 一個用戶任務對應一個頁面 3 業務數據和流程數據分離 4 適用於業務相對固定但復雜、流程相對固定但表現層變化多的情況
因為普通表單中,業務數據和流程數據是分離的,所以存在統一事務管理的問題。要保證Activiti和業務數據操作在同一個事務中執行。前面集成Spring時的事務管理器的配置可表明這點。本節基於第六章的主要針請假流程,對數據和表單都分離的情況下,采用普通表單實現該功能。
本節采用Spring、SpringMVC和Hibernete。
7.5.1 業務建模
(1)表結構
(2)其他
DAO和Manger用來針對請假實體的CRUD,Leave-workflowService用來處理流程相關操作。
7.5.2 啟動流程
(1)部署流程
在啟動流程之前,先部署流程。
第六章已經實現過這個頁面,不再提。總之,點擊瀏覽,選擇文件,然后submit,將chapter7/leave.bpmn和leave.png打包部署。部署完畢之后,在該列表頁面中,會出現一個新的processDefinition記錄。
(2)jsp
普通表單使用時,需要將表單內容保存在一個單獨的文件中。這里采用jsp文件格式。
表單設計如下:

1 <form action="${ctx }/chapter7/leave/start" class="form-horizontal" method="post" onsubmit="beforeSend()"> 2 <input type="hidden" name="startTime" /> 3 <input type="hidden" name="endTime" /> 4 <fieldset> 5 <legend><small>請假申請</small></legend> 6 <div id="messageBox" class="alert alert-error input-large controls" style="display:none">輸入有誤,請先更正。</div> 7 <div class="control-group"> 8 <label for="loginName" class="control-label">請假類型:</label> 9 <div class="controls"> 10 <select id="leaveType" name="leaveType" class="required"> 11 <option>公休</option> 12 <option>病假</option> 13 <option>調休</option> 14 <option>事假</option> 15 <option>婚假</option> 16 </select> 17 </div> 18 </div> 19 <div class="control-group"> 20 <label for="name" class="control-label">開始時間:</label> 21 <div class="controls"> 22 <input type="text" id="startDate" class="datepicker input-small" data-date-format="yyyy-mm-dd" /> 23 <input type="text" id="startTime" class="time input-small" /> 24 </div> 25 </div> 26 <div class="control-group"> 27 <label for="plainPassword" class="control-label">結束時間:</label> 28 <div class="controls"> 29 <input type="text" id="endDate" class="datepicker input-small" data-date-format="yyyy-mm-dd" /> 30 <input type="text" id="endTime" class="time input-small" /> 31 </div> 32 </div> 33 <div class="control-group"> 34 <label for="groupList" class="control-label">請假原因:</label> 35 <div class="controls"> 36 <textarea name="reason"></textarea> 37 </div> 38 </div> 39 <div class="form-actions"> 40 <button type="submit" class="btn"><i class="icon-play"></i>啟動流程</button> 41 </div> 42 </fieldset> 43 </form>
webapp/WEB-INF/views/chapter7/leave/leaveApply.jsp如下:

1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 <!DOCTYPE html> 3 <html> 4 <head> 5 <%@ include file="/common/global.jsp"%> 6 <%@ include file="/common/meta.jsp" %> 7 <%@ include file="/common/include-base-styles.jsp" %> 8 <link rel="stylesheet" href="${ctx}/js/common/plugins/timepicker.css"> 9 <title>請假申請</title> 10 <script type="text/javascript" src="${ctx }/js/common/jquery.js"></script> 11 <script type="text/javascript" src="${ctx }/js/common/bootstrap.min.js"></script> 12 <script type="text/javascript" src="${ctx }/js/common/bootstrap-datepicker.js"></script> 13 <script type="text/javascript" src="${ctx }/js/common/plugins/bootstrap-timepicker.js"></script> 14 <script type="text/javascript"> 15 $(function() { 16 $('.datepicker').datepicker(); 17 $('.time').timepicker({ 18 minuteStep: 10, 19 showMeridian: false 20 }); 21 }); 22 23 function beforeSend() { 24 $('input[name=startTime]').val($('#startDate').val() + ' ' + $('#startTime').val()); 25 $('input[name=endTime]').val($('#endDate').val() + ' ' + $('#endTime').val()); 26 } 27 </script> 28 </head> 29 <body> 30 <c:if test="${not empty message}"> 31 <div id="message" class="alert alert-success">${message}</div> 32 <!-- 自動隱藏提示信息 --> 33 <script type="text/javascript"> 34 setTimeout(function() { 35 $('#message').hide('slow'); 36 }, 5000); 37 </script> 38 </c:if> 39 <form> 40 <!--表單的代碼放在這里--> 41 </form> 42 </body> 43 </html>
(3)Controller
在上面的流程定義列表頁面中,增加一個字段"操作",里面包含兩個button,一個是啟動,一個是刪除。
當點擊"啟動時",應該要顯示表單,並且點擊申請時,流程啟動。
1 @Controller 2 @RequestMapping(value = "/chapter7/leave") 3 public class LeaveController { 4 5 private Logger logger = LoggerFactory.getLogger(getClass()); 6 7 @Autowired 8 private LeaveManager leaveManager; 9 10 @Autowired 11 private LeaveWorkflowService leaveService; 12 13 @Autowired 14 private TaskService taskService; 15 16 @Autowired 17 private RuntimeService runtimeService; 18 19 @RequestMapping(value = {"apply", ""}) 20 public String createForm(Model model) { 21 model.addAttribute("leave", new Leave()); 22 return "/chapter7/leave/leave-apply"; 23 } 24 25 /** 26 * 啟動請假流程 27 */ 28 @RequestMapping(value = "start", method = RequestMethod.POST) 29 public String startWorkflow(Leave leave, RedirectAttributes redirectAttributes, HttpSession session) { 30 try { 31 User user = UserUtil.getUserFromSession(session); 32 Map<String, Object> variables = new HashMap<String, Object>(); 33 ProcessInstance processInstance = leaveService.startWorkflow(leave, user.getId(), variables); 34 redirectAttributes.addFlashAttribute("message", "流程已啟動,流程ID:" + processInstance.getId()); 35 } catch (ActivitiException e) { 36 if (e.getMessage().indexOf("no processes deployed with key") != -1) { 37 logger.warn("沒有部署流程!", e); 38 redirectAttributes.addFlashAttribute("error", "沒有部署請假流程"); 39 } else { 40 logger.error("啟動請假流程失敗:", e); 41 redirectAttributes.addFlashAttribute("error", "系統內部錯誤!"); 42 } 43 } catch (Exception e) { 44 logger.error("啟動請假流程失敗:", e); 45 redirectAttributes.addFlashAttribute("error", "系統內部錯誤!"); 46 } 47 return "redirect:/chapter7/leave/apply"; 48 } 49 50 ... 51 }
(4)Service
前面提過,這里要實現業務和流程數據的分離。即業務數據存在一個表里,流程數據存在另一個表。但是又要將二者聯系起來。
這里采取的辦法是:
1 業務表:增加一個字段process_instance_id,方便從業務層面查詢流程數據。 2 流程表:用entity的ID作為processDefinitionKey,方便從流程層面查詢業務數據。
可以從代碼中看出來,這里不只是單純的啟動流程。還進行了數據的存儲與關聯。
1 @Service 2 @Transactional 3 public class LeaveWorkflowService { 4 5 private Logger logger = LoggerFactory.getLogger(getClass()); 6 7 @Autowired 8 LeaveManager leaveManager; 9 10 @Autowired 11 private IdentityService identityService; 12 13 @Autowired 14 private RuntimeService runtimeService; 15 16 @Autowired 17 private TaskService taskService; 18 19 @Autowired 20 private RepositoryService repositoryService; 21 22 /** 23 * 保存請假實體並啟動流程 24 */ 25 public ProcessInstance startWorkflow(Leave entity, String userId, Map<String, Object> variables) { 26 if (entity.getId() == null) { 27 entity.setApplyTime(new Date()); 28 entity.setUserId(userId); 29 } 30 leaveManager.save(entity);//持久化請假實體 31 String businessKey = entity.getId().toString();//實體保存后的ID,作為流程中的業務key 32 33 // 用來設置啟動流程的人員ID,引擎會自動把用戶ID保存到activiti:initiator中 34 identityService.setAuthenticatedUserId(userId); 35 36 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", businessKey, variables);//將業務主ID設置為流程實例的key 37 String processInstanceId = processInstance.getId(); 38 entity.setProcessInstanceId(processInstanceId);// 將流程實例的ID保存至業務表 39 logger.debug("start process of {key={}, bkey={}, pid={}, variables={}}", new Object[]{"leave", businessKey, processInstanceId, variables}); 40 leaveManager.save(entity); 41 return processInstance; 42 } 43 44 ... 45 }
7.5.3 任務讀取