秒杀前端

1.秒杀:

秒杀概念:所谓“秒杀”,就是网络卖家发布一些超低价格的商品,所有买家在同一时间网上抢购的一种销售方式。

通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动。由于商品价格低廉,往往一上架就被抢购一空,有时只用一秒钟。秒杀商品通常有两种限制:库存限制、时间限制。

需求:

1.2  表结构说明

秒杀商品信息表

CREATETABLE`tb_seckill_goods`(

`id`bigint(20)NOTNULLAUTO_INCREMENT,

`goods_id`bigint(20)DEFAULTNULLCOMMENT'spu ID',

`item_id`bigint(20)DEFAULTNULLCOMMENT'sku ID',

`title`varchar(100)DEFAULTNULLCOMMENT'标题',

`small_pic`varchar(150)DEFAULTNULLCOMMENT'商品图片',

`price`decimal(10,2)DEFAULTNULLCOMMENT'原价格',

`cost_price`decimal(10,2)DEFAULTNULLCOMMENT'秒杀价格',

`seller_id`varchar(100)DEFAULTNULLCOMMENT'商家ID',

`create_time`datetimeDEFAULTNULLCOMMENT'添加日期',

`check_time`datetimeDEFAULTNULLCOMMENT'审核日期',

`status`char(1)DEFAULTNULLCOMMENT'审核状态,0未审核,1审核通过,2审核不通过',

`start_time`datetimeDEFAULTNULLCOMMENT'开始时间',

`end_time`datetimeDEFAULTNULLCOMMENT'结束时间',

`num`int(11)DEFAULTNULLCOMMENT'秒杀商品数',

`stock_count`int(11)DEFAULTNULLCOMMENT'剩余库存数',

`introduction`varchar(2000)DEFAULTNULLCOMMENT'描述',

PRIMARYKEY(`id`)

)ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf8

(1)秒杀频道首页列出秒杀商品(4)点击立即抢购实现秒杀下单,下单时扣减库存。当库存为0或不在活动期范围内时无法秒杀。(5)秒杀下单成功,直接跳转到支付页面(微信扫码),支付成功,跳转到成功页,填写收货地址、电话、收件人等信息,完成订单。(6)当用户秒杀下单5分钟内未支付,取消预订单,调用微信支付的关闭订单接口,恢复库存。

秒杀商品订单表

CREATETABLE`tb_seckill_order`(

`id`bigint(20)NOTNULLCOMMENT'主键',

`seckill_id`bigint(20)DEFAULTNULLCOMMENT'秒杀商品ID',

`money`decimal(10,2)DEFAULTNULLCOMMENT'支付金额',

`user_id`varchar(50)DEFAULTNULLCOMMENT'用户',

`seller_id`varchar(50)DEFAULTNULLCOMMENT'商家',

`create_time`datetimeDEFAULTNULLCOMMENT'创建时间',

`pay_time`datetimeDEFAULTNULLCOMMENT'支付时间',

`status`char(1)DEFAULTNULLCOMMENT'状态,0未支付,1已支付',

`receiver_address`varchar(200)DEFAULTNULLCOMMENT'收货人地址',

`receiver_mobile`varchar(20)DEFAULTNULLCOMMENT'收货人电话',

`receiver`varchar(20)DEFAULTNULLCOMMENT'收货人',

`transaction_id`varchar(30)DEFAULTNULLCOMMENT'交易流水',

PRIMARYKEY(`id`)

)ENGINE=InnoDBDEFAULTCHARSET=utf8;

需要解决的问题

如何解决分布式事务实现,分布式事务锁实现,商品超卖等问题

秒杀服务端:

2.秒杀商品存入redis缓存:


在秒杀服务中将秒杀商品存入mysql中,设置定时任务将秒杀商品从mysql中查询出来存入缓存中,以redis以hash类型进行数据存储,用户可以在前端看到展示的秒杀商品

秒杀服务搭建

1.changgou_service_seckill模块创建

1.启动类创建

1.1

redisTemplate序列化

//设置redisTemplate序列化

