xiongzhu 4 anni fa
parent
commit
b23737b42a

+ 2 - 0
src/main/java/com/izouma/nineth/config/GeneralProperties.java

@@ -24,4 +24,6 @@ public class GeneralProperties {
     private boolean notifyServer;
     private String  updateActivityStockGroup;
     private String  updateActivityStockTopic;
+    private int     dataCenterId;
+    private int     workerId;
 }

+ 21 - 0
src/main/java/com/izouma/nineth/config/RedisKeys.java

@@ -0,0 +1,21 @@
+package com.izouma.nineth.config;
+
+public class RedisKeys {
+    public static final String COLLECTION = "collection::";
+
+    public static final String CREATE_ORDER = "createOrder::";
+
+    public static final String COLLECTION_STOCK = "collectionStock::";
+
+    public static final String COLLECTION_SALE = "collectionSale::";
+
+    public static final String PAY_RECORD = "payRecord::";
+
+    public static final String ORDER_LOCK = "orderLock::";
+
+    public static final String MINT_ACTIVITY_STOCK = "mintActivityStock::";
+
+    public static final String MINT_ORDER_LOCK = "mintOrderLock::";
+
+    public static final String ACTIVITY_PAY_RECORD = "activityPayRecord::";
+}

+ 5 - 2
src/main/java/com/izouma/nineth/config/SnowflakeIdWorkerConfig.java

@@ -1,13 +1,16 @@
 package com.izouma.nineth.config;
 
 import com.izouma.nineth.utils.SnowflakeIdWorker;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
