为了账号安全,请及时绑定邮箱和手机立即绑定

慕课网《Java高并发秒杀API之web层》学习总结

标签:
Java

慕课网《Java高并发秒杀API之web层》学习总结

时间:2017年08月24日星期四
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:无
学习源码:https://github.com/zccodere/study-imooc
第一章:设计Restful接口
1-1 交互流程

学习本门课程前请先学习前置课程《Java高并发秒杀API之业务分析与DAO层》和《Java高并发秒杀API之Service层》

主要内容

前端交互设计
Restful
SpringMVC
Bootstrap + jQuery

前端页面流程

图片描述

详情页流程逻辑

图片描述

1-2 Restful接口

什么是Restful

兴起于Rails
一种优雅的URL表述方式
资源的状态和状态转移

Restful示例

GET /seckill/list 表述秒杀的列表页
POST /seckill/{seckillId}/execution 表述该资源执行execution操作
DELETE /seckill/{id}/delete 表述该资源执行delete操作即删除

Restful规范

GET:查询操作
POST:添加/修改操作
PUT:修改操作
DELETE:删除操作

URL设计

/模块/资源/{标识}/集合1/…
/user/{uid}frends:好友列表
/user/{uid}/followers:关注者列表

秒杀API的URL设计

GET /seckill/list:秒杀列表
GET /seckill/{id}/detail:详情页
GET /seckill/time/now:系统时间
POST /seckill/{id}/exposer:暴露秒杀
POST /seckill/{id}/{md5}/execution:执行秒杀
第二章:SpringMVC整合Spring
2-1 SpringMVC理论

SpringMVC始终围绕Handler开发

数据(Model)
页面(View)

SpringMVC运行流程

图片描述

HTTP请求地址映射原理

图片描述

注解映射技巧

@RequestMapping注解
(1)支持标准的URL
(2)Ant风格URL(?:匹配一个字符;*:匹配任意字符;**:匹配任意URL路径)
(3)带{xxx}占位符的URL
例如:
/user/*/creation
匹配/user/aaa/creation、/user/bbb/creation等URL
/user/**/creation
匹配/user/creation、/user/aaa/bbb/creation等URL
/user/{userId}
匹配/user/123、/user/abc等URL
/company/{companyId}/user/{userId}/detail
匹配/company/123/user/456/detail等URL

请求方法细节处理

1.请求参数绑定
2.请求方式限制
3.请求转发和重定向
4.数据模型赋值
5.放回json数据
6.cookie访问

举个例子

图片描述

返回json数据

图片描述

Cookie访问

图片描述

2-2 SpringMVC配置

代码编写

1.修改web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
    <!-- 修改servlet版本为3.1 -->

<!--    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener> -->

    <!-- 配置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
            Mybatis -> spring -> springMVC
        -->
        <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>

2.编写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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置SpringMVC -->
    <!-- 1:开启SpringMVC注解模式 -->
    <!-- 简化配置:
        (1)自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
        (2)提供一系列:数据绑定,数字和日期的format,@NumberFormat,@DataTimeFormat
            xml,json默认读写支持
     -->
    <mvc:annotation-driven />

    <!-- 2:servlet-mapping 映射路径:"/" -->
    <!--  (1)静态资源默认servlet配置
          (2)允许使用"/"做整体映射
    -->
    <mvc:default-servlet-handler/>

    <!-- 3:配置jsp显示ViewResolver -->
    <bean id="viewResolver" 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="com.myimooc.seckill.web"/>

</beans>
第三章:实现Restful接口
3-1 使用SpringMVC实现(上)

代码编写

1.编写SeckillController类

/**
 * @describe 控制器
 * @author zc
 * @version 1.0 2017-08-24
 */
@Controller
@RequestMapping(value = "/seckill")// url:/模块/资源/{id}/细分
public class SeckillController {

    public SeckillController(){
        System.out.println("SeckillController被初始化");
    }

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SeckillService seckillService;

    // 该注解为组合注解,等价于 @RequestMapping(name="/list",method=RequestMethod.GET)
    @GetMapping(value = "/list")
    public ModelAndView list(Model model){
        logger.info("进入列表页");
        // 获取列表页
        List<Seckill> list = seckillService.getSeckillList();
        logger.info("list = {}",list);
        model.addAttribute(list);
        // list.jsp + model = ModelAndView
        return new ModelAndView("list").addObject("list",list); //    /WEB-INF/jsp/list.jsp
    }

