目录:
(1)秒杀列表与详情
(2)在service-activity-client模块添加接口
(3)秒杀详情页面功能介绍
(1)秒杀列表与详情
封装秒杀列表与详情接口、
封装接口
package com.atguigu.gmall.activity.service;public interface SeckillGoodsService {/*** 返回全部列表* @return*/List<SeckillGoods> findAll();/*** 根据ID获取实体* @param id* @return*/SeckillGoods getSeckillGoods(Long id);
}
完成实现类
package com.atguigu.gmall.activity.service.impl;@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {@Autowiredprivate RedisTemplate redisTemplate;/*** 查询全部*/@Overridepublic List<SeckillGoods> findAll() {List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();return seckillGoodsList;}/*** 根据ID获取实体* @param id* @return*/@Overridepublic SeckillGoods getSeckillGoods(Long id) {return (SeckillGoods) redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(id.toString());}
}
完成控制器
package com.atguigu.gmall.activity.controller;@RestController
@RequestMapping("/api/activity/seckill")
public class SeckillGoodsApiController {@Autowiredprivate SeckillGoodsService seckillGoodsService;@Autowiredprivate UserFeignClient userFeignClient;@Autowiredprivate ProductFeignClient productFeignClient;/*** 返回全部列表** @return*/@GetMapping("/findAll")public Result findAll() {return Result.ok(seckillGoodsService.findAll());}/*** 获取实体 商品详情** @param skuId* @return*/@GetMapping("/getSeckillGoods/{skuId}")public Result getSeckillGoods(@PathVariable("skuId") Long skuId) {return Result.ok(seckillGoodsService.getSeckillGoods(skuId));}
}
(2)在service-activity-client模块添加接口
package com.atguigu.gmall.activity.client;@FeignClient(value = "service-activity", fallback = ActivityDegradeFeignClient.class)
public interface ActivityFeignClient {/*** 返回全部列表** @return*/@GetMapping("/api/activity/seckill/findAll")Result findAll();/*** 获取实体 商品详情** @param skuId* @return*/@GetMapping("/api/activity/seckill/getSeckillGoods/{skuId}")Result getSeckillGoods(@PathVariable("skuId") Long skuId);
}
package com.atguigu.gmall.cart.client.impl;@Component
public class ActivityDegradeFeignClient implements ActivityFeignClient {@Overridepublic Result findAll() {return Result.fail();}@Overridepublic Result getSeckillGoods(Long skuId) {return Result.fail();}
}
页面渲染
在web-all中引入远程依赖
在web-all 中编写控制器
package com.atguigu.gmall.all.controller;@Controller
public class SeckillController {@Autowiredprivate ActivityFeignClient activityFeignClient;/*** 秒杀列表* @param model* @return*/@GetMapping("seckill.html")public String index(Model model) {Result result = activityFeignClient.findAll();model.addAttribute("list", result.getData());return "seckill/index";}
}
列表
页面资源: \templates\seckill\index.html
<div class="goods-list" id="item"><ul class="seckill" id="seckill"><li class="seckill-item" th:each="item: ${list}"><div class="pic" th:@click="|detail(${item.skuId})|"><img th:src="${item.skuDefaultImg}" alt=''></div><div class="intro"><span th:text="${item.skuName}">手机</span></div><div class='price'><b class='sec-price' th:text="'¥'+${item.costPrice}">¥0</b><b class='ever-price' th:text="'¥'+${item.price}">¥0</b></div><div class='num'><div th:text="'已售'+${item.num}">已售1</div><div class='progress'><div class='sui-progress progress-danger'><span style='width: 70%;' class='bar'></span></div></div><div>剩余<b class='owned' th:text="${item.stockCount}">0</b>件</div></div><a class='sui-btn btn-block btn-buy' th:href="'/seckill/'+${item.skuId}+'.html'" target='_blank'>立即抢购</a></li></ul>
</div>
点击秒杀
(3)秒杀详情页面功能介绍
说明:
- 立即购买,该按钮我们要加以控制,该按钮就是一个链接,页面只是控制能不能点击,一般用户可以绕过去,直接点击秒杀下单,所以我们要加以控制,在秒杀没有开始前,不能进入秒杀页面
web-all添加商品详情控制器
SeckillController
@GetMapping("seckill/{skuId}.html")
public String getItem(@PathVariable Long skuId, Model model){// 通过skuId 查询skuInfoResult result = activityFeignClient.getSeckillGoods(skuId);model.addAttribute("item", result.getData());return "seckill/item";
}
详情页面介绍
<div class="product-info"><div class="fl preview-wrap"><!--放大镜效果--><div class="zoom"><!--默认第一个预览--><div id="preview" class="spec-preview"><span class="jqzoom"><img th:jqimg="${item.skuDefaultImg}" th:src="${item.skuDefaultImg}" width="400" height="400"/></span></div></div></div><div class="fr itemInfo-wrap"><div class="sku-name"><h4 th:text="${item.skuName}">三星</h4></div><div class="news"><span><img src="/img/_/clock.png"/>品优秒杀</span><span class="overtime">{{timeTitle}}:{{timeString}}</span></div><div class="summary"><div class="summary-wrap"><div class="fl title"><i>秒杀价</i></div><div class="fl price"><i>¥</i><em th:text="${item.costPrice}">0</em><span th:text="'原价:'+${item.price}">原价:0</span></div><div class="fr remark">剩余库存:<span th:text="${item.stockCount}">0</span></div></div><div class="summary-wrap"><div class="fl title"><i>促 销</i></div><div class="fl fix-width"><i class="red-bg">加价购</i><em class="t-gray">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换购热销商品</em></div></div></div><div class="support"><div class="summary-wrap"><div class="fl title"><i>支 持</i></div><div class="fl fix-width"><em class="t-gray">以旧换新,闲置手机回收 4G套餐超值抢 礼品购</em></div></div><div class="summary-wrap"><div class="fl title"><i>配 送 至</i></div><div class="fl fix-width"><em class="t-gray">满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换购热销商品</em></div></div></div><div class="clearfix choose"><div class="summary-wrap"><div class="fl title"></div><div class="fl"><ul class="btn-choose unstyled"><li><a href="javascript:" v-if="isBuy" @click="queue()" class="sui-btn btn-danger addshopcar">立即抢购</a><a href="javascript:" v-if="!isBuy" class="sui-btn btn-danger addshopcar" disabled="disabled">立即抢购</a></li></ul></div></div></div></div>
</div>
点击立即抢购:
倒计时处理
思路:页面初始化时,拿到商品秒杀开始时间和结束时间等信息,实现距离开始时间和活动倒计时。
活动未开始时,显示距离开始时间倒计时;
活动开始后,显示活动结束时间倒计时。
倒计时代码片段
init() {
// debugger
// 计算出剩余时间
var startTime = new Date(this.data.startTime).getTime();
var endTime = new Date(this.data.endTime).getTime();
var nowTime = new Date().getTime();var secondes = 0;
// 还未开始抢购
if(startTime > nowTime) {this.timeTitle = '距离开始'secondes = Math.floor((startTime - nowTime) / 1000);
}
if(nowTime > startTime && nowTime < endTime) {this.isBuy = truethis.timeTitle = '距离结束'secondes = Math.floor((endTime - nowTime) / 1000);
}
if(nowTime > endTime) {this.timeTitle = '抢购结束'secondes = 0;
}const timer = setInterval(() => {secondes = secondes - 1this.timeString = this.convertTimeString(secondes)
}, 1000);
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {clearInterval(timer);
})
},
时间转换方法
convertTimeString(allseconds) {if(allseconds <= 0) return '00:00:00'// 计算天数var days = Math.floor(allseconds / (60 * 60 * 24));// 小时var hours = Math.floor((allseconds - (days * 60 * 60 * 24)) / (60 * 60));// 分钟var minutes = Math.floor((allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60)) / 60);// 秒var seconds = allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60) - (minutes * 60);//拼接时间var timString = "";if (days > 0) {timString = days + "天:";}return timString += hours + ":" + minutes + ":" + seconds;
}
秒杀按钮控制
在进入秒杀功能前,我们加一个下单码,只有你获取到该下单码,才能够进入秒杀方法进行秒杀
获取下单码
SeckillGoodsApiController
DateUtil.dateCompare(seckillGoods.getStartTime(),curDate):后面的时间大于前面的时间返回true
@GetMapping("auth/getSeckillSkuIdStr/{skuId}")
public Result getSeckillSkuIdStr(@PathVariable("skuId") Long skuId, HttpServletRequest request) {String userId = AuthContextHolder.getUserId(request);SeckillGoods seckillGoods= (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());//SeckillGoods seckillGoods = seckillGoodsService.getSeckillGoods(skuId);if (null != seckillGoods) {Date curTime = new Date();if (DateUtil.dateCompare(seckillGoods.getStartTime(), curTime) && DateUtil.dateCompare(curTime, seckillGoods.getEndTime())) {//可以动态生成,放在redis缓存String skuIdStr = MD5.encrypt(userId);return Result.ok(skuIdStr);}}return Result.fail().message("获取下单码失败");
}
说明:只有在商品秒杀时间范围内,才能获取下单码,这样我们就有效控制了用户非法秒杀,下单码我们可以根据业务自定义规则,目前我们定义为当前用户id MD5加密。
前端页面
页面获取下单码,进入秒杀场景
queue() {seckill.getSeckillSkuIdStr(this.skuId).then(response => {var skuIdStr = response.data.datawindow.location.href = '/seckill/queue.html?skuId='+this.skuId+'&skuIdStr='+skuIdStr})
},
前端js完整代码如下
<script src="/js/api/seckill.js"></script>
<script th:inline="javascript">var item = new Vue({el: '#item',data: {skuId: [[${item.skuId}]],data: [[${item}]],timeTitle: '距离开始',timeString: '00:00:00',isBuy: false},created() {this.init()},methods: {init() {// debugger// 计算出剩余时间var startTime = new Date(this.data.startTime).getTime();var endTime = new Date(this.data.endTime).getTime();var nowTime = new Date().getTime();var secondes = 0;// 还未开始抢购if(startTime > nowTime) {this.timeTitle = '距离开始'secondes = Math.floor((startTime - nowTime) / 1000);}if(nowTime > startTime && nowTime < endTime) {this.isBuy = truethis.timeTitle = '距离结束'secondes = Math.floor((endTime - nowTime) / 1000);}if(nowTime > endTime) {this.timeTitle = '抢购结束'secondes = 0;}const timer = setInterval(() => {secondes = secondes - 1this.timeString = this.convertTimeString(secondes)}, 1000);// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。this.$once('hook:beforeDestroy', () => {clearInterval(timer);})},queue() {seckill.getSeckillSkuIdStr(this.skuId).then(response => {var skuIdStr = response.data.datawindow.location.href = '/seckill/queue.html?skuId='+this.skuId+'&skuIdStr='+skuIdStr})},convertTimeString(allseconds) {if(allseconds <= 0) return '00:00:00'// 计算天数var days = Math.floor(allseconds / (60 * 60 * 24));// 小时var hours = Math.floor((allseconds - (days * 60 * 60 * 24)) / (60 * 60));// 分钟var minutes = Math.floor((allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60)) / 60);// 秒var seconds = allseconds - (days * 60 * 60 * 24) - (hours * 60 * 60) - (minutes * 60);//拼接时间var timString = "";if (days > 0) {timString = days + "天:";}return timString += hours + ":" + minutes + ":" + seconds;}}})
</script>
编写排队跳转下单页面控制器
SeckillController
@GetMapping("seckill/queue.html")
public String queue(@RequestParam(name = "skuId") Long skuId,@RequestParam(name = "skuIdStr") String skuIdStr,HttpServletRequest request){request.setAttribute("skuId", skuId);request.setAttribute("skuIdStr", skuIdStr);return "seckill/queue";
}
页面
页面资源: \templates\seckill\queue.html
<div class="cart py-container" id="item"><div class="seckill_dev" v-if="show == 1">排队中...</div><div class="seckill_dev" v-if="show == 2">{{message}}</div><div class="seckill_dev" v-if="show == 3">抢购成功 <a href="/seckill/trade.html" target="_blank">去下单</a></div><div class="seckill_dev" v-if="show == 4">抢购成功 <a href="/myOrder.html" target="_blank">我的订单</a></div>
</div>
Js部分
<script src="/js/api/seckill.js"></script>
<script th:inline="javascript">var item = new Vue({el: '#item',data: {skuId: [[${skuId}]],skuIdStr: [[${skuIdStr}]],data: {},show: 1,code: 211,message: '',isCheckOrder: false},mounted() {const timer = setInterval(() => {if(this.code != 211) {clearInterval(timer);}this.checkOrder()}, 3000);// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。this.$once('hook:beforeDestroy', () => {clearInterval(timer);})},created() {this.saveOrder();},methods: {saveOrder() {seckill.seckillOrder(this.skuId, this.skuIdStr).then(response => {debuggerconsole.log(JSON.stringify(response))if(response.data.code == 200) {this.isCheckOrder = true} else {this.show = 2this.message = response.data.message}})},checkOrder() {if(!this.isCheckOrder) returnseckill.checkOrder(this.skuId).then(response => {debuggerthis.data = response.data.datathis.code = response.data.codeconsole.log(JSON.stringify(this.data))//排队中if(response.data.code == 211) {this.show = 1} else {//秒杀成功if(response.data.code == 215) {this.show = 3this.message = response.data.message} else {if(response.data.code == 218) {this.show = 4this.message = response.data.message} else {this.show = 2this.message = response.data.message}}}})}}})
</script>
说明:该页面直接通过controller返回页面,进入页面后显示排队中,然后通过异步执行秒杀下单,提交成功,页面通过轮询后台方法查询秒杀状态(代码下章完成)
点击抢购