学习资料:《Activiti实战》
第七章 Spring容器集成应用实例(五)普通表单
第六章中介绍了动态表单、外置表单。这里讲解第三种表单:普通表单。
普通表单的特点:
把表单内容写在表现层(JSP、JSF、HTML)文件中
一个用户任务对应一个页面
业务数据和流程数据分离
适用于业务相对固定但复杂、流程相对固定但表现层变化多的情况
因为普通表单中,业务数据和流程数据是分离的,所以存在统一事务管理的问题。要保证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>
form
webapp/WEB-INF/views/chapter7/leave/leaveApply.jsp如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<%@ include file="/common/global.jsp"%>
<%@ include file="/common/meta.jsp" %>
<%@ include file="/common/include-base-styles.jsp" %>
<link rel="stylesheet" href="${ctx}/js/common/plugins/timepicker.css">
<title>请假申请</title>
<script type="text/javascript" src="${ctx }/js/common/jquery.js"></script>
<script type="text/javascript" src="${ctx }/js/common/bootstrap.min.js"></script>
<script type="text/javascript" src="${ctx }/js/common/bootstrap-datepicker.js"></script>
<script type="text/javascript" src="${ctx }/js/common/plugins/bootstrap-timepicker.js"></script>
<script type="text/javascript">
$(function() {
$('.datepicker').datepicker();
$('.time').timepicker({
minuteStep: 10,
showMeridian: false
});
}); function beforeSend() {
$('input[name=startTime]').val($('#startDate').val() + ' ' + $('#startTime').val());
$('input[name=endTime]').val($('#endDate').val() + ' ' + $('#endTime').val());
}
</script>
</head>
<body>
<c:if test="${not empty message}">
<div id="message" class="alert alert-success">${message}</div>
<!-- 自动隐藏提示信息 -->
<script type="text/javascript">
setTimeout(function() {
$('#message').hide('slow');
}, 5000);
</script>
</c:if>
<form>
<!--表单的代码放在这里-->
</form>
</body>
</html>
(3)Controller
在上面的流程定义列表页面中,增加一个字段"操作",里面包含两个button,一个是启动,一个是删除。
当点击"启动时",应该要显示表单,并且点击申请时,流程启动。
@Controller
@RequestMapping(value = "/chapter7/leave")
public class LeaveController { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private LeaveManager leaveManager; @Autowired
private LeaveWorkflowService leaveService; @Autowired
private TaskService taskService; @Autowired
private RuntimeService runtimeService; @RequestMapping(value = {"apply", ""})
public String createForm(Model model) {
model.addAttribute("leave", new Leave());
return "/chapter7/leave/leave-apply";
} /**
* 启动请假流程
*/
@RequestMapping(value = "start", method = RequestMethod.POST)
public String startWorkflow(Leave leave, RedirectAttributes redirectAttributes, HttpSession session) {
try {
User user = UserUtil.getUserFromSession(session);
Map<String, Object> variables = new HashMap<String, Object>();
ProcessInstance processInstance = leaveService.startWorkflow(leave, user.getId(), 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:/chapter7/leave/apply";
} ...
}
(4)Service
前面提过,这里要实现业务和流程数据的分离。即业务数据存在一个表里,流程数据存在另一个表。但是又要将二者联系起来。
这里采取的办法是:
1 业务表:增加一个字段process_instance_id,方便从业务层面查询流程数据。
2 流程表:用entity的ID作为processDefinitionKey,方便从流程层面查询业务数据。
可以从代码中看出来,这里不只是单纯的启动流程。还进行了数据的存储与关联。
@Service
@Transactional
public class LeaveWorkflowService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
LeaveManager leaveManager; @Autowired
private IdentityService identityService; @Autowired
private RuntimeService runtimeService; @Autowired
private TaskService taskService; @Autowired
private RepositoryService repositoryService; /**
* 保存请假实体并启动流程
*/
public ProcessInstance startWorkflow(Leave entity, String userId, Map<String, Object> variables) {
if (entity.getId() == null) {
entity.setApplyTime(new Date());
entity.setUserId(userId);
}
leaveManager.save(entity);//持久化请假实体
String businessKey = entity.getId().toString();//实体保存后的ID,作为流程中的业务key
// 用来设置启动流程的人员ID,引擎会自动把用户ID保存到activiti:initiator中
identityService.setAuthenticatedUserId(userId); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", businessKey, variables);//将业务主ID设置为流程实例的key
String processInstanceId = processInstance.getId();
entity.setProcessInstanceId(processInstanceId);// 将流程实例的ID保存至业务表
logger.debug("start process of {key={}, bkey={}, pid={}, variables={}}", new Object[]{"leave", businessKey, processInstanceId, variables});
leaveManager.save(entity);
return processInstance;
} ...
}
7.5.3 任务读取