四、高并发秒杀API之Web层设计与实现

来源:互联网 发布:java获取tomcat的端口 编辑:程序博客网 时间:2024/06/05 20:28
Web层涉及到的技术:

前端交互:页面之间的交互和交互细节

Restful:前端设计接口之间的一种常用的规范

Spring MVC:框架整合,以及如何应用设计和实现Restful接口

Bootstrap和jquery:前者负责页面布局和样式控制,后者负责交互的实现。 


1 前端分析与设计


1.1 前端交互设计部分

前端页面流程:

                     

        根据细致的流程逻辑,前端工程师设计页面,后端工程师开发对应的代码,可以使得前端和后端之间相互碰触,更加容易的协调一致。所以,前端交互流程是系统开发中一个很重要的部分。


1.2 Restful接口设计

      什么是Restful? 

     它是一种优雅的URI表述方式,用来设计我们资源的访问URL;通过这个URL的设计,我们就可以很自然的感知到这个URL代表的是哪种业务场景或者什么样的数据或资源。基于Restful设计的URL,对于我们接口的使用者、前端、web系统或者搜索引擎甚至是我们的用户,都是非常友好的。

     看看URL设计规范:

                                 

 

       关于Restful的了解不再做详细介绍,下面看看我们这个秒杀系统的URL设计:

                                             

       接下来基于上述资源接口来开始我们对SpringMVC框架的使用。 


2 整合配置Spring MVC框架

2.1 Spring MVC运行流程

          使用SpringMVC始终是围着着Handle开发,Handler最终的产出就是Model和View,即,模型和视图。下面先回顾一下Spring MVC的运行路程图:

                                            

工作原理(运行流程):

       

1、客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet.

2、DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping(默认使用DefaultAnnotationHandlerMapping)的配置来映URL,找到处理请求的处理器Handler(每一个请求都对应了一个Handler)。

3-4、DispatcherServlet将处理权交给根据HandlerMapping找到对应Handler(Handler将具体的处理进行封装),再由具体的HandlerAdapter对Handler进行具体的调用:默认使用DefaultAnnotationHandlerAdapter来对上一步的Handler进行适配,匹配到我们自己写的Controller类(如果使用到拦截器,则同样把拦截器绑定到这个流程当中)。

5、Handler对数据处理完成以后将返回一个ModelAndView()对象交付给DispatcherServlet。

6、Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过ViewResolver(利用视图解析器InternalResourceViewResolver)将逻辑视图转化为真正的视图View,并用model来承载数据。

7、Dispatcher通过model对ModelAndView()中的参数进行解析,最终展现出完整的view并返回给客户端。


2.1.1 Http请求映射原理

          下面再看一下Http请求地址的映射原理:

                                

          用户发送的Http请求,首先会发送到Servlet容器(Tomcat或Jetty),而Spring MVC则使用的是HandlerMapping来映射URL,然后使用Handler方法来最终执行。(默认使用 DefaultAnnotationHandlerMapping注解来映射,也可以通过XML配置编程来映射)

 

注解映射的技巧:

                

请求方法的细节处理:

1. 请求参数绑定

2. 请求方式限制

3. 请求转发和重定向

4. 数据模型赋值

5. 返回Json数据

6. Cookie访问

     看下面的一个例子:

            

       主要应用了URL书写方式,限制了数据提交方式,判定使用url转发还是重定向,使用model承载数据,返回字符串修改URL链接。


2.1.2如何返回Json数据

               


2.1.3 Cookie访问

                      


 

2.2 Spring MVC整合

          在WEB-INF项目中配置文件web.xml中配置我们的前端中央控制器DispatcherServlet的配置,也是Spring MVC的核心所在,并加载mybatis配置文件

spring-dao.xml,spring配置文件spring-service.xml、spring-web.xml文件,将三个框架整合起来,文件内容如下:

<!DOCTYPE web-app PUBLIC "-//SunMicrosystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app version="2.4"metadata-complete="true"        xmlns="http://java.sun.com/xml/ns/j2ee"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">        <!--用maven创建的web-app需要修改servlet的版本为3.1--><!--配置DispatcherServlet-->    <servlet>        <servlet-name>seckill-dispatcher</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <!--配置SpringMVC 需要配置的文件:                  spring-dao.xml,spring-service.xml,spring-web.xml            Mybites -> spring ->springMvc            Mybatis被整合进SPring中,Spring要整合进SPring MVC当中;                                   不过,Spring MVC本来就出自Spring框架,同属一个框架,已经无缝衔接整合了        -->        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:spring/spring-*.xml</param-value>        </init-param>    </servlet>    <servlet-mapping>        <servlet-name>seckill-dispatcher</servlet-name>        <!--默认匹配所有请求-->        <url-pattern>/</url-pattern>    </servlet-mapping> </web-app>

 

