一:秒杀
1.1 特点
秒杀具有瞬间高并发的特点,针对这一特点,必须要做限流 + 异步 + 缓存 (+ 页面静态化)。
1.2 限流方式
- 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
- nginx限流,直接负载部分请求到错误的静态页面:令牌算法 漏斗算法
- 网关限流,限流的过滤器。或者使用专业的限流组件sentinel
- 代码中使用分布式信号量
- rabbitmq限流(能者多劳:chanel.basicQos(1)),保证发挥所有服务器的性能。
1.3 秒杀流程
二:创建秒杀模块
秒杀建议单独写入一个模块里面,这样可以单独部署,及时秒杀模块出现问题,也不会影响其他模块
2.1 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sysg.gulimail</groupId>
<artifactId>gulimail-seckill</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimail-seckill</name>
<description>谷粒商城-秒杀服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.sysg.gulimail</groupId>
<artifactId>gulimail-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!--排除掉seata依赖-->
<exclusions>
<exclusion>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 application.properties
# name
spring.application.name=gulimail-seckill
# port
server.port=25000
# nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# redis
spring.redis.host=127.0.0.1
2.3 添加注解
在主启动类添加@EnableDiscoveryClient注解,将服务注册到配置中心
在配置类添加@EnableAsync,表示当前方法异步执行
在配置类添加@EnableScheduling,开启定时任务功能
三:秒杀商品定时上架
秒杀系统一次性上架最近三天所需要的商品
3.1 计算出最近三天的时间
当前时间
/**
* 当前时间
* @return
*/
public String startTime(){
LocalDate now = LocalDate.now();
LocalTime min = LocalTime.MIN;
LocalDateTime start = LocalDateTime.of(now, min);
//格式化时间
return start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
三天后的时间
/**
* 结束时间
* @return
*/
public String endTime(){
LocalDate now = LocalDate.now();
LocalDate plus = now.plusDays(2);
LocalTime max = LocalTime.MAX;
LocalDateTime end = LocalDateTime.of(plus, max);
//格式化时间
return end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
3.2 查询最近三天需要参加秒杀商品的信息
controller
/**
* 查询最近三天需要参加秒杀商品的信息
* @return
*/
@GetMapping(value = "/Lates3DaySession")
public R getLates3DaySession() {
List<SeckillSessionEntity> seckillSessionEntities = seckillSessionService.getLates3DaySession();
return R.ok().setData(seckillSessionEntities);
}
service
@Override
public List<SeckillSessionEntity> getLates3DaySession() {
//计算最近三天
//查出这三天参与秒杀活动的商品
QueryWrapper<SeckillSessionEntity> queryWrapper =
new QueryWrapper<SeckillSessionEntity>().between("start_time", startTime(), endTime());
List<SeckillSessionEntity> list = this.baseMapper.selectList(queryWrapper);
if (list != null && list.size() > 0) {
List<SeckillSessionEntity> collect = list.stream().map(session -> {
Long id = session.getId();
//查出sms_seckill_sku_relation表中关联的skuId
List<SeckillSkuRelationEntity> relationSkus = seckillSkuRelationService.list(new QueryWrapper<SeckillSkuRelationEntity>()
.eq("promotion_session_id", id));
session.setRelationSkus(relationSkus);
return session;
}).collect(Collectors.toList());
return collect;
}
return null;
}
3.3 将上架的商品缓存到redis里面
@Override
public void uploadSeckillSkuLatest3Days() {
//1.扫描最近三天需要参与秒杀的活动
R session = couponFeignService.getLates3DaySession();
if(session.getCode() == 0){
//上架商品
List<SeckillSessionWithSkusVo> sessionData = session.getData("data", new TypeReference<List<SeckillSessionWithSkusVo>>() {
});
//将上架的商品缓存到redis里面
//1.缓存活动信息
saveSessionInfos(sessionData);
//2.缓存活动关联的商品信息
saveSessionSkuInfo(sessionData);
}
}
3.3.1 缓存活动信息
/**
* 缓存活动信息
*/
public void saveSessionInfos(List<SeckillSessionWithSkusVo> sessions){
sessions.forEach(session ->{
long startTime = session.getStartTime().getTime();
long endTime = session.getEndTime().getTime();
String key = SESSIONS_CATCH_PREFIX + startTime + "_" + endTime;
List<String> skuIds = session.getRelationSkus().stream().map(item->item.getSkuId().toString()).collect(Collectors.toList());
//缓存活动信息
redisTemplate.opsForList().leftPushAll(key,skuIds);
});
}
3.3.2 缓存活动关联的商品信息
1)准备hash操作
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
2)先查询sku的基本信息,调用远程服务
R info = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
if (info.getCode() == 0) {
SkuInfoVo skuInfo = info.getData("skuInfo",new TypeReference<SkuInfoVo>(){}
);
redisTo.setSkuInfo(skuInfo);
}
3)sku的秒杀信息
BeanUtils.copyProperties(seckillSkuVo,redisTo);
4)设置当前商品的秒杀时间信息
redisTo.setStartTime(session.getStartTime().getTime());
redisTo.setEndTime(session.getEndTime().getTime());
5)设置商品的随机码(防止恶意攻击)
String token = UUID.randomUUID().toString().replace("-", "");
redisTo.setRandomCode(token);
6)设置分布式信号量
信号量就是商品的库存,每进来一个,库存就会减一,而且每次都需要携带随机码
- 获取信号量,信号量作用就是限流
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
- 设置信号量的值,设置商品秒杀的数量作为信号值
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
7)将需要秒杀的商品转化为json
String seckillValue = JSON.toJSONString(redisTo);
ops.put(seckillSkuVo.getSkuId(),seckillValue);
3.4 接口幂等性处理
1)加锁:在多台服务器下,要保证只有一个机器的一个方法能去进行秒杀业务
/**
* 秒杀商品上架功能的锁
*/
private final String upload_lock = "seckill:upload:lock";
/**
* 保证幂等性问题
*/
@Scheduled(cron = "0 0 1/1 * * ? ")
public void uploadSeckillSkuLatest3Days() {
//1、重复上架无需处理
log.info("上架秒杀的商品...");
//分布式锁
RLock lock = redissonClient.getLock(upload_lock);
try {
//加锁,10秒后就自动释放锁
lock.lock(10, TimeUnit.SECONDS);
seckillService.uploadSeckillSkuLatest3Days();
} catch (Exception e) {
e.printStackTrace();
} finally {
//lock.unlock();
}
}
2)判断:通过key在redis里面查询,如果有了就不需要在进行缓存了
/**
* 缓存活动信息
*/
public void saveSessionInfos(List<SeckillSessionWithSkusVo> sessions){
sessions.forEach(session ->{
long startTime = session.getStartTime().getTime();
long endTime = session.getEndTime().getTime();
String key = SESSIONS_CATCH_PREFIX + startTime + "_" + endTime;
//缓存活动信息
//通过key在redis里面查询,如果有了就不需要在进行缓存了
Boolean hasKey = redisTemplate.hasKey(key);
if(!Boolean.TRUE.equals(hasKey)){
List<String> skuIds = session.getRelationSkus().stream().map(item->item.getPromotionSessionId()+"_"+item.getSkuId().toString()).collect(Collectors.toList());
redisTemplate.opsForList().leftPushAll(key,skuIds);
}
});
}
/**
* 缓存活动关联的商品信息
*/
public void saveSessionSkuInfo(List<SeckillSessionWithSkusVo> sessions){
sessions.forEach(session ->{
//准备hash操作
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
session.getRelationSkus().forEach(seckillSkuVo -> {
//设置商品的随机码(防止恶意攻击)
String token = UUID.randomUUID().toString().replace("-", "");
Boolean hasKey = ops.hasKey(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString());
if(!Boolean.TRUE.equals(hasKey)){
//缓存商品
SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
//1、先查询sku的基本信息,调用远程服务
R info = productFeignService.getSkuInfo(seckillSkuVo.getSkuId());
if (info.getCode() == 0) {
SkuInfoVo skuInfo = info.getData("skuInfo",new TypeReference<SkuInfoVo>(){}
);
redisTo.setSkuInfo(skuInfo);
}
//2、sku的秒杀信息
BeanUtils.copyProperties(seckillSkuVo,redisTo);
//3、设置当前商品的秒杀时间信息
redisTo.setStartTime(session.getStartTime().getTime());
redisTo.setEndTime(session.getEndTime().getTime());
//4、设置商品的随机码(防止恶意攻击)
redisTo.setRandomCode(token);
// 将需要秒杀的商品转化为json
String seckillValue = JSON.toJSONString(redisTo);
ops.put(seckillSkuVo.getPromotionSessionId().toString() + "_" + seckillSkuVo.getSkuId().toString(),seckillValue);
//5.设置分布式信号量,信号量就是商品的库存,每进来一个,库存就会减一,而且每次都需要携带随机码
//5.1 获取信号量,信号量作用就是限流
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
//5.2 设置信号量的值,设置商品秒杀的数量作为信号值
semaphore.trySetPermits(seckillSkuVo.getSeckillCount());
}
});
});
}
四:查询秒杀商品
4.1 controller
/**
* 当前时间可以参与秒杀的商品信息
* @return
*/
@GetMapping(value = "/getCurrentSeckillSkus")
@ResponseBody
public R getCurrentSeckillSkus() {
//获取到当前可以参加秒杀商品的信息
List<SeckillSkuRedisTo> vos = seckillService.getCurrentSeckillSkus();
return R.ok().setData(vos);
}
4.2 service
/**
* 获取到当前可以参加秒杀商品的信息
* @return
*/
@Override
public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
//1.确定当前时间属于哪个秒杀场次
long time = new Date().getTime();
//获取所有的keys数据
Set<String> keys = redisTemplate.keys(SESSIONS_CATCH_PREFIX + "*");
if(!CollectionUtils.isEmpty(keys)){
for (String key : keys) {
//分割后获取时间区间
String replace = key.replace(SESSIONS_CATCH_PREFIX, "");
String[] s = replace.split(PREFIX);
//开始时间
long start = Long.parseLong(s[0]);
//结束时间
long end = Long.parseLong(s[1]);
//查询当前的场次信息
if( time >= start && time <= end ){
//2.获取这个场次所有的商品信息
//range获取-100到100区间的数据
List<String> range = redisTemplate.opsForList().range(key, -100, 100);
if(!CollectionUtils.isEmpty(range)){
//获取绑定的hash值
BoundHashOperations<String, String, Object> hashOps = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
List<Object> list = hashOps.multiGet(range);
if(!CollectionUtils.isEmpty(list)){
return list.stream().map(item -> {
//不能将随机码字段也返回,所以需要删除掉
//redisTo.setRandomCode(null);当前秒杀开始就需要随机码
return JSON.parseObject(String.valueOf(item),SeckillSkuRedisTo.class);
}).collect(Collectors.toList());
}
//查询出当前场次以后,后续的就不需要遍历了,直接跳出for循环
break;
}
}
}
}
return null;
}
五:查询商品有没有秒杀信息
5.1 根据skuId查询商品是否参加秒杀活动
- controller
/**
* 根据skuId查询商品是否参加秒杀活动
* @param skuId
* @return
*/
@GetMapping(value = "/sku/seckill/{skuId}")
@ResponseBody
public R getSkuSeckillInfo(@PathVariable("skuId") Long skuId) {
SeckillSkuRedisTo to = seckillService.getSkuSeckilInfo(skuId);
return R.ok().setData(to);
}
- service
/**
* 根据skuId查询商品是否参加秒杀活动
* @param skuId
* @return
*/
@Override
public SeckillSkuRedisTo getSkuSeckilInfo(Long skuId) {
//1.找到所有参与秒杀的商品的key
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
Set<String> keys = hashOps.keys();
if(!CollectionUtils.isEmpty(keys)){
//6_4 通过正则表达式去判断
//d表示匹配一个数字
String regx = "\\d_" + skuId;
for (String key : keys) {
boolean matches = Pattern.matches(regx, key);
if(matches){
String json = hashOps.get(key);
SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
if(redisTo != null){
//随机码
Long startTime = redisTo.getStartTime();
Long endTime = redisTo.getEndTime();
long currentTime = new Date().getTime();
//判断当前时间是否在秒杀时间之间,如果是就返回随机码
if( currentTime < startTime || currentTime > endTime ){
redisTo.setRandomCode(null);
}
return redisTo;
}
}
}
}
return null;
}
5.2 新建远程调用的feign接口
@FeignClient(value = "gulimail-seckill")
public interface SeckillFeignService {
/**
* 根据skuId查询商品是否参加秒杀活动
* @param skuId
* @return
*/
@GetMapping(value = "/sku/seckill/{skuId}")
R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);
}
5.3 远程调用查询当前sku是否参与秒杀优惠活动
//3、远程调用查询当前sku是否参与秒杀优惠活动
CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {
R skuSeckillInfo = seckillFeignService.getSkuSeckillInfo(skuId);
if (skuSeckillInfo.getCode() == 0) {
SeckillSkuVo skuSeckillInfoData = skuSeckillInfo.getData("data", new TypeReference<SeckillSkuVo>() {
});
skuItemVo.setSeckillSkuVo(skuSeckillInfoData);
if (skuSeckillInfoData != null) {
long currentTime = System.currentTimeMillis();
if (currentTime > skuSeckillInfoData.getEndTime()) {
skuItemVo.setSeckillSkuVo(null);
}
}
}
}, executor);
//等到所有任务都完成
CompletableFuture.allOf(saleAttrFuture,descFuture,baseAttrFuture,imageFuture,seckillFuture).get();
六:高并发系统关注的问题
1) 服务单一职责+独立部署
秒杀服务即使自己扛不住压力,挂掉。不要影响别人
2)秒杀链接加密
防止恶意攻击,模拟秒杀请求,1000次/s攻击。
防止链接暴露,自己工作人员,提前秒杀商品。
3)库存预热+快速扣减
秒杀读多写少。无需每次实时校验库存。我们库存预热,放到redis中。信号量控制进来秒杀的请求
4)动静分离
nginx做好动静分离。保证秒杀和商品详情页的动态请求才打到后端的服务集群。使用CDN网络,分担本集群压力
5)恶意请求拦截
识别非法攻击请求并进行拦截,网关层
6)流量错峰
使用各种手段,将流量分担到更大宽度的时间点。比如验证码,加入购物车
7)限流&熔断&降级
前端限流+后端限流。限制次数,限制总量,快速失败降级运行,熔断隔离防止雪崩
8)队列削峰
1万个商品,每个1000件秒杀。双11所有秒杀成功的请求,进入队列,慢慢创建订单,扣减库存即可。
七:秒杀系统设计-立即抢购
7.1 秒杀流程
7.2 发送请求
$(".seckill").click(function () {
var isLogin = [[${session.loginUser != null}]]; //true
if (isLogin) {
var killId = $(this).attr("sessionid") + "-" + $(this).attr("skuid");
var code = $(this).attr("code");
var num = $("#productNum").val();
location.href = "http://seckill.gulimall.com/kill?killId=" + killId + "&key=" + code + "&num=" + num;
} else {
alert("秒杀请先登录");
}
return false;
});
7.3 代码实现
- controller
/**
* 商品进行秒杀(秒杀开始)
* @param killId 秒杀id
* @param key 随机码
* @param num 秒杀的总数
* @return
*/
@GetMapping(value = "/kill")
public String seckill(@RequestParam("killId") String killId,
@RequestParam("key") String key,
@RequestParam("num") Integer num,
Model model) {
String orderSn = null;
try {
//1、判断是否登录
orderSn = seckillService.kill(killId,key,num);
model.addAttribute("orderSn",orderSn);
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
- service
/**
* 商品进行秒杀(秒杀开始)
* @param killId
* @param key
* @param num
* @return
*/
@Override
public String kill(String killId, String key, Integer num) {
//1、判断是否登录,拦截器已经处理,此时无需处理
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
//2.判断参数的合法性
//2.1 获取当前秒杀商品的详细信息
BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_CACHE_PREFIX);
String json = hashOps.get(killId);
if(!StringUtils.isEmpty(json)){
SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
if(redisTo != null){
//2.2 判断秒杀时间是否过期
Long startTime = redisTo.getStartTime();
Long endTime = redisTo.getEndTime();
long currentTime = new Date().getTime();
long ttl = endTime - startTime;
if(startTime <= currentTime && endTime >= currentTime){
//2.3 判断随机码是否正确和商品id是否一致
String skuId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();
if(key.equals(redisTo.getRandomCode()) && killId.equals(skuId)){
//2.4 验证购物数量是否合理
if( num <= redisTo.getSeckillLimit()){
//2.5 验证这个用户是否购买过,幂等性处理。只要秒杀成功,就占位
String redisKey = memberRespVo.getId() + "_" + skuId;
//设置超时时间,只要过了当前秒杀场次就取消,自动过期
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
//如果占位成功就说明这个人从来没买过,就可以买
if(Boolean.TRUE.equals(aBoolean)){
//2.6 使用信号量减库存
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode());
try {
//等上100毫秒
boolean b = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
//秒杀成功,快速下单
//生成订单号
return IdWorker.getTimeId();
} catch (InterruptedException e) {
return null;
}
}
}
}
}
}
}
return null;
}
7.4 将秒杀成功的商品订单信息发送给MQ队列
1)引入依赖
<!--引入mq依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2)配置信息
# RabbitMQ配置
spring.rabbitmq.host=192.168.77.130
spring.rabbitmq.port=5672
# 虚拟主机配置
spring.rabbitmq.virtual-host=/
3)添加配置类
@Configuration
public class MyRabbitMQConfig {
/**
* 配置消息为json类型
* @return
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
4)发送订单号
//2.5 验证这个用户是否购买过,幂等性处理。只要秒杀成功,就占位
String redisKey = memberRespVo.getId() + "_" + skuId;
//设置超时时间,只要过了当前秒杀场次就取消,自动过期
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
//如果占位成功就说明这个人从来没买过,就可以买
if(Boolean.TRUE.equals(aBoolean)){
//2.6 使用信号量减库存
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode());
try {
//等上100毫秒
boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
if(Boolean.TRUE.equals(semaphoreCount)){
//秒杀成功,快速下单
//生成订单号
String timeId = IdWorker.getTimeId();
SeckillOrderTo orderTo = new SeckillOrderTo();
orderTo.setOrderSn(timeId);
orderTo.setMemberId(memberRespVo.getId());
orderTo.setNum(num);
orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
orderTo.setSkuId(redisTo.getSkuId());
orderTo.setSeckillPrice(redisTo.getSeckillPrice());
rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
long s2 = System.currentTimeMillis();
log.info("耗时..." + (s2 - s1));
return timeId;
}
} catch (InterruptedException e) {
return null;
}
}
5)新建队列和绑定关系
/**
* 商品秒杀队列
* @return
*/
@Bean
public Queue orderSecKillOrderQueue() {
Queue queue = new Queue("order.seckill.order.queue", true, false, false);
return queue;
}
@Bean
public Binding orderSecKillOrrderQueueBinding() {
//String destination, DestinationType destinationType, String exchange, String routingKey,
// Map<String, Object> arguments
Binding binding = new Binding(
"order.seckill.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.seckill.order",
null);
return binding;
}
6)新建秒杀监听器
@Slf4j
@Component
@RabbitListener(queues = "order.seckill.order.queue")
public class OrderSeckillListener {
@Autowired
private OrderService orderService;
/**
* 监听订单的entity消息
* @param seckillOrderTo
* @param channel
* @param message
*/
@RabbitHandler
public void listener(SeckillOrderTo seckillOrderTo, Channel channel, Message message) throws IOException {
//关闭OrderEntity订单
try {
log.info("准备创建秒杀单的详细信息:{}",seckillOrderTo.toString());
orderService.createSeckillOrder(seckillOrderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
//true重新回到队列里面,不能丢弃
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
7)创建秒杀订单
/**
* 创建秒杀单
* @param orderTo
*/
@Override
public void createSeckillOrder(SeckillOrderTo orderTo) {
//保存订单信息
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(orderTo.getOrderSn());
orderEntity.setMemberId(orderTo.getMemberId());
orderEntity.setCreateTime(new Date());
BigDecimal totalPrice = orderTo.getSeckillPrice().multiply(BigDecimal.valueOf(orderTo.getNum()));
orderEntity.setPayAmount(totalPrice);
orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
//保存订单
this.save(orderEntity);
//保存订单项信息
OrderItemEntity orderItem = new OrderItemEntity();
orderItem.setOrderSn(orderTo.getOrderSn());
orderItem.setRealAmount(totalPrice);
orderItem.setSkuQuantity(orderTo.getNum());
//保存商品的spu信息
R spuInfo = productFeignService.getSpuInfoBySkuId(orderTo.getSkuId());
SpuInfoVo spuInfoData = spuInfo.getData("data", new TypeReference<SpuInfoVo>() {
});
orderItem.setSpuId(spuInfoData.getId());
orderItem.setSpuName(spuInfoData.getSpuName());
orderItem.setSpuBrand(spuInfoData.getBrandName());
orderItem.setCategoryId(spuInfoData.getCatalogId());
//保存订单项数据
orderItemService.save(orderItem);
}