文章标题

三、组合起来,披萨订购流程

订购披萨的过程可以很好地定义在一个流程中。首先从构建一个高层次的流程开始,它定义了订购披萨的整体过程。

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事件并导致转移到披萨流程的结束状态。

3.3 构建订单

在识别完顾客之后,主流程的下一件事情就是确定他们想要什么类型的披萨。订单子流程就是用于提示用户创建披萨并将其放入订单中的,如下图:
这里写图片描述
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子流程,这也是接下来我们要介绍的流程。

3.4 支付

在披萨流程要结束的时候,最后的子流程提示用户输入他们的支付信息。这个简单的流程如图所示:
这里写图片描述
像其他子流程一样,支付自流程也使用元素接受一个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 实战>的第八章 ,参考代码订单项目示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值