上一章我们创建了配料选择页面,用户在选择好订单后,我们的控制器会跳转到一个订单页面。目前我们的订单页面还没有实现,将在这一章完成它。
前面一章,我们在接收到用户的配料选择信息后,控制器的代码会跳转到订单页面,这个跳转,是通过“重定向”来实现的,语法就是返回的字符串中带有“redirect:”前缀。具体是这样的:
return "redirect:/orders/current";
它表示在方法完成后的页面跳转中,用户的浏览器将会重定向到相对路径是“/order/current”中去。
重定向到这个路径之后做什么呢?同样,这样会触发一个控制器,由这个控制器来决定后面怎么做。那么接下来我们来设计处理“/order/current”的控制器。
一、订单控制器
同样的,我们会使用@Controller、@RequestMapping和@GetMapping注解来实现这个控制器,具体代码如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/current")
public String OrderForm(Model model){
model.addAttribute("order",new Order());
return "orderForm";
}
}
同样也使用了@Slf4j注解,以备后续使用日志功能。@RequestMapping和@GetMapping的组合使用,使得这个控制器能够处理“/order/current”的Get请求。
在IDE的报错中我们可以看到,Order类的实现还没有开发,我们接下来写Order类。
二、订单类的开发
订单类仍然是个领域对象,实现了具体的业务逻辑。领域的概念我们在第二章已经简单介绍了,它是领域驱动设计中的一个概念,可以将业务逻辑和程序控制逻辑相分离,领域对象就是单纯的体现业务逻辑,这里的订单,明显就是一个领域业务概念。
订单类的代码如下。
@Data
public class Order {
private String name;
private String street;
private String city;
private String state;
private String zip;
private String ccNumber;
private String ccExpiration;
private String ccCVV;
}
我们仍然使用了lombok类库的@Data注解,帮助我们后台实现了默认的get、set方法。
三、订单视图
我们在第一部分的控制器中,控制器会返回到一个名为 "orderForm"的逻辑视图。现在我们实现这个视图,仍然采用Thymeleaf模板。具体代码如下。
<!-- tag::allButValidation[] -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<a th:href="@{/design}" id="another">Design another taco</a><br/>
<div th:if="${#fields.hasErrors()}">
<span class="validationError">
Please correct the problems below and resubmit.
</span>
</div>
<h3>Deliver my taco masterpieces to...</h3>
<label for="name">Name: </label>
<input type="text" th:field="*{name}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="street">Street address: </label>
<input type="text" th:field="*{street}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('street')}"
th:errors="*{street}">Street Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="city">City: </label>
<input type="text" th:field="*{city}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('city')}"
th:errors="*{city}">City Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="state">State: </label>
<input type="text" th:field="*{state}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('state')}"
th:errors="*{state}">State Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="zip">Zip code: </label>
<input type="text" th:field="*{zip}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('zip')}"
th:errors="*{zip}">Zip Error</span>
<!-- tag::allButValidation[] -->
<br/>
<h3>Here's how I'll pay...</h3>
<!-- tag::validatedField[] -->
<label for="ccNumber">Credit Card #: </label>
<input type="text" th:field="*{ccNumber}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccNumber')}"
th:errors="*{ccNumber}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<!-- end::validatedField[] -->
<br/>
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpiration}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccExpiration')}"
th:errors="*{ccExpiration}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccCVV')}"
th:errors="*{ccCVV}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<br/>
<input type="submit" value="Submit order"/>
</form>
</body>
</html>
<!-- end::allButValidation[] -->
这是一个典型的Tymeleaf模板,这里特别的是,在这段代码中,特别指定了表单的“action”。如果不特别指定“action”,表单的POST动作将会以表单相同的URL提交到控制器,如果指定了,则按指定的路径提交,这里是按照“/orders”的路径提交。
四、Post控制器
既然订单页面会按照“/orders”的路径提交POST请求,那么我们还要添加其控制器。
在OrderController控制器类中增加如下代码:
@PostMapping
public String ProcessOrder(Order order){
log.info("order submitted:" + order);
return "redirect:/";
}
现在在这个控制器中,会将用户的请求继续重定向到根路径,就是我们默认的主页视图了。
五、结论 忘记了重要的一点--测试
我们一直没有利用好Spring提供的自动测试框架。后面一章我们专门讨论下自动化测试如何实现。