    @GetMapping("/{seckillId}/detail")
    public String detail(@PathVariable("seckillId") Long seckillId, Model model){
        if(null == seckillId){
            // 重定向
            return "redirect:/seckill/list";
        }
        Seckill seckill = seckillService.getById(seckillId);
        if(null == seckill){
            // 请求转发
            return "forward:/seckill/list";
        }
        model.addAttribute("seckill",seckill);
        return "detail";
    }

    // ajax json
    @PostMapping(value = "/{seckillId}/exposer",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;
    }

    @PostMapping(value = "/{seckillId}/{md5}/execution",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(StringUtils.isEmpty(phone)){
            return new SeckillResult<SeckillExecution>(false,"未注册");
        }
//        SeckillResult<SeckillExecution> result;
        try{
            // 存储过程调用
            SeckillExecution execution = seckillService.executeSeckill(seckillId,phone,md5);

            //SeckillExecution execution = seckillService.executeSeckill(seckillId,phone,md5);
            return new SeckillResult<SeckillExecution>(true,execution);
        } catch (SeckillCloseException e1){
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);
            return new SeckillResult<SeckillExecution>(true,execution);
        } catch (RepeatKillException e2){
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
            return new SeckillResult<SeckillExecution>(true,execution);
        } catch (Exception e){
            logger.error(e.getMessage(),e);
            SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
            return new SeckillResult<SeckillExecution>(true,execution);
        }
    }

    @GetMapping("/time/now")
    @ResponseBody
    public SeckillResult<Long> time(){
        Date now = new Date();
        return new SeckillResult<Long>(true,now.getTime());
    }
}
3-2 使用SpringMVC实现(下)

代码编写

1.编写SeckillResult类

package com.myimooc.seckill.dto;

/**
 * @describe VO 对象,所有ajax请求返回类型,封装json结果
 * @author zc
 * @version 1.0 2017-08-24
 */
public class SeckillResult<T> {

    private boolean success;

    private T data;

    private String error;

    // 省略getter and setter 和 构造方法
}
第四章:使用Bootstrap
4-1 基于Bootstrap开发页面

代码编写

1.编写head.jsp

<meta name="viewport" content="width=device-width, initial-scale=1"/>

<!-- Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>

<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->

2.编写tag.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

3.编写list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 引入jstl -->
<%@include file="common/tag.jsp"%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>秒杀列表页</title>
    <%@include file="common/head.jsp"%>
</head>
<body>
    <!-- 页面显示部分 -->
<div class="container">
    <div class="panel panel-default">
        <div class="panel-heading text-center">
            <h1>秒杀列表</h1>
        </div>
        <div class="panel-body">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th>名称</th>
                        <th>库存</th>
                        <th>开始时间</th>
                        <th>结束时间</th>
                        <th>创建时间</th>
                        <th>详情页</th>
                    </tr>
                </thead>
                <tbody>
                    <c:forEach var="sk" items="${list}">
                        <tr>
                            <td>${sk.name}</td>
                            <td>${sk.number}</td>
                            <td><fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                            <td><fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                            <td><fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
                            <td><a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">link</a> </td>
                        </tr>
                    </c:forEach>
                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

4.编写detail.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>秒杀详情页</title>
    <%@include file="common/head.jsp"%>
</head>
<body>

<div class="container">

    <div class="panel panel-default text-center">
        <div class="panel-heading">${seckill.name}</div>
        <div class="panel-body">
            <h2 class="text-danger">
                <!-- 显示time图标 -->
                <span class="glyphicon glyphicon-time"></span>
                <!-- 展示倒计时 -->
                <span class="glyphicon" id="seckill-box"></span>
            </h2>
        </div>
    </div>
</div>

<!-- 登录弹出层,输入电话 -->
<div id="killPhoneModal" class="modal fade">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h3 class="modal-title text-center">
                    <span class="glyphicon glyphicon-phone"></span>秒杀电话:
                </h3>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-xs-8 col-xs-offset-2">
                        <input type="text" name="killPhone" id="killPhoneKey"
                               placeholder="填手机号" class="form-control">
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <!--验证信息-->
                <span id="killPhoneMessage" class="glyphicon"></span>
                <button type="button" id="killPhoneBtn" class="btn btn-success">
                    <span class="glyphicon glyphicon-phone"></span>
                    Submit
                </button>
            </div>
        </div>
    </div>
</div>

</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="https://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js"></script>
<!-- 开始编写交互逻辑 -->
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/resources/script/seckill.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function (){
        // 使用EL表达式传入参数
        seckill.detail.init({
            seckillId : ${seckill.seckillId},
            startTime : ${seckill.startTime.time},//毫秒
            endTime : ${seckill.endTime.time}
        });
    });

</script>
</html>
第五章:交互逻辑
5-1 交互实现

代码编写

1.编写seckill.js