+@Slf4j
 public class SnowflakeIdWorkerConfig {
     @Bean
-    public SnowflakeIdWorker snowflakeIdWorker() {
-        return new SnowflakeIdWorker(0, 0);
+    public SnowflakeIdWorker snowflakeIdWorker(GeneralProperties generalProperties) {
+        log.info("init snowflakeIdWorker worker={} dataCenter={}", generalProperties.getWorkerId(), generalProperties.getDataCenterId());
+        return new SnowflakeIdWorker(generalProperties.getWorkerId(), generalProperties.getDataCenterId());
     }
 }

+ 89 - 0
src/main/java/com/izouma/nineth/domain/BaseEntityNoID.java

@@ -0,0 +1,89 @@
+package com.izouma.nineth.domain;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.hibernate.envers.Audited;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+
+@MappedSuperclass
+@Audited
+@EntityListeners(AuditingEntityListener.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
+public abstract class BaseEntityNoID {
+    @ExcelIgnore
+    @JsonIgnore
+    @CreatedBy
+    private String createdBy;
+
+    @ExcelProperty("创建时间")
+    @JsonIgnore
+    @CreatedDate
+    private LocalDateTime createdAt;
+
+    @ExcelIgnore
+    @JsonIgnore
+    @LastModifiedBy
+    private String modifiedBy;
+
+    @ExcelIgnore
+    @JsonIgnore
+    @LastModifiedDate
+    private LocalDateTime modifiedAt;
+
+    @ExcelIgnore
+    private boolean del;
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    @JsonProperty("createdAt")
+    public LocalDateTime getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(LocalDateTime createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public String getModifiedBy() {
+        return modifiedBy;
+    }
+
+    public void setModifiedBy(String modifiedBy) {
+        this.modifiedBy = modifiedBy;
+    }
+
+    public LocalDateTime getModifiedAt() {
+        return modifiedAt;
+    }
+
+    public void setModifiedAt(LocalDateTime modifiedAt) {
+        this.modifiedAt = modifiedAt;
+    }
+
+    public boolean isDel() {
+        return del;
+    }
+
+    public void setDel(boolean del) {
+        this.del = del;
+    }
+}

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

@@ -0,0 +1,24 @@
+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;
+}

+ 6 - 1
src/main/java/com/izouma/nineth/domain/Order.java

@@ -28,7 +28,12 @@ import java.util.List;
 @NoArgsConstructor
 @Builder
 @ApiModel("订单")
-public class Order extends BaseEntity {
+public class Order extends BaseEntityNoID {
+
+    @Id
+//    @GenericGenerator(name = "custom-id", strategy = "com.izouma.nineth.utils.SnowflakeIdGenerator")
+//    @GeneratedValue(strategy = GenerationType.AUTO, generator = "custom-id")
+    private Long id;
 
     @ApiModelProperty("用户ID")
     @Searchable

+ 28 - 0
src/main/java/com/izouma/nineth/event/CreateOrderEvent.java

@@ -0,0 +1,28 @@
+package com.izouma.nineth.event;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CreateOrderEvent implements Serializable {
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    id;
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    userId;
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    collectionId;
+    private int     qty;
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    addressId;
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    userCouponId;
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long    invitor;
+}

+ 18 - 0
src/main/java/com/izouma/nineth/event/OrderNotifyEvent.java

@@ -0,0 +1,18 @@
+package com.izouma.nineth.event;
+
+import com.izouma.nineth.enums.PayMethod;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OrderNotifyEvent implements Serializable {
+    private Long      orderId;
+    private PayMethod payMethod;
+    private String    transactionId;
+    private long      time;
+}

+ 52 - 0
src/main/java/com/izouma/nineth/listener/CreateOrderListener.java

@@ -0,0 +1,52 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.config.RedisKeys;
+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;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+@RocketMQMessageListener(
+        consumerGroup = "${general.create-order-group}",
+        topic = "${general.create-order-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+//@ConditionalOnProperty(value = "general.notify-server", havingValue = "false", matchIfMissing = true)
+public class CreateOrderListener implements RocketMQListener<CreateOrderEvent> {
+    private OrderService                  orderService;
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Override
+    public void onMessage(CreateOrderEvent event) {
+        log.info("收到订单创建消息: {}", event.getId());
+        Map<String, Object> map = new HashMap<>();
+        try {
+            Order order = orderService.create(event.getUserId(), event.getCollectionId(), event.getQty(),
+                    event.getAddressId(), event.getUserCouponId(), event.getInvitor(), event.getId());
+            map.put("success", true);
+            map.put("data", order);
+        } catch (Exception e) {
+            if (e instanceof BusinessException) {
+                log.error("订单创建失败 {}", e.getMessage());
+            } else {
+                log.error("订单创建失败", e);
+            }
+            map.put("success", false);
+            map.put("data", e.getMessage());
+        }
+        redisTemplate.boundValueOps(RedisKeys.CREATE_ORDER + event.getId()).set(map, 1, TimeUnit.DAYS);
+    }
+}

+ 27 - 0
src/main/java/com/izouma/nineth/listener/OrderNotifyListener.java

@@ -0,0 +1,27 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.event.OrderNotifyEvent;
+import com.izouma.nineth.service.OrderService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+@RocketMQMessageListener(
+        consumerGroup = "${general.order-notify-group}",
+        topic = "${general.order-notify-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+//@ConditionalOnProperty(value = "general.notify-server", havingValue = "true")
+public class OrderNotifyListener implements RocketMQListener<OrderNotifyEvent> {
+    private OrderService orderService;
+
+    @Override
+    public void onMessage(OrderNotifyEvent e) {
+        orderService.notifyOrder(e.getOrderId(), e.getPayMethod(), e.getTransactionId());
+    }
+}

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

@@ -16,4 +16,9 @@ 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, b.sale = b.sale + ?2 where b.id = ?1")
+    @Modifying
+    @Transactional
+    void decreaseStockAndIncreaseSale(Long id, int num);
 }

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

@@ -108,4 +108,10 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     void softDeleteByIdIn(java.util.Collection<Long> id);
 
     void deleteAllByIdIn(java.util.Collection<Long> id);
+
+    @Query("select c.stock from Collection c where c.id = ?1")
+    Integer getStock(Long id);
+
+    @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> {
+}

+ 56 - 14
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -1,6 +1,8 @@
 package com.izouma.nineth.service;
 
 import com.alibaba.fastjson.JSON;
+import com.izouma.nineth.config.GeneralProperties;
+import com.izouma.nineth.config.RedisKeys;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.CollectionDTO;
@@ -20,14 +22,16 @@ import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.commons.lang3.Range;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
 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.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
@@ -37,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;
 
 @Slf4j
@@ -44,14 +49,17 @@ import java.util.stream.Collectors;
 @AllArgsConstructor
 public class CollectionService {
 
-    private CollectionRepo   collectionRepo;
-    private LikeRepo         likeRepo;
-    private BlindBoxItemRepo blindBoxItemRepo;
-    private AppointmentRepo  appointmentRepo;
-    private UserRepo         userRepo;
-    private TaskScheduler    taskScheduler;
-    private CacheService     cacheService;
-    private AssetRepo        assetRepo;
+    private CollectionRepo                collectionRepo;
+    private LikeRepo                      likeRepo;
+    private BlindBoxItemRepo              blindBoxItemRepo;
+    private AppointmentRepo               appointmentRepo;
+    private UserRepo                      userRepo;
+    private TaskScheduler                 taskScheduler;
+    private CacheService                  cacheService;
+    private AssetRepo                     assetRepo;
+    private RedisTemplate<String, Object> redisTemplate;
+    private RocketMQTemplate              rocketMQTemplate;
+    private GeneralProperties             generalProperties;
 
     private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();
 
@@ -261,7 +269,7 @@ public class CollectionService {
                 .build());
     }
 
-    public BlindBoxItem draw(Long collectionId) {
+    public synchronized BlindBoxItem draw(Long collectionId) {
         List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(collectionId);
 
         Map<BlindBoxItem, Range<Integer>> randomRange = new HashMap<>();
@@ -309,9 +317,11 @@ public class CollectionService {
                 throw new BusinessException("盲盒抽卡失败");
             }
         }
-        winItem.setStock(winItem.getStock() - 1);
-        winItem.setSale(winItem.getSale() + 1);
-        blindBoxItemRepo.save(winItem);
+//        winItem.setStock(winItem.getStock() - 1);
+//        winItem.setSale(winItem.getSale() + 1);
+//        blindBoxItemRepo.saveAndFlush(winItem);
+        blindBoxItemRepo.decreaseStockAndIncreaseSale(winItem.getId(), 1);
+        blindBoxItemRepo.flush();
         return winItem;
     }
 
@@ -342,7 +352,7 @@ public class CollectionService {
         collection.setTotal(collection.getTotal() + number);
     }
 
-//    @Scheduled(cron = "0 0 1 ?")
+    //    @Scheduled(cron = "0 0 1 ?")
     public void delCollection() {
         List<Long> assetIds = assetRepo.findAllByStatus(AssetStatus.TRANSFERRED);
         List<Long> collections = collectionRepo.findAllByDelFalseAndAssetIdIn(assetIds);
@@ -352,4 +362,36 @@ public class CollectionService {
         log.info("定时任务删除未删除藏品:{}", collections);
         collectionRepo.softDeleteByIdIn(collections);
     }
+
+    public synchronized Long increaseStock(Long id, int number) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
+        if (ops.get() == null) {
+            Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id))
+                    .orElse(0), 7, TimeUnit.DAYS);
+            log.info("创建redis库存:{}", success);
+        }
+        Long stock = ops.increment(number);
+        rocketMQTemplate.convertAndSend(generalProperties.getUpdateStockTopic(), id);
+        return stock;
+    }
+
+    public synchronized Long decreaseStock(Long id, int number) {
+        return increaseStock(id, -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 decreaseSale(Long id, int number) {
+        return increaseSale(id, -number);
+    }
 }

+ 266 - 152
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -15,15 +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.*;
@@ -34,12 +34,16 @@ 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;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.event.EventListener;
 import org.springframework.core.env.Environment;
 import org.springframework.data.domain.Page;
+import org.springframework.data.redis.core.BoundSetOperations;
+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;
@@ -51,6 +55,7 @@ import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 @Service
 @AllArgsConstructor
@@ -76,108 +81,133 @@ public class OrderService {
     private CommissionRecordRepo          commissionRecordRepo;
     private AdapayProperties              adapayProperties;
     private GeneralProperties             generalProperties;
+    private SnowflakeIdWorker             snowflakeIdWorker;
+    private RocketMQTemplate              rocketMQTemplate;
+    private ErrorOrderRepo                errorOrderRepo;
 
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
     }
 
+    public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor) {
+
+        Long id = snowflakeIdWorker.nextId();
+        SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
+                new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor), 100000);
+        log.info("发送订单到队列: {}, result={}", id, result);
+        return String.valueOf(id);
+    }
+
     @Transactional
