功能分析
1. 前台秒杀商品详情页面
秒杀尚未开始,显示秒杀倒计时
正在秒杀的商品,显示按钮
秒杀时间已过,显示秒杀已结束
以上判断逻辑需要在js中完成,为了页面逻辑看起来清晰,我们创建一个单独的js文件seckill.js来处理,在详情页面中引入该js在页面中我们发送请求使用jQuery,所以需要引入jQuery的js文件
倒计时的显示,我们使用jQuery的countdown插件,所以需要将该插件的js也引入到详情页面中
2. 后台秒杀地址的暴露
如果前台已经开始秒杀,那么需要在后台暴露秒杀地址给前台页面
为了安全,在后台依旧需要对秒杀是否开始进行判断
为了前台处理方便,我们将后台返回的结果封装为自定义RTO(结果传输对象)
1. 在15-seckill-web模块的static目录下,创建js子目录,将jQuery和倒计时插件从07-SecKill\resources\js拷贝进来
倒计时插件的用法在07-SecKill\resources\countDown.txt中,直接拷贝过来用即可
2. 在15-seckill-web模块的static/ js目录下,创建seckill.js文件
3. 在15-seckill-web模块的item.html页面中,导入上面定义三个js文件
<!--导入jQuery的js文件-->
<script th:src="@{/js/jquery.min.js}"></script>
<!--导入倒计时插件的js文件-->
<script th:src="@{/js/jquery.countdown.min.js}"></script>
<!--导入自定义的秒杀js文件-->
<script th:src="@{/js/seckill.js}"></script>
4. 在15-seckill-web中的item.html页面调用秒杀初始化的方法,并传递参数
<script type="text/javascript" th:inline="javascript">
//页面加载完成之后,调用秒杀初始化方法,对时间进行判断
$(function(){
seckillObj.contextPath = [[${#request.getContextPath()}]];
var id = [[${goods.id}]];
//当前时间应该从服务器获取,在跳转到秒杀详情页的时候传递过来
var currentTime = [[${currentTime}]];
//时间为距1970年1月1日 00:00:00的毫秒数
var startTime = [[${goods.starttime.getTime()}]];
var endTime = [[${goods.endtime.getTime()}]];
seckillObj.func.initItem(id,currentTime,startTime,endTime);
});
</script>
5. 在15-seckill-web模块的seckill.js中定义对秒杀时间的判断函数JS的开发方式,有两种
面向过程:面向对象,这里我们采用面向对象的方式
//定义一个json对象
var seckillObj = {
//项目上下文根,我们可以在页面中通过seckillObj对象对其赋值
contextPath:"",
//这样操作可选,一般公司会将获取地址单独抽取出来,方便维护
url:{
randomURL:function () {
return seckillObj.contextPath +"/seckill/random/";
}
},
//定义秒杀时间判断的相关函数,本身还是json的属性,只不过是function类型
func:{
//初始化函数主要用于时间的判断,这些时间可以从item页面的秒杀商品对象上获取
//所以该方法提供参数,接收页面传递的信息,同时将商品的id也传递过来
initItem:function (id,currentTime,startTime,endTime) {
if(currentTime < startTime){
//秒杀尚未开始 使用jquery的倒计时插件实现倒计时
/* + 1000 防止时间偏移 这个没有太多意义,因为我们并不知道客户端和服务器的时间偏移
这个插件简单了解,实际项目不会以客户端时间作为倒计时,一般在服务器端还需要验证*/
var killTime = new Date(startTime + 1000);
$("#seckillTip").countdown(killTime, function (event) {
//时间格式
var format = event.strftime('距秒杀开始还有: %D天 %H时 %M分 %S秒');
$("#seckillTip").html("<span style='color:red;'>"+format+"</span>");
}).on('finish.countdown', function () {
//倒计时结束后回调事件,已经开始秒杀,用户可以进行秒杀了,有两种方式:
//1、刷新当前页面
location.reload();
//或者2、调用秒杀开始的函数
});
}else if(currentTime > endTime){
//秒杀已经结束
$("#seckillTip").html("<span style='color:red;'>来晚了,秒杀活动已结束</span>");
}else{
//秒杀已开始 调用startSeckill函数
seckillObj.func.startSeckill(id);
}
},
startSeckill:function (id) {
//秒杀术语 暴露秒杀地址 http://localhost:8080/15-seckill-web/seckill/goods/dfasjfkdjfkldajs
//发送ajax请求 去后台服务器判断秒杀是否已经开始,如果真正开始,暴露秒杀地址,否则不暴露
$.ajax({
url : seckillObj.url.randomURL() + id,
type : "post",
dataType:"json",
success:function(rtnMessage){
if(rtnMessage.errorCode == 0){
//不能显示秒杀按钮
$("#seckillTip").html("<span style='color:red;'>"+ rtnMessage.errorMessage +"</span>");
}else{
//显示秒杀按钮
$("#seckillTip").html("<button type='button'>立即秒杀</button>");
}
}
});
}
}
}
6. 在15-seckill-web中的item.html页面加显示倒计时及秒杀提示信息的段落标签
<p id="seckillTip">
</p>
7. 数据库时区问题的解决
我们在使用倒计时插件的,发现获取的数据库时间不对,因为我们15-seckill-web的数据是从Redis中获取的,Redis的数据是15-seckill-service中的定时任务获取的,所以我们需要修改15-seckill-service模块中的核心配置文件的数据库连接信息,加serverTimezone=GMT%2B8,指定时区,这个问题从SpringBoot2.1.0以后出现,以前的版本没有。
修改完毕后重新运行15-seckill-service获取时间。
8. 在15-seckill-web中的GoodsController处理暴露地址的请求
@PostMapping("/seckill/random/{id}")
public @ResponseBody ReturnObject random(@PathVariable("id") Integer id){
ReturnObject returnObject = new ReturnObject();
//根据商品的id获取商品对象
String goodsJSON = redisTemplate.opsForValue().get(Constants.REDIS_GOODS + id);
Goods goods = JSONObject.parseObject(goodsJSON,Goods.class);
//验证秒杀时间是否已经真的开始
Long currentTime = System.currentTimeMillis();
Long startTime = goods.getStarttime().getTime();
Long endTime = goods.getEndtime().getTime();
if(currentTime < startTime){
//秒杀尚未开始
returnObject.setErrorCode(Constants. ZERO);
returnObject.setErrorMessage("秒杀尚未开始");
}else if(currentTime > endTime){
//秒杀已经结束
returnObject.setErrorCode(Constants. ZERO);
returnObject.setErrorMessage("秒杀已经结束");
}else{
//秒杀已经开始
returnObject.setErrorCode(Constants. ONE);
returnObject.setErrorMessage("秒杀已开始");
returnObject.setData(goods.getRandomname());
}
return returnObject;
}
9. 在15-seckill-interface中的com.bjpowernode.seckill.rto包下ReturnObject类,用于封装返回结果对象
public class ReturnObject {
private int errorCode;
private String errorMessage;
private Object data;
//get|set方法略
}
10. 在15-seckill-interface中的常量类Constants中定义返回的错误码常量 1成功 0失败
/返回结果码的常量,0失败, 1 成功
public static final int ZERO = 0;
public static final int ONE = 1;
11. 启动15-seckill-service和15-seckill-web,修改数据库商品表数据查看效果