秒杀商品-首页
秒杀商品首页会显示处于秒杀中以及未开始秒杀的商品。
秒杀首页实现分析
秒杀首页需要显示不同时间段的秒杀商品信息,然后当用户选择不同的时间段,查询该时间段下的秒杀商品,实现过程分为两大过程:
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>