Loading

秒杀商品-首页

秒杀商品首页会显示处于秒杀中以及未开始秒杀的商品。

 秒杀首页实现分析

秒杀首页需要显示不同时间段的秒杀商品信息,然后当用户选择不同的时间段,查询该时间段下的秒杀商品,实现过程分为两大过程:

1) 加载时间菜单
2)加载时间菜单下秒杀商品信息

加载时间菜单分析

每2个小时就会切换一次抢购活动,所以商品发布的时候,我们将时间定格在2小时内抢购,每次发布商品的时候,商品抢购开始时间和结束时间是这2小时的边界。

每2小时会有一批商品参与抢购,所以我们可以将24小时切分为12个菜单,每个菜单都是个2小时的时间段,当前选中的时间菜单需要根据当前时间判断,判断当前时间属于哪个秒杀时间段,然后将该时间段作为选中的第1个时间菜单。

加载对应秒杀商品分析

进入首页时,到后台查询时间菜单信息,然后将第1个菜单的时间段作为key,在Redis中查询秒杀商品集合,并显示到页面,页面每次点击切换不同时间段菜单的时候,都将时间段传入到后台,后台根据时间段获取对应的秒杀商品集合。

秒杀渲染服务 - 渲染秒杀首页

新建秒杀渲染服务

1)创建工程changgou_web_seckill,用于秒杀页面渲染

2) 添加依赖

<dependencies>
    <dependency>
        <groupId>com.changgou</groupId>
        <artifactId>changgou_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.changgou.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: changgou_seckill_web_route
          uri: lb://seckill-web
          predicates:
            - Path=/api/wseckillgoods/**
          filters:
            - StripPrefix=1

时间菜单实现

时间菜单显示,先运算出每2小时一个抢购,就需要实现12个菜单,可以先计算出每个时间的临界值,然后根据当前时间判断需要显示12个时间段菜单中的哪个菜单,再在该时间菜单的基础之上往后挪4个菜单,一直显示5个时间菜单。

时间菜单获取

changgou_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;
}

页面加载时间菜单

修改seckill-index.html

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>

效果如下:

时间格式化

上面菜单循环输出后,会出现如上图效果,时间格式全部不对,我们需要引入一个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 。时间菜单效果如下

选中实现

 思路分析

根据原型图,是让当前第一个时间菜单为选中状态,并且加载第一个菜单对应的数据。

我们可以先定义一个ctime=0,用来记录当前选中的菜单下标,因为默认第一个选中,第一个下标为0,所以初始值为0,每次点击对应菜单的时候,将被点击的菜单的下标值赋值给ctime,然后在每个菜单上判断,下标=ctime则让该菜单选中。

代码实现

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>

倒计时实现

倒计时实现
基础数据显示

定义一个集合,用于存放五个时间段的倒计时时间差,集合中每一个角标都对应一个倒计时时间差,比如:集合角标为0,对应第一个倒计时时间差。集合角标为1,对应第二个倒计时时间差,依次类推。

因为要有倒计时的效果,所以后续会遍历该时间集合,并让集合中的每一个时间循环递减即可。

从该集合中获取内容,并更新倒计时时间

访问页面测试,效果如下所示:

 

 每个时间差倒计时实现

周期执行函数用法如下:

window.setInterval(function(){//要做的事},1000);

结束执行周期函数用法如下:

window.clearInterval(timers);
每个时间差倒计时实现

周期执行函数用法如下:

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 。可以发现每一个时间段的时间都在每秒递减。

倒计时时间格式化

将此工具引入页面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个都是距离开始倒计时,每个倒计时其实就是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);
    })
}

加载秒杀商品实现

当前已经完成了秒杀时间段菜单的显示,那么当用户在切换不同的时间段的时候,需要按照用户所选择的时间去显示相对应时间段下的秒杀商品

秒杀服务-查询秒杀商品列表

秒杀服务-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);
    }
}
秒杀服务-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();
    }
}
查询秒杀商品放行

更改秒杀微服务的ResourceServerConfig类,对查询方法放行

@Override
public void configure(HttpSecurity http) throws Exception {
    //所有请求必须认证通过
    http.authorizeRequests()
        //下边的路径放行
        .antMatchers(
        "/seckillgoods/list/**"). //配置地址放行
        permitAll()
        .anyRequest().
        authenticated();    //其他地址需要认证授权
}
杀服务Api- feign接口定义
@FeignClient(name="seckill")
public interface SecKillFeign {
​
    /**
     * 查询秒杀商品列表
     * @param time
     * @return
     */
    @RequestMapping("/seckillgoods/list")
    public Result<List<SeckillGoods>> list(@RequestParam("time") String time);
}

秒杀渲染服务-查询秒杀商品列表

更新changgou_web_seckill的启动类

添加feign接口扫描

@EnableFeignClients(basePackages = "com.changgou.seckill.feign")
更新changgou_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;
}
更新secKill-index.html。添加按照时间查询方法

//按照时间查询秒杀商品列表
searchList:function (time) {
    axios.get('/wseckill/list?time='+time).then(function (response) {
        if (response.data.flag){
            app.goodslist = response.data.data;
        }
    })
}
更新secKill-index.html。 加载页面时,默认当前时间查询

//查询当前时间段对应的秒杀商品
app.searchList(app.dateMenus[0]);
更新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,所以当用户点击立即抢购按钮的时候,则直接进行下单操作。

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='抢单失败';
        }
    })
​
}

调用下单方法

修改抢购按钮,添加事件

<a class='sui-btn btn-block btn-buy'  href='javascript:void(0)' @click="add(item.id)">立即抢购</a>

 

posted @ 2021-08-11 15:44  1640808365  阅读(419)  评论(0编辑  收藏  举报