Parcourir la source

Merge branch 'dev'

xiongzhu il y a 4 ans
Parent
commit
647c7dc7bc
38 fichiers modifiés avec 877 ajouts et 113 suppressions
  1. 5 0
      src/main/java/com/izouma/nineth/config/EventNames.java
  2. 4 0
      src/main/java/com/izouma/nineth/config/GeneralProperties.java
  3. 2 0
      src/main/java/com/izouma/nineth/config/RedisKeys.java
  4. 11 0
      src/main/java/com/izouma/nineth/domain/Collection.java
  5. 4 0
      src/main/java/com/izouma/nineth/domain/Order.java
  6. 29 0
      src/main/java/com/izouma/nineth/domain/PointRecord.java
  7. 7 1
      src/main/java/com/izouma/nineth/domain/User.java
  8. 17 0
      src/main/java/com/izouma/nineth/dto/adapay/DivMembersItem.java
  9. 88 0
      src/main/java/com/izouma/nineth/dto/adapay/PaymentItem.java
  10. 28 0
      src/main/java/com/izouma/nineth/dto/adapay/PaymentList.java
  11. 0 1
      src/main/java/com/izouma/nineth/event/CreateOrderEvent.java
  12. 43 0
      src/main/java/com/izouma/nineth/listener/BroadcastEventListener.java
  13. 1 1
      src/main/java/com/izouma/nineth/listener/CreateOrderListener.java
  14. 28 0
      src/main/java/com/izouma/nineth/listener/UpdateQuotaListener.java
  15. 10 0
      src/main/java/com/izouma/nineth/repo/AssetRepo.java
  16. 26 2
      src/main/java/com/izouma/nineth/repo/CollectionRepo.java
  17. 8 0
      src/main/java/com/izouma/nineth/repo/PointRecordRepo.java
  18. 7 1
      src/main/java/com/izouma/nineth/repo/UserRepo.java
  19. 44 13
      src/main/java/com/izouma/nineth/service/AdapayMerchantService.java
  20. 12 1
      src/main/java/com/izouma/nineth/service/AssetMintService.java
  21. 37 1
      src/main/java/com/izouma/nineth/service/AssetService.java
  22. 25 2
      src/main/java/com/izouma/nineth/service/CollectionService.java
  23. 32 8
      src/main/java/com/izouma/nineth/service/OrderService.java
  24. 32 8
      src/main/java/com/izouma/nineth/service/UserService.java
  25. 1 1
      src/main/java/com/izouma/nineth/web/AdapayMerchantController.java
  26. 15 0
      src/main/java/com/izouma/nineth/web/AssetController.java
  27. 1 1
      src/main/java/com/izouma/nineth/web/OrderController.java
  28. 22 0
      src/main/resources/application.yaml
  29. BIN
      src/main/vue/public/favicon.ico
  30. 2 2
      src/main/vue/public/index.html
  31. 109 54
      src/main/vue/src/views/BlindBoxEdit.vue
  32. 109 11
      src/main/vue/src/views/CollectionEdit.vue
  33. 31 0
      src/test/java/com/izouma/nineth/repo/CollectionRepoTest.java
  34. 5 0
      src/test/java/com/izouma/nineth/repo/UserRepoTest.java
  35. 7 0
      src/test/java/com/izouma/nineth/service/AdapayMerchantServiceTest.java
  36. 7 0
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  37. 2 2
      src/test/java/com/izouma/nineth/service/OrderServiceTest.java
  38. 66 3
      src/test/java/com/izouma/nineth/service/UserServiceTest.java

+ 5 - 0
src/main/java/com/izouma/nineth/config/EventNames.java

@@ -0,0 +1,5 @@
+package com.izouma.nineth.config;
+
+public class EventNames {
+    public final static String SWITCH_ACCOUNT = "switchAccount";
+}

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

@@ -26,4 +26,8 @@ public class GeneralProperties {
     private String  updateActivityStockTopic;
     private String  updateActivityStockTopic;
     private int     dataCenterId;
     private int     dataCenterId;
     private int     workerId;
     private int     workerId;
+    private String  updateQuotaGroup;
+    private String  updateQuotaTopic;
+    private String  broadcastEventGroup;
+    private String  broadcastEventTopic;
 }
 }

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

@@ -9,6 +9,8 @@ public class RedisKeys {
 
 
     public static final String COLLECTION_SALE = "collectionSale::";
     public static final String COLLECTION_SALE = "collectionSale::";
 
 
+    public static final String COLLECTION_QUOTA = "collectionQuota::";
+
     public static final String PAY_RECORD = "payRecord::";
     public static final String PAY_RECORD = "payRecord::";
 
 
     public static final String ORDER_LOCK = "orderLock::";
     public static final String ORDER_LOCK = "orderLock::";

+ 11 - 0
src/main/java/com/izouma/nineth/domain/Collection.java

@@ -171,4 +171,15 @@ public class Collection extends BaseEntity {
     @ApiModelProperty("注册背景")
     @ApiModelProperty("注册背景")
     private String registerBg;
     private String registerBg;
 
 
+    @ApiModelProperty("总额度")
+    private Integer totalQuota;
+
+    @ApiModelProperty("剩余额度")
+    private Integer vipQuota;
+
+    @ApiModelProperty("延迟销售")
+    private Boolean timeDelay;
+
+    @ApiModelProperty("销售时间")
+    private LocalDateTime saleTime;
 }
 }

+ 4 - 0
src/main/java/com/izouma/nineth/domain/Order.java

@@ -37,6 +37,7 @@ import java.util.List;
         @Index(columnList = "collectionId"),
         @Index(columnList = "collectionId"),
         @Index(columnList = "transactionId"),
         @Index(columnList = "transactionId"),
         @Index(columnList = "minterId"),
         @Index(columnList = "minterId"),
+        @Index(columnList = "createdAt")
 })
 })
 @AllArgsConstructor
 @AllArgsConstructor
 @NoArgsConstructor
 @NoArgsConstructor
@@ -205,4 +206,7 @@ public class Order extends BaseEntityNoID {
 
 
     @ApiModelProperty("是否vip")
     @ApiModelProperty("是否vip")
     private boolean vip;
     private boolean vip;
+
+    @ApiModelProperty("vip积分购买")
+    private Integer vipPoint;
 }
 }

+ 29 - 0
src/main/java/com/izouma/nineth/domain/PointRecord.java

@@ -0,0 +1,29 @@
+package com.izouma.nineth.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+@Table(name = "point_record", indexes = {
+        @Index(columnList = "userId"),
+        @Index(columnList = "collectionId")
+})
+public class PointRecord extends BaseEntity {
+    private Long userId;
+
+    private Long collectionId;
+
+    private int point;
+
+    private String type;
+}

+ 7 - 1
src/main/java/com/izouma/nineth/domain/User.java

@@ -32,6 +32,7 @@ import java.util.Set;
         @Index(columnList = "collectionInvitor"),
         @Index(columnList = "collectionInvitor"),
         @Index(columnList = "admin"),
         @Index(columnList = "admin"),
         @Index(columnList = "minter"),
         @Index(columnList = "minter"),
+        @Index(columnList = "createdAt"),
         @Index(columnList = "settleAccountId")
         @Index(columnList = "settleAccountId")
 })
 })
 @AllArgsConstructor
 @AllArgsConstructor
@@ -143,6 +144,11 @@ public class User extends BaseEntity implements Serializable {
 
 
     private boolean minter;
     private boolean minter;
 
 
+    @Column(columnDefinition = "bit default false")
     @ApiModelProperty("使用藏品图片")
     @ApiModelProperty("使用藏品图片")
-    private boolean useCollectionPic;
+    private boolean useCollectionPic = false;
+
+    @Column(columnDefinition = "int(11) default 0")
+    @ApiModelProperty("白名单积分")
+    private int vipPoint = 0;
 }
 }

+ 17 - 0
src/main/java/com/izouma/nineth/dto/adapay/DivMembersItem.java

@@ -0,0 +1,17 @@
+package com.izouma.nineth.dto.adapay;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+@Data
+public class DivMembersItem {
+
+    @JSONField(name = "member_id")
+    private String memberId;
+
+    @JSONField(name = "amount")
+    private String amount;
+
+    @JSONField(name = "fee_flag")
+    private String feeFlag;
+}

+ 88 - 0
src/main/java/com/izouma/nineth/dto/adapay/PaymentItem.java

