目录:
(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 {
@Autowired
private RedisTemplate redisTemplate;
/**
* 查询全部
*/
@Override
public List<SeckillGoods> findAll() {
List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).values();
return seckillGoodsList;
}
/**
* 根据ID获取实体
* @param id
* @return
*/
@Override
public 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 {
@Autowired
private SeckillGoodsService seckillGoodsService;
@Autowired
private UserFeignClient userFeignClient;
@Autowired
private 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 {
@Override
public Result findAll() {
return Result.fail();
}
@Override
public Result getSeckillGoods(Long skuId) {
return Result.fail();
}
}
页面渲染
在web-all中引入远程依赖
在web-all 中编写控制器
package com.atguigu.gmall.all.controller;
@Controller
public class SeckillController {
@Autowired
private 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 查询skuInfo
Result 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 = true
this.timeTitle = '距离结束'
secondes = Math.floor((endTime - nowTime) / 1000);
}
if(nowTime > endTime) {
this.timeTitle = '抢购结束'
secondes = 0;
}
const timer = setInterval(() => {
secondes = secondes - 1
this.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.data
window.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 = true
this.timeTitle = '距离结束'
secondes = Math.floor((endTime - nowTime) / 1000);
}
if(nowTime > endTime) {
this.timeTitle = '抢购结束'
secondes = 0;
}
const timer = setInterval(() => {
secondes = secondes - 1
this.timeString = this.convertTimeString(secondes)
}, 1000);
// 通过$once来监听定时器,在beforeDestroy钩子可以被清除。
this.$once('hook:beforeDestroy', () => {
clearInterval(timer);
})
},
queue() {
seckill.getSeckillSkuIdStr(this.skuId).then(response => {
var skuIdStr = response.data.data
window.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 => {
debugger
console.log(JSON.stringify(response))
if(response.data.code == 200) {
this.isCheckOrder = true
} else {
this.show = 2
this.message = response.data.message
}
})
},
checkOrder() {
if(!this.isCheckOrder) return
seckill.checkOrder(this.skuId).then(response => {
debugger
this.data = response.data.data
this.code = response.data.code
console.log(JSON.stringify(this.data))
//排队中
if(response.data.code == 211) {
this.show = 1
} else {
//秒杀成功
if(response.data.code == 215) {
this.show = 3
this.message = response.data.message
} else {
if(response.data.code == 218) {
this.show = 4
this.message = response.data.message
} else {
this.show = 2
this.message = response.data.message
}
}
}
})
}
}
})
</script>
说明:该页面直接通过controller返回页面,进入页面后显示排队中,然后通过异步执行秒杀下单,提交成功,页面通过轮询后台方法查询秒杀状态(代码下章完成)
点击抢购