-    public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor) {
-        if (qty <= 0) throw new BusinessException("数量必须大于0");
-        User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在"));
-        Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
-        User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
-        UserCoupon coupon = null;
-        if (userCouponId != null) {
-            coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在"));
-            if (coupon.isUsed()) {
-                throw new BusinessException("该兑换券已使用");
+    public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor, Long id) {
+        qty = 1;
+        long t = System.currentTimeMillis();
+        int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
+                .map(Math::toIntExact)
+                .orElseThrow(new BusinessException("很遗憾,藏品已售罄"));
+
+        try {
+            if (stock < 0) {
+                throw new BusinessException("很遗憾,藏品已售罄");
             }
-            if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) {
-                throw new BusinessException("该兑换券不可用");
+
+            User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在"));
+            Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在"));
+            User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
+            UserCoupon coupon = null;
+            if (userCouponId != null) {
+                coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在"));
+                if (coupon.isUsed()) {
+                    throw new BusinessException("该兑换券已使用");
+                }
+                if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) {
+                    throw new BusinessException("该兑换券不可用");
+                }
             }
-        }
-        if (collection.isScheduleSale()) {
-            if (collection.getStartTime().isAfter(LocalDateTime.now())) {
-                throw new BusinessException("当前还未开售");
+            if (collection.isScheduleSale()) {
+                if (collection.getStartTime().isAfter(LocalDateTime.now())) {
+                    throw new BusinessException("当前还未开售");
+                }
             }
-        }
-        if (!collection.isOnShelf()) {
-            throw new BusinessException("藏品已下架");
-        }
-        if (qty > collection.getStock()) {
-            throw new BusinessException("库存不足");
-        }
-        if (!collection.isSalable()) {
-            throw new BusinessException("该藏品当前不可购买");
-        }
-        if (collection.getAssetId() != null) {
-            Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("藏品不存在"));
-            if (asset.getStatus() != AssetStatus.NORMAL) {
+            if (!collection.isOnShelf()) {
                 throw new BusinessException("藏品已下架");
             }
-        }
-        UserAddress userAddress = null;
-        if (addressId != null) {
-            userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
-        }
+            if (qty > collection.getStock()) {
+                throw new BusinessException("库存不足");
+            }
+            if (!collection.isSalable()) {
+                throw new BusinessException("该藏品当前不可购买");
+            }
+            if (collection.getAssetId() != null) {
+                Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("藏品不存在"));
+                if (asset.getStatus() != AssetStatus.NORMAL) {
+                    throw new BusinessException("藏品已下架");
+                }
+            }
+            UserAddress userAddress = null;
+            if (addressId != null) {
+                userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
+            }
 
-        collectionRepo.increaseStock(collectionId, -qty);
-        collectionRepo.increaseSale(collectionId, qty);
-
-        BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee");
-        Order order = Order.builder()
-                .userId(userId)
-                .collectionId(collectionId)
-                .name(collection.getName())
-                .pic(collection.getPic())
-                .detail(collection.getDetail())
-                .properties(collection.getProperties())
-                .category(collection.getCategory())
-                .canResale(collection.isCanResale())
-                .royalties(collection.getRoyalties())
-                .serviceCharge(collection.getServiceCharge())
-                .type(collection.getType())
-                .source(collection.getSource())
-                .minterId(collection.getMinterId())
-                .minter(minter.getNickname())
-                .minterAvatar(minter.getAvatar())
-                .qty(qty)
-                .price(collection.getPrice())
-                .gasPrice(gasFee)
-                .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee))
-                .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
-                .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
-                .address(Optional.ofNullable(userAddress).map(u ->
-                                u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
-                        .orElse(null))
-                .status(OrderStatus.NOT_PAID)
-                .assetId(collection.getAssetId())
-                .couponId(userCouponId)
-                .invitor(invitor)
-                .projectId(collection.getProjectId())
-                .build();
-        if (coupon != null) {
-            coupon.setUsed(true);
-            coupon.setUseTime(LocalDateTime.now());
-            if (coupon.isNeedGas()) {
-                order.setTotalPrice(order.getGasPrice());
-            } else {
-                order.setTotalPrice(BigDecimal.ZERO);
+            BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee");
+            Order order = Order.builder()
+                    .userId(userId)
+                    .collectionId(collectionId)
+                    .name(collection.getName())
+                    .pic(collection.getPic())
+                    .detail(collection.getDetail())
+                    .properties(collection.getProperties())
+                    .category(collection.getCategory())
+                    .canResale(collection.isCanResale())
+                    .royalties(collection.getRoyalties())
+                    .serviceCharge(collection.getServiceCharge())
+                    .type(collection.getType())
+                    .source(collection.getSource())
+                    .minterId(collection.getMinterId())
+                    .minter(minter.getNickname())
+                    .minterAvatar(minter.getAvatar())
+                    .qty(qty)
+                    .price(collection.getPrice())
+                    .gasPrice(gasFee)
+                    .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee))
+                    .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
+                    .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
+                    .address(Optional.ofNullable(userAddress).map(u ->
+                                    u.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress())
+                            .orElse(null))
+                    .status(OrderStatus.NOT_PAID)
+                    .assetId(collection.getAssetId())
+                    .couponId(userCouponId)
+                    .invitor(invitor)
+                    .projectId(collection.getProjectId())
+                    .build();
+            if (coupon != null) {
+                coupon.setUsed(true);
+                coupon.setUseTime(LocalDateTime.now());
+                if (coupon.isNeedGas()) {
+                    order.setTotalPrice(order.getGasPrice());
+                } else {
+                    order.setTotalPrice(BigDecimal.ZERO);
+                }
             }
-        }
 