@@ -0,0 +1,88 @@
+package com.izouma.nineth.dto.adapay;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PaymentItem {
+
+    @JSONField(name = "order_no")
+    private String orderNo;
+
+    @JSONField(name = "created_time")
+    private String createdTime;
+
+    @JSONField(name = "pay_amt")
+    private String payAmt;
+
+    @JSONField(name = "open_id")
+    private String openId;
+
+    @JSONField(name = "confirmed_amt")
+    private String confirmedAmt;
+
+    @JSONField(name = "end_time")
+    private String endTime;
+
+    @JSONField(name = "fee_mode")
+    private String feeMode;
+
+    @JSONField(name = "coupon_infos")
+    private String couponInfos;
+
+    @JSONField(name = "discount_amt")
+    private String discountAmt;
+
+    @JSONField(name = "cash_pay_amt")
+    private String cashPayAmt;
+
+    @JSONField(name = "reserved_amt")
+    private String reservedAmt;
+
+    @JSONField(name = "out_trans_id")
+    private String outTransId;
+
+    @JSONField(name = "party_order_id")
+    private String partyOrderId;
+
+    @JSONField(name = "pay_mode")
+    private String payMode;
+
+    @JSONField(name = "div_members")
+    private List<DivMembersItem> divMembers;
+
+    @JSONField(name = "refunded_amt")
+    private String refundedAmt;
+
+    @JSONField(name = "prod_mode")
+    private String prodMode;
+
+    @JSONField(name = "pay_channel")
+    private String payChannel;
+
+    @JSONField(name = "has_more")
+    private boolean hasMore;
+
+    @JSONField(name = "id")
+    private String id;
+
+    @JSONField(name = "app_id")
+    private String appId;
+
+    @JSONField(name = "fee_amt")
+    private String feeAmt;
+
+    @JSONField(name = "object")
+    private String object;
+
+    @JSONField(name = "status")
+    private String status;
+
+    @JSONField(name = "error_msg")
+    private String errorMsg;
+
+    @JSONField(name = "error_code")
+    private String errorCode;
+}

+ 28 - 0
src/main/java/com/izouma/nineth/dto/adapay/PaymentList.java

@@ -0,0 +1,28 @@
+package com.izouma.nineth.dto.adapay;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PaymentList {
+
+    @JSONField(name = "payments")
+    private List<PaymentItem> payments;
+
+    @JSONField(name = "prod_mode")
+    private String prodMode;
+
+    @JSONField(name = "has_more")
+    private boolean hasMore;
+
+    @JSONField(name = "app_id")
+    private String appId;
+
+    @JSONField(name = "object")
+    private String object;
+
+    @JSONField(name = "status")
+    private String status;
+}

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

@@ -26,5 +26,4 @@ public class CreateOrderEvent implements Serializable {
     @JsonSerialize(using = ToStringSerializer.class)
     @JsonSerialize(using = ToStringSerializer.class)
     private Long    invitor;
     private Long    invitor;
     private boolean vip;
     private boolean vip;
-    private int     vipPurchase;
 }
 }

+ 43 - 0
src/main/java/com/izouma/nineth/listener/BroadcastEventListener.java

@@ -0,0 +1,43 @@
+package com.izouma.nineth.listener;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.izouma.nineth.config.EventNames;
+import com.izouma.nineth.service.AdapayMerchantService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.MessageModel;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+@RocketMQMessageListener(
+        messageModel = MessageModel.BROADCASTING,
+        consumerGroup = "${general.broadcast-event-group}",
+        topic = "${general.broadcast-event-topic}",
+        consumeMode = ConsumeMode.CONCURRENTLY)
+public class BroadcastEventListener implements RocketMQListener<JSONObject> {
+    private AdapayMerchantService adapayMerchantService;
+
+    @Override
+    public void onMessage(JSONObject message) {
+        log.info("接收到广播事件 {}", JSONObject.toJSONString(message, SerializerFeature.PrettyFormat));
+        String name = message.getString("name");
+        if (name != null) {
+            switch (name) {
+                case EventNames.SWITCH_ACCOUNT:
+                    try {
+                        Long id = message.getLong("data");
+                        adapayMerchantService.select(id);
+                    } catch (Exception e) {
+                        log.error("event error", e);
+                    }
+                    break;
+            }
+        }
+    }
+}

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

@@ -37,7 +37,7 @@ public class CreateOrderListener implements RocketMQListener<CreateOrderEvent> {
         try {
         try {
             Order order = orderService.create(event.getUserId(), event.getCollectionId(), event.getQty(),
             Order order = orderService.create(event.getUserId(), event.getCollectionId(), event.getQty(),
                     event.getAddressId(), event.getUserCouponId(), event.getInvitor(), event.getId(),
                     event.getAddressId(), event.getUserCouponId(), event.getInvitor(), event.getId(),
-                    event.isVip(), event.getVipPurchase());
+                    event.isVip());
             map.put("success", true);
             map.put("success", true);
             map.put("data", order);
             map.put("data", order);
         } catch (Exception e) {
         } catch (Exception e) {

+ 28 - 0
src/main/java/com/izouma/nineth/listener/UpdateQuotaListener.java

@@ -0,0 +1,28 @@
+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.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+@RocketMQMessageListener(
+        consumerGroup = "${general.update-quota-group}",
+        topic = "${general.update-quota-topic}",
+        consumeMode = ConsumeMode.ORDERLY)
+@ConditionalOnProperty(value = "general.notify-server", havingValue = "false", matchIfMissing = true)
+public class UpdateQuotaListener implements RocketMQListener<Long> {
+
+    private CollectionService collectionService;
+
+    @Override
+    public void onMessage(Long id) {
+        collectionService.syncQuota(id);
+    }
+}

+ 10 - 0
src/main/java/com/izouma/nineth/repo/AssetRepo.java

@@ -58,4 +58,14 @@ public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationE
     List<Asset> findByTxHashIsNullAndTokenIdNotNullAndCreatedAtBefore(LocalDateTime time);
     List<Asset> findByTxHashIsNullAndTokenIdNotNullAndCreatedAtBefore(LocalDateTime time);
 
 
     List<Asset> findAllByIdInAndUserId(Collection<Long> id, Long userId);
     List<Asset> findAllByIdInAndUserId(Collection<Long> id, Long userId);
+
+    @Query(value = "select c.id, c.pic, c.model3d, c.minter_avatar, c.owner_avatar, c.detail from asset c", nativeQuery = true)
+    List<List<String>> selectResource();
+
+    @Modifying
+    @Transactional
+    @Query(value = "update asset c set c.pic = ?2, c.model3d = ?3, c.minter_avatar = ?4, " +
+            "c.owner_avatar = ?5, c.detail = ?6 where c.id = ?1", nativeQuery = true)
+    int updateCDN(Long id, String pic, String model3d, String minterAvatar,
+                  String ownerAvatar, String detail);
 }
 }

+ 26 - 2
src/main/java/com/izouma/nineth/repo/CollectionRepo.java

@@ -30,12 +30,14 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
             "c.schedule_sale = ?5, c.sort = ?6, c.detail = ?7, c.privileges = ?8, " +
             "c.schedule_sale = ?5, c.sort = ?6, c.detail = ?7, c.privileges = ?8, " +
             "c.properties = ?9, c.model3d = ?10, c.max_count = ?11, c.count_id = ?12, c.scan_code = ?13, " +
             "c.properties = ?9, c.model3d = ?10, c.max_count = ?11, c.count_id = ?12, c.scan_code = ?13, " +
             "c.no_sold_out = ?14, c.assignment = ?15, c.coupon_payment = ?16, c.share_bg = ?17," +
             "c.no_sold_out = ?14, c.assignment = ?15, c.coupon_payment = ?16, c.share_bg = ?17," +
-            "c.register_bg = ?18 where c.id = ?1", nativeQuery = true)
+            "c.register_bg = ?18, c.vip_quota = ?19, c.time_delay = ?20, c.sale_time = ?21 " +
+            "where c.id = ?1", nativeQuery = true)
     @CacheEvict(value = {"collection", "recommend"}, allEntries = true)
     @CacheEvict(value = {"collection", "recommend"}, allEntries = true)
     void update(@Nonnull Long id, boolean onShelf, boolean salable, LocalDateTime startTime,
     void update(@Nonnull Long id, boolean onShelf, boolean salable, LocalDateTime startTime,
                 boolean schedule, int sort, String detail, String privileges,
                 boolean schedule, int sort, String detail, String privileges,
                 String properties, String model3d, int maxCount, String countId, boolean scanCode,
                 String properties, String model3d, int maxCount, String countId, boolean scanCode,
-                boolean noSoldOut, int assignment, boolean couponPayment, String shareBg, String registerBg);
+                boolean noSoldOut, int assignment, boolean couponPayment, String shareBg, String registerBg,
+                Integer vipQuota, Boolean timeDelay, LocalDateTime saleTime);
 
 
     @Cacheable("collection")
     @Cacheable("collection")
     Optional<Collection> findById(@Nonnull Long id);
     Optional<Collection> findById(@Nonnull Long id);
