前言
本篇將完成WEB層的設(shè)計(jì)與開(kāi)發(fā)狸捕,包括:
- Spring MVC與Spring、MyBatis整合
- 設(shè)計(jì)并實(shí)現(xiàn)Restful接口
一、Spring MVC與Spring整合
之前Spring與MyBatis已經(jīng)進(jìn)行過(guò)整合了,當(dāng)通過(guò)DispatcherServlet加載Spring MVC的時(shí)候,DispatcherServlet同時(shí)會(huì)把Spring相關(guān)的配置也會(huì)整合到Spring MVC中昔穴,這樣就實(shí)現(xiàn)了三個(gè)框架的整合,即MyBatis+Spring+Spring MVC
打開(kāi)web.xml提前,在Eclipse中位置是src/main/webapp/WEB-INF
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>seckill-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置Spring MVC需要加載的配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>
首先配置的是Spring MVC中央控制器的Servlet吗货,即DispatcherServlet,所有Spring MVC的請(qǐng)求都由DispatcherServlet來(lái)分發(fā)
然后配置Spring MVC需要加載的配置文件狈网,所有在spring目錄下的xml配置文件都要加載進(jìn)來(lái)宙搬,之前完成的配置文件有spring-dao.xml和spring-service.xml
<servlet-mapping>
<servlet-name>seckill-dispatcher</servlet-name>
<!-- 默認(rèn)匹配所有請(qǐng)求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
接著是servlet-mapping,默認(rèn)匹配所有請(qǐng)求拓哺,也就是所有請(qǐng)求都會(huì)被DispatcherServlet攔截
在src\main\resources\spring下新建spring-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<beans>
把以上內(nèi)容復(fù)制到spring-web.xml
開(kāi)始配置Spring MVC
<!-- 開(kāi)啟Spring MVC注解模式 -->
<mvc:annotation-driven/>
開(kāi)啟Spring MVC注解模式勇垛,這一步是一個(gè)簡(jiǎn)化配置,提供了以下功能:
- 自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping士鸥,也就是默認(rèn)地URL到Handler的映射是通過(guò)注解的方式
- 自動(dòng)注冊(cè)AnnotationMethodHandlerAdapter闲孤,這個(gè)是基于注解的Handler適配器
- 數(shù)據(jù)綁定
- 數(shù)字和日期的format,也就是轉(zhuǎn)換烤礁,例如@NumberFormat讼积,@DataTimeFormat
- 提供xml,json默認(rèn)讀寫(xiě)支持
總而言之脚仔,我們可以通過(guò)不同的注解來(lái)完成以上的功能勤众,當(dāng)然這些功能不僅可以使用注解,也可以使用額外的xml配置文件甚至是編程的方式鲤脏,根據(jù)項(xiàng)目的不同采用不同的方式
<!-- 靜態(tài)資源默認(rèn)servlet配置 -->
<mvc:default-servlet-handler/>
前面配置了servlet-mapping们颜,映射路徑為“/”,使用這樣配置的話,就需要這個(gè)處理方式掌桩,有兩個(gè)作用:
- 加入對(duì)靜態(tài)資源的處理,即js姑食、png等
- 允許使用"/"做整體映射
接著配置jsp
<!-- 配置輸出樣式為JSP 顯示ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
也就是需要默認(rèn)的文檔輸出是jsp和json波岛,不過(guò)json不需要我們提供,因?yàn)樵陂_(kāi)始配置Spring MVC注解模式的時(shí)候音半,已經(jīng)提供了json的讀寫(xiě)支持则拷,只要對(duì)應(yīng)到相應(yīng)的注解就行
因?yàn)榭赡芤玫絜l表達(dá)式或者jstl標(biāo)簽,所以配置一個(gè)viewClass
還要配置一個(gè)識(shí)別JSP文件前綴的屬性曹鸠,設(shè)置jsp文件存放在/WEB-INF/jsp目錄下煌茬,再加上后綴
<!-- 掃描WEB相關(guān)的bean -->
<context:component-scan base-package="org.seckill.web"/>
掃描WEB相關(guān)的bean
接著按照我粗淺的理解,簡(jiǎn)單的說(shuō)一下Spring MVC的運(yùn)行流程:
1彻桃、用戶(hù)發(fā)送的請(qǐng)求坛善,所有的請(qǐng)求都會(huì)映射到DispatcherServlet,這是一個(gè)中央控制器的Servlet邻眷,這個(gè)Servlet會(huì)攔截所有的請(qǐng)求眠屎,對(duì)應(yīng)在項(xiàng)目中應(yīng)該就是web.xml中配置的servlet-mapping標(biāo)簽
2、DispatcherServlet默認(rèn)的會(huì)使用DefaultAnnotation HandlerMapping肆饶,主要的作用就是映射URL改衩,哪個(gè)URL對(duì)應(yīng)哪個(gè)handler,對(duì)應(yīng)在項(xiàng)目中就是在spring-web.xml中mvc:annotation-driven驯镊,即開(kāi)啟Spring MVC的注解模式
3葫督、DispatcherServlet默認(rèn)的會(huì)使用DefaultAnnotation HandlerAdapter,用于做Handler適配板惑,對(duì)應(yīng)在項(xiàng)目中就是在spring-web.xml中mvc:annotation-driven橄镜,即開(kāi)啟Spring MVC的注解模式
4、DefaultAnnotation HandlerAdapter最終會(huì)銜接這次開(kāi)發(fā)的SeckillController冯乘,最終的產(chǎn)生就是ModelAndView
5蛉鹿、ModelAndView會(huì)與中央控制器DispatcherServlet進(jìn)行交互
6、通過(guò)第五步的交互往湿,DispatcherServlet會(huì)發(fā)現(xiàn)應(yīng)用的是InternalResource ViewResolver妖异,這個(gè)其實(shí)就是jsp默認(rèn)的View
7、通過(guò)第五步的交互领追,DispatcherServlet也會(huì)把Model和list.jsp相結(jié)合他膳,
8、最終返回給用戶(hù)
實(shí)際開(kāi)發(fā)的時(shí)候只有藍(lán)色的部分绒窑,其他的可以使用默認(rèn)的注解形式棕孙,非常方便地映射URL,去對(duì)應(yīng)到相應(yīng)的邏輯,同時(shí)控制輸出數(shù)據(jù)和對(duì)應(yīng)的頁(yè)面
二蟀俊、設(shè)計(jì)Restful接口
一種軟件架構(gòu)風(fēng)格钦铺,設(shè)計(jì)風(fēng)格而不是標(biāo)準(zhǔn),只是提供了一組設(shè)計(jì)原則和約束條件肢预。它主要用于客戶(hù)端和服務(wù)器交互類(lèi)的軟件矛洞。基于這個(gè)風(fēng)格設(shè)計(jì)的軟件可以更簡(jiǎn)潔烫映,更有層次沼本,更易于實(shí)現(xiàn)緩存等機(jī)制。--百度百科
通過(guò)這個(gè)項(xiàng)目锭沟,我對(duì)Restful接口的理解是:
這是一種優(yōu)雅的URL表達(dá)方式抽兆,通過(guò)這種URL表達(dá)式可以明顯的感知到這個(gè)URL代表的是什么業(yè)務(wù)場(chǎng)景或者什么的數(shù)據(jù)、資源
以下是本項(xiàng)目的URL設(shè)計(jì):
- /seckill/list:秒殺列表族淮,GET方式
- /seckill/{id}/detail:詳情頁(yè)辫红,GET方式
- /seckill/time/now:系統(tǒng)時(shí)間,通過(guò)系統(tǒng)時(shí)間為基準(zhǔn)祝辣,對(duì)秒殺操作進(jìn)行提前的計(jì)時(shí)的操作邏輯厉熟,GET方式
- /seckill/{id}/exposer:暴露秒殺,通過(guò)這個(gè)URL才能拿到最后要執(zhí)行秒殺操作的URL较幌,POST方式
- /sekcill/{id}/{md5}/execution:執(zhí)行秒殺揍瑟,POST方式
三、使用Spring MVC實(shí)現(xiàn)Restful接口
在org.seckill包下新建一個(gè)web包乍炉,用于存放所有的controller绢片,新建一個(gè)SeckillController類(lèi)
@Controller
@RequestMapping("/seckill")
public class SeckillController
標(biāo)注這個(gè)類(lèi)是一個(gè)Controller,使用@Controller注解岛琼,目的是將這個(gè)類(lèi)放入Spring容器當(dāng)中
還要加上一個(gè)@RequestMapping注解底循,代表的是模塊,由于我們使用比較規(guī)范的URL設(shè)計(jì)風(fēng)格槐瑞,所有的URL應(yīng)該是:
/模塊/資源/{id}/更加細(xì)分
要獲取列表頁(yè)熙涤,也就是要調(diào)用Service
//實(shí)例化日志對(duì)象,導(dǎo)入org.slf4j包
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SeckillService seckillService;
將Service注入到當(dāng)前的Controller下困檩,SeckillService在Spring容器中只有一個(gè)祠挫,Spring容器根據(jù)類(lèi)型匹配,會(huì)直接找到bean的實(shí)例悼沿,然后注入到當(dāng)前的Controller下
1等舔、秒殺列表頁(yè)
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model){
//獲取列表頁(yè)
List<Seckill> list = seckillService.getSeckillList();
model.addAttribute("list", list);
return "list";
}
參數(shù)model就是用來(lái)存放渲染list.jsp的數(shù)據(jù)
@RequestMapping(value = "/list", method = RequestMethod.GET)
這里Spring MVC的注解映射使用的是@RequestMapping注解,其中value的值是二級(jí)URL糟趾,后面的method屬性限制了http請(qǐng)求的方式慌植,這個(gè)方法只接收GET方式的http請(qǐng)求甚牲,如果是POST請(qǐng)求,Spring MVC將不會(huì)做映射
@RequestMapping注解它支持很多種URL映射:
- 支持標(biāo)準(zhǔn)的URL
- 支持Ant風(fēng)格URL蝶柿,即
丈钙?、*交汤、**
等字符-
?
表示匹配一個(gè)字符 -
*
表示匹配任意字符 -
**
表示匹配任意URL路徑
-
- 帶{}占位符的URL
舉個(gè)栗子:
/user/*/creation可以匹配/user/aaa/creation雏赦、/user/bbb/creation等URL
/user/**/creation可以匹配/user/creation、/user/aaa/bbb/creation等URL
/user/{userId}可以匹配user/213蜻展、user/abc等URL 123、abc可以以參數(shù)的方式傳入
/company/{companyId}/user/{userId}/detail匹配/company/123/user/456/detail等URL
在list方法中邀摆,通過(guò)實(shí)例化的SeckillService調(diào)用其中的方法
/**
* 查詢(xún)所有秒殺商品記錄
* @return
*/
List<Seckill> getSeckillList();//這是SeckillService接口中的方法
//獲取列表頁(yè)
List<Seckill> list = seckillService.getSeckillList();
model.addAttribute("list", list);
return "list";
model就是用來(lái)存放數(shù)據(jù)的纵顾,并把返回的數(shù)據(jù)通過(guò)字符串進(jìn)行標(biāo)識(shí),最后返回一個(gè)字符串栋盹,那么這里為什么返回一個(gè)字符串施逾?這個(gè)字符串會(huì)被怎么處理?
之前介紹的Spring MVC運(yùn)行流程
HandlerAdapter在對(duì)Handler例获,即SeckillController進(jìn)行處理之后會(huì)返回一個(gè)ModelAndView對(duì)象汉额,在獲得了ModelAndView對(duì)象之后,Spring就需要把該View渲染給用戶(hù)榨汤,即返回給瀏覽器蠕搜。在這個(gè)渲染的過(guò)程中,發(fā)揮作用的就是ViewResolver和View
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
在spring-web.xml文件中使用的ViewResolver是InternalResourceViewResolver
InternalResourceViewResolver 會(huì)把返回的視圖名稱(chēng)都解析為 InternalResourceView 對(duì)象收壕, InternalResourceView 會(huì)把 Controller 處理器方法返回的模型屬性都存放到對(duì)應(yīng)的 request 屬性中妓灌,然后通過(guò) RequestDispatcher 在服務(wù)器端把請(qǐng)求 forword 重定向到目標(biāo) URL 。比如在 InternalResourceViewResolver 中定義了 prefix=/WEB-INF/ 蜜宪, suffix=.jsp 虫埂,然后請(qǐng)求的 Controller 處理器方法返回的視圖名稱(chēng)為 test ,那么這個(gè)時(shí)候 InternalResourceViewResolver 就會(huì)把 test 解析為一個(gè) InternalResourceView 對(duì)象圃验,先把返回的模型屬性都存放到對(duì)應(yīng)的 HttpServletRequest 屬性中掉伏,然后利用 RequestDispatcher 在服務(wù)器端把請(qǐng)求 forword 到 /WEB-INF/test.jsp
這就是 InternalResourceViewResolver 一個(gè)非常重要的特性,我們都知道存放在 /WEB-INF/ 下面的內(nèi)容是不能直接通過(guò) request 請(qǐng)求的方式請(qǐng)求到的澳窑,為了安全性考慮斧散,我們通常會(huì)把 jsp 文件放在 WEB-INF 目錄下,而 InternalResourceView 在服務(wù)器端跳轉(zhuǎn)的方式可以很好的解決這個(gè)問(wèn)題
2摊聋、秒殺詳情頁(yè)
@RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckillId, Model model){
if(seckillId == null){
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
if(seckill == null){
return "forward:/seckill/list";
}
model.addAttribute("seckill", seckill);
return "detail";
}
之前說(shuō)過(guò)颅湘,@RequestMapping注解支持多種URL映射,本項(xiàng)目所設(shè)計(jì)的URL就有帶{}占位符的URL
@RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
public String detail(@PathVariable("seckillId") Long seckillId, Model model)
通過(guò)@PathVariable注解綁定后面的參數(shù)seckilId栗精,然后對(duì)應(yīng)到URL占位符闯参,當(dāng)用戶(hù)傳遞對(duì)應(yīng)的URL時(shí)瞻鹏,@RequestMapping注解中占位符{seckillId}的值會(huì)傳入detail方法中對(duì)應(yīng)的參數(shù),因?yàn)椴煌拿霘⑸唐酚胁煌脑斍轫?yè)鹿寨,所以在二級(jí)URL上使用占位符標(biāo)識(shí)不同id的秒殺商品
接著對(duì)傳進(jìn)來(lái)的seckillId進(jìn)行判斷
if(seckillId == null){
return "redirect:/seckill/list";
}
Seckill seckill = seckillService.getById(seckillId);
if(seckill == null){
return "forward:/seckill/list";
}
先要判斷seckillId有沒(méi)有傳進(jìn)來(lái)新博,如果沒(méi)有傳進(jìn)來(lái),就請(qǐng)求轉(zhuǎn)發(fā)到list頁(yè)面脚草,會(huì)回到列表頁(yè)赫悄;如果傳進(jìn)來(lái)的seckillId的值不屬于任何秒殺商品,那么仍然會(huì)重定向到列表頁(yè)
這里簡(jiǎn)單說(shuō)下請(qǐng)求轉(zhuǎn)發(fā)與重定向:
- 從地址欄顯示來(lái)說(shuō):
- forward:服務(wù)器請(qǐng)求資源馏慨,服務(wù)器直接訪問(wèn)目標(biāo)地址的URL埂淮,把那個(gè)URL的響應(yīng)內(nèi)容讀取過(guò)來(lái),然后把這些內(nèi)容再發(fā)給瀏覽器写隶,瀏覽器根本不知道服務(wù)器發(fā)送的內(nèi)容從哪里來(lái)的倔撞,所以它的地址欄還是原來(lái)的地址
- redirect:服務(wù)端根據(jù)邏輯,發(fā)送一個(gè)狀態(tài)碼慕趴,告訴瀏覽器重新去請(qǐng)求那個(gè)地址痪蝇,所以地址欄顯示的是新的URL,redirect等于客戶(hù)端向服務(wù)器端發(fā)出兩次request冕房,同時(shí)也接受兩次response
- 從數(shù)據(jù)共享來(lái)說(shuō)
- forward:轉(zhuǎn)發(fā)頁(yè)面和轉(zhuǎn)發(fā)到的頁(yè)面可以共享request里面的數(shù)據(jù)
- redirect:不能共享數(shù)據(jù)
- 從運(yùn)用地方來(lái)說(shuō)
- forward:一般用于用戶(hù)登陸的時(shí)候,根據(jù)角色轉(zhuǎn)發(fā)到相應(yīng)的模塊
- redirect:一般用于用戶(hù)注銷(xiāo)登陸時(shí)返回主頁(yè)面和跳轉(zhuǎn)到其它的網(wǎng)站等
- 從效率來(lái)說(shuō)
- forward:高
- redirect:低
這里使用redirect和forward沒(méi)有特別的用意
model.addAttribute("seckill", seckill);
return "detail";
接著就是使用model存儲(chǔ)數(shù)據(jù)躏啰,并返回一個(gè)字符串
3、秒殺地址暴露
@RequestMapping(
value = "/{seckillId}/exposer",
method = RequestMethod.POST,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId){
SeckillResult<Exposer> result;
try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
result = new SeckillResult<Exposer>(true, exposer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
result = new SeckillResult<Exposer>(false, e.getMessage());
}
return result;
}
同樣在方法上使用@RequestMapping注解設(shè)置二級(jí)URL耙册,限制http請(qǐng)求方式為POST给僵,并且通過(guò)produces返回HttpResponse的hanndler,告訴瀏覽器這是一個(gè)application/json,同時(shí)設(shè)置編碼為UTF-8
使用@ResponseBody注解详拙,Spring MVC會(huì)把返回的數(shù)據(jù)封裝成json
之前說(shuō)過(guò)有個(gè)DTO層想际,主要是用來(lái)封裝Service層與WEB層之間的數(shù)據(jù),這里在dto包下新建一個(gè)SeckillResult類(lèi)溪厘,用于封裝數(shù)據(jù)結(jié)果
//封裝json結(jié)果
public class SeckillResult<T> {
private boolean success;//判斷請(qǐng)求是否成功
private T data;//存放數(shù)據(jù)
private String error;//錯(cuò)誤信息
}
這個(gè)類(lèi)是一個(gè)泛型類(lèi)型
public SeckillResult(boolean success, T data) {
this.success = success;
this.data = data;
}
public SeckillResult(boolean success, String error) {
this.success = success;
this.error = error;
}
通過(guò)success來(lái)判斷請(qǐng)求是否成功胡本,成功的話,返回從數(shù)據(jù)庫(kù)中取得的數(shù)據(jù)畸悬,如果請(qǐng)求不成功侧甫,返回錯(cuò)誤信息
再生成get和set方法
SeckillResult<Exposer> result;
try {
Exposer exposer = seckillService.exportSeckillUrl(seckillId);
result = new SeckillResult<Exposer>(true, exposer);
}
實(shí)例化一個(gè)SeckillResult<Exposer>對(duì)象,調(diào)用SeckillService的exportSeckillUrl方法
/**
* 秒殺開(kāi)啟時(shí)輸出秒殺接口地址
* 否則輸出系統(tǒng)時(shí)間和秒殺時(shí)間
* @param seckillId
* @return
*/
Exposer exportSeckillUrl(long seckillId);//SeckillService接口中的方法
可以看到SeckillService的這個(gè)方法返回的是Exposer類(lèi)型蹋宦,所以實(shí)例化SeckillResult對(duì)象的時(shí)候披粟,泛型中是Exposer類(lèi)型
public class Exposer {
//是否開(kāi)啟秒殺
private boolean exposed;
//加密措施
private String md5;
//id
private long seckillId;
//系統(tǒng)當(dāng)前時(shí)間(毫秒)
private long now;
//秒殺開(kāi)啟時(shí)間
private long start;
//秒殺結(jié)束時(shí)間
private long end;
}
從之前定義好的Exposer類(lèi)中的屬性就可以看到,如果開(kāi)啟秒殺的話冷冗,頁(yè)面會(huì)返回通過(guò)MD5加密過(guò)的秒殺的地址守屉,如果沒(méi)有開(kāi)啟秒殺,則返回系統(tǒng)當(dāng)前時(shí)間及秒殺開(kāi)啟與結(jié)束時(shí)間蒿辙,用于倒計(jì)時(shí)
在SeckillController中拇泛,如果秒殺開(kāi)啟滨巴,通過(guò)調(diào)用SeckillService中的exportSeckillUrl方法返回Exposer對(duì)象,存放的是MD5及seckillId俺叭,然后初始化SeckillResult對(duì)象恭取,參數(shù)為true,成功返回Exposer對(duì)象中的數(shù)據(jù)
如果這期間出現(xiàn)錯(cuò)誤熄守,說(shuō)明沒(méi)有請(qǐng)求成功蜈垮,需要把上面兩步try/catch一下
catch (Exception e) {
logger.error(e.getMessage(), e);
result = new SeckillResult<Exposer>(false, e.getMessage());
}
return result;
因?yàn)闆](méi)請(qǐng)求成功,所以需要輸出錯(cuò)誤信息裕照,同時(shí)說(shuō)明不在秒殺活動(dòng)期內(nèi)攒发,同樣初始化SeckillResult對(duì)象,返回Exposer類(lèi)中的信息晋南,在Exposer中定義的有系統(tǒng)當(dāng)前時(shí)間以及秒殺開(kāi)啟惠猿、結(jié)束時(shí)間,所以如果沒(méi)有請(qǐng)求成功搬俊,在頁(yè)面返回的是倒計(jì)時(shí)或者是秒殺結(jié)束等字樣
4紊扬、執(zhí)行秒殺
@RequestMapping(
value = "/{seckillId}/{md5}/execution",
method = RequestMethod.POST,
produces = {"application/json;charset=UTF-8"})
@ResponseBody
public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
@PathVariable("md5") String md5,
@CookieValue(value = "killPhone", required = false) Long phone){
if(phone == null){
return new SeckillResult<SeckillExecution>(false, "未注冊(cè)");
}
//SeckillResult<SeckillExecution> result;
try {
SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
return new SeckillResult<SeckillExecution>(true, execution);
} catch (RepeatKillException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true, execution);
} catch (SeckillCloseException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
return new SeckillResult<SeckillExecution>(true, execution);
} catch (Exception e) {
logger.error(e.getMessage(), e);
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true, execution);
}
}
方法上的注解就不多說(shuō)了蜒茄,和上面一樣唉擂,所有的ajax請(qǐng)求返回的都是統(tǒng)一的SeckillResult,之前在dto包中已經(jīng)定義了SeckillException檀葛,用于封裝秒殺執(zhí)行后的結(jié)果
/**
* 封裝秒殺執(zhí)行后的結(jié)果
* @author Fzero
*
*/
public class SeckillExecution {
private long seckillId;
//秒殺結(jié)果執(zhí)行后的狀態(tài)
private int state;
//狀態(tài)信息
private String stateInfo;
//秒殺成功對(duì)象
private SuccessKilled successKilled;
}
這里就可以理解DTO作為Service與WEB層之間數(shù)據(jù)傳遞
因?yàn)樗械拿霘⒍家杏脩?hù)的標(biāo)識(shí)玩祟,本項(xiàng)目沒(méi)有做登錄模塊,所以使用手機(jī)號(hào)phone作為用戶(hù)的標(biāo)識(shí)屿聋,可以看到@RequestMapping注解中的請(qǐng)求參數(shù)中沒(méi)有phone空扎,這個(gè)參數(shù)是由用戶(hù)瀏覽器的Request請(qǐng)求的cookie中獲取到的,這里Spring MVC處理cookie有個(gè)小問(wèn)題润讥,如果不設(shè)置required屬性為false的時(shí)候转锈,當(dāng)請(qǐng)求的header中沒(méi)有一個(gè)cookie叫killPhone的時(shí)候,Spring MVC會(huì)報(bào)錯(cuò)楚殿,所以在@CookieValue注解中將required設(shè)置為false
if(phone == null){
return new SeckillResult<SeckillExecution>(false, "未注冊(cè)");
}
try {
SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);
return new SeckillResult<SeckillExecution>(true, execution);
}
這里先使用if簡(jiǎn)單的判斷一下撮慨,實(shí)際項(xiàng)目中,要驗(yàn)證的參數(shù)很多脆粥,可以采用Spring MVC的驗(yàn)證信息砌溺,所以這里的killPhone不是必須的,驗(yàn)證用戶(hù)的邏輯放在代碼中
對(duì)于執(zhí)行秒殺操作变隔,可能會(huì)出現(xiàn)各種異常和錯(cuò)誤规伐,所以這里需要try/catch以下,并且有些特定的異常比如重復(fù)秒殺匣缘、秒殺結(jié)束等猖闪,之前單獨(dú)建立了一個(gè)exception包鲜棠,專(zhuān)門(mén)存放與業(yè)務(wù)相關(guān)的異常
/**
* 執(zhí)行秒殺操作
* @param seckillId
* @param userPhone
* @param md5
* @return
*/
SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException,RepeatKillException,SeckillCloseException;
這是SeckillService中定義的方法,可以看到拋出了不同的異常萧朝,所以對(duì)于這些特定的異常岔留,要單獨(dú)的catch
catch (RepeatKillException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
return new SeckillResult<SeckillExecution>(true, execution);
}
這個(gè)是處理重復(fù)秒殺的異常,重新初始化SeckillExecution對(duì)象检柬,向數(shù)據(jù)字典傳入對(duì)象異常的標(biāo)識(shí)献联,結(jié)果是返回初始化的SeckillResult,上面說(shuō)過(guò)SeckillExecution對(duì)象是用于封裝秒殺執(zhí)行后的結(jié)果何址,這里的參數(shù)為true里逆,因?yàn)楫?dāng)初在SeckillResult定義布爾類(lèi)型的success的時(shí)候就說(shuō)明這是判斷請(qǐng)求是否成功,這里的重復(fù)秒殺顯然是請(qǐng)求成功用爪,所以參數(shù)為true
catch (SeckillCloseException e) {
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
return new SeckillResult<SeckillExecution>(true, execution);
}
秒殺關(guān)閉異常
catch (Exception e) {
logger.error(e.getMessage(), e);
SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
return new SeckillResult<SeckillExecution>(true, execution);
}
如果不是上述特定的兩個(gè)異常原押,其他的異常都視為inner_error
最后一個(gè)方法就是獲取系統(tǒng)時(shí)間
@RequestMapping(value = "/time/now", method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Long> time(){
Date now = new Date();
return new SeckillResult<Long>(true, now.getTime());
}
至此,WEB層完成了