xiongzhu 4 years ago
parent
commit
813ae0d6eb

+ 8 - 3
src/main/java/com/izouma/nineth/listener/CreateOrderListener.java

@@ -2,6 +2,7 @@ package com.izouma.nineth.listener;
 
 import com.izouma.nineth.domain.Order;
 import com.izouma.nineth.event.CreateOrderEvent;
+import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.service.OrderService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 @Service
 @Slf4j
@@ -37,11 +39,14 @@ public class CreateOrderListener implements RocketMQListener<CreateOrderEvent> {
             map.put("success", true);
             map.put("data", order);
         } catch (Exception e) {
-            log.error("订单创建失败", e);
+            if (e instanceof BusinessException) {
+                log.error("订单创建失败 {}", ((BusinessException) e).getError());
+            } else {
+                log.error("订单创建失败", e);
+            }
             map.put("success", false);
             map.put("data", e.getMessage());
         }
-        redisTemplate.opsForValue().set("createOrder::" + event.getId(), map);
-
+        redisTemplate.boundValueOps("createOrder::" + event.getId()).set(map, 1, TimeUnit.DAYS);
     }
 }

+ 1 - 5
src/main/java/com/izouma/nineth/listener/OrderNotifyListener.java

@@ -23,10 +23,6 @@ public class OrderNotifyListener implements RocketMQListener<OrderNotifyEvent> {
 
     @Override
     public void onMessage(OrderNotifyEvent e) {
-        try {
-            orderService.notifyOrder(e.getOrderId(), e.getPayMethod(), e.getTransactionId());
-        } catch (Exception exception) {
-            log.error("处理订单回调出错 orderId: " + e.getOrderId(), exception);
-        }
+        orderService.notifyOrder(e.getOrderId(), e.getPayMethod(), e.getTransactionId());
     }
 }

+ 30 - 0
src/main/java/com/izouma/nineth/repo/BlindBoxItemRepo.java

@@ -16,4 +16,34 @@ public interface BlindBoxItemRepo extends JpaRepository<BlindBoxItem, Long>, Jpa
     void softDelete(Long id);
 
     List<BlindBoxItem> findByBlindBoxId(Long blindBoxId);
+
+    @Query("update BlindBoxItem b set b.stock = b.stock + ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void increaseStock(Long id, int num);
+
+    @Query("update BlindBoxItem b set b.stock = b.stock - ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void decreaseStock(Long id, int num);
+
+    @Query("update BlindBoxItem b set b.sale = b.sale + ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void increaseSale(Long id, int num);
+
+    @Query("update BlindBoxItem b set b.sale = b.sale - ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void decreaseSale(Long id, int num);
+
+    @Query("update BlindBoxItem b set b.stock = b.stock - ?2, b.sale = b.sale + ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void decreaseStockAndIncreaseSale(Long id, int num);
+
+    @Query("update BlindBoxItem b set b.stock = b.stock + ?2, b.sale = b.sale - ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void increaseStockAndDecreaseSale(Long id, int num);
 }

+ 25 - 0
src/main/java/com/izouma/nineth/repo/ErrorOrder.java

@@ -0,0 +1,25 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.BaseEntity;
+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;
+}

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

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

+ 20 - 29
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -29,6 +29,7 @@ import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.redis.core.BoundValueOperations;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.TaskScheduler;
 import org.springframework.stereotype.Service;
@@ -40,6 +41,7 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 @Service
@@ -332,9 +334,10 @@ public class CollectionService {
                 throw new BusinessException("盲盒抽卡失败");
             }
         }
-        winItem.setStock(winItem.getStock() - 1);
-        winItem.setSale(winItem.getSale() + 1);
-        blindBoxItemRepo.saveAndFlush(winItem);
+//        winItem.setStock(winItem.getStock() - 1);
+//        winItem.setSale(winItem.getSale() + 1);
+//        blindBoxItemRepo.saveAndFlush(winItem);
+        blindBoxItemRepo.decreaseStockAndIncreaseSale(winItem.getId(), 1);
         return winItem;
     }
 
@@ -355,44 +358,32 @@ public class CollectionService {
         collectionRepo.increaseTotal(id, number);
     }
 
-    public Long increaseStock(Long id, int number) {
-        if (redisTemplate.opsForValue().get("collectionStock::" + id) == null) {
-            redisTemplate.opsForValue().set("collectionStock::" + id,
-                    Optional.ofNullable(collectionRepo.getStock(id)).orElse(0));
+    public synchronized Long increaseStock(Long id, int number) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps("collectionStock::" + id);
+        if (ops.get() == null) {
+            ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id)).orElse(0), 7, TimeUnit.DAYS);
         }
-        Long stock = redisTemplate.opsForValue().increment("collectionStock::" + id, number);
+        Long stock = ops.increment(number);
         rocketMQTemplate.convertAndSend(generalProperties.getUpdateStockTopic(), id);
         return stock;
     }
 
-    public Long decreaseStock(Long id, int number) {
-        if (redisTemplate.opsForValue().get("collectionStock::" + id) == null) {
-            redisTemplate.opsForValue().set("collectionStock::" + id,
-                    Optional.ofNullable(collectionRepo.getStock(id)).orElse(0));
-        }
-        Long stock = redisTemplate.opsForValue().decrement("collectionStock::" + id, number);
-        rocketMQTemplate.convertAndSend(generalProperties.getUpdateStockTopic(), id);
-        return stock;
+    public synchronized Long decreaseStock(Long id, int number) {
+        return increaseStock(id, -number);
     }
 
-    public Long increaseSale(Long id, int number) {
-        if (redisTemplate.opsForValue().get("collectionSale::" + id) == null) {
-            redisTemplate.opsForValue().set("collectionSale::" + id,
-                    Optional.ofNullable(collectionRepo.getSale(id)).orElse(0));
+    public synchronized Long increaseSale(Long id, int number) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps("collectionSale::" + id);
+        if (ops.get() == null) {
+            ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id)).orElse(0), 7, TimeUnit.DAYS);
         }
-        Long sale = redisTemplate.opsForValue().increment("collectionSale::" + id, number);
+        Long sale = ops.increment(number);
         rocketMQTemplate.convertAndSend(generalProperties.getUpdateSaleTopic(), id);
         return sale;
     }
 
-    public Long decreaseSale(Long id, int number) {
-        if (redisTemplate.opsForValue().get("collectionSale::" + id) == null) {
-            redisTemplate.opsForValue().set("collectionSale::" + id,
-                    Optional.ofNullable(collectionRepo.getSale(id)).orElse(0));
-        }
-        Long sale = redisTemplate.opsForValue().decrement("collectionSale::" + id, number);
-        rocketMQTemplate.convertAndSend(generalProperties.getUpdateSaleTopic(), id);
-        return sale;
+    public synchronized Long decreaseSale(Long id, int number) {
+        return increaseStock(id, -number);
     }
 
     @Debounce(key = "#id", delay = 500)

+ 44 - 0
src/main/java/com/izouma/nineth/service/OrderCancelService.java

@@ -0,0 +1,44 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.config.GeneralProperties;
+import com.izouma.nineth.domain.Order;
+import com.izouma.nineth.enums.OrderStatus;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.OrderRepo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@ConditionalOnProperty(value = "general.notify-server", havingValue = "true")
+@Slf4j
+@AllArgsConstructor
+public class OrderCancelService {
+    private final GeneralProperties generalProperties;
+    private final OrderRepo         orderRepo;
+    private final OrderService      orderService;
+
+    @Scheduled(fixedRate = 30000)
+    public void batchCancel() {
+        if (generalProperties.isNotifyServer()) {
+            return;
+        }
+        List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
+                LocalDateTime.now().minusSeconds(210));
+        orders.parallelStream().forEach(o -> {
+            try {
+                Order order = orderRepo.findById(o.getId()).orElseThrow(new BusinessException("订单不存在"));
+                if (order.getStatus() == OrderStatus.NOT_PAID) {
+                    orderService.cancel(order);
+                }
+            } catch (Exception e) {
+                log.error("取消订单错误 " + o.getId(), e);
+            }
+        });
+    }
+}

