document
API test

秒杀的后续操作

POST

Description or Example

# 整体逻辑 > **seckill首先对请求进行 登录, 超买, 重复, 随机码, 信号量等判断, 若判断成功, 则发送消息, 给MQ微服务慢慢处理** > 首先, 构建订单数据并保存, 然后构建订单项数据并保存, 然后锁定库存, 再然后发送消息(自动取消订单的消息), 最后发送自动释放信号量的消息 # Bug修复 ## 为什么保存在MQ中的对象属性丢失了? > 因为调用远程微服务的时候, 没有指定`Post`请求, 这就会导致对象里面的信息丢失, 本质上是feign的原因 ## 订单项详情的构建细节 > 因为用户列表页面渲染的缘故, 必须要有SKU的相关信息, 因此, 我们在构建的时候, 既是获取不到, 也需要赋予空串, 避免JSON反序列化的时候, 缺失某些参数导致的视图渲染错误 ## 释放信号量的整体逻辑 > 首先判断订单是否存在, 如果订单不存在或订单状态为已取消, 都是要释放信号量 ```java @Bean public Queue orderSeckillSemaphoreDelayQueue() { Map<String, Object> arguments = new HashMap<>(); arguments.put("x-dead-letter-exchange", "order-event-exchange"); arguments.put("x-dead-letter-routing-key", "order-secKill-order"); arguments.put("x-message-ttl", 1800000); // 30分钟后释放信号量 return new Queue("order-seckill-semaphore-delay-queue", true, false, false, arguments); } @Bean public Binding orderSeckillSemaphore() { return new Binding("order-seckill-semaphore-delay-queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order-seckill-semaphore", null); } ``` # 核心代码 ## 主要逻辑 > 秒杀微服务主要负责对秒杀对象的合法性判断, 而MQ监听器则是创建对应的数据库数据和保存, 并发送消息, 自动取消和解锁等... ```java @Component @RabbitListener(queues = "order-secKill-order-queue") public class SecKillListener { @Autowired private MemberFeignService memberFeignService; @Autowired private OrderFeignService orderFeignService; @Autowired private WareFeignService wareFeignService; @Autowired private ThreadPoolExecutor threadPoolExecutor; @Autowired private OrderSender orderSender; @Autowired private RedissonClient redissonClient; @Autowired private StringRedisTemplate redisTemplate; /** * 主动释放信号量 * @param killId * @param message * @param channel * @throws IOException */ @RabbitHandler public void handleSemaphore(String killId, Message message, Channel channel) throws IOException { String[] killBody = killId.split("_"); String sessionId = killBody[0]; String orderSn = killBody[1]; String skuId = killBody[2]; // 首先判断订单状态 R info = orderFeignService.getOrderStatus(orderSn); if (!ifNoProblem(message, channel, info)) return; Integer status = info.getData(new TypeReference<Integer>(){}); if (status == null || OrderStatusEnum.CANCLED.getCode().equals(status)) { // 需要释放信号量 BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SecKillConstant.SEC_KILL_SKU_MAP_SKU_INFO); String json = operations.get(sessionId + "_" + skuId); SeckillSkuTO seckillSkuTO = JSON.parseObject(json, SeckillSkuTO.class); if (seckillSkuTO != null) { String randomToken = seckillSkuTO.getRandomToken(); RSemaphore semaphore = redissonClient.getSemaphore(SecKillConstant.SEC_KILL_RANDOM_CODE_SEMAPHORE + randomToken); semaphore.release(); } } } @RabbitHandler public void handelSecKillOrder(SecKillOrderTO secKillOrderTO, Message message, Channel channel) throws IOException { try { CompletableFuture<Void> task1 = CompletableFuture.supplyAsync(() -> { // 0. 查询用户的地址信息 List<MemberAddressBO> allAddresses = memberFeignService.getMemberAllAddressesWithOrder(secKillOrderTO.getMemberId()); MemberAddressBO memberAddressBO = new MemberAddressBO(); if (allAddresses != null && !allAddresses.isEmpty()) { memberAddressBO = allAddresses.stream().filter(address -> address.getDefaultStatus().equals(1)).collect(Collectors.toList()).get(0); } return memberAddressBO; }, threadPoolExecutor).thenApplyAsync(memberAddressBO -> { // 1. 构建订单 OrderTO orderTO = buildOrder(secKillOrderTO, memberAddressBO); orderFeignService.saveOrder(orderTO); // 保存订单 return orderTO; }, threadPoolExecutor).thenAcceptAsync(orderTO -> { // 2. 构建订单详情 OrderItemBO orderItemBO = buildOrderItem(secKillOrderTO, orderTO); orderFeignService.saveOrderItem(orderItemBO); // 保存订单详情 }, threadPoolExecutor); CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> { // 3. 锁定库存 WareLockTO.OrderItemInfo orderItemInfo = new WareLockTO.OrderItemInfo().setSkuId(secKillOrderTO.getSkuId()).setNum(secKillOrderTO.getSecKillCount()); WareLockTO wareLockTO = new WareLockTO().setOrderSn(secKillOrderTO.getOrderSn()).setOrderItemInfoList(Collections.singletonList(orderItemInfo)); wareFeignService.lockSkuStock(wareLockTO); }, threadPoolExecutor); CompletableFuture.allOf(task1, task2).join(); // 发送消息 -> 自动取消订单 orderSender.sendOrderMsg(secKillOrderTO.getOrderSn()); // 发送消息 -> 释放信号量 String killId = secKillOrderTO.getPromotionSessionId() + "_" + secKillOrderTO.getOrderSn() + "_" + secKillOrderTO.getSkuId(); orderSender.sendReleaseSemaphore(killId); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); }catch (Exception e) { channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); } } private OrderItemBO buildOrderItem(SecKillOrderTO secKillOrderTO, OrderTO orderTO) { OrderItemBO orderItemBO = new OrderItemBO(); return orderItemBO.setOrderId(orderTO.getId()) .setOrderSn(secKillOrderTO.getOrderSn()) .setSkuId(secKillOrderTO.getSkuId()) .setSkuPrice(secKillOrderTO.getSecKillPrice()) .setSkuQuantity(secKillOrderTO.getSecKillCount()) .setSkuName("") .setSkuPic("") .setSkuAttrsVals("") .setPromotionAmount(BigDecimal.ZERO) .setCouponAmount(BigDecimal.ZERO) .setIntegrationAmount(BigDecimal.ZERO) .setRealAmount(secKillOrderTO.getSecKillPrice()) .setGiftGrowth(0) .setGiftIntegration(0); } private OrderTO buildOrder(SecKillOrderTO secKillOrderTO, MemberAddressBO memberAddressBO) { // 1. 快速构建订单 OrderTO order = new OrderTO(); return order.setMemberId(secKillOrderTO.getMemberId()) .setOrderSn(secKillOrderTO.getOrderSn()) .setCreateTime(new Date()) .setMemberUsername(secKillOrderTO.getMemberId().toString()) // 没有用户名, 用用户的id替代 .setTotalAmount(secKillOrderTO.getSecKillPrice()) .setPayAmount(secKillOrderTO.getSecKillPrice()) // 应付金额 和 订单总额 都是秒杀价格, 自动忽略运费信息 .setFreightAmount(BigDecimal.ZERO) .setPromotionAmount(BigDecimal.ZERO) .setIntegrationAmount(BigDecimal.ZERO) .setCouponAmount(BigDecimal.ZERO) .setDiscountAmount(BigDecimal.ZERO) .setPayType(1).setSourceType(0) .setStatus(OrderStatusEnum.CREATE_NEW.getCode()) .setIntegration(0) .setGrowth(0) .setReceiverName(memberAddressBO.getName()) .setReceiverPhone(memberAddressBO.getPhone()) .setReceiverPostCode(memberAddressBO.getPostCode()) .setReceiverProvince(memberAddressBO.getProvince()) .setReceiverCity(memberAddressBO.getCity()) .setReceiverRegion(memberAddressBO.getRegion()) .setReceiverDetailAddress(memberAddressBO.getDetailAddress()); } private Boolean ifNoProblem(Message message, Channel channel, R info) throws IOException { if (info.getCode() != 0) { // 有问题, 需要重新入队 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); return false; } // true是没有问题 return true; } } ``` ```java @RequestMapping("/ware/wareinfo/lock/sku/stock") R lockSkuStock(@RequestBody WareLockTO wareLockTO); ``` ```java @RequestMapping("/order/order/save") R saveOrder(@RequestBody OrderTO order); @RequestMapping("/order/orderitem/save") R saveOrderItem(@RequestBody OrderItemBO orderItem); ``` ```java @FeignClient("bitmall-member") public interface MemberFeignService { @RequestMapping("/member/memberreceiveaddress/{memberId}") List<MemberAddressBO> getMemberAllAddressesWithOrder(@PathVariable Long memberId); } ```