【SpringBoot商城秒杀系统项目实战13】秒杀商品详情页+秒杀倒计时功能实现
在上一节中,我们已经实现秒杀商品的列表页的显示,其中可以点击每一个商品的【详情】查看具体的秒杀信息,那么我们这一节就来实现商品的详情页面的显示以及秒杀倒计时功能实现。【详情】链接中有{goodsId}作为参数,后端@PathVariable(“goodsId”)long goodsId拿到这个goodsId,然后去数据库查询对应的商品信息,并显示秒杀情况。
我们点击商品详情,发送一个连接到后端,那么我们要先设计出一个接收’/goods/to_detail/’+${goods.id}该请求的接口。
秒杀倒计时:
后端根据商品的Id去数据库中获取秒杀开始时间和结束时间,以及系统当前时间,并定义秒杀剩余时间变量和秒杀状态,计算出相应的值,传给前端,前端拿到之后,做相对应的显示逻辑效果。
- 在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传到前端去。
- 前端页面处理逻辑
前端需要获取status和remailSeconds(即秒杀状态和剩余时间变量),定义个一个属性为隐藏的input来接收remainSecode ,并且定义标签来判断status的状态,通过这个值来显示是否开始秒杀,秒杀正在进行中,以及秒杀结束。
这里需要做特别处理的地方是需要自己写一个方法来控制秒杀按钮的的可以点击与不可点击的情况,没开始的时候按钮,不可点击,开始后显示按钮,此时可以点击,但是结束也不可点击。并且从倒计时状态到正在进行秒杀状态的时候要动态切换文案
注意:这里使用setTimeout函数,setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。
这里是1000毫秒,即每过一秒,将remianSecond 值减一,并设置到显示text中(即实现倒计时动态效果),然后在调用coutDown,判断此时是否结束此状态(remailSeconds==0),结束调用。
- 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;
}
}