-        if (collection.getSource() == CollectionSource.TRANSFER) {
-            Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
-            asset.setStatus(AssetStatus.TRADING);
-            assetRepo.save(asset);
-            collectionRepo.setOnShelf(collection.getId(), false);
-        }
-        order = orderRepo.save(order);
-        if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
-            notifyOrder(order.getId(), PayMethod.WEIXIN, null);
+            if (collection.getSource() == CollectionSource.TRANSFER) {
+                Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
+                asset.setStatus(AssetStatus.TRADING);
+                assetRepo.save(asset);
+                collectionRepo.setOnShelf(collection.getId(), false);
+            }
+            order = orderRepo.save(order);
+            if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
+                notifyOrder(order.getId(), PayMethod.WEIXIN, null);
+            }
+            rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
+            log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
+            return order;
+        } catch (Exception e) {
+            collectionService.increaseStock(collectionId, qty);
+            throw e;
         }
-        return order;
     }
 
     public void payOrderAlipay(Long id, Model model) {
@@ -191,7 +221,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"))) {
@@ -228,7 +258,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分
@@ -254,6 +284,7 @@ public class OrderService {
 
     }
 
+    @Cacheable(value = "adapay", key = "#id+'_'+#payChannel")
     public Object payAdapay(Long id, String payChannel, String openId) throws BaseAdaPayException {
         List<String> aliChannels = Arrays.asList("alipay", "alipay_qr", "alipay_wap");
         List<String> wxChannels = Arrays.asList("wx_pub", "wx_lite");
@@ -272,7 +303,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);
@@ -331,6 +362,11 @@ public class OrderService {
             response = Payment.create(paymentParams);
             log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat));
             AdapayService.checkSuccess(response);
