前言
本篇将完成前端页面的设计与开发,包括:
- 使用Bootstrap开发页面结构
- 交互逻辑编程
一、使用Bootstrap开发页面结构
在设计SeckillController中我们已经设置了jsp文件的路径,在/WEB-INF/新建一个jsp目录,在该目录下新建list.jsp和detail.jsp
使用Bootstrap的模板,这个模板基本上是固定的
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Bootstrap 模板</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.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>
<h1>Hello, world!</h1>
<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>
1、list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!-- 引入jstl -->
<%@ include file="common/tag.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>秒杀列表页</title>
<%@ include file="common/head.jsp" %>
</head>
<body>
</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>
在最上面的jsp内置对象page中的contentType修改为UTF-8,这个模板已经引入了一些文件包含了 jquery.js、bootstrap.min.js 和 bootstrap.min.css 文件,用于让一个常规的 HTML 文件变为使用了Bootstrap的模板
最下面有两个script标签,通过CDN加载一些Bootstrap资源,** JavaScript有一个先后引入规则,jQuery作为Bootstrap的底层依赖,要先于Bootstrap声明 **,这两个script标签在上面介绍的网站上都有
这里有些通用的标签以及要引入的文件都单独提取出来,不用把这些相同的代码都写在每一个页面中
在jsp目录下新建一个common目录,专门存放通用的jsp文件
新建一个tag.jsp,用于引入jstl,如果以后还要引入别的标签,再添加
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
新建一个head.jsp,head标签中的内容所有页面基本都一样
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.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]-->
然后使用jsp的内置对象include,静态引入head.jsp,** 静态包含 是会把引入的文件合并过来 ,也就是head.jsp中的内容会放到外层list.jsp中作为一个Servlet输出,如果是 动态包含 的话,那么head.jsp会作为一个 独立的jsp,先转换为Servlet **,转换后的结果再和list.jsp合并
接着开始编写lsit.jsp的细节部分
panel-default、text-center都是使用Bootstrap提供的样式
在panel-body中使用表格,通过jstl提供的方法来显示要展示的秒杀商品
<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>
首先使用jstl的c:forEach标签,用来迭代从SeckillController中的list方法传过来的"list",这个list是存放秒杀的商品,属性var代表当前项目的变量名,items表示进行循环的项目
一个tr标签是一行,每个td标签是一列,数据库有多少个秒杀商品这个表格就有多少行
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model){
//获取列表页
List<Seckill> list = seckillService.getSeckillList();
model.addAttribute("list", list);
return "list";
}
从SeckillController的list方法返回的是字符串,但是之前说过,Spring MVC会拼接成一个URL地址,返回的数据是个泛型,类型是Seckill
public class Seckill {
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
}
这是Seckill定义的属性,所以在list.jsp页面中通过sk.name来调用相关的参数
日期类型的输出默认是直接调用日期类型的toString,这不符合我们的规范,所以使用jstl的fmt:formatDate标签来格式化输出的时间
最后一列给一个超链接,用于链接这个秒杀商品的详情页,可以把这个超链接做成一个按钮,使用的也是Bootstrap的CSS
2、detail.jsp
这是detail.jsp的一个大的框架,先是由两个div组成,一个用于显示日期或者文本的一个显示面板,在显示面板中做一个埋点,因为这个面板在之后的交互逻辑编码中,在不同时间显示的是不同的内容
<h1>${seckill.name }</h1>
这里可以直接这样写的原因是:
model.addAttribute("seckill", seckill);//SeckillController中的detail方法
另一个div就是登录弹出层,在进入详情页的时候,会通过Cookie判断用户时候登录,没有登录的用户的页面会显示这个登录弹出层,提示用户登录
首先在最外围的div中进行埋点
<div id="killPhoneModal" class="modal fade">
因为这个登录弹出层不是每次用户到详情页都要出现,只有验证Cookie中没有用户登录信息才会出现,所以在这里埋点,如果Cookie中有用户的信息,在交互逻辑中我们会控制这个div不出现
登录弹出层实际是一个模态框,在页面显示的时候主要由三个部分:
- modal-header:显示一些文本
- modal-body:用户输入登录信息
- modal-footer:登录按钮
<div class="modal-header">
<h3 class="modal-title text-center">
<span class="glyphicon glyphicon-phone"></span>秒杀电话:
</h3>
</div>
在modal-header中有个span面板用于显示一些文本和图标
<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>
在modal-body中有一个输入框,这里需要在输入框中进行埋点,之后的交互逻辑要通过这个埋点来获取用户输入的信息
<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>
</button>
</div>
在modal-footer中由两部分组成:
- span:显示错误信息
- button:登录按钮
在button中也需要埋点,用于绑定点击事件
body标签中的内容完成了,下面也要通过CDN引入一些文件
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="http://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- 使用CDN获取公共js -->
<!-- jQuery cookie操作插件 -->
<script src="http://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<!-- jQuery countDown倒计时插件 -->
<script src="http://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>
jquery文件和bootstrap.min.js之前在list.jsp也引入了
对Cookie的操作使用jQuery Cookie插件,倒计时使用jQuery的countDown插件
2、交互逻辑
1、交互流程
当用户点击某一个秒杀商品的按钮的时候,会进入到相应的详情页,这个详情页会判断用户是否登录过,如果登录过就展示详情页页面,如果没有登录过,就弹出登录弹出层,在用户正确填写登录信息后就可以进入详情页
获取标准系统时间,因为用户可能处在不同的时区,用户终端的时间也不可能完全一致,所以要统一地采用一个标准时间,也就是服务器时间
-
通过秒杀商品的开始时间和结束时间来做出不同的判断:
- 系统时间大于结束时间:秒杀活动已结束,在detail.jsp的显示面板显示“秒杀结束”字样
- 系统时间小于开始时间:秒杀活动未开始,在detail.jsp的显示面板显示倒计时,使用的是jQuery的countDown插件,倒计时完成后,会出现秒杀按钮,用户可以执行秒杀操作
- 系统时间介于开始时间和结束时间之间:秒杀活动正在进行,直接出现秒杀按钮,用户可以执行秒杀操作
2、页面展示
3、交互逻辑编程
在src/main/webapp目录下新建一个resources文件夹,再在其中新建一个script文件夹,用于存放脚本文件
创建一个seckill.js
这是最后完成的总览,接着一步步来,整个seckil这样写的原因是模拟高级语言分包的概念,使JavaScript模块化,这样当调用一个方法可以用seckill.detail.init(params)的形式
在详情页初始化中,首先要做的就是获取killPhone节点,这个killPhone节点不是程序中具体的标签,而是Cookie中的用于标识用户信息的数据,用户的信息都放在Cookie中名为killPhone的节点
//在cookie中查找手机号
var killPhone = $.cookie('killPhone');
//验证手机号
if(!seckill.validatePhone(killPhone)){
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show : true,//显示登录弹出层
backdrop : 'static',//禁止位置关闭
keyboard : false//关闭键盘事件
});
$('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val();
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手机号写入cookie
window.location.reload();//刷新页面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
});
}
从Cookie的killPhone中获取数据后,就要验证手机号,验证手机号的逻辑建议提取到更上层,因为可能多个地方都要用到
创建一个函数,名字为validatePhone,这个函数的位置在这一节最开始的图片上可以看到
//验证手机号
validatePhone : function(phone){
if(phone && phone.length == 11 && !isNaN(phone)){
return true;
}else{
return false;
}
},
要验证手机号,所以传入一个手机号的参数,这里使用if语句简单的判断一下
首先要判断手机号是否为空,在js中直接传入参数,它会判断这个参数是否为空,空的话就是undefine,就认为是false
手机号长度必须为11位
isNaN是判断这个参数是否是非数字,如果是非数字的话就是true,所以这里要取反
接着就可以在init方法中调用validatePhone函数来验证手机号
if(!seckill.validatePhone(killPhone)){
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show : true,//显示登录弹出层
backdrop : 'static',//禁止位置关闭
keyboard : false//关闭键盘事件
});
如果手机号存在,就可以直接跳转到详情页了,所以这里处理手机号不存在的情况,因为这个if语句中东西比较多,所以分开来说,完整的代码在前面已经展示过了
手机号不存在,就需要用户进行绑定,之前在detail.jsp中也提前做好了一个登录弹出层,并进行了埋点
id为killPhoneModal,在seckill.js中使用jQuery的选择器可以取到这个节点
var killPhoneModal = $('#killPhoneModal');
这个登录弹出层已经不是单纯的div了,因为使用了Bootstrap的modal,它本身有一个modal的方法,向这个方法传入json, 用于设置这个模态框的一些属性
之前在detail.jsp中这个modal的属性为fade,是隐藏的,既然要让用户绑定手机号,所以要把这个弹出层显示出来
killPhoneModal.modal({
show : true,//显示登录弹出层
backdrop : 'static',//禁止位置关闭
keyboard : false//关闭键盘事件
我们希望在用户没有正确的填写手机号之前,是不能关掉这个弹出层,所以把backdrop关掉,因为用户点击其他区域可能把这个弹出层关掉;通过键盘的ESC也可能关闭弹出层,所以要禁止键盘事件
弹出层显示出来后,要给按钮做事件绑定
$('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val();
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手机号写入cookie
window.location.reload();//刷新页面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
});
}
按钮事件绑定完成后整个验证手机号的if语句才完成了
对按钮做绑定,首先就是要获取到按钮在详情页的节点
<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>
</button>
</div>
可以看到,按钮的节点为killPhoneBtn
当用户点击了按钮,我们认为用户已经填写了在登录弹出层的input
<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>
在input中,之前已经提前进行了埋点,id为killPhoneKey
在seckill.js中获取到这个节点,同时使用val()方法获取到用户输入的内容
var inputPhone = $('#killPhoneKey').val();
拿到用户输入的内容,还要再进行验证,再调用用于验证手机号的函数validatePhone
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手机号写入cookie
window.location.reload();//刷新页面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
}
如果验证通过了,先将inputPhone的值也就是用户输入的手机号写入Cookie中
- expires:Cookie的有效期,单位是“天”
- path:给出有效路径,Cookie只在该路径下有效
为什么path不写全路径?
因为当一些URL没有用到这个Cookie的时候,如果把Cookie中的path设置为全路径,那么这个Cookie中的数据也会传递到后端,对后端处理会有一些影响,所以这只这个killPhone只在seckill模块下有效
然后就是刷新页面,会重新调用detail属性的init方法
如果验证没有通过,在detail.jsp中登录弹出层的modal-footer提前预留了一个span,用于显示错误信息
<span id="killPhoneMessage" class="glyphicon"></span>
同样,在seckill.js中获取到这个span节点
$('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误!</label>').show(300);
对html标签进行操作的时候,通常是先隐藏一下,避免用户看到中间过程,然后插入一些内容,显示的时候给一个时间,单位毫秒,这样看起来有动态的效果
插入的是label标签,使用Bootstrap的CSS,这里显示的文本没有经过处理,直接是写死了,实际的工作中这里应该是要配合前端的数据字典,根据不同的情况显示不同的文本
至此,详情页初始化部分完成,也就是开头的if语句
整个前端的流程基本完成
接着是详情页的流程
首先就是要获取标准系统时间
所以在detail.jsp的最下面添加一些内容,首先是要引入seckill.js
<!-- 开始编写交互逻辑 -->
<script src="/resources/script/seckill.js" type="text/javascript"></script>
然后使用EL表达式传入参数
<script type="text/javascript">
$(function(){
//使用EL表达式传入参数
seckill.detail.init({
seckillId : "${seckill.seckillId}",
startTime : "${seckill.startTime.time}",
endTime : "${seckill.endTime.time}"
});
});
</script>
接着在seckill.js中获取到这些参数
//已经登录
//计时交互逻辑
var startTime = parseInt(params['startTime']);
var endTime = parseInt(params['endTime']);
var seckillId = parseInt(params['seckillId']);
$.get(seckill.URL.now(), {}, function(result){
if(result && result['success']){
var nowTime = result['data'];
//时间判断,计时交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
});
** 这里从列表页传递过来的日期参数需要转型,否则之后会出现日期无效的情况 **
然后通过ajax请求来获取到系统当前时间
@RequestMapping(value = "/time/now", method = RequestMethod.GET)
@ResponseBody
public SeckillResult<Long> time(){
Date now = new Date();
return new SeckillResult<Long>(true, now.getTime());
}
在SeckillController中的time方法就是用来获取系统时间的,在@RequestMapping注解中显示系统当前时间的URL是“/time/now”,限制了请求方式为GET,所以在seckill.js中使用$.get()方法
简单说下$.get()方法
$.get(URL,data,function(data,status,xhr),dataType)
- URL:必需,规定您需要请求的 URL
- data:可选,规定连同请求发送到服务器的数据
- function(data,status,xhr):可选,规定当请求成功时运行的函数
- data:包含来自请求的结果数据
- status:包含请求的状态("success"、"notmodified"、"error"、"timeout"、"parsererror")
- xhr:包含 XMLHttpRequest 对象
- dataType:可选,规定预期的服务器响应的数据类型,默认地,jQuery 会智能判断。
可能的类型:- xml - 一个 XML 文档
- html - HTML 作为纯文本
- text - 纯文本字符串
- script - 以 JavaScript 运行响应,并以纯文本返回
- json - 以 JSON 运行响应,并以 JavaScript 对象返回
- jsonp - 使用 JSONP 加载一个 JSON 块,将添加一个 "?callback=?" 到 URL 来规定回调
$.get(seckill.URL.now, {}, function(result){
if(result && result['success']){
var nowTime = result['data'];
//时间判断,计时交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
});
第一个参数是请求的URL,由于URL太多,为了后期维护、代码的整洁,所以要对URL进行统一的管理,在seckill中新建一个属性URL,用于封装秒杀相关ajax的URL
//封装秒杀相关ajax的URL
URl : {
now : function(){
return '/seckill/time/now';
}
},
在SeckillController中的time方法返回的是SeckillResult<Long>类型的对象
public class SeckillResult<T> {
private boolean success;
private T data;
private String error;
}
这是SeckillResult中定义的属性,其中success是判断是否成功请求,所以在$.get()方法的回调函数中要判断请求是否为空,如果不为空,则在控制台输出信息
if(result && result['success']){
var nowTime = result['data'];
//时间判断,计时交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
如果请求成功,就可以获取到系统当前时间,再加上之前获取到的三个参数,就可以进行时间判断,判断系统当前时间在不在秒杀活动期内,如果不在是秒杀未开始还是秒杀已结束
在seckill中创建countdown函数,用于时间判断
countdown : 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秒');
//时间完成后回调事件
}).on('finish.countdown', function(){
//调用执行秒杀的函数
seckill.handleSeckill(seckillId, seckillBox);
});
}else{
//调用执行秒杀的函数
seckill.handleSeckill(seckillId, seckillBox);
}
},
因为对于时间判断的不同结果,要在详情页中展示不同的内容,所以在detail.jsp中专门设置了一个span,用于显示时间判断的结果
<div class="panel-body">
<h2 class="text-danger">
<!-- 显示time图标 -->
<span class="glyphicon glyphicon-time"></span>
<!-- 显示面板 -->
<span class="glyphicon" id="seckill-box"></span>
</h2>
</div>
提前设置了埋点,id为seckill-box,在seckill.js通过jQuery的加载器获取到这个span节点
然后进行时间判断
if(nowTime > endTime){
//秒杀结束
seckillBox.html('秒杀结束!');
}
系统当前时间大于秒杀的结束时间,说明秒杀结束,这里不用和后端做通信,可以直接通过时间的判断就再详情页显示“秒杀结束”的字样,因为时间到了,不管有没有库存,都无所谓了
if(nowTime < startTime){
//秒杀未开始,计时事件绑定
var killTime = new Date(startTime + 1000);//设置基准时间
seckillBox.countdown(killTime, function(event){
//时间格式
var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒');
//时间完成后回调事件
}).on('finish.countdown', function(){
//调用执行秒杀的函数
seckill.handleSeckill(seckillId, seckillBox);
});
}
系统当前时间小于秒杀开启时间,秒杀未开始,在详情页显示倒计时,既然是倒计时,就要给系统一个基准时间,其实也就是秒杀的开启时间,但是这里在秒杀开始时间的基础+1s,防止用户端的计时偏移
接着使用Bootstrap提供的countdown方法,实际上就是一个事件绑定方法
seckillBox.countdown(killTime, function(event){
//时间格式
var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒');
seckillBox.html(format);
//倒计时完成后回调事件
})
countdown事件绑定方法中也有一个回调函数,当日期在不断的变化的时候,这个回调函数会做相应的输出,对日期的格式做个调整
countdown插件只是负责倒计时,倒计时完成后就可以执行秒杀操作了,所以在countdown时间绑定后再接上一个事件操作
.on('finish.countdown', function(){
//调用执行秒杀的函数
seckill.handleSeckill(seckillId, seckillBox);
});
事件的名字是finish.countdown,再加上一个回调函数,用于倒计时完成后回调事件,在这个函数中要调用执行秒杀的函数
这里把执行秒杀的函数单独的提取出来,一是降低耦合,二是避免代码重复,因为在最初调用时间判断函数countdown的时候,可能秒杀正在进行,而上面的代码是秒杀未开始,倒计时完成后才可以执行秒杀,在多个地方需要执行秒杀的操作,所以要把执行秒杀的操作单独创建一个函数
handleSeckill : 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.countdown(seckillId, now, start, end);
}
}else{
console.log('result: ' + result);
}
});
}.
这个方法的参数有个node,用来获取节点的,因为之前在detail.jsp中有专门显示时间判断的结果的span,当可以进行秒杀的时候,这个span显示的就是一个按钮,所以这里也要获取这个span节点,来对这个span进行操作,加入一个button标签
node.hide()
.html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');
插入按钮后先不要显示出来,因为后面还要对用户信息也就是手机号进行验证
执行秒杀操作之前,就要先取得秒杀的地址
@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;
}
在SeckillController的exposer方法就是用来暴露秒杀地址的,这个方法只接收POST请求,返回的是SeckillResult对象,类型是Exposer、
在seckill.js中使用$.post()方法,类似前面讲过的$.get()方法
$.post(seckill.URL.exposer(seckillId), {}, function(result){
//在回调函数中执行交互流程
if(result && result['success']){
var exposer = result['data'];
}else{
console.log('result: ' + result);
}
});
要传入请求的URL,也要放在seckill的URL属性中
exposer : function(seckillId){
return '/seckill/' + seckillId + '/exposer';
}
这个URL需要传递秒杀商品的id,因为不同的秒杀商品需要相应的UEL
首先还是要判断ajax请求是否成功,如果没有请求成功,在控制台打印信息
如果请求成功,获取$.post()方法返回过来的数据,是Exposer类型的,封装在SeckillResult的data属性中
public class Exposer {
//是否开启秒杀
private boolean exposed;
//加密措施
private String md5;
//id
private long seckillId;
//系统当前时间(毫秒)
private long now;
//秒杀开启时间
private long start;
//秒杀结束时间
private long end;
}
获取到Exposer对象后,在Exposer类中有一个exposed属性,用来判断是否开启秒杀,如果开启秒杀,就要控制之前定义的按钮,先绑定点击事件,然后显示出来
如果不开启秒杀,就返回系统当前时间、秒杀开启时间、秒杀结束时间,再调用countdown函数
if(exposer['exposed']){
}else{
//未开启秒杀
var now = exposer['now'];
var start = exposer['start'];
var end = exposer['end'];
seckill.countdown(seckillId, now, start, end);
}
既然都到这一步了,什么情况下还是秒杀未开始?
当不同的终端显示过长的时间的时候,可能出现一些偏差,用户显示已经开启秒杀,但是实际上服务器的时间还没到,虽然时间差很小,但是还是要重新计算计时逻辑,所以调用countdown函数
判断开启秒杀之后,先要获取秒杀地址
//开启秒杀,获取秒杀地址
var md5 = exposer['md5'];
var killUrl = seckill.URL.execution(seckillId, md5);
console.log('killUrl: ' + killUrl);
用于执行秒杀操作的URL需要经过MD5的加密,所以还要从后端获取到MD5,同样,ajax请求的URL都要封装在seckill.js的URL属性中
execution : function(seckillId, md5){
return '/seckill/' + seckillId + '/' + md5 + '/execution';
}
这些URL之前在Controller层都已经定义好的
@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)
获取到了执行秒杀的URL,就可以控制按钮,绑定点击事件
//绑定一次点击事件
$('#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>');
}
});
});
但是只绑定一次点击事件,防止用户连续点击,比如用户不放心页面是否响应,所以可能会连续的点击按钮,如果不在这控制的话,这些点击最后都会发送到服务器端,会造成服务器端在同一时间接到大量相同的URL请求,对各方面都有影响
所以点击完之后就要禁用按钮,通过this指代当前对象,也就是相当于使用$('#killBtn')
之后就是发送秒杀请求,执行秒杀操作,在SeckillController的execute方法只接收POST请求,所以使用$.post()方法
然后通过SeckillResult中的success属性判断是否请求成功
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 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);
}
这是SeckillController的execute方法,返回的都是SeckillExecution对象,这些对象存放在SeckillResult的data属性中
public class SeckillExecution {
private long seckillId;
//秒杀结果执行后的状态
private int state;
//状态信息
private String stateInfo;
//秒杀成功对象
private SuccessKilled successKilled;
}
这是SeckillExecution类中定义的方法,在seckill.js中获取到这些属性
$.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>');
}
});
获取到执行秒杀的结果后,还要在详情页中显示出来,所以控制节点,输出状态信息,因为在SeckillController的execute方法中已经定义了重复秒杀、秒杀结束等异常也算请求成功,只是不对数据库进行操作,但是结果信息要返回到详情页
最后就可以把按钮显示出来了
node.show();
至此,前端页面完成了