文章标题
2015-07-12 14:46
441 查看
三、组合起来,披萨订购流程
订购披萨的过程可以很好地定义在一个流程中。首先从构建一个高层次的流程开始,它定义了订购披萨的整体过程。3.1 定义基本流程
一个新的披萨连锁店Spizza决定允许用户在线订购以减轻店面电话销售的压力。当顾客访问Spizza网站时,它们需要进行用户识别、选择一个或更多披萨添加到订单中、提供支付信息,然后提交订单并等待热呼又新鲜的披萨送过来。
图中方框代表了状态而箭头代表了转移。可以看到,订购披萨的整个流程非常简单且是线性的。在Spring Web Flow中,表示这个流程时很容易的。配置命名为order-flow.xml,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd" start-state="identifyCustomer"> <var name="order" class="com.paner.pizza.domain.Order" /> <var name="flowActions" class="com.paner.pizza.controller.PizzaFlowActions"/> <on-start> <set name="conversationScope.pizzaFlowActions" value="flowActions"></set> </on-start> <!-- 顾客子流程 --> <subflow-state id="identifyCustomer" subflow="lookupcustomer-flow"> <output name="customers" value="order.customer"/> <transition on="customerReady" to="buildOrder" /> </subflow-state> <!-- 订单子流程 --> <subflow-state id="buildOrder" subflow="showorder-flow"> <input name="order" value="order"/> <transition on="orderCreated" to="takePayment" /> </subflow-state> <!-- 支付流程 --> <subflow-state id="takePayment" subflow="takePayment-flow"> <input name="order" value="order"/> <transition on="paymentTaken" to="saveOrder" /> </subflow-state> <!-- 保存订单 --> <action-state id="saveOrder"> <evaluate expression="pizzaFlowActions.saveOrder(order)"></evaluate> <transition to="thankCustomer"></transition> </action-state> <!-- 感谢顾客 --> <view-state id="thankCustomer"> <transition to="endState"></transition> </view-state> <end-state id="endState"></end-state> <!-- 全局取消转移 --> <global-transitions> <transition on="cancle" to="endState"></transition> </global-transitions> </flow>
在流程定义中,你看到的第一件事就是order变量的声明。每次流程开始的时候,都会创建一个Order实例。Order类会包含关于订单的所有信息,包含顾客信息、订购的披萨以及支付详情。如程序清单:
package com.paner.pizza.domain; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class Order implements Serializable { /** * */ private static final long serialVersionUID = 1L; private Payment payment; private Customer customer; private List<Pizza> pizzas; public Order(){ pizzas = new ArrayList<Pizza>(); customer = new Customer(); } public Payment getPayment() { return payment; } public void setPayment(Payment payment) { this.payment = payment; } public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } public List<Pizza> getPizzas() { return pizzas; } public void setPizzas(List<Pizza> pizzas) { this.pizzas = pizzas; } public void addPizza(Pizza pizza){ pizzas.add(pizza); } }
接下来申请的变量就是流程的行为逻辑:
package com.paner.pizza.controller; import java.io.Serializable; import com.paner.pizza.domain.Customer; import com.paner.pizza.domain.Order; import com.paner.pizza.domain.Payment; import com.paner.pizza.domain.PaymentDetails; import com.paner.pizza.service.CustomerNotFoundException; public class PizzaFlowActions implements Serializable{ /** * */ private static final long serialVersionUID = 1L; public Customer lookupCustomer(String phoneNumber) throws CustomerNotFoundException{ if (phoneNumber.equalsIgnoreCase("15856915194")) { Customer customer = new Customer(); customer.setName("paner"); customer.setPhoneNumber("15856915133"); customer.setAddress("china"); customer.setCity("南京"); customer.setState("effective"); customer.setZipCode("211202"); return customer; } else { throw new CustomerNotFoundException("没找到该客户信息"); } } public Payment verifyPayment(PaymentDetails paymentDetails){ Payment payment = new Payment(); String temp = (paymentDetails.getPaymentType()).toString(); payment.setFinished(temp); return payment; } public void saveOrder(Order order) { System.out.println("保存订单"); } public boolean checkDeliveryArea(String zipCode) { if (zipCode.compareToIgnoreCase("999999")>0) { return false; } return true; } public void addCustomer(Customer customer) { System.out.println("将顾客信息保存到数据库中:"+customer.getName()); } }
流程定义的主要组成部分是流程的状态。默认情况下,流程定义文件中的第一个状态也会是流程访问中的第一个状态。在本例中,也就是identfyCustomer状态。
识别顾客、构建披萨订单以及支付这样的活动太复杂了,并不适合将其强行塞入一个状态。这是我们为何在后面将其单独定义为流程的原因。但是为了更好地整体了解披萨流程,这些活动都是以元素来进行展现的。
3.2 收集顾客信息
进入第一个子流程, 在每个披萨订单开始前的提问和回答阶段可以用下图的流程图表示:
这个流程比整体的披萨流程更有意思。这个流程不是线性的,而是在好几个位置根据不同的条件有了分支。
下面的程序展示了识别顾客的流程定义。
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd"> <var name="customers" class="com.paner.pizza.domain.Customer" /> <!-- 欢迎顾客 --> <view-state id="welcome" > <transition on="phoneEntered" to="lookupCustomer"></transition> </view-state> <!-- 查找顾客 --> <action-state id="lookupCustomer"> <evaluate result="customers" expression= "pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)"></evaluate> <transition to="registrationForm" on-exception= "com.paner.pizza.service.CustomerNotFoundException"></transition> <transition to="customerReady"></transition> </action-state> <!-- 注册新顾客 --> <view-state id="registrationForm" model="customers"> <on-entry> <evaluate expression="customers.phoneNumber = requestParameters.phoneNumber"></evaluate> </on-entry> <transition on="submit" to="checkDeliveryArea"></transition> </view-state> <!-- 检查配送区域 --> <decision-state id="checkDeliveryArea"> <if then="addCustomer" test="pizzaFlowActions.checkDeliveryArea(customers.zipCode)" else="deliveryWarning" /> </decision-state> <!-- 显示配送警告 --> <view-state id="deliveryWarning"> <transition on="accept" to="addCustomer"></transition> </view-state> <!-- 添加新顾客 --> <action-state id="addCustomer"> <evaluate expression="pizzaFlowActions.addCustomer(customers)"></evaluate> <transition to="customerReady"> </transition> </action-state> <end-state id="cancle"></end-state> <end-state id="customerReady"> <output name="customers"/> </end-state> <!-- 全局取消转移 --> <global-transitions> <transition on="cancle" to="endState"></transition> </global-transitions> </flow>
这个流程包含了几个新技巧,包括首次使用的
<decision-state>元素。因为它是pizza流程的子流程,所以它也可以接受order对象作为输入。
询问电话号码
Welcome状态是一个很简单的视图状态。它欢迎访问Spizza网站的顾客并要求他们输入电话号码。这个状态并没有什么特殊的。它有两个转移:如果从视图触发phoneEntered事件,转移会将流程定向到lookupCustomer,另外一个就是在全局转移中定义用来响应cancel事件的cancel转移。
welcome状态的有趣之处在于视图本身。视图welcome定义在/WEB-INF/jsp/welcome.jsp中,如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spizza</title> </head> <body> <h2>欢迎来到Spizza!</h2> <form:form> <input type="hidden" name="_flowExecutorKey" value="${flowExecutionKey}"/> <label>电话号码:</label> <input type="text" name="phoneNumber"/><br/> <br> <input type="submit" name="_eventId_phoneEntered" value="查找"/> </form:form> </body> </html>
这个简单的表提示用于输入其电话号码。但是有两个特殊的部分来驱动流程继续。
首先要注意的是隐藏的_flowExecutionKey输入域。当进入视图状态时,流程暂停并等待用户采取一些行为。赋予视图的流程执行键(flow execution key)就是一种返回流程的“回程票(claim ticker)”。当用户提交表单,流程执行键将会在_flowExecutionKey输入域中返回并在流程暂停的位置进行恢复。
还要注意提交按钮的名字。按钮名字的eventId部分是Spring Web Flow的一个线索,它表明了接下来要触发的时间。当点击这个按钮提交表单时,会触发phoneEntered事件进而转移到lookupCustomer。
下面是运行效果:(要运行还要把上面所缺少的类给补全,可以在这儿找到项目位置,然后根据需要把类补全)
开启订单
查找到会调到,订单区域:
查找不到顾客
回调到注册界面:
检查配送区域
在顾客提供其地址后,我们需要确认他的地址是否在配送范围之内。如果Spizza不能派送给他们,那么我们要让顾客知道并建议他们自己到店里自提披萨。
为了作出这个判断,我们使用了决策状态。决策状态checkDeliveryArea有一个if元素,它将顾客的邮政编码传递到pizzaFlowActions Bean的checkDeliveryArea()方法中。这个方法将返回一个Boolean值:如果顾客在配送区域内则是true,否则为false。、
如果顾客在配送区域内的话,那么流程转移到addCustomer状态。否则,顾客带入到deiveryWarning视图状态。deliveryWarning背后的而试图就是WEB-INF/jsp/deliveryWarning.jsp,如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spizza</title> </head> <body> <h2>地址不可达</h2> <p>不好意思,你的地址不在我们的配送范围内;若继续订购,请到店里来自取。</p> <a href="${flowExecutorUrl}&_eventId=accpet">继续,我将自己取。</a> <a href="${flowExecutorUrl}&_eventId=cancel">取消订单</a> </body> </html>
运行效果:
存储顾客数据(这里可以用数据库存储用户信息)
当流程抵达addCustomer状态时,用户已经输入了他们的地址。为了将来使用,这个地址需要一某种方式存储起来。addCustomer状态有一个元素,它会调用pizzaFlowActions Bean的addCustomner()方法,并将customer流程参数传递进去。
一旦这个过程完成,他会执行默认的转移,流程将会转移到ID为customerReady的结束状态。
结束流程
通常,流程的结束状态并不会那么有意思。但是这个流程中,它不仅仅只有一个结束状态,而是两个。当子流程完成时,它会触发一个与结束状态ID相同的流程事件。如果流程只有一个结束状态的话,那么它始终会触发相同的事件。但是如果有两个或更多的结束状态,流程则会影响到调用状态的执行方向。
当customer流程完成所有的路径后,它最终会到达ID为customerReady的结束状态。当调用它的披萨流程恢复时,它会接受到一个customerReady事件,这个事件将使得流程转移到buildOrder状态。
要注意的是customerReady结束状态包含了一个元素。在流程中这个元素等同于java中的return语句。它从子流程中传递一些数据到调用流程。在本实例中,元素返回customer流程变量,这样披萨流程中的identifyCustomer子流程状态可以将其指定给订单。
另一方面,如果在识别顾客流程的任意地方触发了cancel事件,将会通过ID为cancel的结束状态退出流程,这也会在披萨流程中触发cancel事件并导致转移到披萨流程的结束状态。
[b]3.3 构建订单[/b]
在识别完顾客之后,主流程的下一件事情就是确定他们想要什么类型的披萨。订单子流程就是用于提示用户创建披萨并将其放入订单中的,如下图:
showOrder状态位于订单子流程的中心位置。这是用户进入这个流程时看到的第一个状态,它也是用户在添加披萨到订单后要转移到的状态。它展现了订单的当前状态并允许用户添加其他的披萨到订单中。
要添加披萨到订单时,流程会转移到createPizza状态。这是另外一个视图状态,允许用户选择披萨的尺寸和面饼上面的配料。在这里,用户可以添加或取消披萨,两个事件都会是流程转移会showOrder状态。
从showOrder状态,用户可能提交订单也可能取消订单。两种选择都会结束订单子流程,但是主流程会根据选择不同进入不同的执行路径。
程序清单显示了如何将图8.4中所述的内容转变成Spring Web Flow定义。
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd" > <input name="order" required="true"></input> <view-state id="showOrder"> <transition on="createPizza" to="createPizza"></transition> <transition on="checkout" to="orderCreated"></transition> <transition on="cancel" to="endState"></transition> </view-state> <view-state id="createPizza" model="flowScope.pizza" view="createPizza"> <on-entry> <set name="flowScope.pizza" value="new com.paner.pizza.domain.Pizza()"></set> <evaluate result="viewScope.toppingList" expression="T(com.paner.pizza.domain.Topping).asList()"></evaluate> </on-entry> <transition on="addPizza" to="showOrder"> <evaluate expression="order.addPizza(flowScope.pizza)"></evaluate> </transition> <transition on="cancel" to="showOrder"></transition> </view-state> <end-state id="endState"></end-state> <end-state id="orderCreated"></end-state> </flow>
这个子流程实际上会操作主流程创建的Order对象。因此,我们需要以某种方式将Order从主流程传递到子流程。你可能还记得在上一节我们使用了元素来讲Order传递进流程。在这里,使用它接受Order对象。如果你觉得这个流程与Java中的方法有些类似,那么这里使用的元素就有效定义了这个子流程的签名。这个流程需要一个名为order的参数。
接下来我们看到showOrder状态,它是一个基本的视图状态并具有3个不同的转移,分别用于创建披萨、提交订单以及取消订单。
createPizza状态更有意思一些。它的视图是一个表单,这个表单可以添加新的Pizza对象订单中。元素添加了一个新的Pizza对象到流程作用域内,当表单提交时它将填充进订单。需要注意的是,这个视图状态引用的model是流程作用域内同一个Pizza对象。Pizza对象将绑定到创建披萨的表单中,如程序清单8.9所示:
<%@page import="com.paner.pizza.domain.Pizza"%> <%@page import="java.util.List"%> <%@page import="com.paner.pizza.domain.Order"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spizza</title> </head> <body> <h2>${order.getCustomer().getName()},请开启您的披萨订单</h2> <% Order order = (Order)request.getAttribute("order"); if(order.getPizzas().size()>0) { %> <p> 你已经选择的披萨种类:</p> <% } for(Pizza pizza : order.getPizzas()) { %> <p>尺寸:<%=pizza.getSize() %></p> <p>馅料:<%=pizza.getTopping().toString() %></p> <% } %> <form:form> <input type="hidden" name="_flowExecutorKey" value="${flowExecutionKey}"/> <input type="submit" name="_eventId_createPizza" value="请选择订购的披萨"/> <input type="submit" name="_eventId_checkout" value="付钱"/> <input type="submit" name="_eventId_cancel" value="取消订单"/> </form:form> </body> </html>
效果图:
选择过后:
有两种方法来结束这个流程。用户可以点击showOreder视图中的Cancel按钮或者Checkout按钮。这两种操作都会式流程转移到一个。但是选择的结束状态ID决定了退出这个流程时触发事件,进而最终确定了主流程的下一步行为。主流程要么基于cancel要么基于orderCreated事件进行状态转移。在前者情况下,外边的流程会结束;在后者的情况下,它将转移到takePayment子流程,这也是接下来我们要介绍的流程。
[b]3.4 支付[/b]
在披萨流程要结束的时候,最后的子流程提示用户输入他们的支付信息。这个简单的流程如图所示:
像其他子流程一样,支付自流程也使用元素接受一个Order对象作为输入。
可以看到,进入支付子流程的时候,用户会到达takePayment状态。这是一个视图状态,在这里用户可以选择使用信用卡,支票或现金进行支付。提交支付信息后,将进入verifyPayment状态。这是一个行为状态,它将校验支付信息是否可以接受。
使用XML定义支付流程如下:
<?xml version="1.0" encoding="UTF-8"?> <flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.4.xsd"> <input name="order" required="true"/> <view-state id="takePayment" model="flowScope.paymentDetails"> <on-entry> <set name="flowScope.paymentDetails" value="new com.paner.pizza.domain.PaymentDetails()"></set> <evaluate result="viewScope.paymentTypeList" expression="T(com.paner.pizza.domain.PaymentType).asList()"></evaluate> </on-entry> <transition on="paymentSubmitted" to="verifyPayment"></transition> <transition on="cancel" to="cancel"></transition> </view-state> <action-state id="verifyPayment"> <evaluate result="order.payment" expression="pizzaFlowActions.verifyPayment(flowScope.paymentDetails)"></evaluate> <transition to="paymentTaken"></transition> </action-state> <end-state id="endState"></end-state> <end-state id="paymentTaken"></end-state> </flow>
付账的jsp内容:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Spizza</title> </head> <body> <h2>请为您的披萨付款</h2> <form:form commandName="paymentDetails"> <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey} }" /> <label>支付方式:</label><br> <form:radiobuttons path="paymentType" items="${paymentTypeList}" /><br><br> <input type="submit" class="button" name="_eventId_paymentSubmitted" value="付款"/> <input type="submit" class="button" name="_eventId_cancek" value="取消"/> </form:form> </body> </html>
运行效果图:
更多详细请看:
<spring 实战>的第八章 ,参考代码订单项目示例
相关文章推荐
- 判断两个线段是否相交
- android支付平台集成调研
- BZOJ 1008 [HNOI2008]越狱
- BZOJ4184 : shallot
- 关于网上一些兼容性问题的处理总结
- [LeetCode] Lowest Common Ancestor of a Binary Search Tree
- 大整数乘法
- Windows进程内部堆的操作
- 内幕视角揭秘那些年的微软和诺基亚
- C语言之基本算法30—数组的灵活应用(一个正整数的各位数字平方和)
- Embedded_SW_模块化]嵌入式C语言工程文件组织_保持更新
- [LeetCode] Merge k Sorted Lists
- ubuntu14.04下安装node.js
- Reverse Linked List
- java基础——Vector集合知识点
- 快速排序
- from __future__ import absolute_import
- 【最短路】poj2472 SPFA
- Elasticsearch集群无法自动集群处理
- 强制手机浏览器竖屏