publicRedisTemplate<Object,Object>redisTemplate(RedisConnectionFactoryredisConnectionFactory){

//创建redis模板

RedisTemplate<Object,Object>template=newRedisTemplate<>();

//关联redisConnectionFactory

template.setConnectionFactory(redisConnectionFactory);

//创建 序列化类

GenericToStringSerializergenericToStringSerializer=newGenericToStringSerializer(Object.class);

//序列化类 对象映射设置

//设置value转化格式和Key的转化格式

template.setValueSerializer(genericToStringSerializer);

template.setKeySerializer(newStringRedisSerializer());

template.afterPropertiesSet();

returntemplate;

}

2.鉴权公钥文件

config 配置类

packagecom.changgou.seckill.config;

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

importorg.springframework.core.io.ClassPathResource;

importorg.springframework.core.io.Resource;

importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

importorg.springframework.security.config.annotation.web.builders.HttpSecurity;

importorg.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

importorg.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

importorg.springframework.security.oauth2.provider.token.TokenStore;

importorg.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

importorg.springframework.security.oauth2.provider.token.store.JwtTokenStore;

importjava.io.BufferedReader;

importjava.io.IOException;

importjava.io.InputStreamReader;

importjava.util.stream.Collectors;

@Configuration

@EnableResourceServer

//开启方法上的PreAuthorize注解

@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)

publicclassResourceServerConfigextendsResourceServerConfigurerAdapter{

//公钥

privatestaticfinalStringPUBLIC_KEY="public.key";

/***

* 定义JwtTokenStore

* @param jwtAccessTokenConverter

* @return

*/

@Bean

publicTokenStoretokenStore(JwtAccessTokenConverterjwtAccessTokenConverter) {

returnnewJwtTokenStore(jwtAccessTokenConverter);

   }

/***

* 定义JJwtAccessTokenConverter

* @return

*/

@Bean

publicJwtAccessTokenConverterjwtAccessTokenConverter() {

JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();

converter.setVerifierKey(getPubKey());

returnconverter;

   }

/**

* 获取非对称加密公钥 Key

* @return 公钥 Key

*/

privateStringgetPubKey() {

Resourceresource=newClassPathResource(PUBLIC_KEY);

try{

InputStreamReaderinputStreamReader=newInputStreamReader(resource.getInputStream());

BufferedReaderbr=newBufferedReader(inputStreamReader);

returnbr.lines().collect(Collectors.joining("\n"));

}catch(IOExceptionioe) {

returnnull;

       }

   }

/***

* Http安全配置,对每个到达系统的http请求链接进行校验

* @param http

* @throws Exception

*/

@Override

publicvoidconfigure(HttpSecurityhttp)throwsException{

//所有请求必须认证通过

http.authorizeRequests()

.anyRequest()

.authenticated();//其他地址需要认证授权

   }

}

3.通过网关访问

//所有需要传递令牌的地址

publicstaticStringfilterPath="/api/worder/**,/api/wseckillorder,/api/seckill,/api/wxpay,/api/wxpay/**,/api/user/**,/api/address/**,/api/wcart/**,/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**";

packagecom.changgou.web.gateway.filter;

publicclassUrlFilter{

//所有需要传递令牌的地址

publicstaticStringfilterPath="/api/worder/**,/api/wseckillorder,/api/seckill,/api/wxpay,/api/wxpay/**,/api/user/**,/api/address/**,/api/wcart/**,/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**";

publicstaticbooleanhasAuthorize(Stringurl){

String[]split=filterPath.replace("**","").split(",");

for(Stringvalue:split) {

if(url.startsWith(value)){

returntrue;//代表当前的访问地址是需要传递令牌的

           }

       }

returnfalse;//代表当前的访问地址是不需要传递令牌的

   }

}

3.1更改网关配置文件,添加请求路由转发