+
+            // 保存adapay的订单id,用于后续取消订单时的查询
+            BoundSetOperations<String, Object> ops = redisTemplate.boundSetOps(RedisKeys.PAY_RECORD + order.getId());
+            ops.add(MapUtils.getString(response, "id"));
+            ops.expire(7, TimeUnit.DAYS);
         }
 
         switch (payChannel) {
@@ -380,44 +416,92 @@ public class OrderService {
 
     @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());
-                    List<Long> collectionIds = collectionRepo.findAllByAssetId(collection.getAssetId());
-                    log.info("删除collection {}", collectionIds);
-                    if (CollectionUtils.isNotEmpty(collectionIds)) {
-                        collectionRepo.deleteAllByIdIn(collectionIds);
-                    } else {
-                        collectionRepo.delete(collection);
-                    }
+        log.info("订单回调 orderId: {}, payMethod: {}, transactionId: {}", orderId, payMethod, transactionId);
 
-                } else {
+        // 取消订单与订单回调不能同时进行,需要抢锁
+        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());
+                        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
@@ -459,36 +543,53 @@ public class OrderService {
     }
 
     public void cancel(Order order) {
-        if (order.getStatus() != OrderStatus.NOT_PAID) {
-            throw new BusinessException("已支付订单无法取消");
+        if (!getOrderLock(order.getId())) {
+            log.error("订单取消失败 {}, redis锁了", order.getId());
+            return;
         }
-        Collection collection = collectionRepo.findById(order.getCollectionId())
-                .orElseThrow(new BusinessException("藏品不存在"));
-        User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在"));
 
-        if (collection.getSource() == CollectionSource.TRANSFER) {
-            Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
-            if (asset != null) {
-                asset.setStatus(AssetStatus.NORMAL);
-                assetRepo.save(asset);
+        try {
+            if (order.getStatus() != OrderStatus.NOT_PAID) {
+                throw new BusinessException("已支付订单无法取消");
             }
-            collectionRepo.setOnShelf(collection.getId(), true);
-        }
-        collectionRepo.increaseSale(collection.getId(), -order.getQty());
-        collectionRepo.increaseStock(collection.getId(), order.getQty());
+            Collection collection = collectionRepo.findById(order.getCollectionId())
+                    .orElseThrow(new BusinessException("藏品不存在"));
+
+            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);
+                }
+                collectionRepo.setOnShelf(order.getCollectionId(), true);
+            }
+            collectionService.increaseStock(order.getCollectionId(), order.getQty());
 
 
-        order.setStatus(OrderStatus.CANCELLED);
-        order.setCancelTime(LocalDateTime.now());
-        orderRepo.save(order);
+            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);
-            });
+            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) {
+            if (e instanceof BusinessException) {
+                log.error(e.getMessage());
+            } else {
+                log.error("订单取消错误 orderId: " + order.getId(), e);
+            }
         }
