在上一節中,我們已經實現秒殺商品的列表頁的顯示,其中可以點擊每一個商品的【詳情】查看具體的秒殺信息,那麼我們這一節就來實現商品的詳情頁面的顯示以及秒殺倒計時功能實現。【詳情】鏈接中有{goodsId}作爲參數,後端@PathVariable(“goodsId”)long goodsId拿到這個goodsId,然後去數據庫查詢對應的商品信息,並顯示秒殺情況。
我們點擊商品詳情,發送一個連接到後端,那麼我們要先設計出一個接收’/goods/to_detail/’+${goods.id}該請求的接口。
秒殺倒計時:
後端根據商品的Id去數據庫中獲取秒殺開始時間和結束時間,以及系統當前時間,並定義秒殺剩餘時間變量和秒殺狀態,計算出相應的值,傳給前端,前端拿到之後,做相對應的顯示邏輯效果。
1、在GoodsController裏面創建toDetail方法,接收詳情頁面的請求
/**
* 未作頁面緩存
* @param model
* @param user
* @param goodsId
* @return
*/
@RequestMapping("/to_detail/{goodsId}")
public String toDetail(Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId) {//id一般用snowflake算法
model.addAttribute("user", user);
GoodsVo goods=goodsService.getGoodsVoByGoodsId(goodsId);
model.addAttribute("goods", goods);
//既然是秒殺,還要傳入秒殺開始時間,結束時間等信息
long start=goods.getStartDate().getTime();
long end=goods.getEndDate().getTime();
long now=System.currentTimeMillis();
//秒殺狀態量
int status=0;
//開始時間倒計時
int remailSeconds=0;
//查看當前秒殺狀態
if(now<start) {//秒殺還未開始,--->倒計時
status=0;
remailSeconds=(int) ((start-now)/1000); //毫秒轉爲秒
}else if(now>end){ //秒殺已經結束
status=2;
remailSeconds=-1; //毫秒轉爲秒
}else {//秒殺正在進行
status=1;
remailSeconds=0; //毫秒轉爲秒
}
model.addAttribute("status", status);
model.addAttribute("remailSeconds", remailSeconds);
return "goods_detail";//返回頁面login
}
注意:獲取了商品的秒殺開始時間和結束時間,如果秒殺沒有開始,那麼計算一個還剩多少時間,開始,並且定義一個狀態status來表示一個秒殺的狀態,0代表秒殺還未開,1代表秒殺正在進行,2代表秒殺已經結束,秒殺還未開始的情況還要計算出倒計時,(int) ((start-now)/1000),然後將status和remailSeconds傳到前端去。
2、 前端頁面處理邏輯
前端需要獲取status和remailSeconds(即秒殺狀態和剩餘時間變量),定義個一個屬性爲隱藏的input來接收remainSecode ,並且定義標籤來判斷status的狀態,通過這個值來顯示是否開始秒殺,秒殺正在進行中,以及秒殺結束。
這裏需要做特別處理的地方是需要自己寫一個方法來控制秒殺按鈕的的可以點擊與不可點擊的情況,沒開始的時候按鈕,不可點擊,開始後顯示按鈕,此時可以點擊,但是結束也不可點擊。並且從倒計時狀態到正在進行秒殺狀態的時候要動態切換文案
注意:這裏使用setTimeout函數,setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式。
這裏是1000毫秒,即每過一秒,將remianSecond 值減一,並設置到顯示text中(即實現倒計時動態效果),然後在調用coutDown,判斷此時是否結束此狀態(remailSeconds==0),結束調用。
3、goods_detail.html完整代碼:
<!DOCTYPE html>
<!-- 使用thymeleaf,配置相應的 -->
<html xmlns:th="http://www.thymeleaf.org"> <!-- th!!! 命名空間使用 -->
<head>
<meta charset="UTF-8"/><!--<meta charset="UTF-8" /> thymeleaf模板引擎默認是Template modes:HTML5解析的,所以解析比較嚴格。 -->
<title>商品列表</title>
<!-- thymeleaf引入靜態資源的方式,@加大括弧 "/" 代表static路徑-->
<!-- jquery -->
<!-- <script type="text/javascript" th:src="@{/js/jequery.min.js}"></script> -->
<script type="text/javascript" th:src="@{/jquery-validation/lib/jquery-1.11.1.js}"></script>
<!-- bootstrap -->
<!-- <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"/>
-->
<link type="text/css" rel="stylesheet" th:href="@{/bootstrap/css/bootstrap.css}"/>
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-default">
<div class="panel-heading">秒殺商品詳情</div>
<div class="panel-body">
<span th:if="${user eq null}">您還沒有登錄,請登錄後再操作</span>
<span>沒有收貨地址的提示。。。</span>
</div>
<table class="table" id="goodslist">
<tr>
<td>商品名稱</td>
<td colspan="3" th:text="${goods.goodsName}"></td>
</tr>
<tr>
<td>商品圖片</td>
<td colspan="3"><img th:src="@{${goods.goodsImg}}" width="80" height="60"></img></td>
</tr>
<tr>
<td>秒殺開始時間</td>
<td th:text="${#dates.format(goods.startDate,'yyyy-MM-dd HH:mm:ss')}"></td>
<td id="miaoshaTip">
<!-- 先取得這個時間 -->
<input type="hidden" id="remailSeconds" th:value="${remailSeconds}"></input>
<span th:if="${status eq 0}">秒殺還未開始,倒計時:<span id="countDown" th:text="${remailSeconds}"></span>秒</span>
<span th:if="${status eq 1}">秒殺正在進行</span>
<span th:if="${status eq 2}">秒殺已經結束</span>
</td>
<td>
<form id="miaoshaForm" method="post" action="/miaosha/do_miaosha">
<button class="btn btn-primary btn-block" type="submit" id="buyButton">立即秒殺</button>
<input type="hidden" name="goodsId" th:value="${goods.id}"></input>
</form>
</td>
</tr>
<tr>
<td>商品原價</td>
<td colspan="3" th:text="${goods.goodsPrice}"></td>
</tr>
<tr>
<td>秒殺價</td>
<td colspan="3" th:text="${goods.miaoshaPrice}"></td>
</tr>
<tr>
<td>庫存數量</td>
<td colspan="3" th:text="${goods.stockCount}"></td>
</tr>
</table>
</div>
</body>
<script type="text/javascript">
$(function(){
countDown();
});
function countDown(){
//獲取秒殺倒計時進行判斷,0-->正在進行秒殺,-1-->秒殺結束,remailSeconds>0-->代表倒計時
var remailSeconds=$("#remailSeconds").val();
//alert("remailSeconds:"+remailSeconds);
var timeout;
if(remailSeconds>0){//秒殺還沒有開始,進行倒計時功能
$("#buyButton").attr("disabled",true);
//倒計時
timeout=setTimeout(function(){
$("#countDown").text(remailSeconds-1);
$("#remailSeconds").val(remailSeconds-1);//remailSeconds這是input
countDown();
},1000);//一秒鐘之後回調函數
}else if(remailSeconds==0){//正在進行秒殺
$("#buyButton").attr("disabled",false);
if(timeout){//如果timeout有值的情況
clearTimeout(timeout);
}
//將文案修改 df1fab4272a24cdf9432adb9fd69cb38
$("#miaoshaTip").html("秒殺進行中");
}else{
//小於0的情況,秒殺結束,將秒殺按鈕設置爲不可點擊
$("#buyButton").attr("disabled",true);
$("#miaoshaTip").html("秒殺結束");
}
}
</script>
</html>
怎麼注入MiaoshaUser的實例參數
下面是未注入MiaoshaUser參數的版本:
/**
* 之前的版本 1.0 未作user的參數,即未作UserArgumentResolver時調用的detail請求
* @param model
* @param cookieToken
* @param response
* @return
*/
@RequestMapping("/to_detail1")
public String toDetail(Model model,@CookieValue(value=MiaoshaUserService.COOKIE1_NAME_TOKEN)String cookieToken
,HttpServletResponse response) {
//通過取到cookie,首先取@RequestParam沒有再去取@CookieValue
if(StringUtils.isEmpty(cookieToken)) {
return "login";//返回到登錄界面
}
String token=cookieToken;
System.out.println("goods-token:"+token);
System.out.println("goods-cookieToken:"+cookieToken);
MiaoshaUser user=miaoshaUserService.getByToken(token,response);
model.addAttribute("user", user);
return "goods_list";//返回頁面login
}
注入了MiaoshaUser參數的版本:
public String toDetail(Model model,MiaoshaUser user,@PathVariable("goodsId")long goodsId)
思路:實現argumentResolvers
1、新建一個包config,然後新建一個WebConfig類,寫入@Configuration註解,代表這是一個配置類
WebConfig繼承WebMvcConfigurerAdapter類,重寫addArgumentResolvers方法,添加一個解析對象(UserArgumentResolver類的實例userArgumentResolver對象)
WebConfig類:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
/**
* 設置一個MiaoshaUser參數給,toList、toDetail等controller方法使用使用
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//將UserArgumentResolver註冊到config裏面去
argumentResolvers.add(userArgumentResolver);
}
}
2、 新建UserArgumentResolver類,實現HandlerMethodArgumentResolver接口,然後在重寫supportsParameter與 resolveArgument方法。
supportsParameter方法:獲取參數的類型,如果是MiaoshaUser類型則返回true,纔會做下面的處理。
resolveArgument方法:裏面就是需要做出的邏輯處理,即這裏就是去request參數裏面去獲取token參數(從Cookie或者requestParameter)
注:這裏需要HttpServletRequest和HttpServletResponse參數,那麼使用webRequest.getNativeRequest(HttpServletRequest.class)方法來獲取。
UserArgumentResolver類:
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver{
@Autowired //既然能注入service,那麼可以用來容器來管理,將其放在容器中
MiaoshaUserService miaoshaUserService;
public Object resolveArgument(MethodParameter arg0, ModelAndViewContainer arg1, NativeWebRequest webRequest,
WebDataBinderFactory arg3) throws Exception {
HttpServletRequest request=webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response=webRequest.getNativeResponse(HttpServletResponse.class);
String paramToken=request.getParameter(MiaoshaUserService.COOKIE1_NAME_TOKEN);
System.out.println("@UserArgumentResolver-resolveArgument paramToken:"+paramToken);
//獲取cookie
String cookieToken=getCookieValue(request,MiaoshaUserService.COOKIE1_NAME_TOKEN);
System.out.println("@UserArgumentResolver-resolveArgument cookieToken:"+cookieToken);
if(StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
{
return null;
}
String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
//System.out.println("goods-token:"+token);
//System.out.println("goods-cookieToken:"+cookieToken);
MiaoshaUser user=miaoshaUserService.getByToken(token,response);
System.out.println("@UserArgumentResolver--------user:"+user);
//去取得已經保存的user,因爲在用戶登錄的時候,user已經保存到threadLocal裏面了,因爲攔截器首先執行,然後纔是取得參數
//MiaoshaUser user=UserContext.getUser();
return user;
}
public String getCookieValue(HttpServletRequest request, String cookie1NameToken) {//COOKIE1_NAME_TOKEN-->"token"
//遍歷request裏面所有的cookie
Cookie[] cookies=request.getCookies();
if(cookies!=null) {
for(Cookie cookie :cookies) {
if(cookie.getName().equals(cookie1NameToken)) {
System.out.println("getCookieValue:"+cookie.getValue());
return cookie.getValue();
}
}
}
System.out.println("No getCookieValue!");
return null;
}
public boolean supportsParameter(MethodParameter parameter) {
//返回參數的類型
Class<?> clazz=parameter.getParameterType();
return clazz==MiaoshaUser.class;
}
}