@@ -115,4 +117,26 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     List<CollectionStockAndSale> getStockAndSale();
     List<CollectionStockAndSale> getStockAndSale();
 
 
     List<Collection> findAllByIdIn(java.util.Collection<Long> ids);
     List<Collection> findAllByIdIn(java.util.Collection<Long> ids);
+
+    @Query(value = "select c.id, c.pic, c.model3d, c.minter_avatar, c.owner_avatar, c.detail from collection_info c", nativeQuery = true)
+    List<List<String>> selectResource();
+
+    @Query(value = "select c.id, c.pic, c.model3d, c.minter_avatar, c.owner_avatar, c.detail from collection_info c where c.id = ?1", nativeQuery = true)
+    List<List<String>> selectResource(Long id);
+
+    @Modifying
+    @Transactional
+    @Query(value = "update collection_info c set c.pic = ?2, c.model3d = ?3, c.minter_avatar = ?4, " +
+            "c.owner_avatar = ?5, c.detail = ?6 where c.id = ?1", nativeQuery = true)
+    int updateCDN(Long id, String pic, String model3d, String minterAvatar,
+                  String ownerAvatar, String detail);
+
+    @Query("update Collection c set c.vipQuota = ?2 where c.id = ?1")
+    @Transactional
+    @Modifying
+    int updateVipQuota(Long id, int vipQuota);
+
+    @Query("select c.vipQuota from Collection c where c.id = ?1")
+    Integer getVipQuota(Long id);
+
 }
 }

+ 8 - 0
src/main/java/com/izouma/nineth/repo/PointRecordRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.PointRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PointRecordRepo extends JpaRepository<PointRecord, Long> {
+    int countByUserIdAndCollectionId(Long userId, Long collectionId);
+}

+ 7 - 1
src/main/java/com/izouma/nineth/repo/UserRepo.java

@@ -171,7 +171,7 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
 
 
     List<User> findAllByCollectionIdAndCollectionInvitor(Long collectionId, Long collectionInvitor);
     List<User> findAllByCollectionIdAndCollectionInvitor(Long collectionId, Long collectionInvitor);
 
 
-    long countAllByCollectionIdAndCollectionInvitor(Long collectionId, Long collectionInvitor);
+    int countAllByCollectionIdAndCollectionInvitor(Long collectionId, Long collectionInvitor);
 
 
     long countAllByAuthoritiesContainsAndDelFalse(Authority authority);
     long countAllByAuthoritiesContainsAndDelFalse(Authority authority);
 
 
@@ -180,4 +180,10 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
     List<User> findAllByCreatedAtIsAfterAndAuthoritiesContains(LocalDateTime createdAt, Authority authorities);
     List<User> findAllByCreatedAtIsAfterAndAuthoritiesContains(LocalDateTime createdAt, Authority authorities);
 
 
     List<User> findBySettleAccountIdIsNotNull();
     List<User> findBySettleAccountIdIsNotNull();
+
+    @Transactional
+    @Modifying
+    @Query("update User set vipPoint = vipPoint + ?2 where id = ?1")
+    void updateVipPoint(Long id, int num);
+
 }
 }

+ 44 - 13
src/main/java/com/izouma/nineth/service/AdapayMerchantService.java

@@ -1,14 +1,19 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.huifu.adapay.Adapay;
 import com.huifu.adapay.Adapay;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.huifu.adapay.model.*;
 import com.huifu.adapay.model.*;
 import com.izouma.nineth.config.AdapayProperties;
 import com.izouma.nineth.config.AdapayProperties;
+import com.izouma.nineth.config.EventNames;
+import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.AdapayMerchant;
 import com.izouma.nineth.domain.AdapayMerchant;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.adapay.MemberInfo;
 import com.izouma.nineth.dto.adapay.MemberInfo;
+import com.izouma.nineth.dto.adapay.PaymentItem;
+import com.izouma.nineth.dto.adapay.PaymentList;
 import com.izouma.nineth.dto.adapay.SettleAccountsItem;
 import com.izouma.nineth.dto.adapay.SettleAccountsItem;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.AdapayMerchantRepo;
 import com.izouma.nineth.repo.AdapayMerchantRepo;
@@ -18,6 +23,7 @@ import com.izouma.nineth.utils.SnowflakeIdWorker;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.collections.MapUtils;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
@@ -34,6 +40,8 @@ public class AdapayMerchantService {
 
 
     private final AdapayMerchantRepo adapayMerchantRepo;
     private final AdapayMerchantRepo adapayMerchantRepo;
     private final AdapayProperties   adapayProperties;
     private final AdapayProperties   adapayProperties;
+    private final RocketMQTemplate   rocketMQTemplate;
+    private final GeneralProperties  generalProperties;
 
 
     @PostConstruct
     @PostConstruct
     public void init() {
     public void init() {
@@ -72,6 +80,19 @@ public class AdapayMerchantService {
         return record;
         return record;
     }
     }
 
 
+    public void sendSelectEvent(Long id) {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("name", EventNames.SWITCH_ACCOUNT);
+        jsonObject.put("data", id);
+        rocketMQTemplate.convertAndSend(generalProperties.getBroadcastEventTopic(), jsonObject);
+
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
     public void select(Long id) throws Exception {
     public void select(Long id) throws Exception {
         AdapayMerchant merchant = adapayMerchantRepo.findById(id).orElseThrow(new BusinessException("商户不存在"));
         AdapayMerchant merchant = adapayMerchantRepo.findById(id).orElseThrow(new BusinessException("商户不存在"));
 
 
@@ -229,24 +250,34 @@ public class AdapayMerchantService {
 
 
     public Object query(Long merchantId, String id) throws BaseAdaPayException {
     public Object query(Long merchantId, String id) throws BaseAdaPayException {
         AdapayMerchant merchant = adapayMerchantRepo.findById(merchantId).orElseThrow(new BusinessException("商户不存在"));
         AdapayMerchant merchant = adapayMerchantRepo.findById(merchantId).orElseThrow(new BusinessException("商户不存在"));
-        Map<String, Object> map = Payment.query(id, merchant.getName());
-        log.info(JSON.toJSONString(map, SerializerFeature.PrettyFormat));
-        return map;
+        Map<String, Object> paymentParams = new HashMap<>();
+        paymentParams.put("app_id", merchant.getAppId());
+        paymentParams.put("payment_id", id);
+        Map<String, Object> paymentList = Payment.queryList(paymentParams, merchant.getName());
+        log.info(JSON.toJSONString(paymentList, SerializerFeature.PrettyFormat));
+        return paymentList;
     }
     }
 
 
     public Object refund(Long merchantId, String id) throws BaseAdaPayException {
     public Object refund(Long merchantId, String id) throws BaseAdaPayException {
         AdapayMerchant merchant = adapayMerchantRepo.findById(merchantId).orElseThrow(new BusinessException("商户不存在"));
         AdapayMerchant merchant = adapayMerchantRepo.findById(merchantId).orElseThrow(new BusinessException("商户不存在"));
-        Map<String, Object> map = Payment.query(id, merchant.getName());
-        if (!"succeeded".equals(MapUtils.getString(map, "status"))) {
-            return map;
+
+        Map<String, Object> paymentParams = new HashMap<>();
+        paymentParams.put("app_id", merchant.getAppId());
+        paymentParams.put("payment_id", id);
+        Map<String, Object> res = Payment.queryList(paymentParams, merchant.getName());
+        log.info(JSON.toJSONString(res, SerializerFeature.PrettyFormat));
+        PaymentList paymentList = JSON.parseObject(JSON.toJSONString(res), PaymentList.class);
+
+        if (paymentList.getPayments() != null && paymentList.getPayments().size() == 1) {
+            PaymentItem paymentItem = paymentList.getPayments().get(0);
+            Map<String, Object> refundParams = new HashMap<>();
+            refundParams.put("refund_amt", paymentItem.getPayAmt());
+            refundParams.put("refund_order_no", new SnowflakeIdWorker(0, 0).nextId() + "");
+            Map<String, Object> response = Refund.create(id, refundParams, merchant.getName());
+            log.info(JSON.toJSONString(response, SerializerFeature.PrettyFormat));
+            return response;
         }
         }
-        String amt = MapUtils.getString(map, "pay_amt");
-        Map<String, Object> refundParams = new HashMap<>();
-        refundParams.put("refund_amt", amt);
-        refundParams.put("refund_order_no", new SnowflakeIdWorker(0, 0).nextId() + "");
-        Map<String, Object> response = Refund.create(id, refundParams, merchant.getName());
-        log.info(JSON.toJSONString(response, SerializerFeature.PrettyFormat));
-        return response;
+        return res;
     }
     }
 
 
     public static void checkSuccess(Map<String, Object> map) {
     public static void checkSuccess(Map<String, Object> map) {

+ 12 - 1
src/main/java/com/izouma/nineth/service/AssetMintService.java

@@ -17,11 +17,13 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
 import org.springframework.retry.annotation.Backoff;
 import org.springframework.retry.annotation.Backoff;
 import org.springframework.retry.annotation.Retryable;
 import org.springframework.retry.annotation.Retryable;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.io.File;
 import java.io.File;
+import java.util.Arrays;
 
 
 @Service
 @Service
 @Slf4j
 @Slf4j
@@ -31,6 +33,7 @@ public class AssetMintService {
     private UserRepo           userRepo;
     private UserRepo           userRepo;
     private NFTService         nftService;
     private NFTService         nftService;
     private ApplicationContext applicationContext;
     private ApplicationContext applicationContext;
+    private Environment        env;
 
 
     @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 1000))
     @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 1000))
     public void mint(Long assetId) {
     public void mint(Long assetId) {
@@ -93,7 +96,15 @@ public class AssetMintService {
 
 
     public String ipfsUpload(String url) {
     public String ipfsUpload(String url) {
         try {
         try {
-            IPFS ipfs = new IPFS("120.24.204.226", 5001);
+            url = url.replace("raex-meta.oss-cn-shenzhen.aliyuncs.com",
+                            "raex-meta.oss-cn-shenzhen-internal.aliyuncs.com")
+                    .replace("cdn.raex.vip",
+                            "raex-meta.oss-cn-shenzhen-internal.aliyuncs.com");
+            String host = "120.24.204.226";
+            if (Arrays.asList(env.getActiveProfiles()).contains("prod")) {
+                host = "172.29.50.102";
+            }
+            IPFS ipfs = new IPFS(host, 5001);
             HttpRequest request = HttpRequest.get(url);
             HttpRequest request = HttpRequest.get(url);
             File file = File.createTempFile("ipfs", ".tmp");
             File file = File.createTempFile("ipfs", ".tmp");
             request.receive(file);
             request.receive(file);

+ 37 - 1
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -1,7 +1,6 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.convert.Convert;
-import cn.hutool.core.util.ObjectUtil;
 import com.izouma.nineth.TokenHistory;
 import com.izouma.nineth.TokenHistory;
 import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.Collection;
@@ -35,6 +34,8 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 @Service
 @Service
@@ -455,4 +456,39 @@ public class AssetService {
         map.put("buy", buy);
         map.put("buy", buy);
         return map;
         return map;
     }
     }
+
+    public void transferCDN() throws ExecutionException, InterruptedException {
+        ForkJoinPool customThreadPool = new ForkJoinPool(100);
+        customThreadPool.submit(() -> {
+            collectionRepo.selectResource().parallelStream().forEach(list -> {
+                for (int i = 0; i < list.size(); i++) {
+                    list.set(i, replaceCDN(list.get(i)));
+                }
+                collectionRepo.updateCDN(Long.parseLong(list.get(0)),
+                        list.get(1),
+                        list.get(2),
+                        list.get(3),
+                        list.get(4),
+                        list.get(5));
+            });
+
+            assetRepo.selectResource().parallelStream().forEach(list -> {
+                for (int i = 0; i < list.size(); i++) {
+                    list.set(i, replaceCDN(list.get(i)));
+                }
+                assetRepo.updateCDN(Long.parseLong(list.get(0)),
+                        list.get(1),
+                        list.get(2),
+                        list.get(3),
+                        list.get(4),
+                        list.get(5));
+            });
+        }).get();
+    }
+
+    public String replaceCDN(String url) {
+        if (url == null) return null;
+        return url.replaceAll("https://raex-meta\\.oss-cn-shenzhen\\.aliyuncs\\.com",
+                "https://cdn.raex.vip");
+    }
 }
 }

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

@@ -151,7 +151,8 @@ public class CollectionService {
                 record.getDetail(), JSON.toJSONString(record.getPrivileges()),
                 record.getDetail(), JSON.toJSONString(record.getPrivileges()),
                 JSON.toJSONString(record.getProperties()), JSON.toJSONString(record.getModel3d()),
                 JSON.toJSONString(record.getProperties()), JSON.toJSONString(record.getModel3d()),
                 record.getMaxCount(), record.getCountId(), record.isScanCode(), record.isNoSoldOut(),
                 record.getMaxCount(), record.getCountId(), record.isScanCode(), record.isNoSoldOut(),
-                record.getAssignment(), record.isCouponPayment(), record.getShareBg(), record.getRegisterBg());
+                record.getAssignment(), record.isCouponPayment(), record.getShareBg(), record.getRegisterBg(),
+                record.getVipQuota(), record.getTimeDelay(), record.getSaleTime());
 
 
         record = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
         record = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
         onShelfTask(record);
         onShelfTask(record);
@@ -195,7 +196,7 @@ public class CollectionService {
                 if (collection.getType() == CollectionType.BLIND_BOX) {
                 if (collection.getType() == CollectionType.BLIND_BOX) {
                     collectionDTO.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent());
                     collectionDTO.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent());
                 }
                 }
-                if (showVip && user.getVipPurchase() > 0) {
+                if (showVip && collection.getAssignment() > 0 && user.getVipPurchase() > 0) {
                     int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(user.getId(), collection.getId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
                     int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(user.getId(), collection.getId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
                     collectionDTO.setVipSurplus(user.getVipPurchase() - purchase);
                     collectionDTO.setVipSurplus(user.getVipPurchase() - purchase);
                 }
                 }
@@ -416,4 +417,26 @@ public class CollectionService {
             cacheService.clearCollection(id);
             cacheService.clearCollection(id);
         }
         }
     }
     }
+
+    @Debounce(key = "#id", delay = 500)
+    public void syncQuota(Long id) {
+        Integer quota = (Integer) redisTemplate.opsForValue().get(RedisKeys.COLLECTION_QUOTA + id);
+        if (quota != null) {
+            log.info("同步额度信息{}", id);
+            collectionRepo.updateVipQuota(id, quota);
+            cacheService.clearCollection(id);
+        }
+    }
+
+    public synchronized Long decreaseQuota(Long id, int number) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_QUOTA + id);
+        if (ops.get() == null) {
+            Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getVipQuota(id))
+                    .orElse(0), 7, TimeUnit.DAYS);
+            log.info("创建redis额度:{}", success);
+        }
+        Long stock = ops.increment(-number);
+        rocketMQTemplate.convertAndSend(generalProperties.getUpdateQuotaTopic(), id);
+        return stock;
+    }
 }
 }

+ 32 - 8
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -97,7 +97,7 @@ public class OrderService {
     }
     }
 
 
     public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
     public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
-                           String sign, boolean vip, int vipPurchase) {
+                           String sign, boolean vip, int vipPurchase, int vipPoint) {
         String qs = null;
         String qs = null;
         try {
         try {
             qs = AESEncryptUtil.decrypt(sign);
             qs = AESEncryptUtil.decrypt(sign);
@@ -111,13 +111,14 @@ public class OrderService {
 
 
         Long id = snowflakeIdWorker.nextId();
         Long id = snowflakeIdWorker.nextId();
         SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
         SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
-                new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor, vip, vipPurchase), 100000);
+                new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor, vip), 100000);
+
         log.info("发送订单到队列: {}, userId={}, result={}", id, userId, result);
         log.info("发送订单到队列: {}, userId={}, result={}", id, userId, result);
         return String.valueOf(id);
         return String.valueOf(id);
     }
     }
 
 
     public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
     public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
-                        Long id, boolean vip, int vipPurchase) {
+                        Long id, boolean vip) {
         long t = System.currentTimeMillis();
         long t = System.currentTimeMillis();
         qty = 1;
         qty = 1;
         int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
         int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
@@ -181,18 +182,30 @@ public class OrderService {
             }
             }
 
 
             //查询是否有拉新任务,只算官方购买
             //查询是否有拉新任务,只算官方购买
+            int usePoint = 0;
             if (collection.getSource() != CollectionSource.TRANSFER && collection.getAssignment() > 0) {
             if (collection.getSource() != CollectionSource.TRANSFER && collection.getAssignment() > 0) {
+                //延迟销售
+                if (collection.getTimeDelay()) {
+                    if (collection.getSaleTime().isAfter(LocalDateTime.now())) {
+                        throw new BusinessException("当前还未开售");
+                    }
+                }
+                User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
                 if (vip) {
                 if (vip) {
                     int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
                     int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
-                    if (vipPurchase - purchase <= 0) {
+                    if (user.getVipPurchase() - purchase <= 0) {
                         throw new BusinessException("vip名额已使用完毕!");
                         throw new BusinessException("vip名额已使用完毕!");
                     }
                     }
                 } else {
                 } else {
-                    long count = userRepo.countAllByCollectionIdAndCollectionInvitor(collectionId, userId);
-                    int sub = collection.getAssignment() - (int) count;
-                    if (sub > 0) {
-                        throw new BusinessException("再拉新" + sub + "人即可购买");
+//                    long count = userRepo.countAllByCollectionIdAndCollectionInvitor(collectionId, userId);
+//                    int sub = collection.getAssignment() - (int) count;
+//                    if (sub > 0) {
+//                        throw new BusinessException("再拉新" + sub + "人即可购买");
+//                    }
+                    if (user.getVipPoint() < 1) {
+                        throw new BusinessException("没有购买名额");
                     }
                     }
+                    usePoint = 1;
                 }
                 }
             }
             }
 
 
@@ -234,6 +247,7 @@ public class OrderService {
                     .invitor(invitor)
                     .invitor(invitor)
                     .countId(collection.getCountId())
                     .countId(collection.getCountId())
                     .vip(vip)
                     .vip(vip)
+                    .vipPoint(usePoint)
                     .build();
                     .build();
             if (coupon != null) {
             if (coupon != null) {
                 coupon.setUsed(true);
                 coupon.setUsed(true);
@@ -256,6 +270,11 @@ public class OrderService {
             if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
             if (order.getTotalPrice().equals(BigDecimal.ZERO)) {
                 notifyOrder(order.getId(), PayMethod.WEIXIN, null);
                 notifyOrder(order.getId(), PayMethod.WEIXIN, null);
             }
             }
+
+            if (usePoint > 0) {
+                // 扣除积分
+                userRepo.updateVipPoint(userId, -usePoint);
+            }
             rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
             rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
             log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
             log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
             return order;
             return order;
@@ -704,6 +723,11 @@ public class OrderService {
                     userCouponRepo.save(coupon);
                     userCouponRepo.save(coupon);
                 });
                 });
             }
             }
+            //加上积分
+            if (order.getVipPoint() > 0) {
+                userRepo.updateVipPoint(order.getUserId(), order.getVipPoint());
+            }
+
             rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
             rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getCollectionId(), 10000);
             log.info("取消订单{}", order.getId());
             log.info("取消订单{}", order.getId());
         } catch (Exception e) {
         } catch (Exception e) {

+ 32 - 8
src/main/java/com/izouma/nineth/service/UserService.java

@@ -5,7 +5,6 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
 import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.izouma.nineth.TokenHistory;
 import com.izouma.nineth.TokenHistory;
-import com.izouma.nineth.config.AdapayProperties;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.domain.*;
@@ -34,7 +33,6 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.context.ApplicationContext;
 import org.springframework.context.event.EventListener;
 import org.springframework.context.event.EventListener;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.PageImpl;
@@ -62,21 +60,19 @@ public class UserService {
     private SmsService            smsService;
     private SmsService            smsService;
     private StorageService        storageService;
     private StorageService        storageService;
     private JwtTokenUtil          jwtTokenUtil;
     private JwtTokenUtil          jwtTokenUtil;
-    private CaptchaService        captchaService;
     private FollowService         followService;
     private FollowService         followService;
     private FollowRepo            followRepo;
     private FollowRepo            followRepo;
     private IdentityAuthRepo      identityAuthRepo;
     private IdentityAuthRepo      identityAuthRepo;
     private SysConfigService      sysConfigService;
     private SysConfigService      sysConfigService;
-    private AdapayService         adapayService;
     private UserBankCardRepo      userBankCardRepo;
     private UserBankCardRepo      userBankCardRepo;
     private InviteRepo            inviteRepo;
     private InviteRepo            inviteRepo;
     private NFTService            nftService;
     private NFTService            nftService;
     private CacheService          cacheService;
     private CacheService          cacheService;
-    private ApplicationContext    context;
     private TokenHistoryRepo      tokenHistoryRepo;
     private TokenHistoryRepo      tokenHistoryRepo;
     private CollectionRepo        collectionRepo;
     private CollectionRepo        collectionRepo;
-    private AdapayProperties      adapayProperties;
     private AdapayMerchantService adapayMerchantService;
     private AdapayMerchantService adapayMerchantService;
+    private PointRecordRepo       pointRecordRepo;
+    private CollectionService     collectionService;
 
 
     public User update(User user) {
     public User update(User user) {
         User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录"));
         User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录"));
@@ -165,8 +161,9 @@ public class UserService {
             invite = inviteRepo.findFirstByCode(inviteCode).orElse(null);
             invite = inviteRepo.findFirstByCode(inviteCode).orElse(null);
         }
         }
         smsService.verify(phone, code);
         smsService.verify(phone, code);
+        Collection collection = null;
         if (collectionId != null) {
         if (collectionId != null) {
-            Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
+            collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
             if (!collection.isOnShelf() || !collection.isSalable()) {
             if (!collection.isOnShelf() || !collection.isSalable()) {
                 collectionId = null;
                 collectionId = null;
             } else if (collection.isScheduleSale()) {
             } else if (collection.isScheduleSale()) {
@@ -191,6 +188,33 @@ public class UserService {
         if (invite != null) {
         if (invite != null) {
             inviteRepo.increaseNum(invite.getId());
             inviteRepo.increaseNum(invite.getId());
         }
         }
+
+        // 加积分
+        if (collectionId != null && invitor != null) {
+            // 额度
+            if (collection.getVipQuota() > 0) {
+                int countUser = userRepo.countAllByCollectionIdAndCollectionInvitor(collectionId, invitor);
+                // 邀请人数
+                if (countUser >= collection.getAssignment()) {
+                    int point = pointRecordRepo.countByUserIdAndCollectionId(invitor, collectionId);
+                    // 是否已有积分
+                    if (point <= 0) {
+                        long count = userRepo.countAllByCollectionIdAndCollectionInvitor(collectionId, invitor);
+                        if (count >= collection.getAssignment()) {
+                            userRepo.updateVipPoint(invitor, 1);
+                            pointRecordRepo.save(PointRecord.builder()
+                                    .collectionId(collectionId)
+                                    .userId(invitor)
+                                    .type("VIP_POINT")
+                                    .point(1)
+                                    .build());
+                            // 扣除藏品额度
+                            collectionService.decreaseQuota(collectionId, 1);
+                        }
+                    }
+                }
+            }
+        }
         return user;
         return user;
     }
     }
 
 
@@ -495,7 +519,7 @@ public class UserService {
         String accountId = adapayMerchantService.createSettleAccountForAll
         String accountId = adapayMerchantService.createSettleAccountForAll
                 (user.getMemberId(), identityAuth.getRealName(),
                 (user.getMemberId(), identityAuth.getRealName(),
                         identityAuth.getIdNo(), phone, bankNo);
                         identityAuth.getIdNo(), phone, bankNo);
-        user.setSettleAccountId(accountId);
+        user.setSettleAccountId(Optional.ofNullable(accountId).orElse("1"));
         userRepo.save(user);
         userRepo.save(user);
 
 
         userBankCardRepo.save(UserBankCard.builder()
         userBankCardRepo.save(UserBankCard.builder()

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

@@ -56,7 +56,7 @@ public class AdapayMerchantController extends BaseController {
 
 
     @PostMapping("/select")
     @PostMapping("/select")
     public void select(@RequestParam Long id) throws Exception {
     public void select(@RequestParam Long id) throws Exception {
-        adapayMerchantService.select(id);
+        adapayMerchantService.sendSelectEvent(id);
     }
     }
 
 
     @PostMapping("/query")
     @PostMapping("/query")

+ 15 - 0
src/main/java/com/izouma/nineth/web/AssetController.java

@@ -16,6 +16,7 @@ import com.izouma.nineth.utils.excel.ExcelUtils;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
@@ -23,6 +24,7 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 
 
 @RestController
 @RestController
 @RequestMapping("/asset")
 @RequestMapping("/asset")
@@ -128,6 +130,19 @@ public class AssetController extends BaseController {
     public Map<String, BigDecimal> breakdown() {
     public Map<String, BigDecimal> breakdown() {
         return assetService.breakdown(SecurityUtils.getAuthenticatedUser().getId());
         return assetService.breakdown(SecurityUtils.getAuthenticatedUser().getId());
     }
     }
+
+    @PostMapping("/cdn")
+    @PreAuthorize("hasRole('ADMIN')")
+    public String cdn() throws ExecutionException, InterruptedException {
+        new Thread(() -> {
+            try {
+                assetService.transferCDN();
+            } catch (ExecutionException | InterruptedException e) {
+                e.printStackTrace();
+            }
+        }).start();
+        return "ok";
+    }
 }
 }
 
 
 
 

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

@@ -112,7 +112,7 @@ public class OrderController extends BaseController {
         final User user = SecurityUtils.getAuthenticatedUser();
         final User user = SecurityUtils.getAuthenticatedUser();
         return new HashMap<>() {{
         return new HashMap<>() {{
             put("id", orderService.mqCreate(user.getId(), collectionId, qty, addressId, couponId, invitor, sign,
             put("id", orderService.mqCreate(user.getId(), collectionId, qty, addressId, couponId, invitor, sign,
-                    vip, user.getVipPurchase()));
+                    vip, user.getVipPurchase(), user.getVipPoint()));
         }};
         }};
     }
     }
 
 

+ 22 - 0
src/main/resources/application.yaml

@@ -153,6 +153,10 @@ general:
   mint-topic: mint-topic-dev
   mint-topic: mint-topic-dev
   update-activity-stock-group: update-activity-stock-group-dev
   update-activity-stock-group: update-activity-stock-group-dev
   update-activity-stock-topic: update-activity-stock-topic-dev
   update-activity-stock-topic: update-activity-stock-topic-dev
+  update-quota-group: update-quota-group-dev
+  update-quota-topic: update-quota-topic-dev
+  broadcast-event-group: broadcast-event-group-dev
+  broadcast-event-topic: broadcast-event-topic-dev
 mychain:
 mychain:
   rest:
   rest:
     bizid: a00e36c5
     bizid: a00e36c5
@@ -229,6 +233,10 @@ general:
   mint-topic: mint-topic-test
   mint-topic: mint-topic-test
   update-activity-stock-group: update-activity-stock-group-test
   update-activity-stock-group: update-activity-stock-group-test
   update-activity-stock-topic: update-activity-stock-topic-test
   update-activity-stock-topic: update-activity-stock-topic-test
+  update-quota-group: update-quota-group-test
+  update-quota-topic: update-quota-topic-test
+  broadcast-event-group: broadcast-event-group-test
+  broadcast-event-topic: broadcast-event-topic-test
 ---
 ---
 
 
 spring:
 spring:
@@ -256,6 +264,10 @@ general:
   notify-server: true
   notify-server: true
   update-activity-stock-group: update-activity-stock-group-test1
   update-activity-stock-group: update-activity-stock-group-test1
   update-activity-stock-topic: update-activity-stock-topic-test1
   update-activity-stock-topic: update-activity-stock-topic-test1
+  update-quota-group: update-quota-group-test1
+  update-quota-topic: update-quota-topic-test1
+  broadcast-event-group: broadcast-event-group-test1
+  broadcast-event-topic: broadcast-event-topic-test1
 wx:
 wx:
   pay:
   pay:
     notify-url: https://test1.raex.vip/notify/order/weixin
     notify-url: https://test1.raex.vip/notify/order/weixin
@@ -308,6 +320,10 @@ general:
   mint-topic: mint-topic
   mint-topic: mint-topic
   update-activity-stock-group: update-activity-stock-group
   update-activity-stock-group: update-activity-stock-group
   update-activity-stock-topic: update-activity-stock-topic
   update-activity-stock-topic: update-activity-stock-topic
+  update-quota-group: update-quota-group
+  update-quota-topic: update-quota-topic
+  broadcast-event-group: broadcast-event-group
+  broadcast-event-topic: broadcast-event-topic
 wx:
 wx:
   pay:
   pay:
     notify-url: https://www.raex.vip/notify/order/weixin
     notify-url: https://www.raex.vip/notify/order/weixin
@@ -352,6 +368,8 @@ general:
   notify-server: true
   notify-server: true
   update-activity-stock-group: update-activity-stock-group
   update-activity-stock-group: update-activity-stock-group
   update-activity-stock-topic: update-activity-stock-topic
   update-activity-stock-topic: update-activity-stock-topic
+  update-quota-group: update-quota-group
+  update-quota-topic: update-quota-topic
 wx:
 wx:
   pay:
   pay:
     notify-url: https://www.raex.vip/notify/order/weixin
     notify-url: https://www.raex.vip/notify/order/weixin
@@ -394,6 +412,10 @@ general:
   notify-server: true
   notify-server: true
   update-activity-stock-group: update-activity-stock-group-test
   update-activity-stock-group: update-activity-stock-group-test
   update-activity-stock-topic: update-activity-stock-topic-test
   update-activity-stock-topic: update-activity-stock-topic-test
+  update-quota-group: update-quota-group-test
+  update-quota-topic: update-quota-topic-test
+  broadcast-event-group: broadcast-event-group-test
+  broadcast-event-topic: broadcast-event-topic-test
 rocketmq:
 rocketmq:
   name-server: 172.29.50.102:9876
   name-server: 172.29.50.102:9876
   producer:
   producer:

BIN
src/main/vue/public/favicon.ico


+ 2 - 2
src/main/vue/public/index.html

@@ -4,9 +4,9 @@
     <meta charset="utf-8">
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.png">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <script src="<%= BASE_URL %>fontawesome-v5.13.0.js"></script>
     <script src="<%= BASE_URL %>fontawesome-v5.13.0.js"></script>
-    <title>管理后台</title>
+    <title>绿洲管理后台</title>
   </head>
   </head>
   <body>
   <body>
     <noscript>
     <noscript>

+ 109 - 54
src/main/vue/src/views/BlindBoxEdit.vue

@@ -193,28 +193,60 @@
                         <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
                         <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
                         <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
                         <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
                     </el-form-item>
                     </el-form-item>
-                    <el-form-item prop="assignment" label="拉新任务指标">
-                        <el-input-number
-                            type="number"
-                            :min="0"
-                            :step="1"
-                            :max="5"
-                            v-model="formData.assignment"
-                        ></el-input-number>
-                        <div class="tip">0表示无拉新任务限制</div>
+                    <el-form-item prop="couponPayment" label="支付方式">
+                        <el-radio-group v-model="formData.couponPayment">
+                            <el-radio :label="true">兑换券</el-radio>
+                            <el-radio :label="false">支付宝/微信</el-radio>
+                        </el-radio-group>
                     </el-form-item>
                     </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="assignment" label="拉新任务指标">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                :max="5"
+                                v-model="formData.assignment"
+                            ></el-input-number>
+                            <div class="tip">0表示无拉新任务限制</div>
+                        </el-form-item>
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                v-model="formData.totalQuota"
+                                :disabled="!editQuota"
+                            ></el-input-number>
+                            <div class="tip">多少人拉新可获得积分</div>
+                        </el-form-item>
+                    </div>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                            <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
+                            <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="saleTime"
+                            label="销售时间"
+                            v-if="formData.assignment > 0 && formData.timeDelay"
+                            style="margin-left: 22px"
+                        >
+                            <el-date-picker
+                                v-model="formData.saleTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="销售时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
                     <el-form-item label="分享海报" v-if="formData.assignment > 0">
                     <el-form-item label="分享海报" v-if="formData.assignment > 0">
                         <single-upload v-model="formData.shareBg"></single-upload>
                         <single-upload v-model="formData.shareBg"></single-upload>
                     </el-form-item>
                     </el-form-item>
                     <el-form-item label="注册海报" v-if="formData.assignment > 0">
                     <el-form-item label="注册海报" v-if="formData.assignment > 0">
                         <single-upload v-model="formData.registerBg"></single-upload>
                         <single-upload v-model="formData.registerBg"></single-upload>
                     </el-form-item>
                     </el-form-item>
-                    <el-form-item prop="couponPayment" label="支付方式">
-                        <el-radio-group v-model="formData.couponPayment">
-                            <el-radio :label="true">兑换券</el-radio>
-                            <el-radio :label="false">支付宝/微信</el-radio>
-                        </el-radio-group>
-                    </el-form-item>
                     <el-form-item class="form-submit">
                     <el-form-item class="form-submit">
                         <el-button @click="onSave" :loading="saving" type="primary" v-if="!formData.id">
                         <el-button @click="onSave" :loading="saving" type="primary" v-if="!formData.id">
                             保存
                             保存
@@ -331,6 +363,9 @@ export default {
                             res.properties = res.properties || [];
                             res.properties = res.properties || [];
                             res.privileges = res.privileges || [];
                             res.privileges = res.privileges || [];
                             this.formData = res;
                             this.formData = res;
+                            if (res.totalQuota !== res.vipQuota) {
+                                this.editQuota = false;
+                            }
                             resolve();
                             resolve();
                         })
                         })
                         .catch(e => {
                         .catch(e => {
@@ -375,43 +410,6 @@ export default {
                 }
                 }
             });
             });
         });
         });
-        // this.formData = {
-        //     name: 'OASISPUNK绿洲朋克',
-        //     pic: ['https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com/image/2021-10-21-16-44-52kZqxuwhH.gif'],
-        //     minter: '管理员',
-        //     minterId: 1,
-        //     minterAvatar: 'https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com/image/avatar_male.png',
-        //     detail:
-        //         '<div class="content-item" data-v-38285332="">\n<div data-v-38285332="">RAEX绿洲数字藏品中心首次携手星火爱心公益基金及火链Labs,联合发行公益型数字藏品: OASISPUNK绿洲朋克。OASISPUNK绿洲朋克是完全使用算法合成的加密人物头像,仅铸造发行3100枚,每一枚全都不同,更有稀缺度之分。绿洲朋克共分为3种类型:初代目(1500枚),次代目(1500枚),旗帜版(100枚)。每售出一枚绿洲朋克,收益所得将捐赠一定比例给&ldquo;星火爱心公益基金&rdquo;,用于扶贫助困类公益项目,同时买家将收到由星火爱心公益基金颁发的捐款证明,获得投身公益事业的荣誉感。欢迎你来到绿洲元宇宙,共建绿洲生态,共享绿洲文明荣耀。</div>\n</div>',
-        //     type: 'BLIND_BOX',
-        //     source: 'OFFICIAL',
-        //     sale: 0,
-        //     stock: 0,
-        //     total: 23,
-        //     likes: 0,
-        //     onShelf: true,
-        //     salable: true,
-        //     price: 0.01,
-        //     properties: [],
-        //     canResale: false,
-        //     royalties: 0,
-        //     serviceCharge: 0,
-        // };
-        // this.blindBoxItems = [
-        //     {
-        //         collectionId: 212,
-        //         total: 3,
-        //         rare: true
-        //     },
-        //     {
-        //         collectionId: 207,
-        //         total: 6
-        //     },
-        //     {
-        //         collectionId: 201,
-        //         total: 14
-        //     }
-        // ];
     },
     },
     computed: {
     computed: {
         canEdit() {
         canEdit() {
@@ -586,7 +584,60 @@ export default {
                         }
                         }
                     }
                     }
                 ],
                 ],
-                category: [{ required: true, message: '请填写分类' }]
+                category: [{ required: true, message: '请填写分类' }],
+                saleTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.timeDelay) {
+                                if (!value) {
+                                    callback(new Error('请填写销售时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('销售时间不能小于当前时间'));
+                                } else if (this.formData.scheduleSale) {
+                                    if (
+                                        isBefore(
+                                            parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()),
+                                            parse(this.formData.startTime, 'yyyy-MM-dd HH:mm:ss', new Date())
+                                        )
+                                    ) {
+                                        callback(new Error('销售时间不能小于发布时间'));
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                timeDelay: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择是否延迟销售'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                totalQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请输入白名单额度'));
+                                    return;
+                                }
+                            }
+
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ]
             },
             },
             typeOptions: [
             typeOptions: [
                 { label: '默认', value: 'DEFAULT' },
                 { label: '默认', value: 'DEFAULT' },
@@ -614,7 +665,8 @@ export default {
             privelegeRules: {
             privelegeRules: {
                 detail: [{ required: true, message: '请填写内容' }],
                 detail: [{ required: true, message: '请填写内容' }],
                 remark: [{ required: true, message: '请填写说明' }]
                 remark: [{ required: true, message: '请填写说明' }]
-            }
+            },
+            editQuota: true
         };
         };
     },
     },
     methods: {
     methods: {
@@ -628,6 +680,9 @@ export default {
             });
             });
         },
         },
         submit() {
         submit() {
+              if (this.editQuota && this.formData.totalQuota) {
+                this.formData.vipQuota = this.formData.totalQuota;
+            }
             if (this.formData.id) {
             if (this.formData.id) {
                 this.saving = true;
                 this.saving = true;
                 this.$http
                 this.$http

+ 109 - 11
src/main/vue/src/views/CollectionEdit.vue

@@ -179,6 +179,7 @@
                         <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
                         <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
                         <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
                         <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
                     </el-form-item>
                     </el-form-item>
+
                     <div class="inline-wrapper">
                     <div class="inline-wrapper">
                         <el-form-item prop="startTime" label="定时发布">
                         <el-form-item prop="startTime" label="定时发布">
                             <el-radio v-model="formData.scheduleSale" :label="true">是</el-radio>
                             <el-radio v-model="formData.scheduleSale" :label="true">是</el-radio>
@@ -198,6 +199,7 @@
                             ></el-date-picker>
                             ></el-date-picker>
                         </el-form-item>
                         </el-form-item>
                     </div>
                     </div>
+
                     <el-form-item
                     <el-form-item
                         prop="salable"
                         prop="salable"
                         label="可售"
                         label="可售"
@@ -234,22 +236,55 @@
                             <el-radio :label="false">支付宝/微信</el-radio>
                             <el-radio :label="false">支付宝/微信</el-radio>
                         </el-radio-group>
                         </el-radio-group>
                     </el-form-item>
                     </el-form-item>
-                    <el-form-item prop="assignment" label="拉新任务指标">
-                        <el-input-number
-                            type="number"
-                            :min="0"
-                            :step="1"
-                            :max="5"
-                            v-model="formData.assignment"
-                        ></el-input-number>
-                        <div class="tip">0表示无拉新任务限制</div>
-                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="assignment" label="拉新任务指标">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                :max="5"
+                                v-model="formData.assignment"
+                            ></el-input-number>
+                            <div class="tip">0表示无拉新任务限制</div>
+                        </el-form-item>
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                v-model="formData.totalQuota"
+                                :disabled="!editQuota"
+                            ></el-input-number>
+                            <div class="tip">多少人拉新可获得积分</div>
+                        </el-form-item>
+                    </div>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                            <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
+                            <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="saleTime"
+                            label="销售时间"
+                            v-if="formData.assignment > 0 && formData.timeDelay"
+                            style="margin-left: 22px"
+                        >
+                            <el-date-picker
+                                v-model="formData.saleTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="销售时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
                     <el-form-item label="分享海报" v-if="formData.assignment > 0">
                     <el-form-item label="分享海报" v-if="formData.assignment > 0">
                         <single-upload v-model="formData.shareBg"></single-upload>
                         <single-upload v-model="formData.shareBg"></single-upload>
                     </el-form-item>
                     </el-form-item>
                     <el-form-item label="注册海报" v-if="formData.assignment > 0">
                     <el-form-item label="注册海报" v-if="formData.assignment > 0">
                         <single-upload v-model="formData.registerBg"></single-upload>
                         <single-upload v-model="formData.registerBg"></single-upload>
                     </el-form-item>
                     </el-form-item>
+
                     <el-form-item class="form-submit">
                     <el-form-item class="form-submit">
                         <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
                         <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
                         <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
                         <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
@@ -318,6 +353,9 @@ export default {
                             res.properties = res.properties || [];
                             res.properties = res.properties || [];
                             res.privileges = res.privileges || [];
                             res.privileges = res.privileges || [];
                             this.formData = res;
                             this.formData = res;
+                            if (res.totalQuota !== res.vipQuota) {
+                                this.editQuota = false;
+                            }
                             resolve();
                             resolve();
                         })
                         })
                         .catch(e => {
                         .catch(e => {
@@ -509,6 +547,59 @@ export default {
                         },
                         },
                         trigger: 'blur'
                         trigger: 'blur'
                     }
                     }
+                ],
+                saleTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.timeDelay) {
+                                if (!value) {
+                                    callback(new Error('请填写销售时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('销售时间不能小于当前时间'));
+                                } else if (this.formData.scheduleSale) {
+                                    if (
+                                        isBefore(
+                                            parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()),
+                                            parse(this.formData.startTime, 'yyyy-MM-dd HH:mm:ss', new Date())
+                                        )
+                                    ) {
+                                        callback(new Error('销售时间不能小于发布时间'));
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                timeDelay: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择是否延迟销售'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                totalQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请输入白名单额度'));
+                                    return;
+                                }
+                            }
+
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
                 ]
                 ]
             },
             },
             typeOptions: [
             typeOptions: [
@@ -531,7 +622,8 @@ export default {
             },
             },
             customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel'),
             customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel'),
             scale: 1,
             scale: 1,
-            yOffset: 0
+            yOffset: 0,
+            editQuota: true
         };
         };
     },
     },
     methods: {
     methods: {
@@ -549,6 +641,9 @@ export default {
             if (data.model3d) {
             if (data.model3d) {
                 data.model3d.url = data.model3d.url + '?scale=' + this.scale + '&yOffset=' + this.yOffset;
                 data.model3d.url = data.model3d.url + '?scale=' + this.scale + '&yOffset=' + this.yOffset;
             }
             }
+            if (this.editQuota && data.totalQuota) {
+                data.vipQuota = data.totalQuota;
+            }
 
 
             this.saving = true;
             this.saving = true;
             this.$http
             this.$http
@@ -676,4 +771,7 @@ export default {
         display: inline-block;
         display: inline-block;
     }
     }
 }
 }
