基于SpringMVC+Spring+MyBatis实现秒杀系统【客户端交互】

前言

      该篇主要实现客户端和服务的交互。在第一篇概况里我已经贴出了业务场景的交互图片。 客户端交互主要放在seckill.js里来实现。页面展现基于jsp+jstl来实现。

 

准备工作

1、配置web.xml。web.xml里配置springmvc前端控制器时需要把spring托管的3个xml全部加载。分别是spring-dao.xml、spring-service.xml、spring-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"
         metadata-complete="true">
  <display-name>Archetype Created Web Application</display-name>
  <!--配置前端控制器-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>


</web-app>
复制代码

2、配置spring-web.xml

复制代码
<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">

    <!--1、配置spring mvc -->
    <mvc:annotation-driven/>

    <!--2、静态资源默认配置-->
    <mvc:default-servlet-handler/>

    <!--3、配置视图-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--4、扫描web相关controller-->
    <context:component-scan base-package="com.seckill.web"/>
</beans>
复制代码

 

秒杀接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
@Controller
@RequestMapping("/seckill")
public class SeckillController {
 
 
    @Autowired
    SeckillService seckillService;
 
 
 
    @RequestMapping("/list")
    public ModelAndView list(){
 
        ModelAndView mav=new ModelAndView("list");
        List<Seckill> list = seckillService.getSeckillList();
        mav.addObject("list",list);
 
        return mav;
    }
 
 
    /**
     * 返回值如果是ModelAndView时怎么控制重定向和转发呢
     * **/
    @RequestMapping(value="/{seckillId}/detail/",method = RequestMethod.GET)
    public ModelAndView detail(@PathVariable("seckillId")Long seckillId){
 
        ModelAndView mav=new ModelAndView("detail");
        Seckill seckill=seckillService.getById(seckillId);
        mav.addObject("seckill",seckill);
        return mav;
 
    }
 
 
    //处理ajax请求返回json
    @RequestMapping(value="/{seckillId}/exposer",method = RequestMethod.GET,produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<Exposer> exposer(@PathVariable("seckillId")Long seckillId){
 
        SeckillResult<Exposer> result=null;
        try{
            Exposer exposer=seckillService.exposeSeckillUrl(seckillId);
            result=new SeckillResult<Exposer>(true,exposer);
 
        }catch (Exception e){
            result=new SeckillResult<Exposer>(false,e.getMessage());
        }
 
 
        return result;
 
    }
 
 
    @RequestMapping(value="/{seckillId}/{md5}/execute",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId")Long seckillId,
                                                   @PathVariable("md5")String md5,
                                                   @CookieValue(value="phone",required=false)Long phone){
 
        if(phone==null){
            return new SeckillResult<SeckillExecution>(false,"手机号未注册");
        }
 
        SeckillResult<SeckillExecution> result=null;
 
        try{
 
            SeckillExecution execution=seckillService.executeSeckill(seckillId,phone,md5);
            result=new SeckillResult<SeckillExecution>(true,execution);
 
        }catch(RepeatKillException e){
 
            SeckillExecution execution=new SeckillExecution(seckillId,-1,"重复秒杀");
            result=new SeckillResult<SeckillExecution>(true,execution);
 
 
        }catch(SeckillCloseException e){
 
            SeckillExecution execution=new SeckillExecution(seckillId,0,"秒杀结束");
            result=new SeckillResult<SeckillExecution>(true,execution);
 
        }catch (Exception e){
 
            SeckillExecution execution=new SeckillExecution(seckillId,-2,"系统异常");
            result=new SeckillResult<SeckillExecution>(true,execution);
 
        }
 
        return result;
 
    }
 
 
    //返回系统时间
    @RequestMapping(value="/time/now/",method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<Long> time(){
        Date d=new Date();
 
        return new SeckillResult<Long>(true,d.getTime());
    }
}

  

客户端实现

1、秒杀商品列表页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 
 
<html>
<head>
    <title>秒杀列表页</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="utf-8">
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
    <!-- 可选的Bootstrap主题文件(一般不使用) -->
    <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel="stylesheet">
 
    <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
    <!-- 注意: 如果通过 file://  引入 Respond.js 文件,则该文件无法起效果 -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
 
</head>
<body>
 
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading text-center">
                <h2>秒杀列表</h2>
            </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="item" items="${list}">
                            <tr>
                                <td>${item.name}</td>
                                <td>${item.number}</td>
                                <td>
                                    <fmt:formatDate value="${item.startTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                </td>
                                <td>
                                    <fmt:formatDate value="${item.endTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                </td>
                                <td>
                                    <fmt:formatDate value="${item.createTime}" pattern="yyyy-MM-dd HH:mm:ss"/>
                                </td>
                                <td>
                                    <a class="btn btn-info" href="/seckill/${item.seckillId}/detail/">秒杀</a>
                                </td>
                            </tr>
                        </c:forEach>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
 
 
</body>
</html>

2、秒杀商品详情页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 
 
<html>
<head>
    <title>秒杀详情</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="utf-8">
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
    <!-- 可选的Bootstrap主题文件(一般不使用) -->
    <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap-theme.min.css" rel="stylesheet">
    <!-- HTML5 Shim 和 Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
    <!-- 注意: 如果通过 file://  引入 Respond.js 文件,则该文件无法起效果 -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
</head>
<body>
<div class="container">
    <div class="panel panel-default text-center">
        <div class="pannel-heading">
            <h1>${seckill.name}</h1>
        </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="填写手机号^o^" 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>
 
<script src="http://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="http://apps.bdimg.com/libs/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script src="http://cdn.bootcss.com/jquery.countdown/2.1.0/jquery.countdown.min.js"></script>
<script src="/resources/scripts/seckill.js?201806242323235"></script>
<script type="text/javascript">
 
 
    $(function(){
 
        seckill.detail.init({
            seckillId:${seckill.seckillId},
            startTime:${seckill.startTime.time}, //取毫秒数
            endTime:${seckill.endTime.time}
 
        })
 
    })
 
 
 
</script>
</body>
</html>

  

3、秒杀业务逻辑seckill.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
var seckill={
 
    /**秒杀相关url**/
    URL:{
        now:'/seckill/time/now/'
    },
 
    /**验证手机号**/
    validatePhone:function(phone){
        if(phone && phone.length==11 && !isNaN(phone)){
            return true;
        }
 
        return false;
 
    },
 
    /**倒计时**/
    countdown:function(seckillId,nowTime,startTime,endTime){
       console.log(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(e){
               var format = e.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒 ');
               seckillBox.html(format);
           }).on("finish.countdown",function(){
               console.log("倒计时结束,开始秒杀");
               seckill.seckill(seckillId,seckillBox);
           });
       }else{
            //秒杀开始
           seckill.seckill(seckillId,seckillBox);
       }
    },
 
    detail:{
        /**初始化参数**/
        init:function(params){
            var phone=$.cookie('phone');
 
            //验证手机号
            if(!seckill.validatePhone(phone)){
                var killphoneModal=$("#killPhoneModal");
                //如果有取到cookie里的手机,则弹出模拟登陆
                killphoneModal.modal({
                    show: true,//显示弹出层
                    backdrop: 'static',//禁止位置关闭
                    keyboard: false//关闭键盘事件
                });
 
                $("#killPhoneBtn").click(function(){
 
                    var inputphone=$("#killPhoneKey").val();
                    console.log('inputphone:'+inputphone);
                    if(seckill.validatePhone(inputphone)){
                        $.cookie("phone",inputphone,{expires:7,path:'/seckill'});
                        //验证通过,刷新页面
                        window.location.reload();
                    }else{
                        $('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
                    }
                })
 
            }
 
            var seckillId=params["seckillId"];
            var startTime=params["startTime"];
            var endTime=params["endTime"];
            $.get(seckill.URL.now,{},function(result){
                if(result && result["success"]){
                    var nowTime=result["data"];
                    seckill.countdown(seckillId,nowTime,startTime,endTime);
                }else{
                    console.log(result);
                }
            })
 
        }
    },
 
    /**执行秒杀**/
    seckill:function(seckillId,node){
 
        //获取秒杀地址、控制node节点显示,执行秒杀
        node.hide().html("<button id='killBtn' class='btn btn-primary btn-lg'>开始秒杀</button>")
 
        $.get('/seckill/'+seckillId+'/exposer',{},function(result){
 
            if(result && result["success"]){
                //在回调函数中执行秒杀操作
                var exposer=result["data"];
                if(exposer["exposed"]){
                    //秒杀已开始
                    var md5=exposer["md5"];
                    var killUrl='/seckill/'+seckillId+'/'+md5+'/execute';
                    console.log(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"];
 
                                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.countdown(seckillId, now, start, end);
                }
 
            }else{
                console.log('result:'+result); //没有拿到秒杀地址
            }
 
        })
 
    }
 
 
 
}

  

总结

      秒杀相关业务逻辑主要是根据秒杀商品的开始时间、结束时间以及客户端的当前时间来判断秒杀是否开始、是否结束。未开始时调用jquery.countdown来实现倒计时效果。倒计时插件会维护一个倒计时事件,时间结束时直接会调用秒杀接口来实现秒杀业务。

作者:sword-successful

出处:https://www.cnblogs.com/sword-successful/p/9230446.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   歪头儿在北京  阅读(715)  评论(0编辑  收藏  举报
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示