一、 秒杀商品-首页分析
秒杀商品首页会显示处于秒杀中以及未开始秒杀的商品。
秒杀首页的渲染实现,秒杀首页中需要加载两部分内容分别是:
当前秒杀的时间段集合,以及秒杀商品信息。对于秒杀商品信息的加载,我们是从Redis中进行相关数据的获取。
要实现加载秒杀的时间段,以及秒杀商品的话实现思路:
1.1 秒杀首页实现分析
秒杀首页需要显示不同时间段的秒杀商品信息,然后当用户选择不同的时间段,查询该时间段下的秒杀商品,实现过程分为两大过程:
1) 加载时间菜单
2)加载时间菜单下秒杀商品信息
1.1.1 加载时间菜单分析
每2个小时就会切换一次抢购活动,所以商品发布的时候,我们将时间定格在2小时内抢购,每次发布商品的时候,商品抢购开始时间和结束时间是这2小时的边界。
每2小时会有一批商品参与抢购,所以我们可以将24小时切分为12个菜单,每个菜单都是个2小时的时间段,当前选中的时间菜单需要根据当前时间判断,判断当前时间属于哪个秒杀时间段,然后将该时间段作为选中的第1个时间菜单。
1.1.2 加载对应秒杀商品分析
进入首页时,到后台查询时间菜单信息,然后将第1个菜单的时间段作为key,在Redis中查询秒杀商品集合,并显示到页面,页面每次点击切换不同时间段菜单的时候,都将时间段传入到后台,后台根据时间段获取对应的秒杀商品集合。
二、 搭建秒杀渲染服务 - 渲染秒杀首页
2.1 新建秒杀渲染服务
2.1 新建秒杀渲染服务
步骤1):创建工程shangcheng_web_seckill,用于秒杀页面渲染
步骤2) :添加依赖
<dependencies>
<dependency>
<groupId>com.shangcheng</groupId>
<artifactId>shangcheng_service_seckill_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
步骤3): 添加启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.shangcheng.seckill.feign")
public class WebSecKillApplication {
public static void main(String[] args) {
SpringApplication.run(WebSecKillApplication.class,args);
}
@Bean
public FeignInterceptor feignInterceptor(){
return new FeignInterceptor();
}
/**
* 设置 redisTemplate 的序列化设置
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1.创建 redisTemplate 模版
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// 2.关联 redisConnectionFactory
template.setConnectionFactory(redisConnectionFactory);
// 3.创建 序列化类
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
// 6.序列化类,对象映射设置
// 7.设置 value 的转化格式和 key 的转化格式
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
步骤4) :添加application.yml
server:
port: 9104
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
spring:
jackson:
time-zone: GMT+8
thymeleaf:
cache: false
application:
name: seckill-web
main:
allow-bean-definition-overriding: true
redis:
host: 192.168.200.128
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 60000
#请求处理的超时时间
ribbon:
ReadTimeout: 4000
#请求连接的超时时间
ConnectTimeout: 3000
步骤5) :添加静态化资源

步骤6):对接网关
#秒杀渲染微服务
- id: shangcheng_seckill_web_route
uri: lb://seckill-web
predicates:
- Path=/api/wseckillgoods/**
filters:
- StripPrefix=1
三、 时间菜单实现
时间菜单显示,先运算出每2小时一个抢购,就需要实现12个菜单,可以先计算出每个时间的临界值,然后根据当前时间判断需要显示12个时间段菜单中的哪个菜单,再在该时间菜单的基础之上往后挪4个菜单,一直显示5个时间菜单。
3.1 时间菜单获取
shangcheng_web_seckill新增控制类SecKillGoodsController
/*****
* 获取时间菜单
*/
@RequestMapping(value = "/timeMenus")
@ResponseBody
public List<String> dateMenus(){
List<Date> dateMenus = DateUtil.getDateMenus();
List<String> result = new ArrayList<>();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Date dateMenu : dateMenus) {
String format = simpleDateFormat.format(dateMenu);
result.add(format);
}
return result;
}
3.2 页面加载时间菜单
修改seckill-index.html
1565000184944
var app = new Vue({
el: '#app',
data() {
return {
goodslist: [],
dateMenus:[]
}
},
methods:{
loadMenus:function () {
axios.get("/api/wseckill/timeMenus").then(function (response) {
app.dateMenus=response.data;
//查询当前时间段对应的秒杀商品
})
}
},
created:function () {
this.loadMenus();
}
})
</script>
效果如下:
3.3 时间格式化
上面菜单循环输出后,会出现如上图效果,时间格式全部不对,我们需要引入一个moment.min.js来格式化时间。
步骤1):引入moment.min.js
步骤2):添加过滤器
//过滤器
Vue.filter("dateFilter", function(date, formatPattern){
return moment(date).format(formatPattern || "YYYY-MM-DD HH:mm:ss");
});
步骤3) :取值格式化
<div class="time-clock">{
{item | dateFilter('HH:mm')}}</div>
重新访问:http://localhost:9104/wseckill/toIndex 。时间菜单效果如下:
3.4 选中实现
3.4.1 思路分析
根据原型图,是让当前第一个时间菜单为选中状态,并且加载第一个菜单对应的数据。
我们可以先定义一个ctime=0,用来记录当前选中的菜单下标,因为默认第一个选中,第一个下标为0,所以初始值为0,每次点击对应菜单的时候,将被点击的菜单的下标值赋值给ctime,然后在每个菜单上判断,下标=ctime则让该菜单选中。
3.4.2 代码实现
步骤1):定义ctime=0
var app = new Vue({
el: '#app',
data() {
return {
goodslist: [],
dateMenus:[],
ctime:0, //当前时间菜单选中的下标
}
}
})
步骤2):页面样式控制:
<div class="item-time " v-for="(item,index) in dateMenus" :class="['item-time',index==ctime?'active':'']" @click="ctime=index;">
<div class="time-clock">{
{
item | dateFilter('HH:mm')}}</div>
<div class="time-state-on">
<span class="on-text" v-if="index==0">快抢中</span>
<span class="on-over" v-if="index==0">距离结束:01:02:34</span>
<span class="on-text" v-if="index>0">即将开始</span>
<span class="on-over" v-if="index>0">距离开始:01:02:34</span>
</div>
</div>
3.5 倒计时实现
3.5.1 倒计时实现
1) 基础数据显示
定义一个集合,用于存放五个时间段的倒计时时间差,集合中每一个角标都对应一个倒计时时间差,比如:集合角标为0,对应第一个倒计时时间差。集合角标为1,对应第二个倒计时时间差,依次类推。
因为要有倒计时的效果,所以后续会遍历该时间集合,并让集合中的每一个时间循环递减即可。
从该集合中获取内容,并更新倒计时时间:
访问页面测试,效果如下所示:
2) 每个时间差倒计时实现
周期执行函数用法如下:
window.setInterval(function(){
//要做的事},1000);
结束执行周期函数用法如下:
window.clearInterval(timers);
具体代码如下:
//时间差递减
let timers = window.setInterval(function () {
for(var i=0;i<app.alltimes.length;i++){
//时间递减
app.$set(app.alltimes,i,app.alltimes[i]-1000);
if(app.alltimes[i]<=0){
//停止倒计时
window.clearInterval(timers);
//当任意一个时间<=0的时候,需要重新刷新菜单,并刷新对应的数据
app.loadMenus();
}
}
},1000);
测试访问:http://localhost:9104/wseckill/toIndex 。可以发现每一个时间段的时间都在每秒递减。
3) 倒计时时间格式化
将此工具引入页面js方法中,用于时间计算
//将毫秒转换成时分秒
timedown:function(num) {
var oneSecond = 1000;
var oneMinute=oneSecond*60;
var oneHour=oneMinute*60
//小时
var hours =Math.floor(num/oneHour);
//分钟
var minutes=Math.floor((num%oneHour)/oneMinute);
//秒
var seconds=Math.floor((num%oneMinute)/oneSecond);
//拼接时间格式
var str = hours+':'+minutes+':'+seconds;
return str;
}
修改时间差显示设置
<div class="time-state-on">
<span class="on-text" v-if="index==0">快抢中</span>
<span class="on-over" v-if="index==0">距离结束:{
{timedown(alltimes[index])}}</span>
<span class="on-text" v-if="index>0">即将开始</span>
<span class="on-over" v-if="index>0">距离开始:{
{timedown(alltimes[index])}}</span>
</div>
重新访问进行测试。效果如下:
4) 正确倒计时时间显示
现在页面中,对于倒计时时间集合内的数据,暂时写的为假数据,现在需要让集合内容的数据是经过计算得出的。第一个是距离结束时间倒计时,后面的4个都是距离开始倒计时,每个倒计时其实就是2个时差,计算方式如下:
第1个时差:第2个抢购开始时间-当前时间,距离结束时间
第2个时差:第2个抢购开始时间-当前时间,距离开始时间
第3个时差:第3个抢购开始时间-当前时间,距离开始时间
第4个时差:第4个抢购开始时间-当前时间,距离开始时间
第5个时差:第5个抢购开始时间-当前时间,距离开始时间
loadMenus:function () {
axios.get("/wseckill/timeMenus").then(function (response) {
app.dateMenus=response.data;
//查询当前时间段对应的秒杀商品
//循环所有时间菜单
for(var i=0;i<app.dateMenus.length;i++){
//运算每个时间菜单倒计时时间差
if (i==0){
var x =i+1;
app.$set(app.alltimes,i,new Date(app.dateMenus[x]).getTime()-new Date().getTime());
} else{
app.$set(app.alltimes,i,new Date(app.dateMenus[i]).getTime()-new Date().getTime());
}
}
//时间差递减
let timers = window.setInterval(function () {
for(var i=0;i<app.alltimes.length;i++){
//时间递减
app.$set(app.alltimes,i,app.alltimes[i]-1000);
}
},1000);
})
}
四、 加载秒杀商品实现
当前已经完成了秒杀时间段菜单的显示,那么当用户在切换不同的时间段的时候,需要按照用户所选择的时间去显示相对应时间段下的秒杀商品
4.1 秒杀服务-查询秒杀商品列表
4.1.1 秒杀服务-controller
@RestController
@RequestMapping("/seckillgoods")
public class SecKillController {
@Autowired
private SecKillGoodsService secKillGoodsService;
/**
* 查询秒杀商品列表
* @param time
* @return
*/
@RequestMapping("/list")
public Result<List<SeckillGoods>> list(@RequestParam("time") String time){
List<SeckillGoods> seckillGoodsList = secKillGoodsService.list(time);
return new Result<List<SeckillGoods>>(true, StatusCode.OK,"查询秒杀商品成功",seckillGoodsList);
}
}
4.1.2 秒杀服务-service&serviceImpl
public interface SecKillGoodsService {
List<SeckillGoods> list(String time);
}
@Service
public class SecKillGoodsServiceImpl implements SecKillGoodsService {
@Autowired
private RedisTemplate redisTemplate;
private static final String SECKILL_KEY = "SeckillGoods_";
/**
* 查询秒杀商品列表
* @param time
* @return
*/
@Override
public List<SeckillGoods> list(String time) {
return redisTemplate.boundHashOps(SECKILL_KEY+time).values();
}
}
4.1.3 查询秒杀商品放行
更改秒杀微服务的ResourceServerConfig类,对查询方法放行
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/seckillgoods/list/**"). //配置地址放行
permitAll()
.anyRequest().
authenticated(); //其他地址需要认证授权
}
4.1.4 秒杀服务Api- feign接口定义
@FeignClient(name="seckill")
public interface SecKillFeign {
/**
* 查询秒杀商品列表
* @param time
* @return
*/
@RequestMapping("/seckillgoods/list")
public Result<List<SeckillGoods>> list(@RequestParam("time") String time);
}
4.2 秒杀渲染服务-查询秒杀商品列表
1) 更新shangcheng_web_seckill的启动类
添加feign接口扫描
@EnableFeignClients(basePackages = "com.shangcheng.seckill.feign")
2) 更新shangcheng_web_seckill的SecKillGoodsController
注入secKillFeign,并添加获取秒杀商品列表方法实现
/**
* 获取秒杀商品列表
* 默认当前时间
*/
@RequestMapping("/list")
@ResponseBody
public Result<List<SeckillGoods>> list(String time){
Result<List<SeckillGoods>> listResult = secKillFeign.list(DateUtil.formatStr(time));
return listResult;
}
3) 更新secKill-index.html。添加按照时间查询方法
//按照时间查询秒杀商品列表
searchList:function (time) {
axios.get('/wseckill/list?time='+time).then(function (response) {
if (response.data.flag){
app.goodslist = response.data.data;
}
})
}
4) 更新secKill-index.html。 加载页面时,默认当前时间查询
//查询当前时间段对应的秒杀商品
app.searchList(app.dateMenus[0]);
5) 更新secKill-index.html。切换时间菜单,查询秒杀商品
<div class="item-time "
v-for="(item,index) in dateMenus"
:class="['item-time',index==ctime?'active':'']"
@click="ctime=index;searchList(item)">
五、 抢购按钮
因为当前业务设定为用户秒杀商品为sku,所以当用户点击立即抢购按钮的时候,则直接进行下单操作。
5.1 js定义
在秒杀首页添加下单方法
//秒杀下单
add:function(id){
app.msg ='正在下单';
axios.get("/api/wseckillorder/add?time="+moment(app.dateMenus[0]).format("YYYYMMDDHH")+"&id="+id).then(function (response) {
if (response.data.flag){
app.msg='抢单成功,即将进入支付!';
}else{
app.msg='抢单失败';
}
})
}
5.2 调用下单方法
修改抢购按钮,添加事件
<a class='sui-btn btn-block btn-buy' href='javascript:void(0)' @click="add(item.id)">立即抢购</a>