+        releaseOrderLock(order.getId());
     }
 
     @Scheduled(fixedRate = 30000)
@@ -572,7 +673,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);
+    }
+
 }

+ 13 - 2
src/main/java/com/izouma/nineth/web/OrderController.java

@@ -2,6 +2,7 @@ package com.izouma.nineth.web;
 
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.izouma.nineth.domain.Order;
+import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.OrderDTO;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.CollectionType;
@@ -79,8 +80,18 @@ public class OrderController extends BaseController {
                         @RequestParam(required = false) Long addressId,
                         @RequestParam(required = false) Long couponId,
                         @RequestParam(required = false) Long invitor) {
-        return orderService.create(SecurityUtils.getAuthenticatedUser().getId(),
-                collectionId, qty, addressId, couponId, invitor);
+        throw new BusinessException("接口暂不可用,请刷新后重试");
+    }
+
+    @PostMapping("/mqCreate")
+    public HashMap<String, String> mqCreate(@RequestParam Long collectionId, @RequestParam int qty,
+                                            @RequestParam(required = false) Long addressId,
+                                            @RequestParam(required = false) Long couponId,
+                                            @RequestParam(required = false) Long invitor) {
+        final User user = SecurityUtils.getAuthenticatedUser();
+        return new HashMap<>() {{
+            put("id", orderService.mqCreate(user.getId(), collectionId, qty, addressId, couponId, invitor));
+        }};
     }
 
     @PostMapping("/hide")

+ 24 - 6
src/main/java/com/izouma/nineth/web/OrderNotifyController.java

@@ -11,13 +11,19 @@ import com.github.binarywang.wxpay.service.WxPayService;
 import com.huifu.adapay.core.AdapayCore;
 import com.huifu.adapay.core.util.AdapaySign;
 import com.izouma.nineth.config.AlipayProperties;
+import com.izouma.nineth.config.GeneralProperties;
+import com.izouma.nineth.config.RedisKeys;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.service.GiftOrderService;
 import com.izouma.nineth.service.OrderService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.MapUtils;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
@@ -25,6 +31,7 @@ import javax.servlet.http.HttpServletRequest;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import static com.alibaba.fastjson.serializer.SerializerFeature.PrettyFormat;
 
