第1章 秒杀业务分析
一.需求分析
- 所谓“秒杀”,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动。由于商品价格低廉,往往一上架就被抢购一空,有时只用一秒钟。
秒杀商品通常有两种限制:库存限制、时间限制。 - 需求:
二.表结构说明
- 秒杀商品信息表
- 秒杀订单表
三.秒杀需求分析
- 秒杀技术实现核心思想是运用缓存减少数据库瞬间的访问压力!读取商品详细信息时运用缓存,当用户点击抢购时减少缓存中的库存数量,当库存数为0时或活动期结束时,同步到数据库。 产生的秒杀预订单也不会立刻写到数据库中,而是先写到缓存,当用户付款成功后再写入数据库。
- 当然,上面实现的思路只是一种最简单的方式,并未考虑其中一些问题,例如并发状况容易产生的问题。我们看看下面这张思路更严谨的图:
第2章 秒杀服务工程
我们这里秒杀商品列表和秒杀商品详情都是从Redis中取出来的,所以我们首先要将符合参与秒杀的商品定时查询出来,并将数据存入到Redis缓存中。
数据存储类型我们可以选择Hash类型。
秒杀分页列表这里可以通过获取redisTemplate.boundHashOps(key).values()获取数据。
秒杀商品详情,可以通过redisTemplate.boundHashOps(key).get(key)获取详情。
一.搭建秒杀服务工程
- 我们将商品数据压入到Reids缓存,可以在秒杀工程的服务工程中完成,可以按照如下步骤实现:
- 搭建qingcheng_service_seckill,作为秒杀工程的服务提供工程。
- pom.xml
- web.xml
- applicationContext-service.xml
- properties配置文件
- db.properties
- dubbo.properties
- db.properties
二.定时任务配置
- 一会儿我们采用Spring的定时任务定时将符合参与秒杀的商品查询出来再存入到Redis缓存,所以这里需要使用到定时任务。
这里我们了解下定时任务相关的配置,配置步骤如下: - 开启定时任务注解驱动
在秒杀服务工程中添加applicationContext-timer.xml配置文件,并开启定时任务注解驱动,代码如下: - 定时任务方法配置
创建com.qingcheng.timer.SeckillGoodsPushTask类,并在类中加上定时任务执行方法,代码如下: - 定时任务常用时间表达式(了解)
CronTrigger配置完整格式为: [秒][分] [小时][日] [月][周] [年]
三.秒杀商品压入缓存实现
- 按照2.1中的几个步骤实现将秒杀商品从数据库中查询出来,并存入到Redis缓存
上面这里会涉及到时间操作,所以这里提前准备了一个时间工具包DateUtil - 创建Dao操作类
创建一个com.qingcheng.dao.SeckillGoodsMapper接口,并让该接口继承Mapper,代码如下: - 数据压入到Redis缓存
修改com.qingcheng.timer.SeckillGoodsPushTask类的loadGoodsPushRedis方法,按照如上步骤实现秒杀商品压入缓存:
第3章 秒杀商品-频道页
秒杀频道首页,显示正在秒杀的和未开始秒杀的商品(已经开始或者还没开始,未结束的秒杀商品)
一.业务层实现
- 在qingcheng_interface创建指定接口,在qingcheng_service_seckill实现该接口,用来实现秒杀商品频道页数据的显示操作。
- 业务接口层
在qingcheng_interface下创建com.qingcheng.service.seckill.SeckillGoodsService接口,并添加查询方法 - 业务层实现类
创建com.qingcheng.service.impl.SeckillGoodsServiceImpl,实现SeckillGoodsService接口,并实现订单列表查询方法,代码如下:
二.表现层
- 需要搭建一个表现层工程,用于调用Service层获取秒杀商品列表数据。
- 表现层工程搭建
搭建一个web工程,名叫qingcheng_web_seckill,用于实现秒杀频道页显示。 - pom.xml
- web.xml
配置dubbo.properties - 引入静态资源
将css、data、fonts、img、js、seckill-item.html、seckill-index.html引入到工程的webapp目录下。
三.频道页实现分析
- 频道页这里需要显示不同的时间段的商品信息,然后点击不同时间段,查询对应的商品数据,实现过程大致分为2个
步骤:
1)时间菜单
2)加载对应菜单数据 - 时间菜单分析
每2个小时就会切换一次抢购活动,所以商品发布的时候,我们建议将时间定格在2小时内抢购,每次发布商品的时候,商品抢购开始时间和结束时间是这2小时的边界。
每2小时会有一批商品参与抢购,所以我们可以将24小时切分为12个菜单,每个菜单都是个2小时的时间段,当前选中的时间菜单需要根据当前时间判断,判断当前时间属于哪个秒杀时间段,然后将该时间段作为选中的第1个时间菜单。 - 加载对应时间段数据
进入页面后,到后台查询时间段菜单,然后根据第1个菜单的时间段时间去后台Redis中查询秒杀商品集合,并显示到页面,页面每次点击切换不同时间段菜单的时候,都将时间段传入到后台,后台根据时间段获取对应的秒杀商品集合。
四.时间菜单实现
- 时间菜单显示,先运算出每2小时一个抢购,就需要实现12个菜单,可以先计算出每个时间的临界值,然后根据当前时间判断需要显示12个时间段菜单中的哪个菜单,再在该时间菜单的基础之上往后挪4个菜单,一个显示5个菜单。
- 时间菜单计算
修改DateUtil,在该类中添加一个获取时间菜单运算的方法,返回每个时间段菜单的开始时间,代码如下:
编写控制器,调用上面的方法,获取时间菜单,在com.qingcheng.controller.SeckillGoodsController添加如下方法: - 加载时间菜单
页面循环显示时间菜单 - 时间格式化
上面菜单循环输出后,会出现如上图效果,时间格式全部不对,我们需要引入一个moment.min.js来格式化时间。
引入moment.min.js
在js中添加一个过滤器,代码如下:
页面输出 - 选中实现
我们需要要实现的是如下效果,让第一个菜单选中,并加载第一个菜单对应的数据。
我们可以先定义一个ctime=0,用来记录当前选中的菜单下标,因为默认第一个选中,第一个下标为0,所以初始值为0,每次点击对应菜单的时候,将被点击的菜单的下标值赋值给ctime,然后在每个菜单上判断,下标=ctime则让该菜单选中。
定义ctime=0
页面样式控制: - 倒计时时间
倒计时这里,第一个是距离结束时间倒计时,后面的4个都是距离开始倒计时,每个倒计时其实就是2个时差,分析如下:
- 倒计时实现
我们可以先定义5个变量,然后每过1秒中,让5个变量时间都减1秒实现倒计时操作。
周期执行函数用法如下:
结束执行周期函数用法如下:
倒计时实现代码如下:
页面根据对应的菜单下标,到倒计时时差数组中根据下标取数据,而只有第1个菜单属于距离结束时间倒计时,其他的都是距离开始倒计时,如下图: - 时间换算
上面实现了秒杀倒计时,但是我们要的效果是将毫秒转成时、分、秒,而不是毫秒递减,而将毫秒时差转成时分秒,可以先判断下有多少小时,余数再判断有多少分钟,剩下的余数还有多少秒即可,实现代码如下:
页面只需要将时间差值传入该方法即可,代码如下:
效果如下:
- 倒计时实现
五.秒杀时间段商品加载
- 秒杀时间段商品加载时根据商品每个时间段去加载对应的商品信息,在之前的定时任务已经将对应的商品信息存入到了Redis中,我们可以根据秒杀时间菜单的开始时间去Redis中查询数据。
- 控制层实现
创建com.qingcheng.controller.SeckillGoodsController,用于实现人机交互,获取秒杀商品列表,添加list方法,如下代码: - 页面对接
添加一个goodslist存储当前时间菜单对应的秒杀商品集合,如下:
编写一个方法,查询对应时间菜单的秒杀商品,代码如下:
页面可以在每次加载菜单的时候,将第1个菜单的起始时间传入到后台,来查询对应的数据,代码如下:
页面参数绑定
修改seckill-index.html页面,实现参数数据的绑定,如下:
六.时间菜单数据切换
- 每次点击不同时间菜单的时候,将对应的时间菜单的开始时间传入到searchList方法中,实现数据搜索,代码如下:
七.详情页跳转
- 跳转到详情页的时候,需要根据商品ID以及商品秒杀开始时间查询商品详情,所以每次需要将商品ID和开始时间传入到后台去,时间还需要转换成yyyyMMddHH格式,我们可以先定义一个方法,实现地址栏拼接,代码如下:
- 页面点击抢购的时候,href调用上面写好的方法,代码如下:
第4章 秒杀详情页
通过秒杀频道页点击请购按钮,会跳转到商品秒杀详情页,秒杀详情页需要根据商品ID查询商品详情,我们可以在频道页点击秒杀抢购的时候将ID一起传到后台,然后根据ID去Redis中查询详情信息。
一.业务层
- 业务层接口方法添加
修改qingcheng_interface工程的com.qingcheng.service.seckill.SeckillGoodsService接口,添加一个根据ID查询详情方法one(),代码如下: - 业务层实现类
修改qingcheng_service_seckill工程的com.qingcheng.service.impl.SeckillGoodsServiceImpl类,在类中添加一个one()方法,代码如下:
二.控制层
- 修改qingcheng_web_seckill工程,在该工程中的com.qingcheng.controller.SeckillGoodsController类中添加一个one()方法,用于调用Service层实现根据ID查询商品详情,代码如下:
三.页面绑定
- js配置
在页面编写js代码,先定义一个entity对象,用于接收从后台查询的商品详情信息,然后再定义一个getOne方法,用于去后台查询数据,代码如下:
注意:上面用到了getQueryString方法,获取地址栏的指定参数,我们需要在页面引入util.js工具包。 - 页面元素绑定
将数据从entity中绑定输出到页面中
四. 倒计时实现
- 在秒杀页面有一个倒计时功能,这个功能其实只需要我们计算当前时间距离活动结束时间拥有多少天、小时、分钟、秒,我们下面提供一段js代码,实现了时间单位换算,可以将毫秒换成天-小时-分钟-秒。该方法只需要将2个时间的差值传入即可。代码如下:
我们可以将上述代码添加到seckill-item.html的js页面里面,计算时间差。
setInterval: 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
我们可以利用setInterval来实现倒计时功能,只需要定义一个方法,将一个总的时间实现每秒减去秒,然后调用上面时间换算的方法,并定义一个变量startTime接收换算好的时间,即可实现倒计时。
我们定义一个timeCalculate方法,在该方法中,分别传入starttimes和endtimes2个参数,这2个参数分别用于运算是距离开始时间还是距离结束时间的时间差值
我们需要在将商品详情查询完毕后,求出开始时间和结束时间,然后将时间传入到上述的那个方法,代码如下:
页面显示错误信息
第5章 通用跳转
用户每次下单的时候,必须是处于登录状态,这时候我们可以使用CAS做单点登录,登录成功后再跳转到商品详情页,点击下单购买,但问题是登录成功后,再如何跳转到当前商品详情页?
用户每次向后台发送请求的时候,头文件中会有一个referer属性,referer告诉服务器我是从哪个页面链接过来的。我们可以定义一个用户无法直接访问的方法,用户每次访问该方法的时候,会携带referer,此时用户无法访问,会跳转CAS,CAS登录后会再次访问之前要访问的方法,我们可以在后台该方法中获取该头文件属性,然后每次再跳转过来,这样就可以实现页面跳转原路返回了。
一.定义跳转控制器
- 以编写一个controller来实现跳转,创建RedirectController类 ,代码如下:
二.匿名配置
- 创建一个下单控制器,并创建下单方法,让下单方法允许匿名访问,用户每次下单的时候,必须要先登录,可以在下单控制器中获取用户登录名,如果登录名为匿名用户(anonymousUser),则提示用户登录。
- 修改qingcheng_common_cas工程的spring-security.xml,添加匿名访问配置
三.集成SpringSecurity+CAS
- 修改qingcheng_web_seckill工程的web.xml,集成SpringSecurity,在web.xml中添加如下配置:
- 创建spring-security-seckill.xml,取消seckill-index.html和seckill-item.html页面的安全配置,代码如下:
四.下单控制器创建
- 创建一个com.qingcheng.controller.SeckillOrderController控制器,用于实现下单操作,代码如下:
五.页面对接
- 在页面添加一个下单方法,并在方法中实现下单操作,代码如下:
立即抢购 按钮添加一个点击事件,每次点击的时候,调用上面的add下单方法,代码如下:
第6章 下单
用户下单,从控制层->Service层->Dao层,所以我们先把dao创建好,再创建service层,再创建控制层。用户下单,为了提升下单速度,我们将订单数据存入到Redis缓存中,如果用户支付了,则将Reids缓存中的订单存入到MySQL中,并清空Redis缓存中的订单。
一.Dao层创建
- 在qingcheng_service_seckill创建com.qingcheng.dao.SeckillOrderMapper接口代码如下
二.Service层创建
- Service接口创建
在qingcheng_interface中创建com.qingcheng.service.seckill.SeckillOrderService接口,代码如下: - Service接口实现类创建
在qingcheng_service_seckill中创建com.qingcheng.service.impl.SeckillOrderServiceImpl实现类,代码如下:
三.控制层创建
- 在qingcheng_web_seckill中的SeckillOrderController类的add方法中完善添加订单方法,代码如下: