Parcourir la source

Merge branch 'dev'

# Conflicts:
#	src/main/java/com/izouma/nineth/dto/InvitePhoneDTO.java
xiongzhu il y a 4 ans
Parent
commit
57b07a6436
61 fichiers modifiés avec 1237 ajouts et 455 suppressions
  1. 13 1
      pom.xml
  2. 12 0
      src/main/java/com/izouma/nineth/annotations/Debounce.java
  3. 71 0
      src/main/java/com/izouma/nineth/aspect/DebounceAspect.java
  4. 33 0
      src/main/java/com/izouma/nineth/aspect/debounce/DebounceTask.java
  5. 10 0
      src/main/java/com/izouma/nineth/config/GeneralProperties.java
  6. 13 0
      src/main/java/com/izouma/nineth/config/SnowflakeIdWorkerConfig.java
  7. 7 3
      src/main/java/com/izouma/nineth/domain/Order.java
  8. 15 0
      src/main/java/com/izouma/nineth/dto/CollectionStockAndSale.java
  9. 1 1
      src/main/java/com/izouma/nineth/dto/InvitePhoneDTO.java
  10. 18 0
      src/main/java/com/izouma/nineth/event/CreateOrderEvent.java
  11. 12 0
      src/main/java/com/izouma/nineth/event/MyMqEvent.java
  12. 45 0
      src/main/java/com/izouma/nineth/listener/CreateOrderListener.java
  13. 25 0
      src/main/java/com/izouma/nineth/listener/MintListener.java
  14. 23 0
      src/main/java/com/izouma/nineth/listener/TestConsumer.java
  15. 26 0
      src/main/java/com/izouma/nineth/listener/UpdateSaleListener.java
  16. 26 0
      src/main/java/com/izouma/nineth/listener/UpdateStockListener.java
  17. 0 26
      src/main/java/com/izouma/nineth/lock/RedisLockException.java
  18. 0 110
      src/main/java/com/izouma/nineth/lock/RedisLockInterceptor.java
  19. 0 26
      src/main/java/com/izouma/nineth/lock/RedisLockable.java
  20. 22 12
      src/main/java/com/izouma/nineth/repo/CollectionRepo.java
  21. 7 0
      src/main/java/com/izouma/nineth/repo/IdentityAuthRepo.java
  22. 5 0
      src/main/java/com/izouma/nineth/repo/RecommendRepo.java
  23. 8 2
      src/main/java/com/izouma/nineth/repo/UserRepo.java
  24. 1 2
      src/main/java/com/izouma/nineth/security/JwtAuthorizationTokenFilter.java
  25. 2 0
      src/main/java/com/izouma/nineth/security/WebSecurityConfig.java
  26. 2 2
      src/main/java/com/izouma/nineth/service/AirDropService.java
  27. 36 34
      src/main/java/com/izouma/nineth/service/AssetMintService.java
  28. 11 13
      src/main/java/com/izouma/nineth/service/AssetService.java
  29. 8 0
      src/main/java/com/izouma/nineth/service/CacheService.java
  30. 91 10
      src/main/java/com/izouma/nineth/service/CollectionService.java
  31. 15 0
      src/main/java/com/izouma/nineth/service/DebounceTestService.java
  32. 3 2
      src/main/java/com/izouma/nineth/service/GiftOrderService.java
  33. 10 0
      src/main/java/com/izouma/nineth/service/IdentityAuthService.java
  34. 6 5
      src/main/java/com/izouma/nineth/service/NFTService.java
  35. 143 114
      src/main/java/com/izouma/nineth/service/OrderService.java
  36. 62 0
      src/main/java/com/izouma/nineth/service/RedisLock.java
  37. 9 2
      src/main/java/com/izouma/nineth/service/UserService.java
  38. 34 0
      src/main/java/com/izouma/nineth/service/sms/AliSmsService.java
  39. 2 0
      src/main/java/com/izouma/nineth/service/sms/SmsService.java
  40. 1 1
      src/main/java/com/izouma/nineth/utils/TokenUtils.java
  41. 8 3
      src/main/java/com/izouma/nineth/web/CollectionController.java
  42. 9 1
      src/main/java/com/izouma/nineth/web/IdentityAuthController.java
  43. 18 1
      src/main/java/com/izouma/nineth/web/OrderController.java
  44. 7 6
      src/main/java/com/izouma/nineth/web/OrderNotifyController.java
  45. 23 0
      src/main/java/com/izouma/nineth/web/RocketMqController.java
  46. 40 0
      src/main/java/com/izouma/nineth/web/TestStockController.java
  47. 99 3
      src/main/resources/application.yaml
  48. 60 17
      src/main/resources/logback-spring.xml
  49. 38 5
      src/main/vue/src/views/IdentityAuthList.vue
  50. 19 0
      src/test/java/com/izouma/nineth/CommonTest.java
  51. 21 0
      src/test/java/com/izouma/nineth/DebounceTest.java
  52. 7 4
      src/test/java/com/izouma/nineth/RedisTest.java
  53. 5 21
      src/test/java/com/izouma/nineth/service/AdapayServiceTest.java
  54. 8 1
      src/test/java/com/izouma/nineth/service/AdapayTest.java
  55. 5 0
      src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java
  56. 1 6
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  57. 6 5
      src/test/java/com/izouma/nineth/service/CollectionServiceTest.java
  58. 5 3
      src/test/java/com/izouma/nineth/service/NFTServiceTest.java
  59. 8 13
      src/test/java/com/izouma/nineth/service/OrderServiceTest.java
  60. 17 0
      src/test/java/com/izouma/nineth/service/UserServiceTest.java
  61. 5 0
      src/test/java/com/izouma/nineth/service/sms/SmsServiceTest.java

+ 13 - 1
pom.xml

@@ -404,7 +404,19 @@
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid-spring-boot-starter</artifactId>
-            <version>1.1.21</version>
+            <version>1.2.8</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-validator</groupId>
+            <artifactId>commons-validator</artifactId>
+            <version>1.7</version>
         </dependency>
     </dependencies>
 

+ 12 - 0
src/main/java/com/izouma/nineth/annotations/Debounce.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Debounce {
+    String key();
+
+    long delay() default 200L;
+}

+ 71 - 0
src/main/java/com/izouma/nineth/aspect/DebounceAspect.java

@@ -0,0 +1,71 @@
+package com.izouma.nineth.aspect;
+
+import com.izouma.nineth.annotations.Debounce;
+import com.izouma.nineth.aspect.debounce.DebounceTask;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+@Slf4j
+public class DebounceAspect {
+
+    private DefaultParameterNameDiscoverer nameDiscoverer  = new DefaultParameterNameDiscoverer();
+    private HashMap<String, Future<Void>>  debounceStore   = new HashMap<>();
+    private ScheduledExecutorService       executorService = Executors.newScheduledThreadPool(10);
+    private Map<String, Long>              debounceCounter = new HashMap<>();
+
+    @Pointcut("@annotation(com.izouma.nineth.annotations.Debounce)")
+    public void debouncePointCut() {
+    }
+
+    @Around(value = "debouncePointCut() && @annotation(debounce)")
+    public synchronized void debounce(ProceedingJoinPoint joinPoint, Debounce debounce) {
+        ExpressionParser parser = new SpelExpressionParser();
+        EvaluationContext context = new StandardEvaluationContext(joinPoint.getSignature());
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method method = methodSignature.getMethod();
+        String[] paramNames = nameDiscoverer.getParameterNames(method);
+        Object[] args = joinPoint.getArgs();
+        for (int i = 0; i < args.length; i++) {
+            context.setVariable(paramNames[i], args[i]);
+        }
+        String key = Optional.ofNullable(parser.parseExpression(debounce.key()).getValue(context)).map(Object::toString)
+                .orElse("default");
+
+        Future<Void> future = debounceStore.get(key);
+        long lastRun = debounceCounter.getOrDefault(key, 0L);
+        if (future != null && !future.isDone()) {
+            if (System.currentTimeMillis() - lastRun > debounce.delay()) {
+                debounceCounter.put(key, System.currentTimeMillis());
+            } else {
+                future.cancel(false);
+            }
+        }
+        debounceStore.put(key, executorService.schedule(new DebounceTask(joinPoint, (Void) -> {
+            debounceCounter.put(key, System.currentTimeMillis());
+            return null;
+        }), debounce.delay(), TimeUnit.MILLISECONDS));
+
+    }
+
+}

+ 33 - 0
src/main/java/com/izouma/nineth/aspect/debounce/DebounceTask.java

@@ -0,0 +1,33 @@
+package com.izouma.nineth.aspect.debounce;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+@Slf4j
+public class DebounceTask implements Callable<Void> {
+    private final ProceedingJoinPoint  joinPoint;
+    private final Function<Void, Void> callback;
+
+    public DebounceTask(ProceedingJoinPoint joinPoint, Function<Void, Void> callback) {
+        this.joinPoint = joinPoint;
+        this.callback = callback;
+    }
+
+    @Override
+    public Void call() throws Exception {
+        try {
+            this.joinPoint.proceed();
+            if (this.callback != null) {
+                this.callback.apply(null);
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+
+        return null;
+
+    }
+}

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

@@ -11,4 +11,14 @@ public class GeneralProperties {
     private String name;
     private String org;
     private String shortName;
+    private String createOrderGroup;
+    private String createOrderTopic;
+    private String updateStockGroup;
+    private String updateStockTopic;
+    private String updateSaleGroup;
+    private String updateSaleTopic;
+    private String orderNotifyGroup;
+    private String orderNotifyTopic;
+    private String mintGroup;
+    private String mintTopic;
 }

+ 13 - 0
src/main/java/com/izouma/nineth/config/SnowflakeIdWorkerConfig.java

@@ -0,0 +1,13 @@
+package com.izouma.nineth.config;
+
+import com.izouma.nineth.utils.SnowflakeIdWorker;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SnowflakeIdWorkerConfig {
+    @Bean
+    public SnowflakeIdWorker snowflakeIdWorker() {
+        return new SnowflakeIdWorker(0, 0);
+    }
+}

+ 7 - 3
src/main/java/com/izouma/nineth/domain/Order.java

@@ -8,6 +8,7 @@ import com.izouma.nineth.annotations.Searchable;
 import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.PrivilegeListConverter;
 import com.izouma.nineth.converter.PropertyListConverter;
+import com.izouma.nineth.enums.CollectionSource;
 import com.izouma.nineth.enums.CollectionType;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.enums.PayMethod;
@@ -17,7 +18,6 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
-import org.hibernate.annotations.GenericGenerator;
 import org.hibernate.envers.Audited;
 import org.springframework.data.annotation.CreatedBy;
 import org.springframework.data.annotation.CreatedDate;
@@ -45,8 +45,8 @@ import java.util.List;
 public class Order {
 
     @Id
-    @GenericGenerator(name = "custom-id", strategy = "com.izouma.nineth.utils.SnowflakeIdGenerator")
-    @GeneratedValue(strategy = GenerationType.AUTO, generator = "custom-id")
+//    @GenericGenerator(name = "custom-id", strategy = "com.izouma.nineth.utils.SnowflakeIdGenerator")
+//    @GeneratedValue(strategy = GenerationType.AUTO, generator = "custom-id")
     private Long id;
 
     @JsonIgnore
@@ -120,6 +120,10 @@ public class Order {
     @Enumerated(EnumType.STRING)
     private CollectionType type;
 
+    @ApiModelProperty("来源")
+    @Enumerated(EnumType.STRING)
+    private CollectionSource source;
+
     @ApiModelProperty("铸造者")
     @Searchable
     private String minter;

+ 15 - 0
src/main/java/com/izouma/nineth/dto/CollectionStockAndSale.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CollectionStockAndSale {
+    private Long id;
+    private int  stock;
+    private int  sale;
+
+}

+ 1 - 1
src/main/java/com/izouma/nineth/dto/InvitePhoneDTO.java

@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
 @AllArgsConstructor
 public class InvitePhoneDTO {
     @ExcelProperty(value = "昵称")
-    private String nickname;
+    private String nickName;
 
     @ExcelProperty(value = "手机号")
     private String phone;

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

@@ -0,0 +1,18 @@
+package com.izouma.nineth.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CreateOrderEvent {
+    private Long id;
+    private Long userId;
+    private Long collectionId;
+    private int  qty;
+    private Long addressId;
+    private Long userCouponId;
+    private Long invitor;
+}

+ 12 - 0
src/main/java/com/izouma/nineth/event/MyMqEvent.java

@@ -0,0 +1,12 @@
+package com.izouma.nineth.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MyMqEvent {
+    private String data;
+}

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

@@ -0,0 +1,45 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.domain.Order;
+import com.izouma.nineth.event.CreateOrderEvent;
+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;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+@RocketMQMessageListener(
+        consumerGroup = "${general.create-order-group}",
+        topic = "${general.create-order-topic}",
+        consumeMode = ConsumeMode.CONCURRENTLY)
+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) {
+            log.error("订单创建失败", e);
+            map.put("success", false);
+            map.put("data", e.getMessage());
+        }
+        redisTemplate.opsForValue().set("createOrder::" + event.getId(), map);
+
+    }
+}

+ 25 - 0
src/main/java/com/izouma/nineth/listener/MintListener.java

@@ -0,0 +1,25 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.service.AssetMintService;
+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.mint-group}",
+        topic = "${general.mint-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+public class MintListener implements RocketMQListener<Long> {
+    private AssetMintService assetMintService;
+
+    @Override
+    public void onMessage(Long assetId) {
+        assetMintService.mint(assetId);
+    }
+}

+ 23 - 0
src/main/java/com/izouma/nineth/listener/TestConsumer.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.event.MyMqEvent;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+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 = "test-group", topic = "test-add-topic", consumeMode = ConsumeMode.CONCURRENTLY)
+public class TestConsumer implements RocketMQListener<Object> {
+
+    @SneakyThrows
+    public void onMessage(Object event) {
+        log.info("receive message: {}", event);
+    }
+}

+ 26 - 0
src/main/java/com/izouma/nineth/listener/UpdateSaleListener.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.service.CollectionService;
+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.update-sale-group}",
+        topic = "${general.update-sale-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+public class UpdateSaleListener implements RocketMQListener<Long> {
+
+    private CollectionService collectionService;
+
+    @Override
+    public void onMessage(Long id) {
+        collectionService.syncSale(id);
+    }
+}

+ 26 - 0
src/main/java/com/izouma/nineth/listener/UpdateStockListener.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.listener;
+
+import com.izouma.nineth.service.CollectionService;
+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.update-stock-group}",
+        topic = "${general.update-stock-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+public class UpdateStockListener implements RocketMQListener<Long> {
+
+    private CollectionService collectionService;
+
+    @Override
+    public void onMessage(Long id) {
+        collectionService.syncStock(id);
+    }
+}

+ 0 - 26
src/main/java/com/izouma/nineth/lock/RedisLockException.java

@@ -1,26 +0,0 @@
-package com.izouma.nineth.lock;
-
-/**
- * Created by h p on 2017/3/9.
- */
-public class RedisLockException extends RuntimeException {
-
-    public RedisLockException() {
-    }
-
-    public RedisLockException(String message) {
-        super(message);
-    }
-
-    public RedisLockException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public RedisLockException(Throwable cause) {
-        super(cause);
-    }
-
-    public RedisLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
-        super(message, cause, enableSuppression, writableStackTrace);
-    }
-}

+ 0 - 110
src/main/java/com/izouma/nineth/lock/RedisLockInterceptor.java

@@ -1,110 +0,0 @@
-package com.izouma.nineth.lock;
-
-import com.google.common.base.Joiner;
-import com.izouma.nineth.utils.RedisLockUtil;
-import org.apache.commons.lang3.StringUtils;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.aop.support.AopUtils;
-import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
-import org.springframework.expression.EvaluationContext;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
-import org.springframework.stereotype.Component;
-
-import java.lang.reflect.Method;
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author mashaohua
- */
-@Aspect
-@Component
-public class RedisLockInterceptor {
-
-    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
-
-    private static final ExpressionParser PARSER = new SpelExpressionParser();
-
-    @Pointcut("@annotation(com.izouma.nineth.lock.RedisLockable)")
-    public void pointcut() {
-    }
-
-    @Around("pointcut()")
-    public Object doAround(ProceedingJoinPoint point) throws Throwable {
-
-        MethodSignature methodSignature = (MethodSignature) point.getSignature();
-        Method targetMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), point.getTarget().getClass());
-        String targetName = point.getTarget().getClass().getName();
-        String methodName = point.getSignature().getName();
-        Object[] arguments = point.getArgs();
-
-        RedisLockable redisLock = targetMethod.getAnnotation(RedisLockable.class);
-        long expire = redisLock.expiration();
-        String redisKey = getLockKey(redisLock, targetMethod, targetName, methodName, arguments);
-        String uuid;
-        if (redisLock.isWaiting()) {
-            uuid = waitingLock(redisKey, expire, redisLock.retryCount(), redisLock.retryWaitingTime());
-        } else {
-            uuid = noWaitingLock(redisKey, expire);
-        }
-        if (StringUtils.isNotEmpty(uuid)) {
-            try {
-                return point.proceed();
-            } finally {
-                RedisLockUtil.unLock(redisKey, uuid);
-            }
-        } else {
-            throw new RedisLockException(redisKey);
-        }
-    }
-
-    private String getLockKey(RedisLockable redisLock, Method targetMethod,
-                              String targetName, String methodName, Object[] arguments) {
-        String[] keys = redisLock.key();
-        String prefix = redisLock.prefix();
-        StringBuilder sb = new StringBuilder("lock.");
-        if (StringUtils.isEmpty(prefix)) {
-            sb.append(targetName).append(".").append(methodName);
-        } else {
-            sb.append(prefix);
-        }
-        if (keys != null) {
-            String keyStr = Joiner.on("+ '.' +").skipNulls().join(keys);
-            EvaluationContext context = new StandardEvaluationContext(targetMethod);
-            String[] parameterNames = DISCOVERER.getParameterNames(targetMethod);
-            for (int i = 0; i < parameterNames.length; i++) {
-                context.setVariable(parameterNames[i], arguments[i]);
-            }
-            Object key = PARSER.parseExpression(keyStr).getValue(context);
-            sb.append("#").append(key);
-        }
-        return sb.toString();
-    }
-
-    private String noWaitingLock(String key, long expire) {
-        return RedisLockUtil.lock(key, expire);
-    }
-
-    private String waitingLock(String key, long expire, int retryCount, int retryWaitingTime)
-            throws InterruptedException {
-        int count = 0;
-        while (retryCount == -1 || count <= retryCount) {
-            String uuid = noWaitingLock(key, expire);
-            if (!StringUtils.isEmpty(uuid)) {
-                return uuid;
-            }
-            try {
-                TimeUnit.MILLISECONDS.sleep(retryWaitingTime);
-            } catch (InterruptedException e) {
-                throw e;
-            }
-            count++;
-        }
-        return null;
-    }
-}

+ 0 - 26
src/main/java/com/izouma/nineth/lock/RedisLockable.java

@@ -1,26 +0,0 @@
-package com.izouma.nineth.lock;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @author mashaohua
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface RedisLockable {
-
-    String prefix() default "";
-
-    String[] key() default "";
-
-    long expiration() default 60;
-
-    boolean isWaiting() default false; //锁是否等待,默认为不等待
-
-    int retryCount() default -1; // 锁等待重试次数,-1未不限制
-
-    int retryWaitingTime() default 10; // 锁等待重试间隔时间,默认10毫秒
-}

+ 22 - 12
src/main/java/com/izouma/nineth/repo/CollectionRepo.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.Collection;
+import com.izouma.nineth.dto.CollectionStockAndSale;
 import com.izouma.nineth.dto.RecommendCollection;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
@@ -65,18 +66,6 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     @CacheEvict(value = "collection", key = "#id")
     void increaseNumber(Long id, int d);
 
-    @Transactional
-    @Modifying
-    @Query("update Collection c set c.sale = COALESCE(c.sale, 0) + ?2 where c.id = ?1")
-    @CacheEvict(value = "collection", key = "#id")
-    void increaseSale(Long id, int d);
-
-    @Transactional
-    @Modifying
-    @Query("update Collection c set c.stock = COALESCE(c.stock, 0) + ?2 where c.id = ?1")
-    @CacheEvict(value = "collection", key = "#id")
-    void increaseStock(Long id, int d);
-
     @Transactional
     @Modifying
     @Query("update Collection c set c.total = COALESCE(c.total, 0) + ?2 where c.id = ?1")
@@ -101,4 +90,25 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     List<Collection> findByScheduleSaleTrue();
 
     List<Collection> findByNameLike(String name);
+
+    List<Collection> findByStockGreaterThan(int stock);
+
+    @Query("update Collection c set c.stock = ?2 where c.id = ?1")
+    @Transactional
+    @Modifying
+    int updateStock(Long id, int stock);
+
+    @Query("update Collection c set c.sale = ?2 where c.id = ?1")
+    @Transactional
+    @Modifying
+    int updateSale(Long id, int sale);
+
+    @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);
+
+    @Query("select new com.izouma.nineth.dto.CollectionStockAndSale(c.id, c.stock, c.sale) from Collection c where c.stock > 0")
+    List<CollectionStockAndSale> getStockAndSale();
 }

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

@@ -17,9 +17,16 @@ public interface IdentityAuthRepo extends JpaRepository<IdentityAuth, Long>, Jpa
     @Transactional
     void softDelete(Long id);
 
+    @Query("update IdentityAuth t set t.del = true where t.userId = ?1")
+    @Modifying
+    @Transactional
+    void softDeleteByUserId(Long userId);
+
     List<IdentityAuth> findByUserIdAndDelFalse(Long userId);
 
     Optional<IdentityAuth> findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(Long userId, AuthStatus status);
 
     Optional<IdentityAuth> findByIdAndDelFalse(Long id);
+
+    List<IdentityAuth> findAllByIdNoAndUserIdIsNotAndDelFalse(String idNo, Long userId);
 }

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

@@ -1,10 +1,12 @@
 package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.Recommend;
+import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
+import org.springframework.lang.NonNull;
 
 import javax.transaction.Transactional;
 
@@ -13,4 +15,7 @@ public interface RecommendRepo extends JpaRepository<Recommend, Long>, JpaSpecif
     @Modifying
     @Transactional
     void softDelete(Long id);
+
+    @CacheEvict(value = "recommend", allEntries = true)
+    Recommend save(@NonNull Recommend record);
 }

+ 8 - 2
src/main/java/com/izouma/nineth/repo/UserRepo.java

@@ -2,7 +2,7 @@ package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.security.Authority;
-import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -20,10 +20,13 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
     @Query("update User u set u.del = true where u.id = ?1")
     void softDelete(Long id);
 
-    @CacheEvict(value = "user", key = "#user.username", allEntries = true)
     @NonNull
+    @CachePut(value = "userInfo",key = "#user.id")
     User save(@NonNull User user);
 
+    @Cacheable("userInfo")
+    Optional<User> findById(@NonNull Long id);
+
     @Cacheable("user")
     User findByUsernameAndDelFalse(String username);
 
@@ -165,4 +168,7 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
     public void increaseSales(Long id, int num);
 
     List<User> findByInviteCode(String inviteCode);
+
+    @Query("select phone from User where id = ?1 and del = false")
+    String findPhoneById(Long id);
 }

+ 1 - 2
src/main/java/com/izouma/nineth/security/JwtAuthorizationTokenFilter.java

@@ -1,6 +1,5 @@
 package com.izouma.nineth.security;
 
-import com.izouma.nineth.repo.UserTokenRepo;
 import io.jsonwebtoken.ExpiredJwtException;
 import io.jsonwebtoken.SignatureException;
 import lombok.extern.slf4j.Slf4j;
@@ -54,7 +53,7 @@ public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
                 log.error(e.getMessage());
             }
         } else {
-            log.warn("couldn't find bearer string, will ignore the header");
+            // log.warn("couldn't find bearer string, will ignore the header");
         }
 
         log.debug("checking authentication for user '{}'", username);

+ 2 - 0
src/main/java/com/izouma/nineth/security/WebSecurityConfig.java

@@ -98,6 +98,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers("/activity/get/*").permitAll()
                 .antMatchers("/appVersion/checkIosReview").permitAll()
                 .antMatchers("/druid/**").permitAll()
+                .antMatchers("/testmq/**").permitAll()
+                .antMatchers("/teststock/**").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to

+ 2 - 2
src/main/java/com/izouma/nineth/service/AirDropService.java

@@ -60,8 +60,8 @@ public class AirDropService {
                     } else {
                         assetService.createAsset(collection, user, null, null, "空投", collectionService.getNextNumber(collection.getId()));
                     }
-                    collectionRepo.increaseStock(collection.getId(), -1);
-                    collectionRepo.increaseSale(collection.getId(), 1);
+                    collectionService.decreaseStock(collection.getId(), 1);
+                    collectionService.increaseSale(collection.getId(), 1);
                 } catch (Exception e) {
                     log.error("空投出错", e);
                 }

+ 36 - 34
src/main/java/com/izouma/nineth/service/AssetMintService.java

@@ -17,7 +17,8 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.context.ApplicationContext;
-import org.springframework.scheduling.annotation.Async;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
 import org.springframework.stereotype.Service;
 
 import java.io.File;
@@ -31,10 +32,11 @@ public class AssetMintService {
     private NFTService         nftService;
     private ApplicationContext applicationContext;
 
-    @Async
-    public void mint(Long assetId, Long userId) {
-        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
-        if (StringUtils.isEmpty(user.getPublicKey())) {
+    @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 1000))
+    public void mint(Long assetId) {
+        Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("asset不存在"));
+        User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
+        if (StringUtils.isEmpty(user.getNftAccount())) {
             NFTAccount account = nftService.createAccount(user.getUsername() + "_");
             user.setNftAccount(account.getAccountId());
             user.setKmsId(account.getAccountKmsId());
@@ -42,7 +44,6 @@ public class AssetMintService {
             userRepo.save(user);
         }
         try {
-            Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("asset不存在"));
             NFT nft = nftService.createToken(user.getNftAccount(), asset.getTokenId());
             if (nft != null) {
                 asset.setTokenId(nft.getTokenId());
@@ -62,37 +63,37 @@ public class AssetMintService {
         }
     }
 
-    @Async
-    public void mint(Asset asset) {
-        User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
-        if (StringUtils.isEmpty(user.getPublicKey())) {
-            NFTAccount account = nftService.createAccount(user.getUsername() + "_");
-            user.setNftAccount(account.getAccountId());
-            user.setKmsId(account.getAccountKmsId());
-            user.setPublicKey(account.getPublicKey());
-            userRepo.save(user);
-        }
-        try {
-            NFT nft = nftService.createToken(user.getNftAccount(), asset.getTokenId());
-            if (nft != null) {
-                asset.setTokenId(nft.getTokenId());
-                asset.setBlockNumber(nft.getBlockNumber());
-                asset.setTxHash(nft.getTxHash());
-                asset.setGasUsed(nft.getGasUsed());
-                if (asset.getIpfsUrl() == null) {
-                    asset.setIpfsUrl(ipfsUpload(asset.getPic().get(0).getUrl()));
-                }
-                assetRepo.save(asset);
-            }
-        } catch (Exception e) {
-            log.error("铸造失败", e);
-        }
-        applicationContext.publishEvent(new CreateAssetEvent(this, true, asset));
-    }
+//    @Async
+//    public void mint(Asset asset) {
+//        User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
+//        if (StringUtils.isEmpty(user.getPublicKey())) {
+//            NFTAccount account = nftService.createAccount(user.getUsername() + "_");
+//            user.setNftAccount(account.getAccountId());
+//            user.setKmsId(account.getAccountKmsId());
+//            user.setPublicKey(account.getPublicKey());
+//            userRepo.save(user);
+//        }
+//        try {
+//            NFT nft = nftService.createToken(user.getNftAccount(), asset.getTokenId());
+//            if (nft != null) {
+//                asset.setTokenId(nft.getTokenId());
+//                asset.setBlockNumber(nft.getBlockNumber());
+//                asset.setTxHash(nft.getTxHash());
+//                asset.setGasUsed(nft.getGasUsed());
+//                if (asset.getIpfsUrl() == null) {
+//                    asset.setIpfsUrl(ipfsUpload(asset.getPic().get(0).getUrl()));
+//                }
+//                assetRepo.save(asset);
+//            }
+//        } catch (Exception e) {
+//            log.error("铸造失败", e);
+//        }
+//        applicationContext.publishEvent(new CreateAssetEvent(this, true, asset));
+//    }
 
     public String ipfsUpload(String url) {
         try {
-            IPFS ipfs = new IPFS("112.74.34.84", 5001);
+            IPFS ipfs = new IPFS("120.24.204.226", 5001);
             HttpRequest request = HttpRequest.get(url);
             File file = File.createTempFile("ipfs", ".tmp");
             request.receive(file);
@@ -100,6 +101,7 @@ public class AssetMintService {
             MerkleNode put = ipfs.add(file1).get(0);
             Multihash multihash = ipfs.pin.add(put.hash).get(0);
             log.info("上传ipfs成功 {}", multihash.toBase58());
+            file.delete();
             return multihash.toBase58();
         } catch (Exception e) {
         }

+ 11 - 13
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.service;
 
 import com.izouma.nineth.TokenHistory;
+import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.UserHistory;
@@ -10,7 +11,6 @@ import com.izouma.nineth.enums.CollectionType;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.event.TransferAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
-import com.izouma.nineth.lock.RedisLockable;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SecurityUtils;
@@ -18,15 +18,18 @@ import com.izouma.nineth.utils.TokenUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.client.producer.DefaultMQProducer;
+import org.apache.rocketmq.common.message.Message;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.BeanUtils;
 import org.springframework.context.ApplicationContext;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-import org.springframework.web.bind.annotation.RequestParam;
 
 import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
@@ -46,8 +49,9 @@ public class AssetService {
     private ApplicationContext applicationContext;
     private OrderRepo          orderRepo;
     private TokenHistoryRepo   tokenHistoryRepo;
-    private AssetMintService   assetMintService;
     private SysConfigService   sysConfigService;
+    private RocketMQTemplate   rocketMQTemplate;
+    private GeneralProperties  generalProperties;
 
     public Page<Asset> all(PageQuery pageQuery) {
         return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
@@ -59,7 +63,7 @@ public class AssetService {
         asset.setNumber(number);
         asset.setOrderId(orderId);
         asset.setPrice(price);
-        assetRepo.save(asset);
+        assetRepo.saveAndFlush(asset);
 
         tokenHistoryRepo.save(TokenHistory.builder()
                 .tokenId(asset.getTokenId())
@@ -72,7 +76,7 @@ public class AssetService {
                 .operation(type)
                 .price(price)
                 .build());
-        assetMintService.mint(asset);
+        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
         return asset;
     }
 
@@ -95,7 +99,7 @@ public class AssetService {
                 .operation(type)
                 .price(price)
                 .build());
-        assetMintService.mint(asset.getId(), user.getId());
+        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
         return asset;
     }
 
@@ -316,12 +320,6 @@ public class AssetService {
         return tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(tokenId);
     }
 
-    @RedisLockable(key = "#id", expiration = 60, isWaiting = true)
-    public void testLock(String id, String i) throws InterruptedException {
-        Thread.sleep(1000);
-        log.info("" + i);
-    }
-
     public void setHistory() {
         List<Asset> assets = assetRepo.findByCreatedAtBefore(LocalDateTime.of(2021, 11, 22, 23, 59, 59));
         assets.parallelStream().forEach(asset -> {
@@ -375,7 +373,7 @@ public class AssetService {
 
     public String mint() {
         for (Asset asset : assetRepo.findByTxHashIsNullAndTokenIdNotNullAndCreatedAtBefore(LocalDateTime.now())) {
-            assetMintService.mint(asset);
+            rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
         }
         return "ok";
     }

+ 8 - 0
src/main/java/com/izouma/nineth/service/CacheService.java

@@ -20,4 +20,12 @@ public class CacheService {
     @CacheEvict(value = "user", key = "#username")
     public void clearUser(String username) {
     }
+
+    @CacheEvict(value = "userInfo", key = "#id")
+    public void clearUserInfo(Long id) {
+    }
+
+    @CacheEvict(value = "recommend", allEntries = true)
+    public void clearRecommend() {
+    }
 }

+ 91 - 10
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -1,9 +1,12 @@
 package com.izouma.nineth.service;
 
 import com.alibaba.fastjson.JSON;
+import com.izouma.nineth.annotations.Debounce;
+import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.CollectionDTO;
+import com.izouma.nineth.dto.CollectionStockAndSale;
 import com.izouma.nineth.dto.CreateBlindBox;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.CollectionSource;
@@ -13,16 +16,19 @@ import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 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.RedisTemplate;
 import org.springframework.scheduling.TaskScheduler;
 import org.springframework.stereotype.Service;
 
@@ -37,16 +43,20 @@ import java.util.stream.Collectors;
 
 @Service
 @AllArgsConstructor
+@Slf4j
 public class CollectionService {
 
-    private CollectionRepo   collectionRepo;
-    private LikeRepo         likeRepo;
-    private BlindBoxItemRepo blindBoxItemRepo;
-    private AppointmentRepo  appointmentRepo;
-    private UserRepo         userRepo;
-    private AssetService     assetService;
-    private TaskScheduler    taskScheduler;
-    private CacheService     cacheService;
+    private CollectionRepo                collectionRepo;
+    private LikeRepo                      likeRepo;
+    private BlindBoxItemRepo              blindBoxItemRepo;
+    private AppointmentRepo               appointmentRepo;
+    private UserRepo                      userRepo;
+    private AssetService                  assetService;
+    private TaskScheduler                 taskScheduler;
+    private CacheService                  cacheService;
+    private RedisTemplate<String, Object> redisTemplate;
+    private RocketMQTemplate              rocketMQTemplate;
+    private GeneralProperties             generalProperties;
 
     private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();
 
@@ -56,6 +66,14 @@ public class CollectionService {
         for (Collection collection : collections) {
             onShelfTask(collection);
         }
+        for (CollectionStockAndSale collection : collectionRepo.getStockAndSale()) {
+            if (redisTemplate.opsForValue().get("collectionStock::" + collection.getId()) == null) {
+                redisTemplate.opsForValue().set("collectionStock::" + collection.getId(), collection.getStock());
+            }
+            if (redisTemplate.opsForValue().get("collectionSale::" + collection.getId()) == null) {
+                redisTemplate.opsForValue().set("collectionSale::" + collection.getId(), collection.getSale());
+            }
+        }
     }
 
     public Page<Collection> all(PageQuery pageQuery) {
@@ -113,6 +131,8 @@ public class CollectionService {
         }
         record = collectionRepo.save(record);
         onShelfTask(record);
+        redisTemplate.opsForValue().set("collectionStock::" + record.getId(), record.getStock());
+        redisTemplate.opsForValue().set("collectionSale::" + record.getId(), record.getSale());
         return record;
     }
 
@@ -122,6 +142,7 @@ public class CollectionService {
                 record.getDetail(), JSON.toJSONString(record.getPrivileges()),
                 JSON.toJSONString(record.getProperties()), JSON.toJSONString(record.getModel3d()),
                 record.getMaxCount(), record.getCountId(), record.isScanCode());
+
         record = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
         onShelfTask(record);
         return record;
@@ -224,7 +245,7 @@ public class CollectionService {
         createBlindBox.getItems().stream().parallel().forEach(item -> {
             Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny()
                     .orElseThrow(new BusinessException("所选藏品不存在"));
-            collectionRepo.increaseStock(collection.getId(), -item.getTotal());
+            decreaseStock(collection.getId(), item.getTotal());
             BlindBoxItem blindBoxItem = new BlindBoxItem();
             BeanUtils.copyProperties(collection, blindBoxItem);
             blindBoxItem.setId(null);
@@ -235,6 +256,7 @@ public class CollectionService {
             blindBoxItem.setRare(item.isRare());
             blindBoxItem.setBlindBoxId(blindBox.getId());
             blindBoxItemRepo.save(blindBoxItem);
+            log.info("createBlindBoxItemSuccess" + blindBoxItem.getId());
         });
         return blindBox;
     }
@@ -256,6 +278,8 @@ public class CollectionService {
     }
 
     public BlindBoxItem draw(Long collectionId) {
+        log.info("开始盲盒抽卡 {}", collectionId);
+        long t = System.currentTimeMillis();
         List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(collectionId);
 
         Map<BlindBoxItem, Range<Integer>> randomRange = new HashMap<>();
@@ -306,6 +330,7 @@ public class CollectionService {
         winItem.setStock(winItem.getStock() - 1);
         winItem.setSale(winItem.getSale() + 1);
         blindBoxItemRepo.save(winItem);
+        log.info("盲盒抽卡成功 {}ms", System.currentTimeMillis() - t);
         return winItem;
     }
 
@@ -322,7 +347,63 @@ public class CollectionService {
         if (collection.getType() == CollectionType.BLIND_BOX) {
             throw new BusinessException("盲盒无法增发");
         }
-        collectionRepo.increaseStock(id, number);
+        increaseStock(id, number);
         collectionRepo.increaseTotal(id, number);
     }
+
+    public Long increaseStock(Long id, int number) {
+        if (redisTemplate.opsForValue().get("collectionStock::" + id) == null) {
+            redisTemplate.opsForValue().set("collectionStock::" + id, collectionRepo.getStock(id));
+        }
+        Long stock = redisTemplate.opsForValue().increment("collectionStock::" + id, 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, collectionRepo.getStock(id));
+        }
+        Long stock = redisTemplate.opsForValue().decrement("collectionStock::" + id, number);
+        rocketMQTemplate.convertAndSend(generalProperties.getUpdateStockTopic(), id);
+        return stock;
+    }
+
+    public Long increaseSale(Long id, int number) {
+        if (redisTemplate.opsForValue().get("collectionSale::" + id) == null) {
+            redisTemplate.opsForValue().set("collectionSale::" + id, collectionRepo.getSale(id));
+        }
+        Long sale = redisTemplate.opsForValue().increment("collectionSale::" + id, 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, collectionRepo.getSale(id));
+        }
+        Long sale = redisTemplate.opsForValue().decrement("collectionSale::" + id, number);
+        rocketMQTemplate.convertAndSend(generalProperties.getUpdateSaleTopic(), id);
+        return sale;
+    }
+
+    @Debounce(key = "#id", delay = 500)
+    public void syncStock(Long id) {
+        Integer stock = (Integer) redisTemplate.opsForValue().get("collectionStock::" + id);
+        if (stock != null) {
+            log.info("同步库存信息{}", id);
+            collectionRepo.updateStock(id, stock);
+            cacheService.clearCollection(id);
+        }
+    }
+
+    @Debounce(key = "#id", delay = 500)
+    public void syncSale(Long id) {
+        Integer sale = (Integer) redisTemplate.opsForValue().get("collectionSale::" + id);
+        if (sale != null) {
+            log.info("同步销量信息{}", id);
+            collectionRepo.updateSale(id, sale);
+            cacheService.clearCollection(id);
+        }
+    }
 }

+ 15 - 0
src/main/java/com/izouma/nineth/service/DebounceTestService.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.annotations.Debounce;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class DebounceTestService {
+
+    @Debounce(key = "#name", delay = 200)
+    public void testDebounce(String name, String data) {
+        log.info("running debounce method:{} {}", name, data);
+    }
+}

+ 3 - 2
src/main/java/com/izouma/nineth/service/GiftOrderService.java

@@ -68,6 +68,7 @@ public class GiftOrderService {
     private AssetService      assetService;
     private AdapayProperties  adapayProperties;
     private GeneralProperties generalProperties;
+    private SnowflakeIdWorker snowflakeIdWorker;
 
     @Transactional
     public GiftOrder gift(Long userId, Long assetId, Long toUserId) {
@@ -132,7 +133,7 @@ public class GiftOrderService {
             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.getGasPrice().stripTrailingZeros().toPlainString());
             bizContent.put("disable_pay_channels", "pcredit,creditCard");
             if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
@@ -208,7 +209,7 @@ public class GiftOrderService {
         }
 
         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.getGasPrice().setScale(2, RoundingMode.HALF_UP).toPlainString());
         paymentParams.put("app_id", adapayProperties.getAppId());
         paymentParams.put("pay_channel", payChannel);

+ 10 - 0
src/main/java/com/izouma/nineth/service/IdentityAuthService.java

@@ -13,6 +13,7 @@ import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 @AllArgsConstructor
@@ -58,4 +59,13 @@ public class IdentityAuthService {
         user.setAuthStatus(status);
         userRepo.save(user);
     }
+
+    public List<User> repeat(String idNo, Long userId) {
+        List<IdentityAuth> auths = identityAuthRepo.findAllByIdNoAndUserIdIsNotAndDelFalse(idNo, userId);
+        if (auths.isEmpty()) {
+            return null;
+        }
+        List<Long> userIds = auths.stream().map(IdentityAuth::getUserId).distinct().collect(Collectors.toList());
+        return userRepo.findByIdInAndDelFalse(userIds);
+    }
 }

+ 6 - 5
src/main/java/com/izouma/nineth/service/NFTService.java

@@ -33,11 +33,12 @@ public class NFTService {
     private final RestClient           restClient;
     private final RestClientProperties restClientProperties;
     private final GeneralProperties    generalProperties;
+    private final SnowflakeIdWorker    snowflakeIdWorker;
 
     @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 5000), value = BusinessException.class)
     public NFTAccount createAccount(String username) {
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(Constants.bizId)
                 .account(restClientProperties.getAccount())
                 .mykmsKeyId(restClientProperties.getKmsId())
@@ -68,7 +69,7 @@ public class NFTService {
         jsonArray.add(Utils.getIdentityByName(toAccount));
         jsonArray.add(new BigInteger(tokenId, 16));
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName(generalProperties.getContractName())
@@ -125,7 +126,7 @@ public class NFTService {
 
     public String ownerOf(String tokenId) throws Exception {
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName(generalProperties.getContractName())
@@ -164,7 +165,7 @@ public class NFTService {
         jsonArray.add(Utils.getIdentityByName("9th_HCWWflAZ_"));
         jsonArray.add("95057808871064671354760012409081314299");
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName("raex12")
@@ -201,7 +202,7 @@ public class NFTService {
         jsonArray.add(true);
 
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName(generalProperties.getContractName())

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

@@ -24,10 +24,12 @@ 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.TransferAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.security.Authority;
+import com.izouma.nineth.service.sms.SmsService;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SnowflakeIdWorker;
 import lombok.AllArgsConstructor;
@@ -36,6 +38,8 @@ import org.apache.commons.codec.EncoderException;
 import org.apache.commons.codec.net.URLCodec;
 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.context.event.EventListener;
 import org.springframework.core.env.Environment;
 import org.springframework.data.domain.Page;
@@ -71,121 +75,141 @@ public class OrderService {
     private AssetRepo                     assetRepo;
     private UserCouponRepo                userCouponRepo;
     private CollectionService             collectionService;
-    private RedisTemplate<String, Object> redisTemplate;
     private CommissionRecordRepo          commissionRecordRepo;
     private AdapayProperties              adapayProperties;
     private GeneralProperties             generalProperties;
+    private RocketMQTemplate              rocketMQTemplate;
+    private RedisTemplate<String, Object> redisTemplate;
+    private SnowflakeIdWorker             snowflakeIdWorker;
+    private SmsService                    smsService;
 
     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) {
+        long t = System.currentTimeMillis();
+        qty = 1;
+        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("该兑换券不可用");
+            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.isSalable()) {
-            throw new BusinessException("该藏品当前不可购买");
-        }
-        if (!collection.isOnShelf()) {
-            if (!collection.isScanCode()) {
-                throw new BusinessException("藏品已下架");
+            if (!collection.isOnShelf()) {
+                if (!collection.isScanCode()) {
+                    throw new BusinessException("藏品已下架");
+                }
             }
-        }
-        if (qty > collection.getStock()) {
-            throw new BusinessException("库存不足");
-        }
 
-
-        if (collection.getMaxCount() > 0) {
-            int count;
-            if (StringUtils.isNotBlank(collection.getCountId())) {
-                count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
-            } else {
-                count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
+            if (!collection.isSalable()) {
+                throw new BusinessException("该藏品当前不可购买");
             }
-            if (count >= collection.getMaxCount()) {
-                throw new BusinessException("限购" + collection.getMaxCount() + "件");
+
+            if (collection.getMaxCount() > 0) {
+                int count;
+                if (StringUtils.isNotBlank(collection.getCountId())) {
+                    count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
+                } else {
+                    count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
+                }
+                if (count >= collection.getMaxCount()) {
+                    throw new BusinessException("限购" + collection.getMaxCount() + "件");
+                }
             }
-        }
 
-        UserAddress userAddress = null;
-        if (addressId != null) {
-            userAddress = userAddressRepo.findById(addressId).orElseThrow(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())
-                .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)
-                .countId(collection.getCountId())
-                .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()
+                    .id(Optional.ofNullable(id).orElse(snowflakeIdWorker.nextId()))
+                    .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)
+                    .countId(collection.getCountId())
+                    .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(collectionId, 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(collectionId, 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) {
@@ -199,7 +223,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"))) {
@@ -283,7 +307,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);
@@ -405,22 +429,28 @@ public class OrderService {
                 orderRepo.save(order);
                 assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
                         winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
-                addSales(winItem.getMinterId(), order.getQty());
             } 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);
                 }
-                addSales(collection.getMinterId(), order.getQty());
             }
             commission(order);
+            collectionService.increaseSale(order.getCollectionId(), order.getQty());
         } else if (order.getStatus() == OrderStatus.CANCELLED) {
         }
+
     }
 
     @EventListener
@@ -462,20 +492,19 @@ public class OrderService {
         if (order.getStatus() != OrderStatus.NOT_PAID) {
             throw new BusinessException("已支付订单无法取消");
         }
-        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);
+        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) {
                 asset.setStatus(AssetStatus.NORMAL);
                 assetRepo.save(asset);
             }
-            collectionRepo.setOnShelf(collection.getId(), true);
+            collectionRepo.setOnShelf(order.getCollectionId(), true);
         }
-        collectionRepo.increaseSale(collection.getId(), -order.getQty());
-        collectionRepo.increaseStock(collection.getId(), order.getQty());
+        collectionService.increaseStock(order.getCollectionId(), order.getQty());
 
         order.setStatus(OrderStatus.CANCELLED);
         order.setCancelTime(LocalDateTime.now());
@@ -488,6 +517,8 @@ public class OrderService {
                 userCouponRepo.save(coupon);
             });
         }
+        rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
+        log.info("取消订单{}", order.getId());
     }
 
     @Scheduled(fixedRate = 30000)
@@ -497,7 +528,7 @@ public class OrderService {
         }
         List<Order> orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID,
                 LocalDateTime.now().minusSeconds(210));
-        orders.forEach(o -> {
+        orders.parallelStream().forEach(o -> {
             try {
                 cancel(o);
             } catch (Exception ignored) {
@@ -508,12 +539,6 @@ public class OrderService {
     public void refundCancelled(Order order) {
     }
 
-    public synchronized void addSales(Long userId, int num) {
-        if (userId != null) {
-            userRepo.increaseSales(userId, num);
-        }
-    }
-
     public void setNumber() {
         for (Collection collection : collectionRepo.findAll()) {
             if (collection.getSource() != CollectionSource.OFFICIAL) continue;
@@ -573,7 +598,11 @@ 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);
     }
+
+    public Object queryCreateOrder(String id) {
+        return redisTemplate.opsForValue().get("createOrder::" + id);
+    }
 }

+ 62 - 0
src/main/java/com/izouma/nineth/service/RedisLock.java

@@ -0,0 +1,62 @@
+package com.izouma.nineth.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class RedisLock {
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 加锁
+     *
+     * @param key   productId - 商品的唯一标志
+     * @param value 当前时间+超时时间 也就是时间戳
+     * @return
+     */
+    public boolean lock(String key, String value) {
+        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {//对应setnx命令
+            //可以成功设置,也就是key不存在
+            return true;
+        }
+
+        //判断锁超时 - 防止原来的操作异常,没有运行解锁操作  防止死锁
+        String currentValue = stringRedisTemplate.opsForValue().get(key);
+        //如果锁过期
+        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {//currentValue不为空且小于当前时间
+            //获取上一个锁的时间value
+            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);//对应getset,如果key存在
+
+            //假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
+            //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
+            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
+                //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * 解锁
+     *
+     * @param key
+     * @param value
+     */
+    public void unlock(String key, String value) {
+        try {
+            String currentValue = stringRedisTemplate.opsForValue().get(key);
+            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
+                stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key
+            }
+        } catch (Exception e) {
+            log.error("[Redis分布式锁] 解锁出现异常了,{}", e);
+        }
+    }
+}

+ 9 - 2
src/main/java/com/izouma/nineth/service/UserService.java

@@ -67,11 +67,10 @@ public class UserService {
     private NFTService        nftService;
     private CacheService      cacheService;
 
-    @CacheEvict(value = "user", key = "#user.username")
     public User update(User user) {
         User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录"));
         ObjUtils.merge(orig, user);
-        orig = userRepo.save(orig);
+        orig = save(orig);
         userRepo.updateAssetMinter(orig.getId());
         userRepo.updateAssetOwner(orig.getId());
         userRepo.updateCollectionMinter(orig.getId());
@@ -83,6 +82,12 @@ public class UserService {
         return orig;
     }
 
+    public User save(User user) {
+        cacheService.clearUserInfo(user.getId());
+        cacheService.clearUser(user.getUsername());
+        return userRepo.save(user);
+    }
+
     public Page<User> all(PageQuery pageQuery) {
         Specification<User> specification = JpaUtils.toSpecification(pageQuery, User.class);
 
@@ -160,6 +165,8 @@ public class UserService {
             user.setPhone(user.getPhone() + "###" + RandomStringUtils.randomAlphabetic(8));
         }
         userRepo.save(user);
+        //删除实名认证
+        identityAuthRepo.softDeleteByUserId(id);
     }
 
     public User loginByPhone(String phone, String code) {

+ 34 - 0
src/main/java/com/izouma/nineth/service/sms/AliSmsService.java

@@ -37,6 +37,8 @@ public class AliSmsService implements SmsService {
     private String smsSign;
     @Value("${aliyun.sms-code}")
     private String smsCode;
+    @Value("${aliyun.sell-out-code}")
+    private String sellOutCode;
 
     @Autowired
     private SmsRecordRepo smsRecordRepo;
@@ -117,4 +119,36 @@ public class AliSmsService implements SmsService {
                 .compact();
     }
 
+    @Override
+    public void sellOut(String phone) {
+
+        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
+        IAcsClient client = new DefaultAcsClient(profile);
+
+        CommonRequest request = new CommonRequest();
+        request.setMethod(MethodType.POST);
+        request.setDomain("dysmsapi.aliyuncs.com");
+        request.setVersion("2017-05-25");
+        request.setAction("SendSms");
+        request.putQueryParameter("PhoneNumbers", phone);
+        request.putQueryParameter("SignName", smsSign);
+        request.putQueryParameter("TemplateCode", sellOutCode);
+
+        try {
+            CommonResponse response = client.getCommonResponse(request);
+            if (response.getHttpStatus() != 200) {
+                log.error("发送失败," + response.getHttpStatus() + "," + response.getData());
+            }
+            log.info("send sms response {}", response.getData());
+            JSONObject jsonObject = new JSONObject(response.getData());
+            if (!"ok".equalsIgnoreCase(jsonObject.getString("Code"))) {
+                log.error("发送失败," + jsonObject.getString("Message"));
+            }
+
+        } catch (ClientException | JSONException e) {
+            e.printStackTrace();
+            log.error("发送失败," + e.getMessage());
+        }
+    }
+
 }

+ 2 - 0
src/main/java/com/izouma/nineth/service/sms/SmsService.java

@@ -13,6 +13,8 @@ public interface SmsService {
 
     String verify(String phone, String code);
 
+    void sellOut(String phone);
+
     default String verifyToken(String token) {
         try {
             Claims claims = Jwts.parser()

+ 1 - 1
src/main/java/com/izouma/nineth/utils/TokenUtils.java

@@ -7,7 +7,7 @@ import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
 
 public class TokenUtils {
-    public static String genTokenId() {
+    public synchronized static String genTokenId() {
         try {
             Random random = ThreadLocalRandom.current();
             byte[] r = new byte[32];

+ 8 - 3
src/main/java/com/izouma/nineth/web/CollectionController.java

@@ -14,7 +14,9 @@ import com.izouma.nineth.utils.excel.ExcelUtils;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
 import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.domain.Page;
 import org.springframework.web.bind.annotation.*;
 
@@ -97,14 +99,17 @@ public class CollectionController extends BaseController {
     }
 
     @GetMapping("/recommend")
+    @Cacheable("recommend")
     public List<CollectionDTO> recommend(@RequestParam String type) {
-        return collectionService.toDTO(collectionRepo.recommend(type).stream().map(rc -> {
+        return collectionRepo.recommend(type).stream().map(rc -> {
             if (StringUtils.isNotBlank(rc.getRecommend().getPic())) {
                 rc.getCollection().setPic(Collections.singletonList(new FileObject(null, rc.getRecommend()
                         .getPic(), null, null)));
             }
-            return rc.getCollection();
-        }).collect(Collectors.toList()));
+            CollectionDTO collectionDTO = new CollectionDTO();
+            BeanUtils.copyProperties(rc.getCollection(), collectionDTO);
+            return collectionDTO;
+        }).collect(Collectors.toList());
     }
 }
 

+ 9 - 1
src/main/java/com/izouma/nineth/web/IdentityAuthController.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.web;
 
 import com.izouma.nineth.domain.IdentityAuth;
+import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.AuthStatus;
 import com.izouma.nineth.exception.BusinessException;
@@ -8,6 +9,7 @@ import com.izouma.nineth.repo.IdentityAuthRepo;
 import com.izouma.nineth.service.IdentityAuthService;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
+import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -24,7 +26,7 @@ public class IdentityAuthController extends BaseController {
     private IdentityAuthService identityAuthService;
     private IdentityAuthRepo    identityAuthRepo;
 
-//    @PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
+    //    @PreAuthorize("hasAnyRole('ADMIN','OPERATOR')")
     @PostMapping("/all")
     public Page<IdentityAuth> all(@RequestBody PageQuery pageQuery) {
         return identityAuthService.all(pageQuery);
@@ -67,5 +69,11 @@ public class IdentityAuthController extends BaseController {
     public void deny(@RequestParam Long id) {
         identityAuthService.audit(id, AuthStatus.FAIL);
     }
+
+    @ApiOperation("查找重复身份证")
+    @PostMapping("/repeat")
+    public List<User> repeat(@RequestParam String idNo, @RequestParam Long userId) {
+        return identityAuthService.repeat(idNo, userId);
+    }
 }
 

+ 18 - 1
src/main/java/com/izouma/nineth/web/OrderController.java

@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.time.OffsetTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -99,7 +100,18 @@ public class OrderController extends BaseController {
                         @RequestParam(required = false) Long couponId,
                         @RequestParam(required = false) Long invitor) {
         return orderService.create(SecurityUtils.getAuthenticatedUser().getId(),
-                collectionId, qty, addressId, couponId, invitor);
+                collectionId, qty, addressId, couponId, invitor, null);
+    }
+
+    @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) {
+        return new HashMap<>() {{
+            put("id", orderService.mqCreate(SecurityUtils.getAuthenticatedUser().getId(),
+                    collectionId, qty, addressId, couponId, invitor));
+        }};
     }
 
     @PostMapping("/hide")
@@ -141,5 +153,10 @@ public class OrderController extends BaseController {
     public void testNotify(@RequestParam Long id) {
         orderService.notifyOrder(id, PayMethod.ALIPAY, new SnowflakeIdWorker(0, 0).nextId() + "");
     }
+
+    @GetMapping("/createResult")
+    public Object createResult(@RequestParam String id) {
+        return orderService.queryCreateOrder(id);
+    }
 }
 

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

@@ -36,11 +36,12 @@ 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 SnowflakeIdWorker snowflakeIdWorker;
 
     @PostMapping("/order/alipay")
     @ResponseBody
@@ -127,7 +128,7 @@ public class OrderNotifyController {
             status = jsonObject.getInteger("status");
         }
         if (status == 0) {
-            orderService.notifyOrder(orderId, PayMethod.WEIXIN, new SnowflakeIdWorker(0, 0).nextId() + "");
+            orderService.notifyOrder(orderId, PayMethod.WEIXIN, snowflakeIdWorker.nextId() + "");
         }
         return "ok";
     }

+ 23 - 0
src/main/java/com/izouma/nineth/web/RocketMqController.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.event.MyMqEvent;
+import lombok.AllArgsConstructor;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/testmq")
+@AllArgsConstructor
+public class RocketMqController {
+
+    private RocketMQTemplate rocketMQTemplate;
+
+    @GetMapping("/send")
+    public String send(String data) {
+        rocketMQTemplate.syncSend("test-add-topic", new MyMqEvent(data));
+        return "ok";
+    }
+
+}

+ 40 - 0
src/main/java/com/izouma/nineth/web/TestStockController.java

@@ -0,0 +1,40 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.service.RedisLock;
+import lombok.AllArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.PostConstruct;
+
+@RestController
+@RequestMapping("/teststock")
+@AllArgsConstructor
+public class TestStockController {
+
+    private RedisLock                     redisLock;
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @PostConstruct
+    public void init() {
+        redisTemplate.opsForValue().set("teststock", 1000);
+    }
+
+    @GetMapping("/setup")
+    public void setup() {
+        redisTemplate.opsForValue().getAndSet("teststock", 1000);
+    }
+
+    @GetMapping("/test")
+    public String test() {
+        int stock = Math.toIntExact(redisTemplate.opsForValue().decrement("teststock", 1));
+        if (stock < 0) {
+            redisTemplate.opsForValue().increment("teststock", 1);
+            return "库存不足";
+        } else {
+            return "ok";
+        }
+    }
+}

+ 99 - 3
src/main/resources/application.yaml

@@ -8,6 +8,10 @@ server:
   error:
     whitelabel:
       enabled: false
+  tomcat:
+    accept-count: 10000
+    threads:
+      max: 3000
 spring:
   profiles:
     active: dev
@@ -37,7 +41,7 @@ spring:
       # 初始化大小,最小,最大
       initial-size: 10
       min-idle: 10
-      maxActive: 300
+      maxActive: 5000
       # 配置获取连接等待超时的时间
       maxWait: 10000
       # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
@@ -129,12 +133,24 @@ aliyun:
   oss-domain: https://raex-meta.oss-cn-shenzhen.aliyuncs.com
   sms-sign: 华储艺术品中心
   sms-code: SMS_228870098
+  sell-out-code: SMS_232907378
+
 general:
   host: https://test.raex.vip
   contract-name: raex_new
-  name: 绿洲数字藏品中心
+  name: 绿洲宇宙
   org: 华储艺术品中心(深圳)有限公司
   short-name: 华储
+  create-order-group: create-order-group-dev
+  create-order-topic: create-order-topic-dev
+  update-stock-group: update-stock-group-dev
+  update-stock-topic: update-stock-topic-dev
+  update-sale-group: update-sale-group-dev
+  update-sale-topic: update-sale-topic-dev
+  order-notify-group: order-notify-group-dev
+  order-notify-topic: order-notify-topic-dev
+  mint-group: mint-group-dev
+  mint-topic: mint-topic-dev
 mychain:
   rest:
     bizid: a00e36c5
@@ -169,6 +185,11 @@ adapay:
   app-public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwN6xgd6Ad8v2hIIsQVnbt8a3JituR8o4Tc3B5WlcFR55bz4OMqrG/356Ur3cPbc2Fe8ArNd/0gZbC9q56Eb16JTkVNA/fye4SXznWxdyBPR7+guuJZHc/VW2fKH2lfZ2P3Tt0QkKZZoawYOGSMdIvO+WqK44updyax0ikK6JlNQIDAQAB
   wx-app-id:
   notify-url: https://test.raex.vip/notify/adapay
+rocketmq:
+  name-server: 120.24.204.226:9876
+  producer:
+    group: my-producer-dev
+    send-message-timeout: 30000
 ---
 
 spring:
@@ -181,6 +202,67 @@ spring:
     host: 127.0.0.1
     database: 0
     password: jV%93RtjUx82Tp
+general:
+  create-order-group: create-order-group-test
+  create-order-topic: create-order-topic-test
+  update-stock-group: update-stock-group-test
+  update-stock-topic: update-stock-topic-test
+  update-sale-group: update-sale-group-test
+  update-sale-topic: update-sale-topic-test
+  order-notify-group: order-notify-group-test
+  order-notify-topic: order-notify-topic-test
+  mint-group: mint-group-test
+  mint-topic: mint-topic-test
+---
+
+spring:
+  profiles: test1
+  datasource:
+    url: jdbc:mysql://rm-wz9sc79f5255780op.mysql.rds.aliyuncs.com/raex_test1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
+    username: raex
+    password: K@SdS7e6NTw4CK
+  redis:
+    host: 127.0.0.1
+    database: 2
+    password: jV%93RtjUx82Tp
+general:
+  host: https://www.raex.vip
+  create-order-group: create-order-group-test1
+  create-order-topic: create-order-topic-test1
+  update-stock-group: update-stock-group-test1
+  update-stock-topic: update-stock-topic-test1
+  update-sale-group: update-sale-group-test1
+  update-sale-topic: update-sale-topic-test1
+  order-notify-group: order-notify-group-test1
+  order-notify-topic: order-notify-topic-test1
+  mint-group: mint-group-test1
+  mint-topic: mint-topic-test1
+wx:
+  pay:
+    notify-url: https://test1.raex.vip/notify/order/weixin
+    refund-notify-url: https://test1.raex.vip/wx/refundNotify
+    return-url: https://test1.raex.vip/9th/orders
+alipay:
+  notify-url: https://test1.raex.vip/notify/order/alipay
+  return-url: https://test1.raex.vip/9th/home
+adapay:
+  notify-url: https://test1.raex.vip/notify/adapay
+rocketmq:
+  name-server: 172.29.50.102:9876
+  producer:
+    group: my-producer
+---
+
+spring:
+  profiles: local
+  datasource:
+    url: jdbc:mysql://192.168.50.10/raex_test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
+    username: root
+    password: 123456
+  redis:
+    host: 192.168.50.16
+    database: 0
+    password:
 ---
 
 spring:
@@ -195,6 +277,16 @@ spring:
     password: jV%93RtjUx82Tp
 general:
   host: https://www.raex.vip
+  create-order-group: create-order-group
+  create-order-topic: create-order-topic
+  update-stock-group: update-stock-group
+  update-stock-topic: update-stock-topic
+  update-sale-group: update-sale-group
+  update-sale-topic: update-sale-topic
+  order-notify-group: order-notify-group
+  order-notify-topic: order-notify-topic
+  mint-group: mint-group
+  mint-topic: mint-topic
 wx:
   pay:
     notify-url: https://www.raex.vip/notify/order/weixin
@@ -204,4 +296,8 @@ alipay:
   notify-url: https://www.raex.vip/notify/order/alipay
   return-url: https://www.raex.vip/9th/home
 adapay:
-  notify-url: https://www.raex.vip/notify/adapay
+  notify-url: https://www.raex.vip/notify/adapay
+rocketmq:
+  name-server: 172.29.50.102:9876
+  producer:
+    group: my-producer

+ 60 - 17
src/main/resources/logback-spring.xml

@@ -26,8 +26,30 @@
             </encoder>
         </appender>
 
-        <!-- ding open sdk log -->
-        <appender name="DING_OPEN_CLIENT_SDK_LOGGER_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
+<!--        <logger name="org.hibernate.SQL" level="DEBUG"/>-->
+<!--        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>-->
+<!--        <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>-->
+<!--        <logger name="org.springframework.jdbc.core.StatementCreatorUtils" level="TRACE"/>-->
+<!--        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>-->
+<!--        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>-->
+    </springProfile>
+
+    <springProfile name="local">
+        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+            <encoder>
+                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            </encoder>
+        </appender>
+        <root level="INFO">
+            <appender-ref ref="CONSOLE"/>
+        </root>
+        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>
+        <!--swagger 类型转换异常日志去除-->
+        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>
+        <logger name="org.freemarker" level="DEBUG"/>
+
+        <!-- dingding log -->
+        <appender name="HTTP_INVOKE_LOGGER_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
             <param name="append" value="true"/>
             <param name="encoding" value="UTF-8"/>
             <param name="threshold" value="INFO"/>
@@ -35,15 +57,13 @@
                 <pattern>${CONSOLE_LOG_PATTERN}</pattern>
             </encoder>
         </appender>
-        <logger name="HTTP_INVOKE_LOGGER" additivity="false">
-            <level value="WARN"/>
-            <appender-ref ref="HTTP_INVOKE_LOGGER_APPENDER"/>
-        </logger>
-        <logger name="DING_OPEN_CLIENT_SDK_LOGGER" additivity="false">
-            <level value="WARN"/>
-            <appender-ref ref="DING_OPEN_CLIENT_SDK_LOGGER_APPENDER"/>
-        </logger>
-        <!-- end of dingding log -->
+
+        <!--        <logger name="org.hibernate.SQL" level="DEBUG"/>-->
+        <!--        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>-->
+        <!--        <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>-->
+        <!--        <logger name="org.springframework.jdbc.core.StatementCreatorUtils" level="TRACE"/>-->
+        <!--        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>-->
+        <!--        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>-->
     </springProfile>
 
     <springProfile name="test">
@@ -61,12 +81,35 @@
         <root level="INFO">
             <appender-ref ref="FILE"/>
         </root>
-        <logger name="org.hibernate.SQL" level="DEBUG"/>
-        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
-        <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>
-        <logger name="org.springframework.jdbc.core.StatementCreatorUtils" level="TRACE"/>
-        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>
-        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>
+<!--        <logger name="org.hibernate.SQL" level="DEBUG"/>-->
+<!--        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>-->
+<!--        <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>-->
+<!--        <logger name="org.springframework.jdbc.core.StatementCreatorUtils" level="TRACE"/>-->
+<!--        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>-->
+<!--        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>-->
+    </springProfile>
+
+    <springProfile name="test1">
+        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+            <encoder>
+                <pattern>${FILE_LOG_PATTERN}</pattern>
+            </encoder>
+            <file>/var/www/9th_test_1/app.log</file>
+            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+                <fileNamePattern>app.log.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
+                <maxFileSize>10MB</maxFileSize>
+                <maxHistory>60</maxHistory>
+            </rollingPolicy>
+        </appender>
+        <root level="INFO">
+            <appender-ref ref="FILE"/>
+        </root>
+        <!--        <logger name="org.hibernate.SQL" level="DEBUG"/>-->
+        <!--        <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>-->
+        <!--        <logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>-->
+        <!--        <logger name="org.springframework.jdbc.core.StatementCreatorUtils" level="TRACE"/>-->
+        <!--        <logger name="cn.binarywang.wx.miniapp" level="DEBUG"/>-->
+        <!--        <logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="ERROR"/>-->
     </springProfile>
 
     <springProfile name="prod">

+ 38 - 5
src/main/vue/src/views/IdentityAuthList.vue

@@ -65,15 +65,23 @@
                         ></el-image>
                     </template>
                 </el-table-column>
-                <el-table-column prop="orgLicenseExpire" label="营业执照有效期" min-width="160" show-overflow-tooltip key="3">
+                <el-table-column
+                    prop="orgLicenseExpire"
+                    label="营业执照有效期"
+                    min-width="160"
+                    show-overflow-tooltip
+                    key="3"
+                >
                     <template v-slot="{ row }">
                         <span>{{ row.orgLicenseExpire || '长期' }}</span>
                     </template>
                 </el-table-column>
             </template>
             <template v-else>
-                <el-table-column prop="phone" label="手机" min-width="150" show-overflow-tooltip key="4"> </el-table-column>
-                <el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip key="5"> </el-table-column>
+                <el-table-column prop="phone" label="手机" min-width="150" show-overflow-tooltip key="4">
+                </el-table-column>
+                <el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip key="5">
+                </el-table-column>
             </template>
 
             <el-table-column prop="idNo" label="身份证" min-width="200" show-overflow-tooltip> </el-table-column>
@@ -99,8 +107,9 @@
             </el-table-column>
             <el-table-column prop="status" label="审核状态" :formatter="statusFormatter" width="120" align="center">
             </el-table-column>
-            <el-table-column label="操作" align="center" fixed="right" width="150">
+            <el-table-column label="操作" align="center" fixed="right" width="210">
                 <template slot-scope="{ row }">
+                    <el-button @click="repeat(row)" type="success" size="mini" plain> 查重 </el-button>
                     <el-button @click="pass(row)" type="primary" size="mini" plain v-if="row.status === 'PENDING'">
                         通过
                     </el-button>
@@ -131,6 +140,16 @@
             >
             </el-pagination>
         </div>
+
+        <el-dialog :visible.sync="showDialog" title="重复列表" width="800px" top="10vh">
+            <el-table :data="list" v-loading="dialogLoading" height="40vh" empty-text="暂无重复">
+                <el-table-column prop="id" label="ID" width="80"></el-table-column>
+                <el-table-column prop="nickname" label="昵称"></el-table-column>
+                <el-table-column prop="phone" label="手机"></el-table-column>
+                <el-table-column prop="createdAt" label="注册时间" min-width="110"></el-table-column>
+                <el-table-column prop="authStatus" label="认证状态" :formatter="statusFormatter"></el-table-column>
+            </el-table>
+        </el-dialog>
     </div>
 </template>
 <script>
@@ -153,7 +172,10 @@ export default {
                 { label: '失败', value: 'FAIL' }
             ],
             status: 'PENDING',
-            org: false
+            org: false,
+            showDialog: false,
+            dialogLoading: false,
+            list: []
         };
     },
     computed: {
@@ -263,6 +285,17 @@ export default {
                     this.getData();
                 })
                 .catch(e => {});
+        },
+        repeat(row) {
+            this.list = [];
+            this.showDialog = true;
+            this.dialogLoading = true;
+            this.$http.post('/identityAuth/repeat', { idNo: row.idNo, userId: row.userId }).then(res => {
+                if (res != '') {
+                    this.list = res;
+                }
+                this.dialogLoading = false;
+            });
         }
     }
 };

+ 19 - 0
src/test/java/com/izouma/nineth/CommonTest.java

@@ -61,6 +61,8 @@ import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
 import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.ForkJoinPool;
 import java.util.regex.Pattern;
 import java.util.stream.IntStream;
@@ -415,4 +417,21 @@ public class CommonTest {
             }
         }
     }
+
+    @Test
+    public void testnewSingleThreadScheduledExecutor() throws InterruptedException {
+        ExecutorService service = Executors.newFixedThreadPool(10);
+        final int[] c = {0};
+        for (int i = 0; i < 1000; i++) {
+            service.submit(new Runnable() {
+                @SneakyThrows
+                @Override
+                public void run() {
+                    Thread.sleep(300);
+                    System.out.println(++c[0]);
+                }
+            });
+        }
+        Thread.sleep(20000);
+    }
 }

+ 21 - 0
src/test/java/com/izouma/nineth/DebounceTest.java

@@ -0,0 +1,21 @@
+package com.izouma.nineth;
+
+import com.izouma.nineth.service.DebounceTestService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class DebounceTest extends ApplicationTests {
+    @Autowired
+    private DebounceTestService debounceTestService;
+
+    @Test
+    public void testDebounce() throws InterruptedException {
+        String[] names = new String[]{"name1"};
+        int c = 0;
+        for (int i = 0; i < 1000000000; i++) {
+            Thread.sleep(1);
+            debounceTestService.testDebounce(names[i % names.length], String.valueOf(++c));
+        }
+
+    }
+}

+ 7 - 4
src/test/java/com/izouma/nineth/RedisTest.java

@@ -2,7 +2,6 @@ package com.izouma.nineth;
 
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.BoundValueOperations;
 import org.springframework.data.redis.core.RedisTemplate;
 
 public class RedisTest extends ApplicationTests {
@@ -10,8 +9,12 @@ public class RedisTest extends ApplicationTests {
     private RedisTemplate<String, Object> redisTemplate;
 
     @Test
-    public void testINC() {
-        redisTemplate.opsForValue().increment("collectionNumber::2");
-        System.out.println(redisTemplate.opsForValue().get("collectionNumber::2"));
+    public void testOrder() {
+        System.out.println(redisTemplate.opsForValue().get("createOrder::931502789098471424"));
+    }
+
+    @Test
+    public void testasdf() {
+        System.out.println(redisTemplate.opsForValue().get("collectionStock::sdfasdf"));
     }
 }

+ 5 - 21
src/test/java/com/izouma/nineth/service/AdapayServiceTest.java

@@ -1,36 +1,18 @@
 package com.izouma.nineth.service;
 
 import com.alibaba.excel.EasyExcel;
-import com.alibaba.excel.annotation.ExcelProperty;
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.huifu.adapay.Adapay;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
-import com.huifu.adapay.model.AdapayCommon;
-import com.huifu.adapay.model.MerConfig;
-import com.huifu.adapay.model.Payment;
 import com.huifu.adapay.model.Refund;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.domain.AdaTrade;
 import com.izouma.nineth.repo.GiftOrderRepo;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.utils.SnowflakeIdWorker;
-import com.izouma.nineth.utils.excel.BigIntegerConverter;
-import com.izouma.nineth.utils.excel.LocalDateConverter;
-import com.izouma.nineth.utils.excel.LocalDateTimeConverter;
-import lombok.Data;
-import org.apache.commons.collections.MapUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -40,9 +22,11 @@ import java.util.stream.Collectors;
 
 public class AdapayServiceTest extends ApplicationTests {
     @Autowired
-    private OrderRepo     orderRepo;
+    private OrderRepo         orderRepo;
     @Autowired
-    private GiftOrderRepo giftOrderRepo;
+    private GiftOrderRepo     giftOrderRepo;
+    @Autowired
+    private SnowflakeIdWorker snowflakeIdWorker;
 
     private final String appId = "app_0e8d3acb-3d95-4ebb-8445-e470c378a787";
 
@@ -75,7 +59,7 @@ public class AdapayServiceTest extends ApplicationTests {
         for (AdaTrade adaTrade : list) {
             Map<String, Object> refundParams = new HashMap<>();
             refundParams.put("refund_amt", adaTrade.get交易金额());
-            refundParams.put("refund_order_no", new SnowflakeIdWorker(0, 0).nextId() + "");
+            refundParams.put("refund_order_no", snowflakeIdWorker.nextId() + "");
             try {
                 Map<String, Object> response = Refund.create(adaTrade.get订单号(), refundParams);
             } catch (BaseAdaPayException e) {

+ 8 - 1
src/test/java/com/izouma/nineth/service/AdapayTest.java

@@ -21,7 +21,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 
 import java.math.BigDecimal;
-import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
@@ -198,4 +197,12 @@ public class AdapayTest {
         }
         System.out.println(list);
     }
+
+    @Test
+    public void rrr() throws BaseAdaPayException {
+        Map<String, Object> queryParams = new HashMap<>();
+        queryParams.put("payment_id", "002112022011117055410326902706185465856");
+        Map<String, Object> refund = Refund.query(queryParams);
+        System.out.println(JSON.toJSONString(refund, SerializerFeature.PrettyFormat));
+    }
 }

+ 5 - 0
src/test/java/com/izouma/nineth/service/AssetMintServiceTest.java

@@ -13,4 +13,9 @@ public class AssetMintServiceTest extends ApplicationTests {
     @Test
     public void mint() {
     }
+
+    @Test
+    public void ipfsUpload() {
+        assetMintService.ipfsUpload("https://raex-meta.oss-cn-shenzhen.aliyuncs.com/nft/2022-01-16-00-49-11HVgyXDDl.png?x-oss-process=image/resize,h_800,m_lfit");
+    }
 }

+ 1 - 6
src/test/java/com/izouma/nineth/service/AssetServiceTest.java

@@ -48,11 +48,6 @@ class AssetServiceTest extends ApplicationTests {
     void testCancelConsignment() {
     }
 
-    @Test
-    public void testLock() throws InterruptedException {
-        assetService.testLock("123", "ddd");
-    }
-
     @Test
     public void testCreateAsset() {
         Order order = orderRepo.findById(4618L).get();
@@ -65,7 +60,7 @@ class AssetServiceTest extends ApplicationTests {
     @Test
     public void mint() {
         Asset asset = assetRepo.findById(4622L).get();
-        assetMintService.mint(asset);
+        assetMintService.mint(asset.getId());
     }
 
     @Test

+ 6 - 5
src/test/java/com/izouma/nineth/service/CollectionServiceTest.java

@@ -50,10 +50,10 @@ class CollectionServiceTest extends ApplicationTests {
 
     @Test
     public void batchUpload() throws IOException {
-        AtomicInteger num = new AtomicInteger(2001);
+        AtomicInteger num = new AtomicInteger(4001);
         List<BlindBoxItem> items = new ArrayList<>();
         String jsonStr = FileUtils.readFileToString(new File("/Users/drew/Downloads/003.json"), "UTF-8");
-        Arrays.stream(new File("/Users/drew/Downloads/s3-1000").listFiles())
+        Arrays.stream(new File("/Users/drew/Downloads/s5-1000").listFiles())
                 .filter(f -> Pattern.matches("^BJ.*\\.png$", f.getName()))
                 .parallel().forEach(file -> {
                     try {
@@ -66,9 +66,10 @@ class CollectionServiceTest extends ApplicationTests {
                         collection.setMinterId(8666L);
                         collection.setOnShelf(false);
                         collection.setSalable(false);
-                        collection.setPic(Collections.singletonList(new FileObject("", "https://raex-meta.oss-cn-shenzhen.aliyuncs.com/image/s3/" + file.getName(), null, "png")));
+                        collection.setPic(Collections.singletonList(new FileObject("", "https://raex-meta.oss-cn-shenzhen.aliyuncs.com/image/s5/" + file.getName(), null, "png")));
 
                         collectionRepo.save(collection);
+                        System.out.println("保存成功" + collection.getId());
 
                         items.add(BlindBoxItem
                                 .builder()
@@ -88,7 +89,7 @@ class CollectionServiceTest extends ApplicationTests {
     public void createBlindBox() throws IOException {
         List<Collection> items = collectionRepo.findByNameLike("MAYBEMAN #%").stream().filter(i -> {
             int num = Integer.parseInt(i.getName().substring("MAYBEMAN #".length()));
-            return num > 2000 && num <= 3000;
+            return num > 4000 && num <= 5000;
         }).collect(Collectors.toList());
 
         String jsonStr = FileUtils.readFileToString(new File("/Users/drew/Downloads/003.json"), "UTF-8");
@@ -98,7 +99,7 @@ class CollectionServiceTest extends ApplicationTests {
         blindBox.setOnShelf(false);
         blindBox.setSalable(false);
         blindBox.setMinterId(8666L);
-        blindBox.setName("MAYBEMAN潮流艺术限定盲盒S3");
+        blindBox.setName("MAYBEMAN潮流艺术限定盲盒S5");
         blindBox.setPic(Arrays.asList(new FileObject(null, "https://raex-meta.oss-cn-shenzhen.aliyuncs.com/nft/2022-01-07-10-08-30DpIYYBOv.jpg", null, null)));
         collectionService.createBlindBox(new CreateBlindBox(blindBox, items.stream().map(i -> {
             BlindBoxItem item = new BlindBoxItem();

+ 5 - 3
src/test/java/com/izouma/nineth/service/NFTServiceTest.java

@@ -36,6 +36,8 @@ public class NFTServiceTest extends ApplicationTests {
     private RestClientProperties restClientProperties;
     @Autowired
     private GeneralProperties    generalProperties;
+    @Autowired
+    private SnowflakeIdWorker    snowflakeIdWorker;
 
     @Test
     public void createAccount() {
@@ -52,7 +54,7 @@ public class NFTServiceTest extends ApplicationTests {
         jsonArray.add(new BigInteger(s, 16).toString());
         log.info("data {}", jsonArray.toJSONString());
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName(generalProperties.getContractName())
@@ -139,7 +141,7 @@ public class NFTServiceTest extends ApplicationTests {
     @Test
     public void ownerof() throws Exception {
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName("raex12")
@@ -184,7 +186,7 @@ public class NFTServiceTest extends ApplicationTests {
         jsonArray.add(Utils.getIdentityByName("9th_HCWWflAZ_"));
         jsonArray.add("95057808871064671354760012409081314299");
         CallRestBizParam callRestBizParam = CallRestBizParam.builder()
-                .orderId(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()))
+                .orderId(String.valueOf(snowflakeIdWorker.nextId()))
                 .bizid(restClientProperties.getBizid())
                 .account(restClientProperties.getAccount())
                 .contractName("raex12")

+ 8 - 13
src/test/java/com/izouma/nineth/service/OrderServiceTest.java

@@ -11,12 +11,9 @@ import com.izouma.nineth.dto.UserBankCard;
 import com.izouma.nineth.enums.AssetStatus;
 import com.izouma.nineth.enums.AuthStatus;
 import com.izouma.nineth.enums.OrderStatus;
-import com.izouma.nineth.enums.PayMethod;
-import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
 import com.izouma.nineth.utils.FileUtils;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.codec.EncoderException;
 import org.apache.commons.collections.MapUtils;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -50,9 +47,12 @@ public class OrderServiceTest extends ApplicationTests {
     private CollectionService collectionService;
 
     @Test
-    public void create() throws EncoderException, WxPayException {
-        Order order = orderService.create(1110L, 1777L, 1, null, 1896L, null);
-        assert order.getStatus() == OrderStatus.FINISH;
+    public void create() {
+        Order order = new Order();
+        order.setId(1234L);
+        orderRepo.save(order);
+
+        assert orderRepo.findById(1234L).orElse(null) != null;
     }
 
     @Test
@@ -115,8 +115,8 @@ public class OrderServiceTest extends ApplicationTests {
                 }
             }
             orderRepo.delete(errOrder);
-            collectionRepo.increaseStock(collection.getId(), 1);
-            collectionRepo.increaseSale(collection.getId(), -1);
+            collectionService.increaseStock(collection.getId(), 1);
+            collectionService.decreaseSale(collection.getId(), -1);
         }
     }
 
@@ -225,9 +225,4 @@ public class OrderServiceTest extends ApplicationTests {
         });
 
     }
-
-    @Test
-    public void notifyOrder() {
-        orderService.notifyOrder(923604613259591680L, PayMethod.ALIPAY, "11111");
-    }
 }

+ 17 - 0
src/test/java/com/izouma/nineth/service/UserServiceTest.java

@@ -4,6 +4,7 @@ import com.github.kevinsawicki.http.HttpRequest;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.domain.User;
+import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.UserRegister;
 import com.izouma.nineth.enums.AuthorityName;
 import com.izouma.nineth.repo.UserRepo;
@@ -17,6 +18,9 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 public class UserServiceTest extends ApplicationTests {
 
@@ -101,4 +105,17 @@ public class UserServiceTest extends ApplicationTests {
                 "13799940755\n" +
                 "18301015323", "123456");
     }
+
+    @Test
+    public void test() {
+        PageQuery pageQuery = new PageQuery();
+        Map<String, Object> query = pageQuery.getQuery();
+        query.put("inviteCode", "JL21FF");
+        pageQuery.setSize(5000);
+        List<User> phone = userService.all(pageQuery)
+                .getContent();
+        phone.forEach(user -> System.out.println(user.getPhone() + ":" + user.getCreatedAt()));
+        // 2022-01-14T15:53:58 504 第一次查询
+        phone.forEach(user -> System.out.print(user.getPhone() + " "));
+    }
 }

+ 5 - 0
src/test/java/com/izouma/nineth/service/sms/SmsServiceTest.java

@@ -18,4 +18,9 @@ public class SmsServiceTest extends ApplicationTests {
     public void verify() throws SmsService.SmsVerifyException {
         smsService.verify("15077886171", "5274");
     }
+
+    @Test
+    public void sellOut(){
+        smsService.sellOut("18205083565");
+    }
 }