高并發(fā)秒殺API(四)

前言

本篇將完成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)行流程:


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)行流程


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層完成了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末偎血,一起剝皮案震驚了整個(gè)濱河市诸衔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颇玷,老刑警劉巖笨农,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異帖渠,居然都是意外死亡谒亦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)空郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)份招,“玉大人,你說(shuō)我怎么就攤上這事狞甚∷ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵哼审,是天一觀的道長(zhǎng)谐腰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)棺蛛,這世上最難降的妖魔是什么怔蚌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮旁赊,結(jié)果婚禮上桦踊,老公的妹妹穿的比我還像新娘。我一直安慰自己终畅,他們只是感情好籍胯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布竟闪。 她就那樣靜靜地躺著,像睡著了一般杖狼。 火紅的嫁衣襯著肌膚如雪炼蛤。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天蝶涩,我揣著相機(jī)與錄音理朋,去河邊找鬼。 笑死绿聘,一個(gè)胖子當(dāng)著我的面吹牛嗽上,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熄攘,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼兽愤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了挪圾?” 一聲冷哼從身側(cè)響起浅萧,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哲思,沒(méi)想到半個(gè)月后洼畅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡也殖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年土思,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了务热。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忆嗜。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖崎岂,靈堂內(nèi)的尸體忽然破棺而出捆毫,到底是詐尸還是另有隱情,我是刑警寧澤冲甘,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布绩卤,位于F島的核電站,受9級(jí)特大地震影響江醇,放射性物質(zhì)發(fā)生泄漏濒憋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一陶夜、第九天 我趴在偏房一處隱蔽的房頂上張望凛驮。 院中可真熱鬧,春花似錦条辟、人聲如沸黔夭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)本姥。三九已至肩袍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婚惫,已是汗流浹背氛赐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留先舷,地道東北人鹰祸。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像密浑,于是被迫代替她去往敵國(guó)和親蛙婴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容