#秒杀微服务

   - id: changgou_seckill_route

     uri: lb://seckill

     predicates:

       - Path=/api/seckill/**

     filters:

       - StripPrefix=1

changgou_service下

模块创建流程:

创建changgou_service_seckill

pom引入

common——db依赖  eureka依赖 changgou_service_order_api依赖 changgou_service_seckill_api依赖

changgou_service_goods_api依赖

spring-rabbit依赖

changgou_service_order_api模块创建

数据库表实体创建 (pojo)秒杀商品 秒杀订单

feign包

4.秒杀时间段计算:

package com.changgou.util;

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Calendar;import java.util.Date;import java.util.List;

public class DateUtil {

从格式转成格式获取指定日期的凌晨

/***

* 时间增加N分钟

* @param date

* @param minutes

* @return

*/

publicstaticDateaddDateMinutes(Datedate,intminutes){

Calendarcalendar=Calendar.getInstance();

calendar.setTime(date);

calendar.add(Calendar.MINUTE,minutes);// 24小时制

date=calendar.getTime();

returndate;

}

/***

* 时间递增N小时

* @param hour

* @return

*/

publicstaticDateaddDateHour(Datedate,inthour){

Calendarcalendar=Calendar.getInstance();

calendar.setTime(date);

calendar.add(Calendar.HOUR,hour);// 24小时制

date=calendar.getTime();

returndate;

}

/***

* 获取时间菜单

* @return

*/

publicstaticList<Date>getDateMenus(){


//定义一个List<Date>集合,存储所有时间段

List<Date>dates=newArrayList<Date>();


//循环12次

Datedate=toDayStartHour(newDate());//凌晨

for(inti=0;i<12;i++) {

//每次递增2小时,将每次递增的时间存入到List<Date>集合中

dates.add(addDateHour(date,i*2));

   }

//判断当前时间属于哪个时间范围

Datenow=newDate();

for(Datecdate:dates) {

//开始时间<=当前时间<开始时间+2小时

if(cdate.getTime()<=now.getTime()&&now.getTime()<addDateHour(cdate,2).getTime()){

now=cdate;

break;

       }

   }

//当前需要显示的时间菜单

List<Date>dateMenus=newArrayList<Date>();


for(inti=0;i<5;i++) {

dateMenus.add(addDateHour(now,i*2));

   }

returndateMenus;

}

/***

* 时间转成yyyyMMddHH

* @param date

* @return

*/

publicstaticStringdate2Str(Datedate){

SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyyMMddHH");

returnsimpleDateFormat.format(date);

}

}


每个秒杀时间段间隔两小时,一天存在12个秒杀时间段,每个秒杀时间段有商品,每个秒杀商品存在开始时间结束时间(只要每个秒杀商品时间大于开始时间段,并且小于秒杀商品结束时间段,那么该秒杀商品就是属于这个秒杀时间段的)所以每个秒杀时间段中有哪些商品呢?上面提供了秒杀Utills

工具类中测试方法获取12个时间段:

publicstaticvoidmain(String[]args) {

//集合存储数据结果

List<Date>dateList=newArrayList<>();

//获取本日凌晨时间点

DatestartHour=DateUtil.toDayStartHour(newDate());

//循环12次

for(inti=0;i<12;i++) {

dateList.add(addDateHour(startHour,i*2));

   }

for(Datedate:dateList) {

//输出打印 日期格式化

SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Stringformat=simpleDateFormat.format(date);

System.out.println(format);

   }

}

需求:静态原型中只展示5个时间段

/***

* 获取时间菜单

* @return

*/

publicstaticList<Date>getDateMenus(){


//定义一个List<Date>集合,存储所有时间段

List<Date>dates=newArrayList<Date>();


//循环12次

Datedate=toDayStartHour(newDate());//凌晨

for(inti=0;i<12;i++) {

//每次递增2小时,将每次递增的时间存入到List<Date>集合中

dates.add(addDateHour(date,i*2));

   }

//判断当前时间属于哪个时间范围

Datenow=newDate();

for(Datecdate:dates) {

//开始时间<=当前时间<开始时间+2小时

if(cdate.getTime()<=now.getTime()&&now.getTime()<addDateHour(cdate,2).getTime()){

now=cdate;

break;

       }

   }

//当前需要显示的时间菜单

List<Date>dateMenus=newArrayList<Date>();


for(inti=0;i<5;i++) {

dateMenus.add(addDateHour(now,i*2));

   }

returndateMenus;

}