// 存放主要交互逻辑js代码
// javascript模块化
var seckill = {
    // 封装秒杀相关ajax的url
    URL:{
        now : function(){
            return '/seckill/time/now';
        },
        exposer : function (seckillId) {
            return '/seckill/'+seckillId+'/exposer';
        },
        execution : function (seckillId,md5) {
            return '/seckill/'+seckillId+'/'+md5+'/execution';
        }
    },
    // 处理秒杀逻辑
    handleSeckillKill : function (seckillId,node) {
        // 获取秒杀地址,控制显示逻辑,执行秒杀
        node.hide()
            .html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');//按钮
        $.post(seckill.URL.exposer(seckillId),{},function (result) {
            // 在回调函数中,执行交互流程
            if(result && result['success']){
                var exposer = result['data'];
                if(exposer['exposed']){
                    // 开启秒杀
                    // 获取秒杀地址
                    var md5 = exposer['md5'];
                    var killUrl = seckill.URL.execution(seckillId,md5);
                    console.log("killUrl:"+killUrl);
                    // 绑定一次点击事件
                    $('#killBtn').one('click',function () {
                        // 执行秒杀请求
                        // 1:先禁用按钮
                        $(this).addClass('disabled');
                        // 2:发送秒杀请求
                        $.post(killUrl,{},function(result){
                            if(result && result['success']){
                                var killResult = result['data'];
                                var state = killResult['state'];
                                var stateInfo = killResult['stateInfo'];
                                // 3:显示秒杀结果
                                node.html('<span class="label label-success">'+stateInfo+'</span>');
                            }
                        });
                    });
                    node.show();
                }else {
                    // 未开启秒杀
                    var now = exposer['now'];
                    var start = exposer['start'];
                    var end = exposer['end'];
                    // 重新计算计时逻辑
                    seckill.mycountdown(seckillId,now,start,end);
                }
            }else{
                console.log('result:'+result);
            }
        });
    },
    // 验证手机号
    validatePhone:function (phone) {
        if(phone && phone.length == 11 && !isNaN(phone)){
            return true;
        }else{
            return false;
        }
    },
    mycountdown : function(seckillId,nowTime,startTime,endTime){
        var seckillBox = $('#seckill-box');
        // 时间判断
        if(nowTime > endTime){
            // 秒杀结束
            seckillBox.html('秒杀结束!');
        }else if(nowTime < startTime){
            // 秒杀未开始
            var killTime = new Date(startTime + 1000);
            seckillBox.countdown(killTime,function(event){
                // 时间格式
                var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒');
                seckillBox.html(format);
                // 时间完成后回调时间
            }).on('finish.countdown',function () {
                // 获取秒杀地址,控制显示逻辑,执行秒杀
                seckill.handleSeckillKill(seckillId,seckillBox);
            });
        }else {
            // 秒杀开始
            seckill.handleSeckillKill(seckillId,seckillBox);
        }
    },
    // 详情页秒杀逻辑
    detail:{
        // 详情页初始化
        init : function (params) {
            // 手机验证和登录 , 计时交互
            // 规划我们的交互流程
            // 在cookie中查找手机号
            var killPhone = $.cookie('killPhone');
            // 验证手机号
            if(!seckill.validatePhone(killPhone)){
                // 绑定phone
                // 控制输出
                var killPhoneModal = $('#killPhoneModal');
                // 显示弹出层
                killPhoneModal.modal({
                    //显示弹出层
                    show:true,
                    // 禁止位置关闭
                    backdrop:'static',
                    // 关闭键盘事件
                    keyboard:false
                });
                $('#killPhoneBtn').click(function(){
                    var inputPhone = $('#killPhoneKey').val();
                    console.log('inputPhone='+inputPhone);//TODO
                    if(!seckill.validatePhone(killPhone)){
                        // 电话写入cookie
                        $.cookie('killPhone',inputPhone,{expires:7,path:'/seckill'});
                       // 刷新页面
                        window.location.reload();
                    }else {
                        $('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
                    }
                });
            }
            // 已经登录
            // 计时交互
            var startTime = params['startTime'];
            var endTime = params['endTime'];
            var seckillId = params['seckillId'];
            $.get(seckill.URL.now(),{},function(result){
                if(result &&result['success']){
                    var nowTime = result['data'];
                    // 时间判断,计时交互
                    seckill.mycountdown(seckillId,nowTime,startTime,endTime);
                }else{
                    console.log('result:'+result);
                }
            });
        }
    }
}
第六章:课程总结
6-1 总结

技术回顾

前端交互设计过程
Restful接口设计
SpringMVC使用技巧
Bootstrap和JS使用
点击查看更多内容
4人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消