前言
本篇將完成前端頁面的設(shè)計(jì)與開發(fā),包括:
- 使用Bootstrap開發(fā)頁面結(jié)構(gòu)
- 交互邏輯編程
一、使用Bootstrap開發(fā)頁面結(jié)構(gòu)
在設(shè)計(jì)SeckillController中我們已經(jīng)設(shè)置了jsp文件的路徑,在/WEB-INF/新建一個(gè)jsp目錄,在該目錄下新建list.jsp和detail.jsp
使用Bootstrap的模板鸟废,這個(gè)模板基本上是固定的
<%@ 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 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文件盒延。務(wù)必在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內(nèi)置對(duì)象page中的contentType修改為UTF-8缩擂,這個(gè)模板已經(jīng)引入了一些文件包含了 jquery.js、bootstrap.min.js 和 bootstrap.min.css 文件添寺,用于讓一個(gè)常規(guī)的 HTML 文件變?yōu)槭褂昧薆ootstrap的模板
最下面有兩個(gè)script標(biāo)簽胯盯,通過CDN加載一些Bootstrap資源,** JavaScript有一個(gè)先后引入規(guī)則计露,jQuery作為Bootstrap的底層依賴博脑,要先于Bootstrap聲明 **,這兩個(gè)script標(biāo)簽在上面介紹的網(wǎng)站上都有
這里有些通用的標(biāo)簽以及要引入的文件都單獨(dú)提取出來薄坏,不用把這些相同的代碼都寫在每一個(gè)頁面中
在jsp目錄下新建一個(gè)common目錄趋厉,專門存放通用的jsp文件
新建一個(gè)tag.jsp,用于引入jstl胶坠,如果以后還要引入別的標(biāo)簽君账,再添加
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
新建一個(gè)head.jsp,head標(biāo)簽中的內(nèi)容所有頁面基本都一樣
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link 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的內(nèi)置對(duì)象include乡数,靜態(tài)引入head.jsp,** 靜態(tài)包含 是會(huì)把引入的文件合并過來 闻牡,也就是head.jsp中的內(nèi)容會(huì)放到外層list.jsp中作為一個(gè)Servlet輸出净赴,如果是 動(dòng)態(tài)包含 的話,那么head.jsp會(huì)作為一個(gè) 獨(dú)立的jsp罩润,先轉(zhuǎn)換為Servlet **玖翅,轉(zhuǎn)換后的結(jié)果再和list.jsp合并
接著開始編寫lsit.jsp的細(xì)節(jié)部分
panel-default、text-center都是使用Bootstrap提供的樣式
在panel-body中使用表格割以,通過jstl提供的方法來顯示要展示的秒殺商品
<thead>
<tr>
<th>名稱</th>
<th>庫存</th>
<th>開始時(shí)間</th>
<th>結(jié)束時(shí)間</th>
<th>創(chuàng)建時(shí)間</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標(biāo)簽金度,用來迭代從SeckillController中的list方法傳過來的"list",這個(gè)list是存放秒殺的商品严沥,屬性var代表當(dāng)前項(xiàng)目的變量名猜极,items表示進(jìn)行循環(huán)的項(xiàng)目
一個(gè)tr標(biāo)簽是一行,每個(gè)td標(biāo)簽是一列消玄,數(shù)據(jù)庫有多少個(gè)秒殺商品這個(gè)表格就有多少行
@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會(huì)拼接成一個(gè)URL地址翩瓜,返回的數(shù)據(jù)是個(gè)泛型受扳,類型是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來調(diào)用相關(guān)的參數(shù)
日期類型的輸出默認(rèn)是直接調(diào)用日期類型的toString奥溺,這不符合我們的規(guī)范辞色,所以使用jstl的fmt:formatDate標(biāo)簽來格式化輸出的時(shí)間
最后一列給一個(gè)超鏈接,用于鏈接這個(gè)秒殺商品的詳情頁,可以把這個(gè)超鏈接做成一個(gè)按鈕相满,使用的也是Bootstrap的CSS
2层亿、detail.jsp
這是detail.jsp的一個(gè)大的框架,先是由兩個(gè)div組成立美,一個(gè)用于顯示日期或者文本的一個(gè)顯示面板匿又,在顯示面板中做一個(gè)埋點(diǎn),因?yàn)檫@個(gè)面板在之后的交互邏輯編碼中建蹄,在不同時(shí)間顯示的是不同的內(nèi)容
<h1>${seckill.name }</h1>
這里可以直接這樣寫的原因是:
model.addAttribute("seckill", seckill);//SeckillController中的detail方法
另一個(gè)div就是登錄彈出層碌更,在進(jìn)入詳情頁的時(shí)候,會(huì)通過Cookie判斷用戶時(shí)候登錄洞慎,沒有登錄的用戶的頁面會(huì)顯示這個(gè)登錄彈出層痛单,提示用戶登錄
首先在最外圍的div中進(jìn)行埋點(diǎn)
<div id="killPhoneModal" class="modal fade">
因?yàn)檫@個(gè)登錄彈出層不是每次用戶到詳情頁都要出現(xiàn),只有驗(yàn)證Cookie中沒有用戶登錄信息才會(huì)出現(xiàn)劲腿,所以在這里埋點(diǎn)旭绒,如果Cookie中有用戶的信息,在交互邏輯中我們會(huì)控制這個(gè)div不出現(xiàn)
登錄彈出層實(shí)際是一個(gè)模態(tài)框焦人,在頁面顯示的時(shí)候主要由三個(gè)部分:
- 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中有個(gè)span面板用于顯示一些文本和圖標(biāo)
<div class="modal-body">
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" name="killPhone" id="killPhoneKey"
placeholder="填寫手機(jī)號(hào)^o^" class="form-control">
</div>
</div>
</div>
在modal-body中有一個(gè)輸入框挥吵,這里需要在輸入框中進(jìn)行埋點(diǎn),之后的交互邏輯要通過這個(gè)埋點(diǎn)來獲取用戶輸入的信息
<div class="modal-footer">
<!-- 驗(yàn)證信息 -->
<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:顯示錯(cuò)誤信息
- button:登錄按鈕
在button中也需要埋點(diǎn)花椭,用于綁定點(diǎn)擊事件
body標(biāo)簽中的內(nèi)容完成了忽匈,下面也要通過CDN引入一些文件
<!-- jQuery文件。務(wù)必在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倒計(jì)時(shí)插件 -->
<script src="http://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>
jquery文件和bootstrap.min.js之前在list.jsp也引入了
對(duì)Cookie的操作使用jQuery Cookie插件矿辽,倒計(jì)時(shí)使用jQuery的countDown插件
2丹允、交互邏輯
1、交互流程
當(dāng)用戶點(diǎn)擊某一個(gè)秒殺商品的按鈕的時(shí)候袋倔,會(huì)進(jìn)入到相應(yīng)的詳情頁嫌松,這個(gè)詳情頁會(huì)判斷用戶是否登錄過,如果登錄過就展示詳情頁頁面奕污,如果沒有登錄過,就彈出登錄彈出層液走,在用戶正確填寫登錄信息后就可以進(jìn)入詳情頁
獲取標(biāo)準(zhǔn)系統(tǒng)時(shí)間碳默,因?yàn)橛脩艨赡芴幵诓煌臅r(shí)區(qū),用戶終端的時(shí)間也不可能完全一致缘眶,所以要統(tǒng)一地采用一個(gè)標(biāo)準(zhǔn)時(shí)間嘱根,也就是服務(wù)器時(shí)間
-
通過秒殺商品的開始時(shí)間和結(jié)束時(shí)間來做出不同的判斷:
- 系統(tǒng)時(shí)間大于結(jié)束時(shí)間:秒殺活動(dòng)已結(jié)束,在detail.jsp的顯示面板顯示“秒殺結(jié)束”字樣
- 系統(tǒng)時(shí)間小于開始時(shí)間:秒殺活動(dòng)未開始巷懈,在detail.jsp的顯示面板顯示倒計(jì)時(shí)该抒,使用的是jQuery的countDown插件,倒計(jì)時(shí)完成后顶燕,會(huì)出現(xiàn)秒殺按鈕凑保,用戶可以執(zhí)行秒殺操作
- 系統(tǒng)時(shí)間介于開始時(shí)間和結(jié)束時(shí)間之間:秒殺活動(dòng)正在進(jìn)行冈爹,直接出現(xiàn)秒殺按鈕,用戶可以執(zhí)行秒殺操作
2欧引、頁面展示
3频伤、交互邏輯編程
在src/main/webapp目錄下新建一個(gè)resources文件夾,再在其中新建一個(gè)script文件夾芝此,用于存放腳本文件
創(chuàng)建一個(gè)seckill.js
這是最后完成的總覽憋肖,接著一步步來,整個(gè)seckil這樣寫的原因是模擬高級(jí)語言分包的概念婚苹,使JavaScript模塊化岸更,這樣當(dāng)調(diào)用一個(gè)方法可以用seckill.detail.init(params)的形式
在詳情頁初始化中,首先要做的就是獲取killPhone節(jié)點(diǎn)膊升,這個(gè)killPhone節(jié)點(diǎn)不是程序中具體的標(biāo)簽怎炊,而是Cookie中的用于標(biāo)識(shí)用戶信息的數(shù)據(jù),用戶的信息都放在Cookie中名為killPhone的節(jié)點(diǎn)
//在cookie中查找手機(jī)號(hào)
var killPhone = $.cookie('killPhone');
//驗(yàn)證手機(jī)號(hào)
if(!seckill.validatePhone(killPhone)){
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show : true,//顯示登錄彈出層
backdrop : 'static',//禁止位置關(guān)閉
keyboard : false//關(guān)閉鍵盤事件
});
$('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val();
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
window.location.reload();//刷新頁面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
}
});
}
從Cookie的killPhone中獲取數(shù)據(jù)后用僧,就要驗(yàn)證手機(jī)號(hào)结胀,驗(yàn)證手機(jī)號(hào)的邏輯建議提取到更上層,因?yàn)榭赡芏鄠€(gè)地方都要用到
創(chuàng)建一個(gè)函數(shù)责循,名字為validatePhone糟港,這個(gè)函數(shù)的位置在這一節(jié)最開始的圖片上可以看到
//驗(yàn)證手機(jī)號(hào)
validatePhone : function(phone){
if(phone && phone.length == 11 && !isNaN(phone)){
return true;
}else{
return false;
}
},
要驗(yàn)證手機(jī)號(hào),所以傳入一個(gè)手機(jī)號(hào)的參數(shù)院仿,這里使用if語句簡單的判斷一下
首先要判斷手機(jī)號(hào)是否為空秸抚,在js中直接傳入?yún)?shù),它會(huì)判斷這個(gè)參數(shù)是否為空歹垫,空的話就是undefine剥汤,就認(rèn)為是false
手機(jī)號(hào)長度必須為11位
isNaN是判斷這個(gè)參數(shù)是否是非數(shù)字,如果是非數(shù)字的話就是true排惨,所以這里要取反
接著就可以在init方法中調(diào)用validatePhone函數(shù)來驗(yàn)證手機(jī)號(hào)
if(!seckill.validatePhone(killPhone)){
var killPhoneModal = $('#killPhoneModal');
killPhoneModal.modal({
show : true,//顯示登錄彈出層
backdrop : 'static',//禁止位置關(guān)閉
keyboard : false//關(guān)閉鍵盤事件
});
如果手機(jī)號(hào)存在吭敢,就可以直接跳轉(zhuǎn)到詳情頁了,所以這里處理手機(jī)號(hào)不存在的情況暮芭,因?yàn)檫@個(gè)if語句中東西比較多鹿驼,所以分開來說,完整的代碼在前面已經(jīng)展示過了
手機(jī)號(hào)不存在辕宏,就需要用戶進(jìn)行綁定畜晰,之前在detail.jsp中也提前做好了一個(gè)登錄彈出層,并進(jìn)行了埋點(diǎn)
id為killPhoneModal瑞筐,在seckill.js中使用jQuery的選擇器可以取到這個(gè)節(jié)點(diǎn)
var killPhoneModal = $('#killPhoneModal');
這個(gè)登錄彈出層已經(jīng)不是單純的div了凄鼻,因?yàn)槭褂昧薆ootstrap的modal,它本身有一個(gè)modal的方法,向這個(gè)方法傳入json块蚌, 用于設(shè)置這個(gè)模態(tài)框的一些屬性
之前在detail.jsp中這個(gè)modal的屬性為fade闰非,是隱藏的,既然要讓用戶綁定手機(jī)號(hào)匈子,所以要把這個(gè)彈出層顯示出來
killPhoneModal.modal({
show : true,//顯示登錄彈出層
backdrop : 'static',//禁止位置關(guān)閉
keyboard : false//關(guān)閉鍵盤事件
我們希望在用戶沒有正確的填寫手機(jī)號(hào)之前河胎,是不能關(guān)掉這個(gè)彈出層,所以把backdrop關(guān)掉虎敦,因?yàn)橛脩酎c(diǎn)擊其他區(qū)域可能把這個(gè)彈出層關(guān)掉游岳;通過鍵盤的ESC也可能關(guān)閉彈出層,所以要禁止鍵盤事件
彈出層顯示出來后其徙,要給按鈕做事件綁定
$('#killPhoneBtn').click(function(){
var inputPhone = $('#killPhoneKey').val();
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
window.location.reload();//刷新頁面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
}
});
}
按鈕事件綁定完成后整個(gè)驗(yàn)證手機(jī)號(hào)的if語句才完成了
對(duì)按鈕做綁定胚迫,首先就是要獲取到按鈕在詳情頁的節(jié)點(diǎn)
<div class="modal-footer">
<!-- 驗(yàn)證信息 -->
<span id="killPhoneMessage" class="glyphicon"></span>
<button type="button" id="killPhoneBtn" class="btn btn-success">
<span class="glyphicon glyphicon-phone"></span>
</button>
</div>
可以看到,按鈕的節(jié)點(diǎn)為killPhoneBtn
當(dāng)用戶點(diǎn)擊了按鈕唾那,我們認(rèn)為用戶已經(jīng)填寫了在登錄彈出層的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="填寫手機(jī)號(hào)^o^" class="form-control">
</div>
</div>
</div>
在input中访锻,之前已經(jīng)提前進(jìn)行了埋點(diǎn),id為killPhoneKey
在seckill.js中獲取到這個(gè)節(jié)點(diǎn)闹获,同時(shí)使用val()方法獲取到用戶輸入的內(nèi)容
var inputPhone = $('#killPhoneKey').val();
拿到用戶輸入的內(nèi)容期犬,還要再進(jìn)行驗(yàn)證,再調(diào)用用于驗(yàn)證手機(jī)號(hào)的函數(shù)validatePhone
if(seckill.validatePhone(inputPhone)){
$.cookie('killPhone', inputPhone, {expires:7, path:'/seckill'});//手機(jī)號(hào)寫入cookie
window.location.reload();//刷新頁面
}else{
$('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
}
如果驗(yàn)證通過了避诽,先將inputPhone的值也就是用戶輸入的手機(jī)號(hào)寫入Cookie中
- expires:Cookie的有效期龟虎,單位是“天”
- path:給出有效路徑,Cookie只在該路徑下有效
為什么path不寫全路徑沙庐?
因?yàn)楫?dāng)一些URL沒有用到這個(gè)Cookie的時(shí)候鲤妥,如果把Cookie中的path設(shè)置為全路徑,那么這個(gè)Cookie中的數(shù)據(jù)也會(huì)傳遞到后端拱雏,對(duì)后端處理會(huì)有一些影響棉安,所以這只這個(gè)killPhone只在seckill模塊下有效
然后就是刷新頁面,會(huì)重新調(diào)用detail屬性的init方法
如果驗(yàn)證沒有通過铸抑,在detail.jsp中登錄彈出層的modal-footer提前預(yù)留了一個(gè)span贡耽,用于顯示錯(cuò)誤信息
<span id="killPhoneMessage" class="glyphicon"></span>
同樣,在seckill.js中獲取到這個(gè)span節(jié)點(diǎn)
$('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)錯(cuò)誤!</label>').show(300);
對(duì)html標(biāo)簽進(jìn)行操作的時(shí)候鹊汛,通常是先隱藏一下菇爪,避免用戶看到中間過程,然后插入一些內(nèi)容柒昏,顯示的時(shí)候給一個(gè)時(shí)間,單位毫秒熙揍,這樣看起來有動(dòng)態(tài)的效果
插入的是label標(biāo)簽职祷,使用Bootstrap的CSS,這里顯示的文本沒有經(jīng)過處理,直接是寫死了有梆,實(shí)際的工作中這里應(yīng)該是要配合前端的數(shù)據(jù)字典是尖,根據(jù)不同的情況顯示不同的文本
至此,詳情頁初始化部分完成泥耀,也就是開頭的if語句
整個(gè)前端的流程基本完成
接著是詳情頁的流程
首先就是要獲取標(biāo)準(zhǔn)系統(tǒng)時(shí)間
所以在detail.jsp的最下面添加一些內(nèi)容饺汹,首先是要引入seckill.js
<!-- 開始編寫交互邏輯 -->
<script src="/resources/script/seckill.js" type="text/javascript"></script>
然后使用EL表達(dá)式傳入?yún)?shù)
<script type="text/javascript">
$(function(){
//使用EL表達(dá)式傳入?yún)?shù)
seckill.detail.init({
seckillId : "${seckill.seckillId}",
startTime : "${seckill.startTime.time}",
endTime : "${seckill.endTime.time}"
});
});
</script>
接著在seckill.js中獲取到這些參數(shù)
//已經(jīng)登錄
//計(jì)時(shí)交互邏輯
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'];
//時(shí)間判斷,計(jì)時(shí)交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
});
** 這里從列表頁傳遞過來的日期參數(shù)需要轉(zhuǎn)型痰催,否則之后會(huì)出現(xiàn)日期無效的情況 **
然后通過ajax請求來獲取到系統(tǒng)當(dā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());
}
在SeckillController中的time方法就是用來獲取系統(tǒng)時(shí)間的兜辞,在@RequestMapping注解中顯示系統(tǒng)當(dāng)前時(shí)間的URL是“/time/now”,限制了請求方式為GET夸溶,所以在seckill.js中使用$.get()方法
簡單說下$.get()方法
$.get(URL,data,function(data,status,xhr),dataType)
- URL:必需逸吵,規(guī)定您需要請求的 URL
- data:可選,規(guī)定連同請求發(fā)送到服務(wù)器的數(shù)據(jù)
- function(data,status,xhr):可選缝裁,規(guī)定當(dāng)請求成功時(shí)運(yùn)行的函數(shù)
- data:包含來自請求的結(jié)果數(shù)據(jù)
- status:包含請求的狀態(tài)("success"扫皱、"notmodified"、"error"捷绑、"timeout"韩脑、"parsererror")
- xhr:包含 XMLHttpRequest 對(duì)象
- dataType:可選,規(guī)定預(yù)期的服務(wù)器響應(yīng)的數(shù)據(jù)類型粹污,默認(rèn)地段多,jQuery 會(huì)智能判斷。
可能的類型:- xml - 一個(gè) XML 文檔
- html - HTML 作為純文本
- text - 純文本字符串
- script - 以 JavaScript 運(yùn)行響應(yīng)厕怜,并以純文本返回
- json - 以 JSON 運(yùn)行響應(yīng)衩匣,并以 JavaScript 對(duì)象返回
- jsonp - 使用 JSONP 加載一個(gè) JSON 塊,將添加一個(gè) "?callback=?" 到 URL 來規(guī)定回調(diào)
$.get(seckill.URL.now, {}, function(result){
if(result && result['success']){
var nowTime = result['data'];
//時(shí)間判斷粥航,計(jì)時(shí)交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
});
第一個(gè)參數(shù)是請求的URL琅捏,由于URL太多,為了后期維護(hù)递雀、代碼的整潔柄延,所以要對(duì)URL進(jìn)行統(tǒng)一的管理,在seckill中新建一個(gè)屬性URL缀程,用于封裝秒殺相關(guān)ajax的URL
//封裝秒殺相關(guān)ajax的URL
URl : {
now : function(){
return '/seckill/time/now';
}
},
在SeckillController中的time方法返回的是SeckillResult<Long>類型的對(duì)象
public class SeckillResult<T> {
private boolean success;
private T data;
private String error;
}
這是SeckillResult中定義的屬性搜吧,其中success是判斷是否成功請求,所以在$.get()方法的回調(diào)函數(shù)中要判斷請求是否為空杨凑,如果不為空滤奈,則在控制臺(tái)輸出信息
if(result && result['success']){
var nowTime = result['data'];
//時(shí)間判斷,計(jì)時(shí)交互
seckill.countdown(seckillId, nowTime, startTime, endTime);
}else{
console.log('result: ' + result);
}
如果請求成功撩满,就可以獲取到系統(tǒng)當(dāng)前時(shí)間蜒程,再加上之前獲取到的三個(gè)參數(shù)绅你,就可以進(jìn)行時(shí)間判斷,判斷系統(tǒng)當(dāng)前時(shí)間在不在秒殺活動(dòng)期內(nèi)昭躺,如果不在是秒殺未開始還是秒殺已結(jié)束
在seckill中創(chuàng)建countdown函數(shù)忌锯,用于時(shí)間判斷
countdown : function(seckillId, nowTime, startTime, endTime){
var seckillBox = $('#seckill-box');
//時(shí)間判斷
if(nowTime > endTime){
//秒殺結(jié)束
seckillBox.html('秒殺結(jié)束!');
}else if(nowTime < startTime){
//秒殺未開始,計(jì)時(shí)事件綁定
var killTime = new Date(startTime + 1000);//設(shè)置基準(zhǔn)時(shí)間
seckillBox.countdown(killTime, function(event){
//時(shí)間格式
var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
//時(shí)間完成后回調(diào)事件
}).on('finish.countdown', function(){
//調(diào)用執(zhí)行秒殺的函數(shù)
seckill.handleSeckill(seckillId, seckillBox);
});
}else{
//調(diào)用執(zhí)行秒殺的函數(shù)
seckill.handleSeckill(seckillId, seckillBox);
}
},
因?yàn)閷?duì)于時(shí)間判斷的不同結(jié)果领炫,要在詳情頁中展示不同的內(nèi)容偶垮,所以在detail.jsp中專門設(shè)置了一個(gè)span,用于顯示時(shí)間判斷的結(jié)果
<div class="panel-body">
<h2 class="text-danger">
<!-- 顯示time圖標(biāo) -->
<span class="glyphicon glyphicon-time"></span>
<!-- 顯示面板 -->
<span class="glyphicon" id="seckill-box"></span>
</h2>
</div>
提前設(shè)置了埋點(diǎn)帝洪,id為seckill-box似舵,在seckill.js通過jQuery的加載器獲取到這個(gè)span節(jié)點(diǎn)
然后進(jìn)行時(shí)間判斷
if(nowTime > endTime){
//秒殺結(jié)束
seckillBox.html('秒殺結(jié)束!');
}
系統(tǒng)當(dāng)前時(shí)間大于秒殺的結(jié)束時(shí)間,說明秒殺結(jié)束碟狞,這里不用和后端做通信啄枕,可以直接通過時(shí)間的判斷就再詳情頁顯示“秒殺結(jié)束”的字樣,因?yàn)闀r(shí)間到了族沃,不管有沒有庫存频祝,都無所謂了
if(nowTime < startTime){
//秒殺未開始,計(jì)時(shí)事件綁定
var killTime = new Date(startTime + 1000);//設(shè)置基準(zhǔn)時(shí)間
seckillBox.countdown(killTime, function(event){
//時(shí)間格式
var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
//時(shí)間完成后回調(diào)事件
}).on('finish.countdown', function(){
//調(diào)用執(zhí)行秒殺的函數(shù)
seckill.handleSeckill(seckillId, seckillBox);
});
}
系統(tǒng)當(dāng)前時(shí)間小于秒殺開啟時(shí)間脆淹,秒殺未開始常空,在詳情頁顯示倒計(jì)時(shí),既然是倒計(jì)時(shí)盖溺,就要給系統(tǒng)一個(gè)基準(zhǔn)時(shí)間漓糙,其實(shí)也就是秒殺的開啟時(shí)間,但是這里在秒殺開始時(shí)間的基礎(chǔ)+1s烘嘱,防止用戶端的計(jì)時(shí)偏移
接著使用Bootstrap提供的countdown方法昆禽,實(shí)際上就是一個(gè)事件綁定方法
seckillBox.countdown(killTime, function(event){
//時(shí)間格式
var format = event.strftime('秒殺倒計(jì)時(shí): %D天 %H時(shí) %M分 %S秒');
seckillBox.html(format);
//倒計(jì)時(shí)完成后回調(diào)事件
})
countdown事件綁定方法中也有一個(gè)回調(diào)函數(shù),當(dāng)日期在不斷的變化的時(shí)候蝇庭,這個(gè)回調(diào)函數(shù)會(huì)做相應(yīng)的輸出醉鳖,對(duì)日期的格式做個(gè)調(diào)整
countdown插件只是負(fù)責(zé)倒計(jì)時(shí),倒計(jì)時(shí)完成后就可以執(zhí)行秒殺操作了哮内,所以在countdown時(shí)間綁定后再接上一個(gè)事件操作
.on('finish.countdown', function(){
//調(diào)用執(zhí)行秒殺的函數(shù)
seckill.handleSeckill(seckillId, seckillBox);
});
事件的名字是finish.countdown盗棵,再加上一個(gè)回調(diào)函數(shù),用于倒計(jì)時(shí)完成后回調(diào)事件北发,在這個(gè)函數(shù)中要調(diào)用執(zhí)行秒殺的函數(shù)
這里把執(zhí)行秒殺的函數(shù)單獨(dú)的提取出來纹因,一是降低耦合,二是避免代碼重復(fù)琳拨,因?yàn)樵谧畛跽{(diào)用時(shí)間判斷函數(shù)countdown的時(shí)候瞭恰,可能秒殺正在進(jìn)行,而上面的代碼是秒殺未開始狱庇,倒計(jì)時(shí)完成后才可以執(zhí)行秒殺惊畏,在多個(gè)地方需要執(zhí)行秒殺的操作是牢,所以要把執(zhí)行秒殺的操作單獨(dú)創(chuàng)建一個(gè)函數(shù)
handleSeckill : function(seckillId, node){
//獲取秒殺地址,控制顯示邏輯陕截,執(zhí)行秒殺
node.hide()
.html('<button class="btn btn-primary btn-lg" id="killBtn">開始秒殺</button>');
$.post(seckill.URL.exposer(seckillId), {}, function(result){
//在回調(diào)函數(shù)中執(zhí)行交互流程
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);
//綁定一次點(diǎn)擊事件
$('#killBtn').one('click', function(){
//執(zhí)行秒殺請求
//1.禁用按鈕
$(this).addClass('disabled');
//2.發(fā)送秒殺請求執(zhí)行秒殺
$.post(killUrl, {}, function(result){
if(result && result['success']){
var killResult = result['data'];
var state = killResult['state'];
var stateInfo = killResult['stateInfo'];
//3.顯示秒殺結(jié)果
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);
}
});
}.
這個(gè)方法的參數(shù)有個(gè)node批什,用來獲取節(jié)點(diǎn)的农曲,因?yàn)橹霸赿etail.jsp中有專門顯示時(shí)間判斷的結(jié)果的span,當(dāng)可以進(jìn)行秒殺的時(shí)候驻债,這個(gè)span顯示的就是一個(gè)按鈕乳规,所以這里也要獲取這個(gè)span節(jié)點(diǎn),來對(duì)這個(gè)span進(jìn)行操作合呐,加入一個(gè)button標(biāo)簽
node.hide()
.html('<button class="btn btn-primary btn-lg" id="killBtn">開始秒殺</button>');
插入按鈕后先不要顯示出來暮的,因?yàn)楹竺孢€要對(duì)用戶信息也就是手機(jī)號(hào)進(jìn)行驗(yàn)證
執(zhí)行秒殺操作之前,就要先取得秒殺的地址
@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方法就是用來暴露秒殺地址的淌实,這個(gè)方法只接收POST請求冻辩,返回的是SeckillResult對(duì)象,類型是Exposer拆祈、
在seckill.js中使用$.post()方法恨闪,類似前面講過的$.get()方法
$.post(seckill.URL.exposer(seckillId), {}, function(result){
//在回調(diào)函數(shù)中執(zhí)行交互流程
if(result && result['success']){
var exposer = result['data'];
}else{
console.log('result: ' + result);
}
});
要傳入請求的URL,也要放在seckill的URL屬性中
exposer : function(seckillId){
return '/seckill/' + seckillId + '/exposer';
}
這個(gè)URL需要傳遞秒殺商品的id放坏,因?yàn)椴煌拿霘⑸唐沸枰鄳?yīng)的UEL
首先還是要判斷ajax請求是否成功咙咽,如果沒有請求成功,在控制臺(tái)打印信息
如果請求成功淤年,獲取$.post()方法返回過來的數(shù)據(jù)钧敞,是Exposer類型的,封裝在SeckillResult的data屬性中
public class Exposer {
//是否開啟秒殺
private boolean exposed;
//加密措施
private String md5;
//id
private long seckillId;
//系統(tǒng)當(dāng)前時(shí)間(毫秒)
private long now;
//秒殺開啟時(shí)間
private long start;
//秒殺結(jié)束時(shí)間
private long end;
}
獲取到Exposer對(duì)象后麸粮,在Exposer類中有一個(gè)exposed屬性溉苛,用來判斷是否開啟秒殺,如果開啟秒殺豹休,就要控制之前定義的按鈕炊昆,先綁定點(diǎn)擊事件,然后顯示出來
如果不開啟秒殺威根,就返回系統(tǒng)當(dāng)前時(shí)間凤巨、秒殺開啟時(shí)間、秒殺結(jié)束時(shí)間洛搀,再調(diào)用countdown函數(shù)
if(exposer['exposed']){
}else{
//未開啟秒殺
var now = exposer['now'];
var start = exposer['start'];
var end = exposer['end'];
seckill.countdown(seckillId, now, start, end);
}
既然都到這一步了敢茁,什么情況下還是秒殺未開始?
當(dāng)不同的終端顯示過長的時(shí)間的時(shí)候留美,可能出現(xiàn)一些偏差彰檬,用戶顯示已經(jīng)開啟秒殺伸刃,但是實(shí)際上服務(wù)器的時(shí)間還沒到,雖然時(shí)間差很小逢倍,但是還是要重新計(jì)算計(jì)時(shí)邏輯捧颅,所以調(diào)用countdown函數(shù)
判斷開啟秒殺之后,先要獲取秒殺地址
//開啟秒殺较雕,獲取秒殺地址
var md5 = exposer['md5'];
var killUrl = seckill.URL.execution(seckillId, md5);
console.log('killUrl: ' + killUrl);
用于執(zhí)行秒殺操作的URL需要經(jīng)過MD5的加密碉哑,所以還要從后端獲取到MD5,同樣亮蒋,ajax請求的URL都要封裝在seckill.js的URL屬性中
execution : function(seckillId, md5){
return '/seckill/' + seckillId + '/' + md5 + '/execution';
}
這些URL之前在Controller層都已經(jīng)定義好的
@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)
獲取到了執(zhí)行秒殺的URL扣典,就可以控制按鈕,綁定點(diǎn)擊事件
//綁定一次點(diǎn)擊事件
$('#killBtn').one('click', function(){
//執(zhí)行秒殺請求
//1.禁用按鈕
$(this).addClass('disabled');
//2.發(fā)送秒殺請求執(zhí)行秒殺
$.post(killUrl, {}, function(result){
if(result && result['success']){
var killResult = result['data'];
var state = killResult['state'];
var stateInfo = killResult['stateInfo'];
//3.顯示秒殺結(jié)果
node.html('<span class="label label-success">' + stateInfo + '</span>');
}
});
});
但是只綁定一次點(diǎn)擊事件慎玖,防止用戶連續(xù)點(diǎn)擊贮尖,比如用戶不放心頁面是否響應(yīng),所以可能會(huì)連續(xù)的點(diǎn)擊按鈕趁怔,如果不在這控制的話湿硝,這些點(diǎn)擊最后都會(huì)發(fā)送到服務(wù)器端,會(huì)造成服務(wù)器端在同一時(shí)間接到大量相同的URL請求痕钢,對(duì)各方面都有影響
所以點(diǎn)擊完之后就要禁用按鈕图柏,通過this指代當(dāng)前對(duì)象,也就是相當(dāng)于使用$('#killBtn')
之后就是發(fā)送秒殺請求任连,執(zhí)行秒殺操作蚤吹,在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對(duì)象裁着,這些對(duì)象存放在SeckillResult的data屬性中
public class SeckillExecution {
private long seckillId;
//秒殺結(jié)果執(zhí)行后的狀態(tài)
private int state;
//狀態(tài)信息
private String stateInfo;
//秒殺成功對(duì)象
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.顯示秒殺結(jié)果
node.html('<span class="label label-success">' + stateInfo + '</span>');
}
});
獲取到執(zhí)行秒殺的結(jié)果后拱她,還要在詳情頁中顯示出來二驰,所以控制節(jié)點(diǎn),輸出狀態(tài)信息秉沼,因?yàn)樵赟eckillController的execute方法中已經(jīng)定義了重復(fù)秒殺桶雀、秒殺結(jié)束等異常也算請求成功,只是不對(duì)數(shù)據(jù)庫進(jìn)行操作唬复,但是結(jié)果信息要返回到詳情頁
最后就可以把按鈕顯示出來了
node.show();
至此矗积,前端頁面完成了