licailing 3 年之前
父節點
當前提交
d89fcfb26d

+ 26 - 0
src/main/java/com/izouma/nineth/domain/ErrorOrder.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.domain;
+
+import com.izouma.nineth.enums.PayMethod;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ErrorOrder extends BaseEntity {
+    private Long orderId;
+
+    private String errorMessage;
+
+    private String transactionId;
+
+    private PayMethod payMethod;
+
+    private String type;
+}

+ 3 - 0
src/main/java/com/izouma/nineth/repo/CollectionRepo.java

@@ -114,4 +114,7 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     @Transactional
     @Modifying
     int updateStock(Long id, int stock);
+
+    @Query("select c.sale from Collection c where c.id = ?1")
+    Integer getSale(Long id);
 }

+ 7 - 0
src/main/java/com/izouma/nineth/repo/ErrorOrderRepo.java

@@ -0,0 +1,7 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.ErrorOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ErrorOrderRepo extends JpaRepository<ErrorOrder, Long> {
+}

+ 12 - 0
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -349,6 +349,18 @@ public class CollectionService {
         collection.setTotal(collection.getTotal() + number);
     }
 
+    public synchronized Long increaseSale(Long id, int number) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_SALE + id);
+        if (ops.get() == null) {
+            Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getSale(id))
+                    .orElse(0), 7, TimeUnit.DAYS);
+            log.info("创建redis销量:{}", success);
+        }
+        Long sale = ops.increment(number);
+        rocketMQTemplate.convertAndSend(generalProperties.getUpdateSaleTopic(), id);
+        return sale;
+    }
+
     public synchronized Long increaseStock(Long id, int number) {
         BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
         if (ops.get() == null) {

+ 114 - 36
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -15,16 +15,15 @@ import com.github.binarywang.wxpay.service.WxPayService;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.huifu.adapay.model.AdapayCommon;
 import com.huifu.adapay.model.Payment;
-import com.izouma.nineth.config.AdapayProperties;
-import com.izouma.nineth.config.AlipayProperties;
-import com.izouma.nineth.config.GeneralProperties;
-import com.izouma.nineth.config.WxPayProperties;
+import com.huifu.adapay.model.Refund;
+import com.izouma.nineth.config.*;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.event.CreateOrderEvent;
+import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.event.TransferAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
@@ -35,6 +34,7 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.EncoderException;
 import org.apache.commons.codec.net.URLCodec;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.client.producer.SendResult;
@@ -42,6 +42,8 @@ import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.context.event.EventListener;
 import org.springframework.core.env.Environment;
 import org.springframework.data.domain.Page;
+import org.springframework.data.redis.core.BoundValueOperations;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.ui.Model;
@@ -52,6 +54,7 @@ import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 @Service
 @AllArgsConstructor
@@ -77,6 +80,8 @@ public class OrderService {
     private GeneralProperties             generalProperties;
     private SnowflakeIdWorker             snowflakeIdWorker;
     private RocketMQTemplate              rocketMQTemplate;
+    private RedisTemplate<String, Object> redisTemplate;
+    private ErrorOrderRepo                errorOrderRepo;
 
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
@@ -203,7 +208,7 @@ public class OrderService {
             JSONObject bizContent = new JSONObject();
             bizContent.put("notifyUrl", alipayProperties.getNotifyUrl());
             bizContent.put("returnUrl", alipayProperties.getReturnUrl());
-            bizContent.put("out_trade_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
+            bizContent.put("out_trade_no", String.valueOf(snowflakeIdWorker.nextId()));
             bizContent.put("total_amount", order.getTotalPrice().stripTrailingZeros().toPlainString());
             bizContent.put("disable_pay_channels", "pcredit,creditCard");
             if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
@@ -240,7 +245,7 @@ public class OrderService {
 
         WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
         request.setBody(order.getName());
-        request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
+        request.setOutTradeNo(String.valueOf(snowflakeIdWorker.nextId()));
         request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
         if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
             // 测试环境设为1分
@@ -284,7 +289,7 @@ public class OrderService {
         }
 
         Map<String, Object> paymentParams = new HashMap<>();
-        paymentParams.put("order_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
+        paymentParams.put("order_no", String.valueOf(snowflakeIdWorker.nextId()));
         paymentParams.put("pay_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP).toPlainString());
         paymentParams.put("app_id", adapayProperties.getAppId());
         paymentParams.put("pay_channel", payChannel);
@@ -389,39 +394,99 @@ public class OrderService {
         return restAmount.subtract(divAmount);
     }
 
-    @Transactional
     public void notifyOrder(Long orderId, PayMethod payMethod, String transactionId) {
-        Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
-        Collection collection = collectionRepo.findById(order.getCollectionId())
-                .orElseThrow(new BusinessException("藏品不存在"));
-        User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
-        if (order.getStatus() == OrderStatus.NOT_PAID) {
-            order.setStatus(OrderStatus.PROCESSING);
-            order.setPayTime(LocalDateTime.now());
-            order.setTransactionId(transactionId);
-            order.setPayMethod(payMethod);
-            if (order.getType() == CollectionType.BLIND_BOX) {
-                BlindBoxItem winItem = collectionService.draw(collection.getId());
-                order.setWinCollectionId(winItem.getCollectionId());
-                orderRepo.save(order);
-                assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
-                        collectionService.getNextNumber(winItem.getCollectionId()));
-                addSales(winItem.getMinterId());
-            } else {
-                if (collection.getSource() == CollectionSource.TRANSFER) {
-                    Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
-                    assetService.transfer(asset, order.getPrice(), user, "转让", order.getId());
-                    collectionRepo.delete(collection);
-                } else {
+        log.info("订单回调 orderId: {}, payMethod: {}, transactionId: {}", orderId, payMethod, transactionId);
+
+        // 取消订单与订单回调不能同时进行,需要抢锁
+        if (!getOrderLock(orderId)) {
+            log.info("订单回调失败 orderId: {} redis锁定, 重新发送到队列", orderId);
+            rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
+                    new OrderNotifyEvent(orderId, payMethod, transactionId, System.currentTimeMillis()));
+            return;
+        }
+
+        try {
+            Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
+            Collection collection = collectionRepo.findById(order.getCollectionId())
+                    .orElseThrow(new BusinessException("藏品不存在"));
+            User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
+            if (order.getStatus() == OrderStatus.NOT_PAID) {
+                order.setStatus(OrderStatus.PROCESSING);
+                order.setPayTime(LocalDateTime.now());
+                order.setTransactionId(transactionId);
+                order.setPayMethod(payMethod);
+                if (order.getType() == CollectionType.BLIND_BOX) {
+                    log.info("开始盲盒抽卡 orderId: {}, collectionId: {}", orderId, collection.getId());
+                    BlindBoxItem winItem = null;
+                    try {
+                        winItem = collectionService.draw(collection.getId());
+                    } catch (BusinessException ignored) {
+                    }
+                    if (winItem == null) {
+                        log.info("抽卡失败退款 orderId: {}", orderId);
+                        order.setStatus(OrderStatus.CANCELLED);
+                        order.setCancelTime(LocalDateTime.now());
+
+                        Map<String, Object> refundParams = new HashMap<>();
+                        refundParams.put("refund_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP)
+                                .toPlainString());
+                        refundParams.put("refund_order_no", String.valueOf(snowflakeIdWorker.nextId()));
+                        try {
+                            Map<String, Object> response = Refund.create(transactionId, refundParams);
+                        } catch (BaseAdaPayException e) {
+                            e.printStackTrace();
+                        }
+                        orderRepo.save(order);
+                        throw new BusinessException("抽卡失败, 已退款 " + orderId);
+                    }
+                    log.info("抽卡成功 orderId: {}, collectionId: {}, winCollectionId: {}", orderId, collection.getId(), winItem.getCollectionId());
+                    order.setWinCollectionId(winItem.getCollectionId());
                     orderRepo.save(order);
-                    assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售",
-                            collectionService.getNextNumber(order.getCollectionId()));
+                    assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
+                            winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
+                } else {
+                    if (collection.getSource() == CollectionSource.TRANSFER) {
+                        orderRepo.save(order);
+                        Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
+                        assetService.transfer(asset, order.getPrice(), user, "转让", order.getId());
+                        List<Long> collectionIds = collectionRepo.findAllByAssetId(collection.getAssetId());
+                        if (CollectionUtils.isNotEmpty(collectionIds)) {
+                            log.info("删除collection {}", collectionIds);
+                            collectionRepo.deleteAllByIdIn(collectionIds);
+                        } else {
+                            log.info("删除collection {}", collection.getId());
+                            collectionRepo.delete(collection);
+                        }
+
+                    } else {
+                        orderRepo.save(order);
+                        assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售",
+                                collection.getTotal() > 1 ? collectionService.getNextNumber(order.getCollectionId()) : null);
+                    }
+                }
+                commission(order);
+                if (collection.getAssetId() == null) {
+                    collectionService.increaseSale(order.getCollectionId(), order.getQty());
                 }
-                addSales(collection.getMinterId());
+            } else {
+                throw new BusinessException("状态错误 " + order.getStatus());
             }
-            commission(order);
-        } else if (order.getStatus() == OrderStatus.CANCELLED) {
+        } catch (Exception e) {
+            ErrorOrder errorOrder = ErrorOrder.builder()
+                    .orderId(orderId)
+                    .transactionId(transactionId)
+                    .payMethod(payMethod)
+                    .build();
+            if (e instanceof BusinessException) {
+                log.error("订单回调出错 orderId: {} {}", orderId, e.getMessage());
+            } else {
+                log.error("订单回调出错 orderId: " + orderId, e);
+            }
+            errorOrder.setErrorMessage(e.getMessage());
+            errorOrderRepo.save(errorOrder);
+
         }
+        releaseOrderLock(orderId);
     }
 
     @EventListener
@@ -573,7 +638,20 @@ public class OrderService {
         request.setTransactionId(order.getTransactionId());
         request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
         request.setRefundFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
-        request.setOutRefundNo(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
+        request.setOutRefundNo(String.valueOf(snowflakeIdWorker.nextId()));
         wxPayService.refund(request);
     }
+
+    // 获取订单锁,有效时间1小时
+    public boolean getOrderLock(Long orderId) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.ORDER_LOCK + orderId);
+        Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.HOURS);
+        return Boolean.TRUE.equals(flag);
+    }
+
+    // 释放订单锁
+    public void releaseOrderLock(Long orderId) {
+        redisTemplate.delete(RedisKeys.ORDER_LOCK + orderId);
+    }
+
 }