+ 123 - 97
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -46,6 +46,7 @@ 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;
@@ -57,6 +58,7 @@ import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 @Service
 @AllArgsConstructor
@@ -85,6 +87,7 @@ public class OrderService {
     private RedisTemplate<String, Object> redisTemplate;
     private SnowflakeIdWorker             snowflakeIdWorker;
     private SmsService                    smsService;
+    private ErrorOrderRepo                errorOrderRepo;
 
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
@@ -471,68 +474,99 @@ public class OrderService {
     @Transactional
     public void notifyOrder(Long orderId, PayMethod payMethod, String transactionId) {
         log.info("订单回调 orderId: {}, payMethod: {}, transactionId: {}", orderId, payMethod, 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) {
-                log.info("开始盲盒抽卡 orderId: {}, collectionId: {}", orderId, collection.getId());
-                BlindBoxItem winItem = null;
-                try {
-                    winItem = collectionService.draw(collection.getId());
-                } catch (BusinessException e) {
-                }
-                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()));
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps("orderLock::" + orderId);
+        Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS);
+        if (!Boolean.TRUE.equals(flag)) {
+            log.info("订单回调失败 orderId: {} redis锁定", orderId);
+            errorOrderRepo.save(ErrorOrder.builder()
+                    .orderId(orderId)
+                    .transactionId(transactionId)
+                    .payMethod(payMethod)
+                    .errorMessage("redis锁定")
+                    .build());
+            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 {
-                        Map<String, Object> response = Refund.create(transactionId, refundParams);
-                    } catch (BaseAdaPayException e) {
-                        e.printStackTrace();
+                        winItem = collectionService.draw(collection.getId());
+                    } catch (BusinessException e) {
                     }
-                    orderRepo.save(order);
-                    return;
-                }
-                log.info("抽卡成功 orderId: {}, collectionId: {}, winCollectionId: {}", orderId, collection.getId(), winItem.getCollectionId());
-                order.setWinCollectionId(winItem.getCollectionId());
-                orderRepo.save(order);
-                assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
-                        winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
-            } 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);
-
-                    // 发送短信提醒用户转让成功
-                    if (asset != null && asset.getUserId() != null) {
-                        smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
+                    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);
+                        return;
                     }
-
-                } else {
+                    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(), "出售",
-                            collection.getTotal() > 1 ? collectionService.getNextNumber(order.getCollectionId()) : null);
+                    assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
+                            winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
+                } 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);
+
+                        // 发送短信提醒用户转让成功
+                        if (asset != null && asset.getUserId() != null) {
+                            smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
+                        }
+
+                    } 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());
                 }
+            } else {
+                throw new BusinessException("状态错误 " + order.getStatus());
             }
-            commission(order);
-            if (collection.getAssetId() == null) {
-                collectionService.increaseSale(order.getCollectionId(), order.getQty());
+        } catch (Exception e) {
+            ErrorOrder errorOrder = ErrorOrder.builder()
+                    .orderId(orderId)
+                    .transactionId(transactionId)
+                    .payMethod(payMethod)
+                    .build();
+            if (e instanceof BusinessException) {
+                log.error("订单回调出错 orderId: {} {}", orderId, ((BusinessException) e).getError());
+                errorOrder.setErrorMessage(((BusinessException) e).getError());
+            } else {
+                log.error("订单回调出错 orderId: " + orderId, e);
+                errorOrder.setErrorMessage(e.getMessage());
             }
-        } else {
-            log.info("订单回调状态错误 {} {}", orderId, order.getStatus());
+            errorOrderRepo.save(errorOrder);
+
         }