+.right-margin {
+    margin-left: 50px;
+}
 </style>
 </style>

+ 31 - 0
src/test/java/com/izouma/nineth/repo/CollectionRepoTest.java

@@ -0,0 +1,31 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.ApplicationTests;
+import com.izouma.nineth.service.AssetService;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class CollectionRepoTest extends ApplicationTests {
+    @Autowired
+    private CollectionRepo collectionRepo;
+    @Autowired
+    private AssetService   assetService;
+
+    @Test
+    public void updateCDN() {
+        List<List<String>> list = collectionRepo.selectResource(83346L);
+        for (int i = 0; i < list.get(0).size(); i++) {
+            list.get(0).set(i, assetService.replaceCDN(list.get(0).get(i)));
+        }
+        collectionRepo.updateCDN(Long.parseLong(list.get(0).get(0)),
+                list.get(0).get(1),
+                list.get(0).get(2),
+                list.get(0).get(3),
+                list.get(0).get(4),
+                list.get(0).get(5));
+    }
+}

+ 5 - 0
src/test/java/com/izouma/nineth/repo/UserRepoTest.java

@@ -51,4 +51,9 @@ public class UserRepoTest {
                 .build());
                 .build());
         System.out.println(list);
         System.out.println(list);
     }
     }
+
+    @Test
+    public void test() {
+        System.out.println(userRepo.findById(150843L).orElse(null));
+    }
 }
 }

+ 7 - 0
src/test/java/com/izouma/nineth/service/AdapayMerchantServiceTest.java

@@ -1,9 +1,16 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.ApplicationTests;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
 
 
 import static org.junit.Assert.*;
 import static org.junit.Assert.*;
 
 
 public class AdapayMerchantServiceTest extends ApplicationTests {
 public class AdapayMerchantServiceTest extends ApplicationTests {
+    @Autowired
+    private AdapayMerchantService adapayMerchantService;
 
 
+    @Test
+    public void createSettleAccountForAll() {
+    }
 }
 }

+ 7 - 0
src/test/java/com/izouma/nineth/service/AssetServiceTest.java

@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 @Slf4j
 @Slf4j
@@ -165,6 +166,7 @@ class AssetServiceTest extends ApplicationTests {
         System.out.println(asset);
         System.out.println(asset);
     }
     }
 
 
+
     @Data
     @Data
     @NoArgsConstructor
     @NoArgsConstructor
     @AllArgsConstructor
     @AllArgsConstructor
@@ -235,4 +237,9 @@ class AssetServiceTest extends ApplicationTests {
             rocketMQTemplate.syncSend("mint-topic", asset.getId());
             rocketMQTemplate.syncSend("mint-topic", asset.getId());
         }
         }
     }
     }
+
+    @Test
+    public void transferCDN() throws ExecutionException, InterruptedException {
+        assetService.transferCDN();
+    }
 }
 }

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

@@ -244,8 +244,8 @@ public class OrderServiceTest extends ApplicationTests {
 
 
     @Test
     @Test
     public void test() {
     public void test() {
-        orderService.create(9850L, 196308L, 1, null, null, null,
-                945378720611303424L, true, 2);
+        orderService.create(9972L, 83346L, 1, null, null, null,
+                112342311L, false);
     }
     }
 
 
     @Test
     @Test

+ 66 - 3
src/test/java/com/izouma/nineth/service/UserServiceTest.java

@@ -1,16 +1,26 @@
 package com.izouma.nineth.service;
 package com.izouma.nineth.service;
 
 
 import com.github.kevinsawicki.http.HttpRequest;
 import com.github.kevinsawicki.http.HttpRequest;
+import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.config.Constants;
+import com.izouma.nineth.domain.IdentityAuth;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.domain.User;
+import com.izouma.nineth.dto.BankValidate;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.UserBankCard;
 import com.izouma.nineth.dto.UserRegister;
 import com.izouma.nineth.dto.UserRegister;
+import com.izouma.nineth.enums.AuthStatus;
 import com.izouma.nineth.enums.AuthorityName;
 import com.izouma.nineth.enums.AuthorityName;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.IdentityAuthRepo;
+import com.izouma.nineth.repo.UserBankCardRepo;
 import com.izouma.nineth.repo.UserRepo;
 import com.izouma.nineth.repo.UserRepo;
 import com.izouma.nineth.security.Authority;
 import com.izouma.nineth.security.Authority;
 import com.izouma.nineth.service.storage.StorageService;
 import com.izouma.nineth.service.storage.StorageService;
+import com.izouma.nineth.utils.BankUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 
 
@@ -24,11 +34,17 @@ import java.util.Map;
 public class UserServiceTest extends ApplicationTests {
 public class UserServiceTest extends ApplicationTests {
 
 
     @Autowired
     @Autowired
-    private UserService    userService;
+    private UserService           userService;
     @Autowired
     @Autowired
-    private UserRepo       userRepo;
+    private UserRepo              userRepo;
     @Autowired
     @Autowired
-    private StorageService storageService;
+    private StorageService        storageService;
+    @Autowired
+    private UserBankCardRepo      userBankCardRepo;
+    @Autowired
+    private AdapayMerchantService adapayMerchantService;
+    @Autowired
+    private IdentityAuthRepo      identityAuthRepo;
 
 
     @Test
     @Test
     public void findByUsernameAndDelFalse1() {
     public void findByUsernameAndDelFalse1() {
@@ -122,4 +138,51 @@ public class UserServiceTest extends ApplicationTests {
     public void switchAccount() {
     public void switchAccount() {
         userService.checkSettleAccount();
         userService.checkSettleAccount();
     }
     }
+
+    @Test
+    public void phoneRegister() {
+        userService.phoneRegister("18100004444", "1234", "123456", null, 9972L, 206925L);
+    }
+
+    @Test
+    public void addBankCard() throws BaseAdaPayException {
+        Long userId = 134613L;
+        String bankNo = "6222024301070380165";
+        String phone = "15077886171";
+        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
+        IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
+                .orElseThrow(new BusinessException("用户未认证"));
+        if (identityAuth.isOrg()) {
+            //throw new BusinessException("企业认证用户请绑定对公账户");
+        }
+        if (!StringUtils.isBlank(user.getSettleAccountId())) {
+            throw new BusinessException("此账号已绑定");
+        }
+        BankValidate bankValidate = BankUtils.validate(bankNo);
+        if (!bankValidate.isValidated()) {
+            throw new BusinessException("暂不支持此卡");
+        }
+
+        adapayMerchantService.createMemberForAll(userId.toString(), user.getPhone(), identityAuth.getRealName(), identityAuth.getIdNo());
+        user.setMemberId(user.getId().toString());
+        userRepo.save(user);
+
+        String accountId = adapayMerchantService.createSettleAccountForAll
+                (user.getMemberId(), identityAuth.getRealName(),
+                        identityAuth.getIdNo(), phone, bankNo);
+        user.setSettleAccountId(accountId);
+        userRepo.save(user);
+
+        userBankCardRepo.save(UserBankCard.builder()
+                .bank(bankValidate.getBank())
+                .bankName(bankValidate.getBankName())
+                .bankNo(bankNo)
+                .cardType(bankValidate.getCardType())
+                .cardTypeDesc(bankValidate.getCardTypeDesc())
+                .userId(userId)
+                .phone(phone)
+                .realName(identityAuth.getRealName())
+                .idNo(identityAuth.getIdNo())
+                .build());
+    }
 }
 }