5.秒杀商品存入缓存实现:

1.定义定时任务,查询符合条件的秒杀商品

逻辑:

1.获取时间段集合并循环遍历出每一个时间段

2.获取每个时间段名称,用于后续redis中Key的设置

3.秒杀商品状态必须为审核通过status=1

4.商品库存个数>0

5.商品秒杀开始时间>=当前时间段

6.秒杀商品结束<当前时间段+2

7.排除之前已经加载到Redis缓存中的商品数据

8.执行查询获取对应的结果集

2.将秒杀商品存入缓存

3.定义定时任务类 SecKillGoodsPushTask

1.秒杀服务启动类添加定时任务注解

2.定时任务包task 方法 方法注解Scheduled(设置定时执行时间):

publice void loadSecKillGoodsToRedis(){

1.创建秒杀时间段展示集合

2.遍历进行格式转化(使用工具类)

3.获取每个时间段名称作为redis的Key

4.进行查询 秒杀商品查询mapper进行注入

秒杀Mapper.selectByExample(examle);创建example传入操作的秒杀商品表实体类,获取查询条件对象,设置status状态,addGreaterThan(属性名 大于的值)商品库存个数>0,addGreaterThanOrEqualTo(开始时间属性名称,开始时间值ps:注意格式化)设置商品秒杀开始时间>=当前时间段,addLessThan(结束时间属性名称,)秒杀商品结束<当前时间段+2,排除之前已经加载到Redis缓存中的商品数据 注入RedisTemplate  定义常量 调用redisTemplate.boundHashOps(常量+redisKey).keys();获取redis中的值,拿到值进行判断有没有这个值(keys!=null&&keys.size()>0),执行查询获取对应的结果集,遍历结果集redisTemplate.opsForHash().put(大Key,秒杀商品Id小key,秒杀商品对象)

注意:大Key是boundHashOps中常量+之前获取的redisKey,小Key为秒杀商品实体中的Id,value为秒杀商品对象,这三个值可以通过opsForHash传入进行秒杀商品存入缓存的添加

}

6.秒杀商品列表展示:

需求:当前已经完成了秒杀时间段菜单的显示,当用户在切换时间段时按照不同的时间段展示不同时间段下的秒杀商品

实现流程:秒杀渲染服务基于Feign会调用秒杀服务,在秒杀服务定义方法

秒杀服务定义service接口 SeckillGoodsService 

接口方法:List<SeckillGoods> list(String time)

实现类:SecKillGoodsServiceImpl

返回值时间集合list

表现层:SecKillGoodsController

返回值:查询商品列表集合给前端:seckillGoodsList

返回值:Result<List<SecKillGoods>>

Oauth2必须对所有请求进行放行,在配置类ResourceSeviceConfig  configura方法中对请求路径进行放行:


再定义Feign接口进行Feign暴露 :


7.秒杀商品列表秒杀渲染服务显示数据

实现流程:在秒杀渲染服务controller中注入SecKillGoodsFeign,进行远程调用

SecKillGoodsController中定义方法

/**

* 秒杀时间段下商品列表显示

*/

@Autowired

privateSecKillGoodsFeignsecKillGoodsFeign;

@RequestMapping("/list")

@ResponseBody

publicResult<List<SeckillGoods>>list(Stringtime){

Result<List<SeckillGoods>>secKillGoodsList=secKillGoodsFeign.list(time);

returnsecKillGoodsList;

}

前端代码发起异步,经过前端网关通过类路径调用/api/wseckillgoods/list?time 传入时间参数获取 后台渲染服务controller中的返回值,前端还需要通过用户点击秒杀时间获取时间参数传入后台渲染服务controller,这样就完成了秒杀时间段下商品列表的数据展示了


测试:


bug:没查到?因为时间格式不对 使用时间格式化工具:

DateUtil.formatStr(time)


时间参数正确 展示成功


立即抢购实现秒杀下单Js

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355