然后,在Spring容器中进行web层相关的bean配置,即Controller的配置,在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-3.0.xsd         http://www.springframework.org/schema/mvc         http://www.springframework.org/schema/mvc/spring-mvc.xsd         " >    <!--配置spring mvc-->    <!--1:开启springmvc注解模式-->    <!-- 监护配置        a.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter        b.默认提供一系列的功能:数据绑定,数字和日期的format@NumberFormat,@DateTimeFormat,xml,json的默认读写支持-->    <mvc:annotation-driven/>     <!--2:静态资源默认servlet配置-->    <!--        1):加入对静态资源处理:js,gif,png        2):允许使用 "/" 做整体映射    -->    <mvc:default-servlet-handler/>     <!--3:配置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>     <!--4:扫描web相关的bean-->    <context:component-scan base-package="org.seckill.web"/>         </beans>

如此,便完成了SpringMVC框架的配置,将框架整合到了项目当中,接下来则是开发Controller了,这里是基于Restful接口进行我们的项目的Controller开发。

 

3 基于Restful的Controller开发

         这里Restful接口使用Spring MVC实现的,Controller中的每一个方法都对应我们系统中的一个资源URL,其设计应该遵循Restful接口的设计风格。在org.seckill包下创建一个web包用于放web层Controller开发的代码,在该包下创建一个SeckillController.java :

@Controller@RequestMapping("/seckill") //url:/模块/资源/{id}/细分/seckill/listpublic class SeckillController {    @Autowired    private SeckillService seckillService;       @RequestMapping(value="/list",method=RequestMethod.GET)    public String list(Model model)    {        List<Seckill> list=seckillService.getSeckillList();        model.addAttribute("list",list);        //list.jsp+model=ModelAndView        return "lsit";    }       @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";                   }        return "detail";           }       @RequestMapping(value = "/{seckillId}/exposer",            method = RequestMethod.POST,            produces = { "application/json;charset=UTF-8" })    @ResponseBody //封装成json    public SeckillResult<Exposer> exposer(Long seckillId) {        SeckillResult<Exposer> result;        try {            //Exposer:存放是否秒杀的状态信息。            Exposer exposer = seckillService.exportSeckillUrl(seckillId);                       result = new SeckillResult<Exposer>(true, exposer);        } catch (Exception e) {            e.printStackTrace();            result = new SeckillResult<Exposer>(false, e.getMessage());        }         return result;    }     /*     * md5:验证用户的请求有没有被篡改     * 默认的ajax输出是Json格式,所以将输出结果都封装成Json格式。     */    @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,            //required = false表示电话号码不是必须的。            @CookieValue(value = "killPhone", required = false) Long phone) {        //Spring MVC valid        if (phone == null) {            return new SeckillResult<SeckillExecution>(false, "未注册");        }               SeckillResult<SeckillExecution> result;        try {            SeckillExecution execution = seckillService.executeSeckill(seckillId, phone, md5);            return new SeckillResult<SeckillExecution>(true, execution);        } catch (RepeatKillException e1) {            SeckillExecution execution = new SeckillExecution(seckillId,                    SeckillStateEnum.REPEAT_KILL);            return new SeckillResult<SeckillExecution>(false, execution);        } catch (SeckillCloseException e2) {            SeckillExecution execution = new SeckillExecution(seckillId,                    SeckillStateEnum.END);            return new SeckillResult<SeckillExecution>(false, execution);        } catch (Exception e) {            SeckillExecution execution = new SeckillExecution(seckillId,                    SeckillStateEnum.INNER_ERROR);            return new SeckillResult<SeckillExecution>(false, execution);        }     }     // 获取系统时间    @RequestMapping(value = "/time/now", method = RequestMethod.GET)    public SeckillResult<Long> time() {        Date now = new Date();        return new SeckillResult<Long>(true, now.getTime());    } }

 

Controller中的方法的开发完全是按照Service接口中的方法进行开发的:

第一个方法用于访问我们商品的列表页;

第二个方法访问商品的详情页;

第三个方法用于返回一个json数据,数据中封装了我们商品的秒杀地址;

第四个方法用于封装用户是否秒杀成功的信息;

第五个方法用于返回系统当前时间。

代码中涉及到一个将返回秒杀商品地址封装为json数据的一个Vo类,即SeckillResult.java,在dto包中创建此类:

//所有ajax请求的返回类型,封装为json结果类型public class SeckillResult<T> //泛型类型的类{    private boolean success;    private T data;    private String error;    public SeckillResult(boolean success, T data) {        super();        this.success = success;        this.data = data;    }    public SeckillResult(boolean success, String error) {        super();        this.success = success;        this.error = error;    }    //getter and setter……,自行补充}

 

到此处,Controller的开发内容完成,剩下的就是对Web页面内容进行开发。


4 基于bootstrap开发页面结构

bootStrap是目前比较受欢迎的前端框架,是基于HTML、CSS、JavaScript的,简洁灵活,使得Web开发更加快捷方便。

BootStrap详细教程可参考:http://www.runoob.com/bootstrap/bootstrap-tutorial.html

 

 

原创粉丝点击