@@ -34,11 +41,14 @@ import static com.alibaba.fastjson.serializer.SerializerFeature.PrettyFormat;
 @AllArgsConstructor
 public class OrderNotifyController {
 
-    private final AlipayProperties alipayProperties;
-    private final OrderService     orderService;
-    private final WxPayService     wxPayService;
-    private final AssetService     assetService;
-    private final GiftOrderService giftOrderService;
+    private final AlipayProperties              alipayProperties;
+    private final OrderService                  orderService;
+    private final WxPayService                  wxPayService;
+    private final AssetService                  assetService;
+    private final GiftOrderService              giftOrderService;
+    private final RedisTemplate<String, Object> redisTemplate;
+    private final RocketMQTemplate              rocketMQTemplate;
+    private final GeneralProperties             generalProperties;
 
     @PostMapping("/order/alipay")
     @ResponseBody
@@ -122,7 +132,15 @@ public class OrderNotifyController {
                     JSONObject jsonObject = JSON.parseObject(data);
                     String channel = jsonObject.getString("pay_channel");
                     String id = jsonObject.getString("id");
-                    orderService.notifyOrder(orderId, channel.startsWith("wx") ? PayMethod.WEIXIN : PayMethod.ALIPAY, id);
+                    PayMethod payMethod = channel.startsWith("wx") ? PayMethod.WEIXIN : PayMethod.ALIPAY;
+
+                    BoundSetOperations<String, Object> listOps = redisTemplate.boundSetOps(RedisKeys.PAY_RECORD + orderId);
+                    listOps.add(id);
+                    listOps.expire(7, TimeUnit.DAYS);
+
+                    rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(),
+                            new OrderNotifyEvent(orderId, payMethod, id, System.currentTimeMillis()));
+
                 }
             }
         } catch (Exception e) {

+ 43 - 13
src/main/nine-space/src/views/Submit.vue

@@ -106,7 +106,8 @@ export default {
             enable_wx_lite: false,
             enable_wx_pub: false,
             launchName: '',
-            launchPath: ''
+            launchPath: '',
+            createOrderTimer: null
         };
     },
     computed: {
@@ -125,6 +126,9 @@ export default {
     },
     beforeRouteLeave(to, from, next) {
         console.log(to);
+        if (this.createOrderTimer) {
+            clearInterval(this.createOrderTimer);
+        }
         if (to.path !== '/couponList') {
             this.$store.commit('setCouponInfo', null);
         }
@@ -226,22 +230,48 @@ export default {
                     });
                 });
         },
-        submit() {
+        createOrder() {
             if (!this.payType) {
                 this.$toast('请选择支付方式');
                 return;
             }
-            this.$toast.loading('加载中');
-            let url = '/order/create?collectionId=' + this.$route.query.id + '&qty=1';
-            if (this.couponInfo) {
-                url += '&couponId=' + this.couponInfo.id;
-            }
-            let invitor = sessionStorage.getItem('invitor');
-            if (invitor) {
-                url += '&invitor=' + invitor;
-            }
-            this.$http
-                .post(url)
+            this.$toast.loading('请不要离开当前页面');
+
+            let params = {
+                collectionId: this.$route.query.id,
+                qty: 1,
+                couponId: (this.couponInfo || {}).id || '',
+                invitor: sessionStorage.getItem('invitor')
+            };
+            return this.$http.post('/order/mqCreate', params).then(res => {
+                return new Promise((resolve, reject) => {
+                    let checkOrder = () => {
+                        this.$http
+                            .get('/order/createResult', { id: res.id })
+                            .then(res => {
+                                if (res) {
+                                    clearInterval(this.createOrderTimer);
+                                    this.createOrderTimer = null;
+                                    if (res.success) {
+                                        resolve(res.data);
+                                    } else {
+                                        reject({ error: res.data });
+                                    }
+                                }
+                            })
+                            .catch(e => {
+                                clearInterval(this.createOrderTimer);
+                                this.createOrderTimer = null;
+                                reject(e);
+                            });
+                    };
+                    setTimeout(checkOrder, 500);
+                    this.createOrderTimer = setInterval(checkOrder, 2000);
+                });
+            });
+        },
+        submit() {
+            this.createOrder()
                 .then(res => {
                     if (this.money) {
                         this.$toast.clear();

+ 0 - 2
src/test/java/com/izouma/nineth/service/OrderServiceTest.java

@@ -49,8 +49,6 @@ public class OrderServiceTest extends ApplicationTests {
 
     @Test
     public void create() throws EncoderException, WxPayException {
-        Order order = orderService.create(1110L, 1777L, 1, null, 1896L, null);
-        assert order.getStatus() == OrderStatus.FINISH;
     }
 
     @Test