+        redisTemplate.delete("orderLock::" + orderId);
     }
 
     @EventListener
@@ -571,56 +605,48 @@ public class OrderService {
     }
 
     public void cancel(Order order) {
-        if (order.getStatus() != OrderStatus.NOT_PAID) {
-            throw new BusinessException("已支付订单无法取消");
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps("orderLock::" + order.getId());
+        Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS);
+        if (!Boolean.TRUE.equals(flag)) {
+            log.error("订单取消失败 {}, redis锁了", order.getId());
+            return;
         }
+        try {
+            if (order.getStatus() != OrderStatus.NOT_PAID) {
+                throw new BusinessException("已支付订单无法取消");
+            }
 
-        CollectionSource source = Optional.ofNullable(order.getSource()).orElseGet(() ->
-                collectionRepo.findById(order.getCollectionId()).map(Collection::getSource).orElse(null));
+            CollectionSource source = Optional.ofNullable(order.getSource()).orElseGet(() ->
+                    collectionRepo.findById(order.getCollectionId()).map(Collection::getSource).orElse(null));
 
-        if (source == CollectionSource.TRANSFER) {
-            Asset asset = assetRepo.findById(order.getAssetId()).orElse(null);
-            if (asset != null) {
-                log.info("set normal cancelOrder {}", order.getId());
-                asset.setStatus(AssetStatus.NORMAL);
-                assetRepo.save(asset);
+            if (source == CollectionSource.TRANSFER) {
+                Asset asset = assetRepo.findById(order.getAssetId()).orElse(null);
+                if (asset != null) {
+                    log.info("set normal cancelOrder {}", order.getId());
+                    asset.setStatus(AssetStatus.NORMAL);
+                    assetRepo.save(asset);
+                }
+                collectionRepo.setOnShelf(order.getCollectionId(), true);
             }
-            collectionRepo.setOnShelf(order.getCollectionId(), true);
-        }
-        collectionService.increaseStock(order.getCollectionId(), order.getQty());
+            collectionService.increaseStock(order.getCollectionId(), order.getQty());
 
-        order.setStatus(OrderStatus.CANCELLED);
-        order.setCancelTime(LocalDateTime.now());
-        orderRepo.save(order);
-
-        if (order.getCouponId() != null) {
-            userCouponRepo.findById(order.getCouponId()).ifPresent(coupon -> {
-                coupon.setUsed(false);
-                coupon.setUseTime(null);
-                userCouponRepo.save(coupon);
-            });
-        }
-        rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
-        log.info("取消订单{}", order.getId());
-    }
+            order.setStatus(OrderStatus.CANCELLED);
+            order.setCancelTime(LocalDateTime.now());
+            orderRepo.save(order);
 
-    @Scheduled(fixedRate = 30000)
-    public void batchCancel() {
-        if (generalProperties.isNotifyServer()) {
-            return;
-        }
-        if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
-            return;
-        }
-        List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
-                LocalDateTime.now().minusSeconds(210));
-        orders.parallelStream().forEach(o -> {
-            try {
-                cancel(o);
-            } catch (Exception e) {
-                log.error("取消订单错误 " + o.getId(), e);
+            if (order.getCouponId() != null) {
+                userCouponRepo.findById(order.getCouponId()).ifPresent(coupon -> {
+                    coupon.setUsed(false);
+                    coupon.setUseTime(null);
+                    userCouponRepo.save(coupon);
+                });
             }
-        });
+            rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
+            log.info("取消订单{}", order.getId());
+        } catch (Exception e) {
+            log.error("订单取消错误 orderId: " + order.getId(), e);
+        }
+        redisTemplate.delete("orderLock::" + order.getId());
     }
 
     public void refundCancelled(Order order) {