一、秒杀(高并发)系统关注的问题
秒杀业务:
秒杀具有瞬间高并发的特点,针对这一特点,必须要做限流 + 异步 + 缓存(页面静态化)+ 独立部署
限流方式:
1. 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
nginx,限流,直接负载部分请求到错误的静态页面:令牌算法漏斗算法 网关限流,限流的过滤器 代码中使用分布式信号量 rabbitmq 限流(能者多劳: chanel.basicOos(1)),保证发挥所有服务器的性能。 秒杀流程:
1、先新增秒杀场次到 DB【后台系统新增】 2、再关联商品【后台系统关联】 3、定时任务将最近三天的场次+关联商品上传到 redis 中【定时 上架 3 天内的秒杀场次+商品】
二、创建秒杀服务 添加 gateway 路由转发
1 2 3 4 5 6 - id: coupon_route uri: lb://gulimall-coupon predicates: - Path=/api/coupon/** filters: - RewritePath=/api/(?<segment>.*),/$\{segment}
登录后台管理界面,添加秒杀场次
**例如: **添加 8 点场,对应表 sms_seckill_session【秒杀场次表】
秒杀场次关联商品
sms_seckill_sku_relation【关联表】 字段: promotion_id【活动 id】、promotion_session_id【活动场次 id】、sku_id、排序、价格、总量、每人限购数量
SeckillSkuRelationServiceImpl.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public PageUtils queryPage (Map<String, Object> params) { QueryWrapper<SeckillSkuRelationEntity> queryWrapper = new QueryWrapper <>(); String promotionSessionId = (String) params.get("promotionSessionId" ); if (!StringUtils.isEmpty(promotionSessionId)) { queryWrapper.eq("promotion_session_id" , promotionSessionId); } IPage<SeckillSkuRelationEntity> page = this .page( new Query <SeckillSkuRelationEntity>().getPage(params), queryWrapper ); return new PageUtils (page); }
创建秒杀 gulimall-seckill 微服务
redis、openFeign、spring boot devtools、spring web、lombok
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <dependencies > <dependency > <groupId > com.oy.gulimall</groupId > <artifactId > gulimall-common</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > <exclusions > <exclusion > <groupId > io.lettuce</groupId > <artifactId > lettuce-core</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</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 >
三、定时任务-QUARTZ 多种实现方式:
Timer、线程池、mq 的延迟队列、QUARTZ【搭配 cron 表达式使用】、spring 框架的定时任务,可以整合 QUARTZ(springboot 默认定时任务框架不是 QUARTZ,如果需要使用引入即可)
最终解决方案:使用异步任务 + 定时任务来完成定时任务不阻塞的功能
1、减轻 DB 压力,定时任务查询需要上架的秒杀商品上架到 redis 中,库存信息等
2、语法:秒 分 时 日 月 周 年 (spring 不支持年,所以可以不写)
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Format A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the allowed values, along with various combinations of the allowed special characters for that field. The fields are as follows: Field Name Mandatory Allowed Values Allowed Special Characters Seconds YES 0 -59 , - * / Minutes YES 0 -59 , - * / Hours YES 0 -23 , - * / Day of month YES 1 -31 , - * ? / L W Month YES 1 -12 or JAN-DEC , - * / Day of week YES 1 -7 or SUN-SAT , - * ? / L # Year NO empty, 1970 -2099 , - * / 特殊字符: ,:枚举; (cron="7,9,23****?" ):任意时刻的7 ,9 ,23 秒启动这个任务; -:范围: (cron="7-20****?" "):任意时刻的7-20秒之间,每秒启动一次 *:任意; 指定位置的任意时刻都可以 /:步长; (cron=" 7 /5 ****?"):第7秒启动,每5秒一次; (cron=" */5 ****?"):任意秒启动,每5秒一次; ? :(出现在日和周几的位置):为了防止日和周冲突,在周和日上如果要写通配符使用? (cron=" ***1 *?"):每月的1号,而且必须是周二然后启动这个任务; L:(出现在日和周的位置)”, last:最后一个 (cron=" ***?*3L "):每月的最后一个周二 W:Work Day:工作日 (cron=" ***W*?"):每个月的工作日触发 (cron=" ***LW*?"):每个月的最后一个工作日触发 #:第几个 (cron=" ***?*5 #2 "):每个月的 第2个周4
3、在线定时器 https://cron.qqe2.com/
4、Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Examples Here are some full examples: **Expression** **Meaning** 0 0 12 * * ? Fire at 12pm (noon) every day0 15 10 ? * * Fire at 10 :15am every day0 15 10 * * ? Fire at 10 :15am every day0 15 10 * * ? * Fire at 10 :15am every day0 15 10 * * ? 2005 Fire at 10 :15am every day during the year 2005 0 * 14 * * ? Fire every minute starting at 2pm and ending at 2 :59pm, every day0 0 /5 14 * * ? Fire every 5 minutes starting at 2pm and ending at 2 :55pm, every day0 0 /5 14 ,18 * * ? Fire every 5 minutes starting at 2pm and ending at 2 :55pm, AND fire every 5 minutes starting at 6pm and ending at 6 :55pm, every day0 0 -5 14 * * ? Fire every minute starting at 2pm and ending at 2 :05pm, every day0 10 ,44 14 ? 3 WED Fire at 2 :10pm and at 2 :44pm every Wednesday in the month of March.0 15 10 ? * MON-FRI Fire at 10 :15am every Monday, Tuesday, Wednesday, Thursday and Friday0 15 10 15 * ? Fire at 10 :15am on the 15th day of every month0 15 10 L * ? Fire at 10 :15am on the last day of every month0 15 10 L-2 * ? Fire at 10 :15am on the 2nd-to-last last day of every month0 15 10 ? * 6L Fire at 10 :15am on the last Friday of every month0 15 10 ? * 6L Fire at 10 :15am on the last Friday of every month0 15 10 ? * 6L 2002 -2005 Fire at 10 :15am on every last friday of every month during the years 2002 , 2003 , 2004 and 2005 0 15 10 ? * 6 #3 Fire at 10 :15am on the third Friday of every month0 0 12 1 /5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.0 11 11 11 11 ? Fire every November 11th at 11 :11am.
springboot 开启定时任务 Demo
解决:使用异步任务 + 定时任务来完成定时任务不阻塞的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 、加在类上 @Component @EnableScheduling 开启定时任务【spring 默认是使用自己的定时任务】 @EnableAsync :开启异步任务【定时任务不应该阻塞,需要异步执行(不加该注解是同步的,例如方法内部sleep会阻塞)】 解决办法:1 、自己异步执行【CompletableFuture.runAsync】 2 、使用spring的 定时任务线程池scheduling.pool.size: 5 3 、使用springboot的异步定时任务@EnableAsync 然后配置异步任务的属性 spring: task: execution: pool: core-size: 5 max-size: 50 然后给定时任务方法加上@Async 【这个注解就是异步执行,不一定是定时任务】
2、加载异步定时任务方法上
1 2 @Async 异步执行的方法标注@Scheduled(cron = "*/5 * * ? * 4")
3、编写任务
4、spring 注意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 1 )spring周一都周天就是1 -7 2 )没有年,只有6 个 @Slf4j @Component public class HelloScheduled { @Async @Scheduled(cron = "*/5 * * ? * 4") public void hello () { log.info("hello..." ); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
四、秒杀架构设计 4.1 秒杀架构图 项目独立部署,独立秒杀模块gulimall-seckill
使用定时任务每天三点上架最新秒杀商品,削减高峰期压力 秒杀链接加密,为秒杀商品添加唯一商品随机码,在开始秒杀时才暴露接口 库存预热,先从数据库中扣除一部分库存以redisson 信号量
的形式存储在 redis 中 队列削峰,秒杀成功后立即返回,然后以发送消息的形式创建订单 4.2 存储模型设计 秒杀场次存储的List
可以当做hash key
在SECKILL_CHARE_PREFIX
中获得对应的商品数据 1 2 3 4 5 6 7 8 9 10 11 12 13 private final String SESSION_CACHE_PREFIX = "seckill:sessions:" ;private final String SECKILL_CHARE_PREFIX = "seckill:skus" ;private final String SKU_STOCK_SEMAPHORE = "seckill:stock:" ;
存储后的效果
用来存储的 to
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Data public class SeckillSkuRedisTo { private Long id; private Long promotionId; private Long promotionSessionId; private Long skuId; private BigDecimal seckillPrice; private Integer seckillCount; private Integer seckillLimit; private Integer seckillSort; private SkuInfoVo skuInfoVo; private Long startTime; private Long endTime; private String randomCode; }
4.3 商品上架 4.3.1 定时上架 1 2 3 4 5 @EnableAsync @EnableScheduling @Configuration public class ScheduledConfig {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private final String upload_lock = "seckill:upload:lock" ;@Async @Scheduled(cron = "0 0 3 * * ?") public void uploadSeckillSkuLatest3Days () { RLock lock = redissonClient.getLock(upload_lock); try { lock.lock(10 , TimeUnit.SECONDS); secKillService.uploadSeckillSkuLatest3Days(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } @Override public void uploadSeckillSkuLatest3Days () { R r = couponFeignService.getSeckillSessionsIn3Days(); if (r.getCode() == 0 ) { List<SeckillSessionWithSkusVo> sessions = r.getData(new TypeReference <List<SeckillSessionWithSkusVo>>() { }); saveSecKillSession(sessions); saveSecKillSku(sessions); } }
4.3.2 获取最近三天的秒杀信息 获取最近三天的秒杀场次信息,再通过秒杀场次 id 查询对应的商品信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override public List<SeckillSessionEntity> getSeckillSessionsIn3Days () { QueryWrapper<SeckillSessionEntity> queryWrapper = new QueryWrapper <SeckillSessionEntity>() .between("start_time" , getStartTime(), getEndTime()); List<SeckillSessionEntity> seckillSessionEntities = this .list(queryWrapper); List<SeckillSessionEntity> list = seckillSessionEntities.stream().map(session -> { List<SeckillSkuRelationEntity> skuRelationEntities = seckillSkuRelationService.list(new QueryWrapper <SeckillSkuRelationEntity>().eq("promotion_session_id" , session.getId())); session.setRelations(skuRelationEntities); return session; }).collect(Collectors.toList()); return list; } private String getStartTime () { LocalDate now = LocalDate.now(); LocalDateTime time = now.atTime(LocalTime.MIN); String format = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" )); return format; } private String getEndTime () { LocalDate now = LocalDate.now(); LocalDateTime time = now.plusDays(2 ).atTime(LocalTime.MAX); String format = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" )); return format; }
4.3.3 在 redis 中保存秒杀场次信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void saveSessionInfos (List<SeckillSessionWithSkusVo> sessions) { if (sessions != null && sessions.size() > 0 ){ sessions.stream().forEach(session -> { long startTime = session.getStartTime().getTime(); long endTime = session.getEndTime().getTime(); String key = SESSION_CACHE_PREFIX + startTime + "_" + endTime; Boolean hasKey = redisTemplate.hasKey(key); if (!hasKey) { List<String> skuIds = session.getRelationSkus().stream() .map(item -> item.getPromotionSessionId() + "-" + item.getSkuId().toString()).collect(Collectors.toList()); redisTemplate.opsForList().leftPushAll(key,skuIds); } }); } }
4.3.4 在 redis 中保存秒杀商品信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private void saveSessionSkuInfo (List<SeckillSessionWithSkusVo> sessions) { if (sessions != null && sessions.size() > 0 ){ sessions.stream().forEach(session -> { BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX); session.getRelationSkus().stream().forEach(seckillSkuVo -> { String token = UUID.randomUUID().toString().replace("-" , "" ); String redisKey = seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString(); if (!operations.hasKey(redisKey)) { SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo (); Long skuId = seckillSkuVo.getSkuId(); R info = productFeignService.info(skuId); if (info.getCode() == 0 ) { SkuInfoVo skuInfo = info.getData("skuInfo" ,new TypeReference <SkuInfoVo>(){}); redisTo.setSkuInfoVo(skuInfo); } BeanUtils.copyProperties(seckillSkuVo,redisTo); redisTo.setStartTime(session.getStartTime().getTime()); redisTo.setEndTime(session.getEndTime().getTime()); redisTo.setRandomCode(token); String seckillValue = JSON.toJSONString(redisTo); operations.put(seckillSkuVo.getPromotionSessionId().toString() + "-" + seckillSkuVo.getSkuId().toString(),seckillValue); RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token); semaphore.trySetPermits(seckillSkuVo.getSeckillCount()); } }); }); } }
4.4 首页展示秒杀活动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Controller public class SeckillController { @Autowired private SeckillService seckillService; @GetMapping(value = "/getCurrentSeckillSkus") @ResponseBody public R getCurrentSeckillSkus () { List<SeckillSkuRedisTo> vos = seckillService.getCurrentSeckillSkus(); return R.ok().setData(vos); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public List<SeckillSkuRedisTo> getCurrentSeckillSkus () { Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*" ); if (keys != null && keys.size() > 0 ){ long currentTime = System.currentTimeMillis(); for (String key : keys) { String replace = key.replace(SESSION_CACHE_PREFIX, "" ); String[] split = replace.split("_" ); long startTime = Long.parseLong(split[0 ]); long endTime = Long.parseLong(split[1 ]); if (currentTime > startTime && currentTime < endTime){ List<String> range = redisTemplate.opsForList().range(key, -100 , 100 ); BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX); List<SeckillSkuRedisTo> collect = range.stream().map(s -> { String json = ops.get(s); SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class); return redisTo; }).collect(Collectors.toList()); return collect; } } } return null ; }
首页获取并拼装数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <div class="swiper-slide" > <!-- 动态拼装秒杀商品信息 --> <ul id="seckillSkuContent" ></ul> </div> <script type="text/javascript" > $.get("http://seckill.gulimall.com/getCurrentSeckillSkus" , function (res) { if (res.data.length > 0 ) { res.data.forEach(function (item) { $("<li onclick='toDetail(" + item.skuId + ")'></li>" ).append($("<img style='width: 130px; height: 130px' src='" + item.skuInfoVo.skuDefaultImg + "' />" )) .append($("<p>" +item.skuInfoVo.skuTitle+"</p>" )) .append($("<span>" + item.seckillPrice + "</span>" )) .append($("<s>" + item.skuInfoVo.price + "</s>" )) .appendTo("#seckillSkuContent" ); }) } }) function toDetail (skuId) { location.href = "http://item.gulimall.com/" + skuId + ".html" ; } </script>
首页展示效果
4.5 获取当前商品的秒杀信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @ResponseBody @GetMapping(value = "/getSeckillSkuInfo/{skuId}") public R getSeckillSkuInfo (@PathVariable("skuId") Long skuId) { SeckillSkuRedisTo to = secKillService.getSeckillSkuInfo(skuId); return R.ok().setData(to); } @Override public SeckillSkuRedisTo getSeckillSkuInfo (Long skuId) { BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX); Set<String> keys = ops.keys(); for (String key : keys) { if (Pattern.matches("\\d-" + skuId,key)) { String v = ops.get(key); SeckillSkuRedisTo redisTo = JSON.parseObject(v, SeckillSkuRedisTo.class); if (redisTo!=null ){ long current = System.currentTimeMillis(); if (redisTo.getStartTime() < current && redisTo.getEndTime() > current) { return redisTo; } redisTo.setRandomCode(null ); return redisTo; } } } return null ; }
在查询商品详情页的接口中查询秒杀对应信息
更改商品详情页的显示效果
1 2 3 4 5 6 7 8 9 10 11 12 13 <li style ="color: red" th:if ="${item.seckillSkuVo != null}" > <span th:if ="${#dates.createNow().getTime() < item.seckillSkuVo.startTime}" > 商品将会在[[${#dates.format(new java.util.Date(item.seckillSkuVo.startTime),"yyyy-MM-dd HH:mm:ss")}]]进行秒杀 </span > <span th:if ="${#dates.createNow().getTime() >= item.seckillSkuVo.startTime && #dates.createNow().getTime() <= item.seckillSkuVo.endTime}" > 秒杀价 [[${#numbers.formatDecimal(item.seckillSkuVo.seckillPrice,1,2)}]] </span > </li >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <div class ="box-btns-two" th:if ="${item.seckillSkuVo == null }" > <a class ="addToCart" href ="http://cart.gulimall.com/addToCart" th:attr ="skuId=${item.info.skuId}" > 加入购物车 </a > </div > <div class ="box-btns-two" th:if ="${item.seckillSkuVo != null && (#dates.createNow().getTime() >= item.seckillSkuVo.startTime && #dates.createNow().getTime() <= item.seckillSkuVo.endTime)}" > <a class ="seckill" href ="#" th:attr ="skuId=${item.info.skuId},sessionId=${item.seckillSkuVo.promotionSessionId},code=${item.seckillSkuVo.randomCode}" > 立即抢购 </a > </div >
五、秒杀 5.1 秒杀接口 点击立即抢购,会发送请求 秒杀请求会对请求校验时效、商品随机码、当前用户是否已经抢购过当前商品、库存和购买量
,通过校验的则秒杀成功,发送消息创建订单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @GetMapping("/kill") public String kill (@RequestParam("killId") String killId, @RequestParam("key") String key, @RequestParam("num") Integer num, Model model) { String orderSn= null ; try { orderSn = secKillService.kill(killId, key, num); model.addAttribute("orderSn" , orderSn); } catch (InterruptedException e) { e.printStackTrace(); } return "success" ; } @Override public String kill (String killId, String key, Integer num) throws InterruptedException { BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX); String json = ops.get(killId); String orderSn = null ; if (!StringUtils.isEmpty(json)){ SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class); long current = System.currentTimeMillis(); if (current >= redisTo.getStartTime() && current <= redisTo.getEndTime()) { String redisKey = redisTo.getPromotionSessionId() + "-" + redisTo.getSkuId(); if (redisKey.equals(killId) && redisTo.getRandomCode().equals(key)) { MemberResponseVo memberResponseVo = LoginInterceptor.loginUser.get(); long ttl = redisTo.getEndTime() - System.currentTimeMillis(); Boolean occupy = redisTemplate.opsForValue().setIfAbsent(memberResponseVo.getId()+"-" +redisTo.getSkuId(), num.toString(), ttl, TimeUnit.MILLISECONDS); if (occupy){ if (num <= redisTo.getSeckillLimit()) { RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + redisTo.getRandomCode()); boolean acquire = semaphore.tryAcquire(num,100 ,TimeUnit.MILLISECONDS); if (acquire) { orderSn = IdWorker.getTimeId(); SeckillOrderTo orderTo = new SeckillOrderTo (); orderTo.setMemberId(memberResponseVo.getId()); orderTo.setNum(num); orderTo.setOrderSn(orderSn); orderTo.setPromotionSessionId(redisTo.getPromotionSessionId()); orderTo.setSeckillPrice(redisTo.getSeckillPrice()); orderTo.setSkuId(redisTo.getSkuId()); rabbitTemplate.convertAndSend("order-event-exchange" , "order.seckill.order" , orderTo); } } } } } return orderSn; }
5.2 创建订单 发送消息
1 2 rabbitTemplate.convertAndSend("order-event-exchange" , "order.seckill.order" , orderTo);
创建秒杀所需队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Bean public Queue orderSecKillOrrderQueue () { Queue queue = new Queue ("order.seckill.order.queue" , true , false , false ); return queue; } @Bean public Binding orderSecKillOrrderQueueBinding () { Binding binding = new Binding ( "order.seckill.order.queue" , Binding.DestinationType.QUEUE, "order-event-exchange" , "order.seckill.order" , null ); return binding; }
监听队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component @RabbitListener(queues = "order.seckill.order.queue") public class SeckillOrderListener { @Autowired private OrderService orderService; @RabbitHandler public void createOrder (SeckillOrderTo orderTo, Message message, Channel channel) throws IOException { System.out.println("***********接收到秒杀消息" ); long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { orderService.createSeckillOrder(orderTo); channel.basicAck(deliveryTag, false ); } catch (Exception e) { channel.basicReject(deliveryTag,true ); } } }
创建订单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Transactional @Override public void createSeckillOrder (SeckillOrderTo orderTo) { MemberResponseVo memberResponseVo = LoginInterceptor.loginUser.get(); OrderEntity orderEntity = new OrderEntity (); orderEntity.setOrderSn(orderTo.getOrderSn()); orderEntity.setMemberId(orderTo.getMemberId()); orderEntity.setMemberUsername(memberResponseVo.getUsername()); orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode()); orderEntity.setCreateTime(new Date ()); orderEntity.setPayAmount(orderTo.getSeckillPrice().multiply(new BigDecimal (orderTo.getNum()))); this .save(orderEntity); R r = productFeignService.info(orderTo.getSkuId()); if (r.getCode() == 0 ) { SeckillSkuInfoVo skuInfo = r.getData("skuInfo" , new TypeReference <SeckillSkuInfoVo>() { }); OrderItemEntity orderItemEntity = new OrderItemEntity (); orderItemEntity.setOrderSn(orderTo.getOrderSn()); orderItemEntity.setSpuId(skuInfo.getSpuId()); orderItemEntity.setCategoryId(skuInfo.getCatalogId()); orderItemEntity.setSkuId(skuInfo.getSkuId()); orderItemEntity.setSkuName(skuInfo.getSkuName()); orderItemEntity.setSkuPic(skuInfo.getSkuDefaultImg()); orderItemEntity.setSkuPrice(skuInfo.getPrice()); orderItemEntity.setSkuQuantity(orderTo.getNum()); orderItemService.save(orderItemEntity); } }
页面跳转效果