xiongzhu 4 rokov pred
rodič
commit
870bbe68ec
58 zmenil súbory, kde vykonal 2130 pridanie a 130 odobranie
  1. 9 0
      src/main/java/com/izouma/nineth/config/CacheConfig.java
  2. 2 0
      src/main/java/com/izouma/nineth/config/RedisKeys.java
  3. 9 1
      src/main/java/com/izouma/nineth/domain/Asset.java
  4. 6 0
      src/main/java/com/izouma/nineth/domain/Collection.java
  5. 2 3
      src/main/java/com/izouma/nineth/domain/News.java
  6. 1 0
      src/main/java/com/izouma/nineth/domain/NewsLike.java
  7. 7 0
      src/main/java/com/izouma/nineth/domain/Recommend.java
  8. 36 0
      src/main/java/com/izouma/nineth/domain/ShowCollection.java
  9. 52 0
      src/main/java/com/izouma/nineth/domain/Showroom.java
  10. 14 0
      src/main/java/com/izouma/nineth/dto/RecommendDTO.java
  11. 15 0
      src/main/java/com/izouma/nineth/dto/RecommendNews.java
  12. 36 0
      src/main/java/com/izouma/nineth/dto/ShowroomDTO.java
  13. 2 1
      src/main/java/com/izouma/nineth/enums/CollectionType.java
  14. 1 1
      src/main/java/com/izouma/nineth/exception/BusinessException.java
  15. 4 3
      src/main/java/com/izouma/nineth/repo/CollectionRepo.java
  16. 2 0
      src/main/java/com/izouma/nineth/repo/NewsLikeRepo.java
  17. 8 0
      src/main/java/com/izouma/nineth/repo/NewsRepo.java
  18. 36 0
      src/main/java/com/izouma/nineth/repo/ShowCollectionRepo.java
  19. 26 0
      src/main/java/com/izouma/nineth/repo/ShowroomRepo.java
  20. 1 1
      src/main/java/com/izouma/nineth/repo/TokenHistoryRepo.java
  21. 4 4
      src/main/java/com/izouma/nineth/repo/UserRepo.java
  22. 1 0
      src/main/java/com/izouma/nineth/security/WebSecurityConfig.java
  23. 15 1
      src/main/java/com/izouma/nineth/service/AssetService.java
  24. 7 2
      src/main/java/com/izouma/nineth/service/BannerService.java
  25. 14 0
      src/main/java/com/izouma/nineth/service/CacheService.java
  26. 2 2
      src/main/java/com/izouma/nineth/service/CollectionService.java
  27. 3 0
      src/main/java/com/izouma/nineth/service/MintOrderService.java
  28. 20 0
      src/main/java/com/izouma/nineth/service/NewsLikeService.java
  29. 26 26
      src/main/java/com/izouma/nineth/service/OrderService.java
  30. 7 2
      src/main/java/com/izouma/nineth/service/PurchaseLevelService.java
  31. 20 0
      src/main/java/com/izouma/nineth/service/ShowCollectionService.java
  32. 158 0
      src/main/java/com/izouma/nineth/service/ShowroomService.java
  33. 1 3
      src/main/java/com/izouma/nineth/service/StatisticService.java
  34. 12 0
      src/main/java/com/izouma/nineth/service/UserService.java
  35. 4 0
      src/main/java/com/izouma/nineth/utils/TokenUtils.java
  36. 13 14
      src/main/java/com/izouma/nineth/web/AssetController.java
  37. 0 2
      src/main/java/com/izouma/nineth/web/AuthenticationController.java
  38. 13 5
      src/main/java/com/izouma/nineth/web/BannerController.java
  39. 58 7
      src/main/java/com/izouma/nineth/web/CollectionController.java
  40. 13 20
      src/main/java/com/izouma/nineth/web/OrderController.java
  41. 4 4
      src/main/java/com/izouma/nineth/web/PurchaseLevelController.java
  42. 9 3
      src/main/java/com/izouma/nineth/web/RecommendController.java
  43. 60 0
      src/main/java/com/izouma/nineth/web/ShowCollectionController.java
  44. 64 0
      src/main/java/com/izouma/nineth/web/ShowroomController.java
  45. 16 1
      src/main/java/com/izouma/nineth/web/StatisticController.java
  46. 1 2
      src/main/java/com/izouma/nineth/web/UserController.java
  47. 1 0
      src/main/resources/genjson/ShowCollection.json
  48. 1 0
      src/main/resources/genjson/Showroom.json
  49. 19 2
      src/main/vue/src/router.js
  50. 9 2
      src/main/vue/src/views/CollectionEdit.vue
  51. 53 9
      src/main/vue/src/views/RecommendEdit.vue
  52. 5 5
      src/main/vue/src/views/RecommendList.vue
  53. 779 0
      src/main/vue/src/views/ShowroomEdit.vue
  54. 257 0
      src/main/vue/src/views/ShowroomList.vue
  55. 14 3
      src/test/java/com/izouma/nineth/repo/UserRepoTest.java
  56. 5 0
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  57. 0 1
      src/test/java/com/izouma/nineth/service/OrderServiceTest.java
  58. 173 0
      src/test/java/com/izouma/nineth/service/ShowroomServiceTest.java

+ 9 - 0
src/main/java/com/izouma/nineth/config/CacheConfig.java

@@ -104,6 +104,15 @@ public class CacheConfig {
                 .entryTtl(Duration.ofSeconds(10))
                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
 
+        cacheNamesConfigurationMap.put("myUserInfo", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+        cacheNamesConfigurationMap.put("userStat", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
+
         RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                 .entryTtl(Duration.ofDays(7))
                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));

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

@@ -24,4 +24,6 @@ public class RedisKeys {
     public static final String UPDATE_SALE = "updateSale";
 
     public static final String UPDATE_STOCK = "updateSale";
+
+    public static final String LIMIT_REQ = "limitReq::";
 }

+ 9 - 1
src/main/java/com/izouma/nineth/domain/Asset.java

@@ -164,6 +164,12 @@ public class Asset extends BaseEntity {
     @ApiModelProperty("持有几天")
     private Integer holdDays;
 
+    @ApiModelProperty("展厅背景")
+    private String showroomBg;
+
+    @ApiModelProperty("最多可放藏品数量")
+    private Integer maxCollection;
+
     @Transient
     private boolean opened = true;
 
@@ -189,8 +195,10 @@ public class Asset extends BaseEntity {
                 .owner(user.getNickname())
                 .ownerId(user.getId())
                 .ownerAvatar(user.getAvatar())
-                .type(CollectionType.DEFAULT)
+                .type(collection.getType())
                 .holdDays(collection.getHoldDays())
+                .maxCollection(collection.getMaxCollection())
+                .showroomBg(collection.getShowroomBg())
                 .build();
     }
 

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

@@ -188,4 +188,10 @@ public class Collection extends BaseEntity {
 
     @ApiModelProperty("开启抢白名单")
     private Boolean openQuota;
+
+    @ApiModelProperty("展厅背景")
+    private String showroomBg;
+
+    @ApiModelProperty("最多可放藏品数量")
+    private Integer maxCollection;
 }

+ 2 - 3
src/main/java/com/izouma/nineth/domain/News.java

@@ -6,14 +6,13 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Transient;
+import javax.persistence.*;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
+@Table(indexes = {@Index(columnList = "title")})
 @Entity
 public class News extends BaseEntity {
     private String title;

+ 1 - 0
src/main/java/com/izouma/nineth/domain/NewsLike.java

@@ -24,4 +24,5 @@ public class NewsLike extends BaseEntity {
 
     private Long newsId;
 
+    private Long showroomId;
 }

+ 7 - 0
src/main/java/com/izouma/nineth/domain/Recommend.java

@@ -7,11 +7,14 @@ import lombok.Data;
 import lombok.NoArgsConstructor;
 
 import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
 
 @Data
 @Entity
 @AllArgsConstructor
 @NoArgsConstructor
+@Table(indexes = {@Index(columnList = "category")})
 @Builder
 public class Recommend extends BaseEntity {
 
@@ -28,4 +31,8 @@ public class Recommend extends BaseEntity {
     private String type;
 
     private String pic;
+
+    /*藏品,新闻*/
+    @ApiModelProperty("种类")
+    private String category;
 }

+ 36 - 0
src/main/java/com/izouma/nineth/domain/ShowCollection.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.domain;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+@Where(clause = "del = 0")
+@Table(indexes = {
+        @Index(columnList = "showroomId"),
+        @Index(columnList = "collectionId")
+})
+@ApiModel("展厅藏品")
+public class ShowCollection extends BaseEntity {
+
+    private Long showroomId;
+
+    private Long assetId;
+
+    private Long collectionId;
+
+    private String pic;
+
+    private int sort;
+}

+ 52 - 0
src/main/java/com/izouma/nineth/domain/Showroom.java

@@ -0,0 +1,52 @@
+package com.izouma.nineth.domain;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+@Table(indexes = {
+        @Index(columnList = "userId"),
+        @Index(columnList = "assetId")
+})
+@Where(clause = "del = 0")
+@ApiModel("展厅")
+public class Showroom extends BaseEntity {
+    private Long userId;
+
+    private Long assetId;
+
+    @ApiModelProperty("头像")
+    private String pic;
+
+    private String introduction;
+
+    private int likes;
+
+    private int share;
+
+    private boolean publish;
+
+    @ApiModelProperty("最多可放藏品数量")
+    private int maxCollection;
+
+    @ApiModelProperty("展厅背景")
+    private String showroomBg;
+
+    @Transient
+    private List<ShowCollection> collections;
+}

+ 14 - 0
src/main/java/com/izouma/nineth/dto/RecommendDTO.java

@@ -0,0 +1,14 @@
+package com.izouma.nineth.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class RecommendDTO {
+    private Integer sort;
+    private Object obj;
+    private String type;
+}

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

@@ -0,0 +1,15 @@
+package com.izouma.nineth.dto;
+
+import com.izouma.nineth.domain.News;
+import com.izouma.nineth.domain.Recommend;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class RecommendNews {
+    private News      news;
+    private Recommend recommend;
+}

+ 36 - 0
src/main/java/com/izouma/nineth/dto/ShowroomDTO.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.dto;
+
+import com.izouma.nineth.domain.BaseEntity;
+import com.izouma.nineth.domain.ShowCollection;
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("展厅")
+public class ShowroomDTO {
+    private Long id;
+
+    private Long assetId;
+
+    private String pic;
+
+    private String introduction;
+
+    private boolean publish;
+
+    @Transient
+    private List<Long> collections;
+}

+ 2 - 1
src/main/java/com/izouma/nineth/enums/CollectionType.java

@@ -3,7 +3,8 @@ package com.izouma.nineth.enums;
 public enum CollectionType {
     DEFAULT("默认"),
     BLIND_BOX("盲盒"),
-    AUCTION("拍卖");
+    AUCTION("拍卖"),
+    SHOWROOM("展厅");
 
     private final String description;
 

+ 1 - 1
src/main/java/com/izouma/nineth/exception/BusinessException.java

@@ -24,7 +24,7 @@ public class BusinessException extends RuntimeException implements Supplier<Busi
     }
 
     public BusinessException(String error, int code) {
-        super();
+        super(error);
         this.error = error;
         this.code = code;
     }

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

@@ -31,13 +31,14 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
             "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.register_bg = ?18, c.vip_quota = ?19, c.time_delay = ?20, c.sale_time = ?21, c.hold_days = ?22, " +
-            "c.open_quota = ?23 where c.id = ?1", nativeQuery = true)
+            "c.open_quota = ?23, c.showroom_bg = ?24, c.max_collection = ?25 where c.id = ?1", nativeQuery = true)
     @CacheEvict(value = {"collection", "recommend"}, allEntries = true)
     void update(@Nonnull Long id, boolean onShelf, boolean salable, LocalDateTime startTime,
                 boolean schedule, int sort, String detail, String privileges,
                 String properties, String model3d, int maxCount, String countId, boolean scanCode,
                 boolean noSoldOut, int assignment, boolean couponPayment, String shareBg, String registerBg,
-                Integer vipQuota, Boolean timeDelay, LocalDateTime saleTime, Integer holdDays, Boolean openQuota);
+                Integer vipQuota, Boolean timeDelay, LocalDateTime saleTime, Integer holdDays, Boolean openQuota,
+                String showroomBg, Integer maxCollection);
 
     @Cacheable("collection")
     Optional<Collection> findById(@Nonnull Long id);
@@ -61,7 +62,7 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     Collection save(@Nonnull Collection collection);
 
     @Query("select new com.izouma.nineth.dto.RecommendCollection(c,r) from Collection c join Recommend r on c.id = r.collectionId " +
-            "where c.del = false and c.onShelf = true and r.type = ?1 order by r.sort desc")
+            "where c.del = false and c.onShelf = true and r.type = ?1 and r.category = 'COLLECTION' order by r.sort desc")
     List<RecommendCollection> recommend(String type);
 
     @Transactional

+ 2 - 0
src/main/java/com/izouma/nineth/repo/NewsLikeRepo.java

@@ -16,4 +16,6 @@ public interface NewsLikeRepo extends JpaRepository<NewsLike, Long>, JpaSpecific
     void softDelete(Long id);
 
     List<NewsLike> findByUserIdAndNewsId(Long userId, Long newsId);
+
+    List<NewsLike> findByUserIdAndShowroomId(Long userId, Long showroomId);
 }

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

@@ -1,6 +1,8 @@
 package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.News;
+import com.izouma.nineth.dto.RecommendCollection;
+import com.izouma.nineth.dto.RecommendNews;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -8,6 +10,7 @@ import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 
 import javax.transaction.Transactional;
+import java.util.List;
 
 public interface NewsRepo extends JpaRepository<News, Long>, JpaSpecificationExecutor<News> {
     @Query("update News t set t.del = true where t.id = ?1")
@@ -20,4 +23,9 @@ public interface NewsRepo extends JpaRepository<News, Long>, JpaSpecificationExe
     @Transactional
     @CacheEvict(value = "news", key = "#id")
     void addLike(Long id, int num);
+
+    @Query("select new com.izouma.nineth.dto.RecommendNews(n,r) from News n join Recommend r on n.id = r.collectionId " +
+                "where n.del = false and r.type = ?1 and r.category = 'NEWS' order by r.sort desc")
+    List<RecommendNews> recommend(String type);
+
 }

+ 36 - 0
src/main/java/com/izouma/nineth/repo/ShowCollectionRepo.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.ShowCollection;
+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 javax.transaction.Transactional;
+import java.util.Collection;
+import java.util.List;
+
+public interface ShowCollectionRepo extends JpaRepository<ShowCollection, Long>, JpaSpecificationExecutor<ShowCollection> {
+    @Query("update ShowCollection t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    @Query("update ShowCollection t set t.del = true where t.collectionId = ?1")
+    @Modifying
+    @Transactional
+    void softDeleteCollection(Long collectionId);
+
+    List<ShowCollection> findAllByShowroomId(Long showroomId);
+
+    @Query("update ShowCollection t set t.del = true where t.showroomId = ?1")
+    @Modifying
+    @Transactional
+    void softDeleteByRoom(Long showroomId);
+
+    @Query("update ShowCollection t set t.del = true where t.showroomId in ?1")
+    @Modifying
+    @Transactional
+    void softDeleteByIdIn(Collection<Long> ids);
+
+}

+ 26 - 0
src/main/java/com/izouma/nineth/repo/ShowroomRepo.java

@@ -0,0 +1,26 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Showroom;
+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 javax.transaction.Transactional;
+import java.util.Optional;
+
+public interface ShowroomRepo extends JpaRepository<Showroom, Long>, JpaSpecificationExecutor<Showroom> {
+    @Query("update Showroom t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    @Query("update Showroom t set t.likes = t.likes + ?2 where t.id = ?1")
+    @Modifying
+    @Transactional
+    void addLike(Long id, int num);
+
+    Optional<Showroom> findByUserIdAndAssetId(Long userId, Long assetId);
+
+    Optional<Showroom> findByAssetId(Long assetId);
+}

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

@@ -41,7 +41,7 @@ public interface TokenHistoryRepo extends JpaRepository<TokenHistory, Long>, Jpa
 
     List<TokenHistory> findByOperationAndPriceNull(String oper);
 
-    @Query(nativeQuery = true, value = "select to_user_id , to_user, sum(price) total from token_history " +
+    @Query(nativeQuery = true, value = "select to_user_id , to_user, to_avatar, sum(price) total from token_history " +
             "where created_at between ?1 and ?2 group by to_user_id order by sum(price) desc limit ?3")
     List<Map<String, Object>> sumPrice(LocalDateTime start, LocalDateTime end, int size);
 }

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

@@ -181,10 +181,10 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
 
     List<User> findBySettleAccountIdIsNotNull();
 
-//    @Transactional
-//    @Modifying
-//    @Query("update User set vipPoint = vipPoint + ?2 where id = ?1")
-//    void updateVipPoint(Long id, int num);
+    @Transactional
+    @Modifying
+    @Query("update User set vipPoint = vipPoint + ?2 where id = ?1")
+    void addVipPoint(Long id, int num);
 
     @Transactional
     @Modifying

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

@@ -108,6 +108,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers("/news/get/**").permitAll()
                 .antMatchers("/druid/**").permitAll()
                 .antMatchers("/identityAuth/autoAuth").permitAll()
+                .antMatchers("/statistic/weekTop").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to

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

@@ -23,6 +23,7 @@ import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.BeanUtils;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.ApplicationContext;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
@@ -52,6 +53,9 @@ public class AssetService {
     private SysConfigService   sysConfigService;
     private RocketMQTemplate   rocketMQTemplate;
     private GeneralProperties  generalProperties;
+    private ShowroomRepo       showroomRepo;
+    private ShowCollectionRepo showCollectionRepo;
+
 
     public Page<Asset> all(PageQuery pageQuery) {
         Page<Asset> all = assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
@@ -188,6 +192,14 @@ public class AssetService {
         if (asset.isPublicShow()) {
             cancelPublic(asset);
         }
+
+        //寄售中的展厅需要先删除展厅
+        if (CollectionType.SHOWROOM.equals(asset.getType())) {
+            if (showroomRepo.findByAssetId(id).isPresent()) {
+                throw new BusinessException("请先删除展厅");
+            }
+        }
+
         Collection collection = Collection.builder()
                 .name(asset.getName())
                 .pic(asset.getPic())
@@ -269,6 +281,8 @@ public class AssetService {
         Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                 .orElseThrow(new BusinessException("无展示记录"));
         collectionRepo.delete(collection);
+        // 如果展厅有此藏品
+        showCollectionRepo.softDeleteCollection(asset.getPublicCollectionId());
 
         asset.setPublicShow(false);
         asset.setPublicCollectionId(null);
@@ -451,7 +465,7 @@ public class AssetService {
         return "ok";
     }
 
-
+    @Cacheable(value = "userStat", key = "#userId")
     public Map<String, BigDecimal> breakdown(Long userId) {
         List<TokenHistory> page = tokenHistoryRepo.userHistory(userId);
         BigDecimal sale = page.stream()

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

@@ -2,9 +2,11 @@ package com.izouma.nineth.service;
 
 import com.izouma.nineth.domain.Banner;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.PageWrapper;
 import com.izouma.nineth.repo.BannerRepo;
 import com.izouma.nineth.utils.JpaUtils;
 import lombok.AllArgsConstructor;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
@@ -14,7 +16,10 @@ public class BannerService {
 
     private BannerRepo bannerRepo;
 
-    public Page<Banner> all(PageQuery pageQuery) {
-        return bannerRepo.findAll(JpaUtils.toSpecification(pageQuery, Banner.class), JpaUtils.toPageRequest(pageQuery));
+    @Cacheable(value = "bannerList", key = "#pageQuery.hashCode()")
+    public PageWrapper<Banner> all(PageQuery pageQuery) {
+        Page<Banner> page = bannerRepo.findAll(JpaUtils.toSpecification(pageQuery, Banner.class), JpaUtils.toPageRequest(pageQuery));
+        return new PageWrapper<>(page.getContent(), page.getPageable().getPageNumber(),
+                page.getPageable().getPageSize(), page.getTotalElements());
     }
 }

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

@@ -1,6 +1,7 @@
 package com.izouma.nineth.service;
 
 import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -33,6 +34,10 @@ public class CacheService {
     public void clearUserInfo(Long id) {
     }
 
+    @CacheEvict(value = "myUserInfo", key = "#id")
+    public void clearUserMy(Long id) {
+    }
+
     @CacheEvict(value = "recommend", allEntries = true)
     public void clearRecommend() {
     }
@@ -40,4 +45,13 @@ public class CacheService {
     @CacheEvict(value = "collectionList", allEntries = true)
     public void clearCollectionList() {
     }
+
+    @Scheduled(cron = "1 0 0 ? * 2")
+    @CacheEvict(value = "weekTop", allEntries = true)
+    public void clearWeekTop() {
+    }
+
+    @CacheEvict(value = "bannerList", allEntries = true)
+    public void clearBannerList() {
+    }
 }

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

@@ -154,7 +154,7 @@ public class CollectionService {
                 record.getMaxCount(), record.getCountId(), record.isScanCode(), record.isNoSoldOut(),
                 record.getAssignment(), record.isCouponPayment(), record.getShareBg(), record.getRegisterBg(),
                 record.getVipQuota(), record.getTimeDelay(), record.getSaleTime(), record.getHoldDays(),
-                record.getOpenQuota());
+                record.getOpenQuota(), record.getShowroomBg(), record.getMaxCollection());
 
         record = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
         onShelfTask(record);
@@ -424,7 +424,7 @@ public class CollectionService {
         }
     }
 
-//    @Debounce(key = "#id", delay = 500)
+    //    @Debounce(key = "#id", delay = 500)
     public void syncSale(Long id) {
         Integer sale = (Integer) redisTemplate.opsForValue().get(RedisKeys.COLLECTION_SALE + id);
         if (sale != null) {

+ 3 - 0
src/main/java/com/izouma/nineth/service/MintOrderService.java

@@ -76,6 +76,7 @@ public class MintOrderService {
     private RocketMQTemplate              rocketMQTemplate;
     private CollectionRepo                collectionRepo;
     private OrderRepo                     orderRepo;
+    private ShowCollectionRepo            showCollectionRepo;
 
     public Page<MintOrder> all(PageQuery pageQuery) {
         return mintOrderRepo.findAll(JpaUtils.toSpecification(pageQuery, MintOrder.class), JpaUtils.toPageRequest(pageQuery));
@@ -253,6 +254,8 @@ public class MintOrderService {
                         Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
                                 .orElseThrow(new BusinessException("无展示记录"));
                         collectionRepo.delete(collection);
+                        // 如果展厅有此藏品
+                        showCollectionRepo.softDeleteCollection(asset.getPublicCollectionId());
 
                         asset.setPublicShow(false);
                         asset.setPublicCollectionId(null);

+ 20 - 0
src/main/java/com/izouma/nineth/service/NewsLikeService.java

@@ -4,6 +4,7 @@ import com.izouma.nineth.domain.NewsLike;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.repo.NewsLikeRepo;
 import com.izouma.nineth.repo.NewsRepo;
+import com.izouma.nineth.repo.ShowroomRepo;
 import com.izouma.nineth.utils.JpaUtils;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
@@ -17,6 +18,7 @@ public class NewsLikeService {
 
     private NewsLikeRepo newsLikeRepo;
     private NewsRepo     newsRepo;
+    private ShowroomRepo showroomRepo;
 
     public Page<NewsLike> all(PageQuery pageQuery) {
         return newsLikeRepo.findAll(JpaUtils.toSpecification(pageQuery, NewsLike.class), JpaUtils.toPageRequest(pageQuery));
@@ -39,4 +41,22 @@ public class NewsLikeService {
             newsRepo.addLike(newsId, -list.size());
         }
     }
+
+    public void likeRoom(Long userId, Long roomId) {
+        List<NewsLike> list = newsLikeRepo.findByUserIdAndShowroomId(userId, roomId);
+        if (!list.isEmpty()) return;
+        newsLikeRepo.save(NewsLike.builder()
+                .userId(userId)
+                .showroomId(roomId)
+                .build());
+        showroomRepo.addLike(roomId, 1);
+    }
+
+    public void unlikeRoom(Long userId, Long roomId) {
+        List<NewsLike> list = newsLikeRepo.findByUserIdAndShowroomId(userId, roomId);
+        if (!list.isEmpty()) {
+            newsLikeRepo.deleteAll(list);
+            showroomRepo.addLike(roomId, -list.size());
+        }
+    }
 }

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

@@ -7,7 +7,6 @@ import com.alipay.api.AlipayClient;
 import com.alipay.api.request.AlipayTradeWapPayRequest;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
 import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
-import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
 import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
@@ -57,6 +56,7 @@ import org.springframework.ui.Model;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
@@ -80,7 +80,6 @@ public class OrderService {
     private WxPayProperties               wxPayProperties;
     private AssetService                  assetService;
     private SysConfigService              sysConfigService;
-    private BlindBoxItemRepo              blindBoxItemRepo;
     private AssetRepo                     assetRepo;
     private UserCouponRepo                userCouponRepo;
     private CollectionService             collectionService;
@@ -92,6 +91,7 @@ public class OrderService {
     private SnowflakeIdWorker             snowflakeIdWorker;
     private SmsService                    smsService;
     private ErrorOrderRepo                errorOrderRepo;
+    private ShowCollectionRepo            showCollectionRepo;
 
     public Page<Order> all(PageQuery pageQuery) {
         return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
@@ -110,10 +110,11 @@ public class OrderService {
             throw new BusinessException("签名已过期");
         }
 
-        Integer stock = collectionService.getStock(collectionId);
-        if (stock == null || stock <= 0) {
-            throw new BusinessException("藏品已售罄", ErrorCode.SOLD_OUT);
-        }
+//        Integer stock = collectionService.getStock(collectionId);
+//        if (stock == null || stock <= 0) {
+//            throw new BusinessException("藏品已售罄", ErrorCode.SOLD_OUT);
+//        }
+        limitReq(collectionId);
 
         Long id = snowflakeIdWorker.nextId();
         SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(),
@@ -123,6 +124,15 @@ public class OrderService {
         return String.valueOf(id);
     }
 
+    public void limitReq(Long collectionId) {
+        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.LIMIT_REQ + collectionId);
+        ops.setIfAbsent(3000, Duration.ofSeconds(30));
+        Long val = ops.decrement();
+        if (val == null || val < 0) {
+            throw new BusinessException("很遗憾,藏品已售罄");
+        }
+    }
+
     public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor,
                         Long id, boolean vip) {
         long t = System.currentTimeMillis();
@@ -130,6 +140,8 @@ public class OrderService {
         int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty))
                 .map(Math::toIntExact)
                 .orElseThrow(new BusinessException("很遗憾,藏品已售罄", ErrorCode.SOLD_OUT));
+
+        int usePoint = 0;
         // 创建订单出错后需要回滚库存,所以需要try-catch
         try {
             if (stock < 0) {
@@ -188,7 +200,6 @@ public class OrderService {
             }
 
             //查询是否有拉新任务,只算官方购买
-            int usePoint = 0;
             if (collection.getSource() != CollectionSource.TRANSFER && collection.getAssignment() > 0) {
                 //延迟销售
                 if (!vip && collection.getTimeDelay()) {
@@ -207,11 +218,6 @@ public class OrderService {
                         collectionService.decreaseQuota(collectionId, 1);
                     }
                 } else {
-//                    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("没有购买名额");
                     }
@@ -283,13 +289,18 @@ public class OrderService {
 
             if (usePoint > 0) {
                 // 扣除积分
-                userRepo.updateVipPoint(userId, -usePoint);
+                userRepo.addVipPoint(userId, -usePoint);
             }
             rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000);
             log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t);
             return order;
         } catch (Exception e) {
             collectionService.increaseStock(collectionId, qty);
+            if (usePoint > 0) {
+                // 扣除积分
+                userRepo.addVipPoint(userId, usePoint);
+                log.info("取消加积分用户ID:{}, 积分:{}", userId, usePoint);
+            }
             throw e;
         }
     }
@@ -583,6 +594,8 @@ public class OrderService {
                         Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
                         assetService.transfer(asset, order.getPrice(), user, "转让", order.getId());
                         collectionRepo.delete(collection);
+                        // 如果展厅有此藏品
+                        showCollectionRepo.softDeleteCollection(order.getCollectionId());
 
                         // 发送短信提醒用户转让成功
                         if (asset != null && asset.getUserId() != null) {
@@ -810,19 +823,6 @@ public class OrderService {
         }
     }
 
-    public void refund(Long id) throws WxPayException {
-        Order order = orderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
-        if (order.getStatus() != OrderStatus.FINISH) {
-            throw new BusinessException("订单未付款");
-        }
-        WxPayRefundRequest request = new WxPayRefundRequest();
-        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(snowflakeIdWorker.nextId()));
-        wxPayService.refund(request);
-    }
-
     public Object queryCreateOrder(String id) {
         Object res = redisTemplate.opsForValue().get(RedisKeys.CREATE_ORDER + id);
         if (res != null) {

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

@@ -2,9 +2,11 @@ package com.izouma.nineth.service;
 
 import com.izouma.nineth.domain.PurchaseLevel;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.PageWrapper;
 import com.izouma.nineth.repo.PurchaseLevelRepo;
 import com.izouma.nineth.utils.JpaUtils;
 import lombok.AllArgsConstructor;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
@@ -14,7 +16,10 @@ public class PurchaseLevelService {
 
     private PurchaseLevelRepo purchaseLevelRepo;
 
-    public Page<PurchaseLevel> all(PageQuery pageQuery) {
-        return purchaseLevelRepo.findAll(JpaUtils.toSpecification(pageQuery, PurchaseLevel.class), JpaUtils.toPageRequest(pageQuery));
+    @Cacheable(value = "purchaseLevelList", key = "#pageQuery.hashCode()")
+    public PageWrapper<PurchaseLevel> all(PageQuery pageQuery) {
+        Page<PurchaseLevel> page = purchaseLevelRepo.findAll(JpaUtils.toSpecification(pageQuery, PurchaseLevel.class), JpaUtils.toPageRequest(pageQuery));
+        return new PageWrapper<>(page.getContent(), page.getPageable().getPageNumber(),
+                page.getPageable().getPageSize(), page.getTotalElements());
     }
 }

+ 20 - 0
src/main/java/com/izouma/nineth/service/ShowCollectionService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.ShowCollection;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.ShowCollectionRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ShowCollectionService {
+
+    private ShowCollectionRepo showCollectionRepo;
+
+    public Page<ShowCollection> all(PageQuery pageQuery) {
+        return showCollectionRepo.findAll(JpaUtils.toSpecification(pageQuery, ShowCollection.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 158 - 0
src/main/java/com/izouma/nineth/service/ShowroomService.java

@@ -0,0 +1,158 @@
+package com.izouma.nineth.service;
+
+import cn.hutool.core.collection.CollUtil;
+import com.izouma.nineth.domain.Asset;
+import com.izouma.nineth.domain.Collection;
+import com.izouma.nineth.domain.ShowCollection;
+import com.izouma.nineth.domain.Showroom;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.enums.AssetStatus;
+import com.izouma.nineth.enums.CollectionType;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.repo.CollectionRepo;
+import com.izouma.nineth.repo.ShowCollectionRepo;
+import com.izouma.nineth.repo.ShowroomRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import com.izouma.nineth.utils.ObjUtils;
+import lombok.AllArgsConstructor;
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import javax.persistence.Id;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@AllArgsConstructor
+public class ShowroomService {
+
+    private ShowroomRepo       showroomRepo;
+    private AssetRepo          assetRepo;
+    private CollectionRepo     collectionRepo;
+    private ShowCollectionRepo showCollectionRepo;
+
+    public Page<Showroom> all(PageQuery pageQuery) {
+        return showroomRepo.findAll(JpaUtils.toSpecification(pageQuery, Showroom.class), JpaUtils.toPageRequest(pageQuery));
+    }
+
+    /*
+    不做盲盒
+     */
+    public void save(Long userId, Showroom showroom) {
+        Asset asset = assetRepo.findById(showroom.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
+        if (!userId.equals(asset.getUserId())) {
+            throw new BusinessException("该资产不属于你");
+        }
+        if (!CollectionType.SHOWROOM.equals(asset.getType())) {
+            throw new BusinessException("不是展厅藏品");
+        }
+        if (!AssetStatus.NORMAL.equals(asset.getStatus())) {
+            throw new BusinessException("该状态不可创建展厅");
+        }
+        if (asset.isConsignment()) {
+            throw new BusinessException("寄售中不可以创建");
+        }
+
+        if (showroomRepo.findByAssetId(showroom.getAssetId()).isPresent()) {
+            throw new BusinessException("已创建过展厅");
+        }
+
+        List<ShowCollection> showCollections = showroom.getCollections();
+
+//        Collection showColl = collectionRepo.findById(asset.getCollectionId())
+//                .orElseThrow(new BusinessException("无藏品"));
+        if (asset.getMaxCollection() < showCollections.size()) {
+            throw new BusinessException("选择的藏品数量大于最多可放置数量");
+        }
+
+        List<Long> collectionIds = showCollections.stream()
+                .map(ShowCollection::getCollectionId)
+                .distinct()
+                .collect(Collectors.toList());
+
+        List<Collection> collections = collectionRepo.findAllByIdIn(collectionIds);
+        Map<Long, Collection> collectionMap = new HashMap<>();
+        collections.forEach(collection -> {
+            if (!userId.equals(collection.getOwnerId())) {
+                throw new BusinessException("该藏品不属于你");
+            }
+            collectionMap.put(collection.getId(), collection);
+        });
+
+        showroom.setUserId(userId);
+        showroom.setShowroomBg(asset.getShowroomBg());
+        showroom.setMaxCollection(asset.getMaxCollection());
+        Showroom show = showroomRepo.save(showroom);
+
+        showCollections.forEach(showCollection -> {
+            Collection collection = collectionMap.get(showCollection.getCollectionId());
+            if (ObjectUtils.isNotEmpty(collection)) {
+                showCollection.setPic(collection.getPic().get(0).getUrl());
+                showCollection.setShowroomId(show.getId());
+                showCollection.setAssetId(collection.getAssetId());
+                showCollectionRepo.save(showCollection);
+            }
+        });
+    }
+
+    public void update(Showroom showroom) {
+        Showroom recordRoom = showroomRepo.findById(showroom.getId()).orElseThrow(new BusinessException("无展厅"));
+        // 上传的藏品
+        List<ShowCollection> showCollections = showroom.getCollections();
+
+        List<ShowCollection> recordShowColls = showCollectionRepo.findAllByShowroomId(showroom.getId());
+        Map<Long, ShowCollection> showCollectionMap = recordShowColls.stream()
+                .collect(Collectors.toMap(ShowCollection::getId, coll -> coll));
+
+        Set<Long> removeRecord = new HashSet<>(showCollectionMap.keySet());
+
+        if (CollUtil.isNotEmpty(showCollections)) {
+
+            if (recordRoom.getMaxCollection() < showCollections.size()) {
+                throw new BusinessException("选择的藏品数量大于最多可放置数量");
+            }
+            List<Long> collectionIds = showCollections
+                    .stream()
+                    .filter(show -> ObjectUtils.isEmpty(show.getId()))
+                    .map(ShowCollection::getCollectionId)
+                    .collect(Collectors.toList());
+            Map<Long, Collection> collectionMap = collectionRepo.findAllByIdIn(collectionIds)
+                    .stream()
+                    .collect(Collectors.toMap(Collection::getId, coll -> coll));
+
+
+            showCollections.forEach(coll -> {
+                if (coll.getId() != null) {
+                    ShowCollection showCollection = showCollectionMap.get(coll.getId());
+                    if (ObjectUtils.isNotEmpty(showCollection)) {
+                        ObjUtils.merge(showCollection, coll);
+                        showCollectionRepo.save(showCollection);
+                        // 移除掉
+                        removeRecord.remove(coll.getId());
+                    }
+                } else {
+                    Collection collection = collectionMap.get(coll.getCollectionId());
+                    if (ObjectUtils.isNotEmpty(collection)) {
+                        coll.setPic(collection.getPic().get(0).getUrl());
+                        coll.setShowroomId(recordRoom.getId());
+                        coll.setAssetId(collection.getAssetId());
+                        showCollectionRepo.save(coll);
+                    }
+                }
+            });
+        } else {
+            // 删掉本次没有的
+            showCollectionRepo.softDeleteByRoom(recordRoom.getId());
+        }
+        if (CollUtil.isNotEmpty(removeRecord)) {
+            showCollectionRepo.softDeleteByIdIn(removeRecord);
+        }
+
+        ObjUtils.merge(recordRoom, showroom);
+        showroomRepo.save(recordRoom);
+    }
+
+}

+ 1 - 3
src/main/java/com/izouma/nineth/service/StatisticService.java

@@ -116,9 +116,7 @@ public class StatisticService {
         return trend;
     }
 
-    public String top(int year, int month, int size) {
-        LocalDateTime start = LocalDateTime.of(year, month, 1, 0, 0, 0);
-        LocalDateTime end = start.plusMonths(1).minusSeconds(1);
+    public String top(LocalDateTime start, LocalDateTime end, int size) {
         List<Map<String, Object>> maps = tokenHistoryRepo.sumPrice(start, end, size);
         return JSONObject.toJSONString(maps);
     }

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

@@ -34,6 +34,7 @@ import org.apache.commons.lang3.RandomStringUtils;
 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.context.event.EventListener;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -76,6 +77,11 @@ public class UserService {
     private CollectionService     collectionService;
 
     public User update(User user) {
+        if (!SecurityUtils.hasRole(AuthorityName.ROLE_ADMIN)) {
+            if (!SecurityUtils.getAuthenticatedUser().getId().equals(user.getId())) {
+                throw new BusinessException("无权限");
+            }
+        }
         User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录"));
         ObjUtils.merge(orig, user);
         orig = save(orig);
@@ -87,6 +93,7 @@ public class UserService {
         userRepo.updateHistoryFromUser(orig.getId());
         userRepo.updateHistoryToUser(orig.getId());
         cacheService.clearCollection();
+        cacheService.clearUserMy(user.getId());
         return orig;
     }
 
@@ -658,4 +665,9 @@ public class UserService {
             log.info("checkSettleAccount {}/{}", count.get(), list.size());
         });
     }
+
+    @Cacheable(value = "myUserInfo", key = "#id")
+    public User my(Long id) {
+        return userRepo.findById(id).orElseThrow(new BusinessException("用户不存在"));
+    }
 }

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

@@ -20,4 +20,8 @@ public class TokenUtils {
         }
         return null;
     }
+
+    public static void main(String[] args) {
+        System.out.println(TokenUtils.genTokenId());
+    }
 }

+ 13 - 14
src/main/java/com/izouma/nineth/web/AssetController.java

@@ -10,7 +10,6 @@ import com.izouma.nineth.repo.AssetRepo;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.service.GiftOrderService;
-import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import io.swagger.annotations.ApiOperation;
@@ -37,15 +36,15 @@ public class AssetController extends BaseController {
     private OrderRepo        orderRepo;
 
     //@PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public Asset save(@RequestBody Asset record) {
-        if (record.getId() != null) {
-            Asset orig = assetRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, record);
-            return assetRepo.save(orig);
-        }
-        return assetRepo.save(record);
-    }
+//    @PostMapping("/save")
+//    public Asset save(@RequestBody Asset record) {
+//        if (record.getId() != null) {
+//            Asset orig = assetRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+//            ObjUtils.merge(orig, record);
+//            return assetRepo.save(orig);
+//        }
+//        return assetRepo.save(record);
+//    }
 
 
     //@PreAuthorize("hasRole('ADMIN')")
@@ -61,10 +60,10 @@ public class AssetController extends BaseController {
         return asset;
     }
 
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable Long id) {
-        assetRepo.softDelete(id);
-    }
+//    @PostMapping("/del/{id}")
+//    public void del(@PathVariable Long id) {
+//        assetRepo.softDelete(id);
+//    }
 
     @GetMapping("/excel")
     @ResponseBody

+ 0 - 2
src/main/java/com/izouma/nineth/web/AuthenticationController.java

@@ -3,11 +3,9 @@ package com.izouma.nineth.web;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.enums.AuthorityName;
 import com.izouma.nineth.exception.AuthenticationException;
-import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.security.JwtTokenUtil;
 import com.izouma.nineth.security.JwtUser;
 import com.izouma.nineth.security.JwtUserFactory;
-import com.izouma.nineth.service.CacheService;
 import com.izouma.nineth.service.CaptchaService;
 import com.izouma.nineth.service.UserService;
 import io.swagger.annotations.ApiOperation;

+ 13 - 5
src/main/java/com/izouma/nineth/web/BannerController.java

@@ -1,10 +1,11 @@
 package com.izouma.nineth.web;
 
 import com.izouma.nineth.domain.Banner;
-import com.izouma.nineth.service.BannerService;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.BannerRepo;
+import com.izouma.nineth.service.BannerService;
+import com.izouma.nineth.service.CacheService;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
@@ -22,16 +23,21 @@ import java.util.List;
 public class BannerController extends BaseController {
     private BannerService bannerService;
     private BannerRepo    bannerRepo;
+    private CacheService  cacheService;
 
-    //@PreAuthorize("hasRole('ADMIN')")
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     public Banner save(@RequestBody Banner record) {
         if (record.getId() != null) {
             Banner orig = bannerRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
             ObjUtils.merge(orig, record);
-            return bannerRepo.save(orig);
+            orig = bannerRepo.save(orig);
+            cacheService.clearBannerList();
+            return orig;
         }
-        return bannerRepo.save(record);
+        record = bannerRepo.save(record);
+        cacheService.clearBannerList();
+        return record;
     }
 
 
@@ -39,7 +45,7 @@ public class BannerController extends BaseController {
     @PostMapping("/all")
     public Page<Banner> all(@RequestBody PageQuery pageQuery) {
         pageQuery.getQuery().put("del", false);
-        return bannerService.all(pageQuery);
+        return bannerService.all(pageQuery).toPage();
     }
 
     @GetMapping("/get/{id}")
@@ -47,9 +53,11 @@ public class BannerController extends BaseController {
         return bannerRepo.findById(id).orElseThrow(new BusinessException("无记录"));
     }
 
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/del/{id}")
     public void del(@PathVariable Long id) {
         bannerRepo.softDelete(id);
+        cacheService.clearBannerList();
     }
 
     @GetMapping("/excel")

+ 58 - 7
src/main/java/com/izouma/nineth/web/CollectionController.java

@@ -5,8 +5,11 @@ import com.izouma.nineth.domain.FileObject;
 import com.izouma.nineth.dto.CollectionDTO;
 import com.izouma.nineth.dto.CreateBlindBox;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.RecommendDTO;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.CollectionRepo;
+import com.izouma.nineth.repo.NewsRepo;
+import com.izouma.nineth.service.CacheService;
 import com.izouma.nineth.service.CollectionService;
 import com.izouma.nineth.service.LikeService;
 import com.izouma.nineth.utils.SecurityUtils;
@@ -18,10 +21,12 @@ 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.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -33,13 +38,16 @@ public class CollectionController extends BaseController {
     private CollectionService collectionService;
     private CollectionRepo    collectionRepo;
     private LikeService       likeService;
+    private NewsRepo          newsRepo;
+    private CacheService      cacheService;
 
-    //@PreAuthorize("hasRole('ADMIN')")
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     public Collection save(@RequestBody Collection record) {
         return collectionService.update(record);
     }
 
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/create")
     public Collection create(@RequestBody Collection record) {
         return collectionService.create(record);
@@ -57,10 +65,11 @@ public class CollectionController extends BaseController {
                 .orElseThrow(new BusinessException("无记录")), true, true);
     }
 
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable Long id) {
-        collectionRepo.softDelete(id);
-    }
+//    @PreAuthorize("hasRole('ADMIN')")
+//    @PostMapping("/del/{id}")
+//    public void del(@PathVariable Long id) {
+//        collectionRepo.softDelete(id);
+//    }
 
     @GetMapping("/excel")
     @ResponseBody
@@ -89,6 +98,7 @@ public class CollectionController extends BaseController {
         return collectionService.toDTO(collectionRepo.userLikes(SecurityUtils.getAuthenticatedUser().getId()));
     }
 
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/createBlindBox")
     public Collection createBlindBox(@RequestBody CreateBlindBox createBlindBox) {
         return collectionService.createBlindBox(createBlindBox);
@@ -99,8 +109,8 @@ public class CollectionController extends BaseController {
         collectionService.appointment(id, SecurityUtils.getAuthenticatedUser().getId());
     }
 
-    @GetMapping("/recommend")
-    @Cacheable("recommend")
+    //    @GetMapping("/recommend")
+//    @Cacheable("recommend")
     public List<CollectionDTO> recommend(@RequestParam String type) {
         return collectionRepo.recommend(type).stream().map(rc -> {
             if (StringUtils.isNotBlank(rc.getRecommend().getPic())) {
@@ -109,8 +119,49 @@ public class CollectionController extends BaseController {
             }
             CollectionDTO collectionDTO = new CollectionDTO();
             BeanUtils.copyProperties(rc.getCollection(), collectionDTO);
+
+            // 减少数据量
+            collectionDTO.setDetail(null);
+            collectionDTO.setPrivileges(null);
+            collectionDTO.setProperties(null);
+
             return collectionDTO;
         }).collect(Collectors.toList());
     }
+
+    @GetMapping("/recommend")
+    @Cacheable("recommend")
+    public List<RecommendDTO> recommendAll() {
+        List<RecommendDTO> recommedDTOS = new ArrayList<>();
+
+        List<RecommendDTO> collectionDTOS = collectionRepo.recommend("LIST").stream().map(rc -> {
+            if (StringUtils.isNotBlank(rc.getRecommend().getPic())) {
+                rc.getCollection().setPic(Collections.singletonList(new FileObject(null, rc.getRecommend()
+                        .getPic(), null, null)));
+            }
+            CollectionDTO collectionDTO = new CollectionDTO();
+            BeanUtils.copyProperties(rc.getCollection(), collectionDTO);
+            return new RecommendDTO(rc.getRecommend().getSort(), collectionDTO, "collection");
+        }).collect(Collectors.toList());
+
+        List<RecommendDTO> news = newsRepo.recommend("LIST").stream().map(rn -> {
+            if (StringUtils.isNotBlank(rn.getRecommend().getPic())) {
+                rn.getNews().setPic(rn.getRecommend().getPic());
+            }
+            return new RecommendDTO(rn.getRecommend().getSort(), rn.getNews(), "news");
+        }).collect(Collectors.toList());
+
+        recommedDTOS.addAll(collectionDTOS);
+        recommedDTOS.addAll(news);
+        recommedDTOS.sort((a, b) -> b.getSort().compareTo(a.getSort()));
+        return recommedDTOS;
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/clearRecommend")
+    public String clearRecommend() {
+        cacheService.clearRecommend();
+        return "ok";
+    }
 }
 

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

@@ -1,6 +1,5 @@
 package com.izouma.nineth.web;
 
-import com.github.binarywang.wxpay.exception.WxPayException;
 import com.izouma.nineth.domain.Order;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.OrderDTO;
@@ -12,7 +11,6 @@ import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.repo.UserRepo;
 import com.izouma.nineth.service.OrderService;
-import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.SnowflakeIdWorker;
 import com.izouma.nineth.utils.excel.ExcelUtils;
@@ -39,15 +37,15 @@ public class OrderController extends BaseController {
     private UserRepo     userRepo;
 
     //@PreAuthorize("hasRole('ADMIN')")
-    @PostMapping("/save")
-    public Order save(@RequestBody Order record) {
-        if (record.getId() != null) {
-            Order orig = orderRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, record);
-            return orderRepo.save(orig);
-        }
-        return orderRepo.save(record);
-    }
+//    @PostMapping("/save")
+//    public Order save(@RequestBody Order record) {
+//        if (record.getId() != null) {
+//            Order orig = orderRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+//            ObjUtils.merge(orig, record);
+//            return orderRepo.save(orig);
+//        }
+//        return orderRepo.save(record);
+//    }
 
 
     //@PreAuthorize("hasRole('ADMIN')")
@@ -74,10 +72,10 @@ public class OrderController extends BaseController {
         return orderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
     }
 
-    @PostMapping("/del/{id}")
-    public void del(@PathVariable Long id) {
-        orderRepo.softDelete(id);
-    }
+//    @PostMapping("/del/{id}")
+//    public void del(@PathVariable Long id) {
+//        orderRepo.softDelete(id);
+//    }
 
     @PostMapping("/excel")
     @ResponseBody
@@ -123,11 +121,6 @@ public class OrderController extends BaseController {
         orderRepo.save(order);
     }
 
-    @PostMapping("/refund")
-    public void refund(@RequestParam Long id) throws WxPayException {
-        orderService.refund(id);
-    }
-
     @GetMapping("/{id}/status")
     public Object status(@PathVariable Long id) {
         Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在"));

+ 4 - 4
src/main/java/com/izouma/nineth/web/PurchaseLevelController.java

@@ -1,14 +1,14 @@
 package com.izouma.nineth.web;
+
 import com.izouma.nineth.domain.PurchaseLevel;
-import com.izouma.nineth.service.PurchaseLevelService;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.PurchaseLevelRepo;
+import com.izouma.nineth.service.PurchaseLevelService;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
@@ -20,7 +20,7 @@ import java.util.List;
 @AllArgsConstructor
 public class PurchaseLevelController extends BaseController {
     private PurchaseLevelService purchaseLevelService;
-    private PurchaseLevelRepo purchaseLevelRepo;
+    private PurchaseLevelRepo    purchaseLevelRepo;
 
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
@@ -37,7 +37,7 @@ public class PurchaseLevelController extends BaseController {
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     public Page<PurchaseLevel> all(@RequestBody PageQuery pageQuery) {
-        return purchaseLevelService.all(pageQuery);
+        return purchaseLevelService.all(pageQuery).toPage();
     }
 
     @GetMapping("/get/{id}")

+ 9 - 3
src/main/java/com/izouma/nineth/web/RecommendController.java

@@ -4,6 +4,7 @@ import com.izouma.nineth.domain.Recommend;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.RecommendRepo;
+import com.izouma.nineth.service.CacheService;
 import com.izouma.nineth.service.RecommendService;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
@@ -20,7 +21,8 @@ import java.util.List;
 @AllArgsConstructor
 public class RecommendController extends BaseController {
     private RecommendService recommendService;
-    private RecommendRepo recommendRepo;
+    private RecommendRepo    recommendRepo;
+    private CacheService     cacheService;
 
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
@@ -28,9 +30,13 @@ public class RecommendController extends BaseController {
         if (record.getId() != null) {
             Recommend orig = recommendRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
             ObjUtils.merge(orig, record);
-            return recommendRepo.save(orig);
+            orig = recommendRepo.save(orig);
+            cacheService.clearRecommend();
+            return orig;
         }
-        return recommendRepo.save(record);
+        record = recommendRepo.save(record);
+        cacheService.clearRecommend();
+        return record;
     }
 
 

+ 60 - 0
src/main/java/com/izouma/nineth/web/ShowCollectionController.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.ShowCollection;
+import com.izouma.nineth.service.ShowCollectionService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.ShowCollectionRepo;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/showCollection")
+@AllArgsConstructor
+public class ShowCollectionController extends BaseController {
+    private ShowCollectionService showCollectionService;
+    private ShowCollectionRepo showCollectionRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public ShowCollection save(@RequestBody ShowCollection record) {
+        if (record.getId() != null) {
+            ShowCollection orig = showCollectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return showCollectionRepo.save(orig);
+        }
+        return showCollectionRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<ShowCollection> all(@RequestBody PageQuery pageQuery) {
+        return showCollectionService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public ShowCollection get(@PathVariable Long id) {
+        return showCollectionRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        showCollectionRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<ShowCollection> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 64 - 0
src/main/java/com/izouma/nineth/web/ShowroomController.java

@@ -0,0 +1,64 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.Showroom;
+import com.izouma.nineth.repo.ShowCollectionRepo;
+import com.izouma.nineth.service.ShowroomService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.ShowroomRepo;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/showroom")
+@AllArgsConstructor
+public class ShowroomController extends BaseController {
+    private ShowroomService    showroomService;
+    private ShowroomRepo       showroomRepo;
+    private ShowCollectionRepo showCollectionRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Showroom save(@RequestBody Showroom record) {
+        if (record.getId() != null) {
+            Showroom orig = showroomRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return showroomRepo.save(orig);
+        }
+        return showroomRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Showroom> all(@RequestBody PageQuery pageQuery) {
+        return showroomService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Showroom get(@PathVariable Long id) {
+        return showroomRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        showCollectionRepo.softDeleteByRoom(id);
+        showroomRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Showroom> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

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

@@ -2,11 +2,15 @@ package com.izouma.nineth.web;
 
 import com.izouma.nineth.service.StatisticService;
 import lombok.AllArgsConstructor;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.Map;
 
 @RestController
@@ -37,6 +41,17 @@ public class StatisticController {
 
     @GetMapping("/top")
     public String top(int year, int month) {
-        return statisticService.top(year, month, 50);
+        LocalDateTime start = LocalDateTime.of(year, month, 1, 0, 0, 0);
+        LocalDateTime end = start.plusMonths(1).minusSeconds(1);
+        return statisticService.top(start, end, 50);
+    }
+
+    @GetMapping("/weekTop")
+    @Cacheable("weekTop")
+    public String weekTop() {
+        LocalDate now = LocalDate.now();
+        LocalDate endDate = now.minusDays(now.getDayOfWeek().getValue());
+        LocalDateTime start = LocalDateTime.of(endDate.minusDays(6), LocalTime.MIN);
+        return statisticService.top(start, LocalDateTime.of(endDate, LocalTime.MAX), 50);
     }
 }

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

@@ -69,8 +69,7 @@ public class UserController extends BaseController {
 
     @GetMapping("/my")
     public User my() {
-        return userRepo.findById(SecurityUtils.getAuthenticatedUser().getId())
-                .orElseThrow(new BusinessException("用户不存在"));
+        return userService.my(SecurityUtils.getAuthenticatedUser().getId());
     }
 
     @GetMapping("/myAdmin")

+ 1 - 0
src/main/resources/genjson/ShowCollection.json

@@ -0,0 +1 @@
+{"tableName":"ShowCollection","className":"ShowCollection","remark":"展厅藏品","genTable":true,"genClass":true,"genList":false,"genForm":false,"genRouter":false,"javaPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/java/com/izouma/nineth","viewPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/vue/src/views","routerPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/vue/src","resourcesPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"showroomId","modelName":"showroomId","remark":"showroomId","showInList":true,"showInForm":true,"formType":"number"},{"name":"collectionId","modelName":"collectionId","remark":"collectionId","showInList":true,"showInForm":true,"formType":"number"},{"name":"pic","modelName":"pic","remark":"pic","showInList":true,"showInForm":true,"formType":"number"},{"name":"sort","modelName":"sort","remark":"sort","showInList":true,"showInForm":true,"formType":"singleLineText"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.ShowCollection"}

+ 1 - 0
src/main/resources/genjson/Showroom.json

@@ -0,0 +1 @@
+{"tableName":"Showroom","className":"Showroom","remark":"展厅","genTable":true,"genClass":true,"genList":false,"genForm":false,"genRouter":false,"javaPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/java/com/izouma/nineth","viewPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/vue/src/views","routerPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/vue/src","resourcesPath":"/Users/qiufangchao/Desktop/project/raex_back/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"userId","modelName":"userId","remark":"userId","showInList":true,"showInForm":true,"formType":"number"},{"name":"assetId","modelName":"assetId","remark":"assetId","showInList":true,"showInForm":true,"formType":"number"},{"name":"pic","modelName":"pic","remark":"pic","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"introduction","modelName":"introduction","remark":"introduction","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"likes","modelName":"likes","remark":"likes","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"share","modelName":"share","remark":"share","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"status","modelName":"status","remark":"status","showInList":true,"showInForm":true,"formType":"singleLineText"},{"name":"collections","modelName":"collections","remark":"collections","showInList":true,"showInForm":true,"formType":"singleLineText"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.Showroom"}

+ 19 - 2
src/main/vue/src/router.js

@@ -514,9 +514,26 @@ const router = new Router({
                 {
                     path: '/adapayMerchantList',
                     name: 'AdapayMerchantList',
-                    component: () => import(/* webpackChunkName: "adapayMerchantList" */ '@/views/AdapayMerchantList.vue'),
+                    component: () =>
+                        import(/* webpackChunkName: "adapayMerchantList" */ '@/views/AdapayMerchantList.vue'),
+                    meta: {
+                        title: 'ada'
+                    }
+                },
+                {
+                    path: '/showroomEdit',
+                    name: 'ShowroomEdit',
+                    component: () => import(/* webpackChunkName: "showroomEdit" */ '@/views/ShowroomEdit.vue'),
+                    meta: {
+                        title: '展厅编辑'
+                    }
+                },
+                {
+                    path: '/showroomList',
+                    name: 'ShowroomList',
+                    component: () => import(/* webpackChunkName: "showroomList" */ '@/views/ShowroomList.vue'),
                     meta: {
-                        title: 'ada',
+                        title: '展厅'
                     },
                 },
                 {

+ 9 - 2
src/main/vue/src/views/CollectionEdit.vue

@@ -16,7 +16,7 @@
                     size="small"
                 >
                     <el-form-item prop="name" label="名称">
-                        <el-input v-model="formData.name" :disabled="!canEdit"></el-input>
+                        <el-input v-model="formData.name" :disabled="!canEdit" style="width: 500px"></el-input>
                     </el-form-item>
                     <el-form-item prop="pic" label="图片">
                         <object-upload
@@ -228,7 +228,14 @@
                     </div>
 
                     <el-form-item prop="holdDays" label="持有天数">
-                        <el-input-number type="number" :min="0" :step="1" :max="2147483647" v-model="formData.holdDays" style="width: 180px"></el-input-number>
+                        <el-input-number
+                            type="number"
+                            :min="0"
+                            :step="1"
+                            :max="2147483647"
+                            v-model="formData.holdDays"
+                            style="width: 180px"
+                        ></el-input-number>
                         <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数。</div>
                     </el-form-item>
                     <el-form-item prop="noSoldOut" label="售罄">

+ 53 - 9
src/main/vue/src/views/RecommendEdit.vue

@@ -16,13 +16,36 @@
                     size="small"
                     style="max-width: 500px"
                 >
-                    <el-form-item prop="collectionId" label="藏品/盲盒ID">
+                    <el-form-item prop="type" label="类型">
+                        <el-select v-model="formData.category" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="collectionId" label="藏品/盲盒ID" v-if="formData.category === 'COLLECTION'">
                         <collection-search v-model="formData.collectionId" @select="onSelect"></collection-search>
                     </el-form-item>
+                    <el-form-item prop="collectionId" label="新闻ID" v-else>
+                        <el-select
+                            v-model="formData.collectionId"
+                            clearable
+                            filterable
+                            placeholder="请选择"
+                            @change="onSelectNews"
+                        >
+                            <el-option v-for="item in newsList" :key="item.value" :label="item.title" :value="item.id">
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
                     <el-form-item prop="sort" label="排序">
                         <el-input-number v-model="formData.sort" :min="0"></el-input-number>
                     </el-form-item>
-                    <el-form-item prop="type" label="类型">
+                    <!-- <el-form-item prop="type" label="类型">
                         <el-select v-model="formData.type" clearable filterable placeholder="请选择">
                             <el-option
                                 v-for="item in typeOptions"
@@ -32,7 +55,7 @@
                             >
                             </el-option>
                         </el-select>
-                    </el-form-item>
+                    </el-form-item> -->
                     <el-form-item label="图片" prop="pic">
                         <single-upload v-model="formData.pic"></single-upload>
                         <div class="tip">推荐分辨率:列表1000x1000,轮播765x420</div>
@@ -64,11 +87,22 @@ export default {
                     this.$message.error(e.error);
                 });
         }
+        this.$http
+            .post('news/all', { size: 100, sort: 'sort,desc', query: { del: false } }, { body: 'json' })
+            .then(res => {
+                this.newsList = res.content;
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
     },
     data() {
         return {
             saving: false,
-            formData: {},
+            formData: {
+                type: 'LIST'
+            },
             rules: {
                 collectionId: [
                     {
@@ -101,9 +135,10 @@ export default {
                 pic: [{ required: true, message: '请上传图片' }]
             },
             typeOptions: [
-                { label: '列表', value: 'LIST' },
-                { label: '轮播', value: 'BANNER' }
-            ]
+                { label: '藏品/盲盒', value: 'COLLECTION' },
+                { label: '新闻', value: 'NEWS' }
+            ],
+            newsList: []
         };
     },
     methods: {
@@ -153,14 +188,23 @@ export default {
             console.log(e);
             if (e) {
                 this.$set(this.formData, 'name', e.name);
-                if (e.pic && e.pic[0]){
+                if (e.pic && e.pic[0]) {
                     if (/\.mp4$/.test(e.pic[0].url)) {
                         this.$set(this.formData, 'pic', e.pic[0].thumb);
                     } else {
                         this.$set(this.formData, 'pic', e.pic[0].url);
                     }
                 }
-
+            }
+        },
+        onSelectNews(val) {
+            let e = this.newsList.find(i => i.id === val);
+            console.log(e);
+            if (e) {
+                this.$set(this.formData, 'name', e.title);
+                if (e.pic) {
+                    this.$set(this.formData, 'pic', e.pic);
+                }
             }
         }
     }

+ 5 - 5
src/main/vue/src/views/RecommendList.vue

@@ -38,10 +38,10 @@
         >
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
-            <el-table-column prop="collectionId" label="藏品/盲盒ID"> </el-table-column>
+            <el-table-column prop="collectionId" label="相关ID"> </el-table-column>
             <el-table-column prop="name" label="名称"> </el-table-column>
             <el-table-column prop="sort" label="排序"> </el-table-column>
-            <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
+            <el-table-column prop="category" label="类型" :formatter="typeFormatter"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" width="150">
                 <template slot-scope="{ row }">
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
@@ -86,8 +86,8 @@ export default {
             url: '/recommend/all',
             downloading: false,
             typeOptions: [
-                { label: '列表', value: 'LIST' },
-                { label: '轮播', value: 'BANNER' }
+                { label: '藏品/盲盒', value: 'COLLECTION' },
+                { label: '新闻', value: 'NEWS' }
             ],
             type: null,
             sortStr: 'sort,desc'
@@ -107,7 +107,7 @@ export default {
             return '';
         },
         beforeGetData() {
-            return { search: this.search, query: { del: false, type: this.type } };
+            return { search: this.search, query: { del: false, category: this.type } };
         },
         toggleMultipleMode(multipleMode) {
             this.multipleMode = multipleMode;

+ 779 - 0
src/main/vue/src/views/ShowroomEdit.vue

@@ -0,0 +1,779 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id"> 删除 </el-button> -->
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form
+                    :model="formData"
+                    :rules="rules"
+                    ref="form"
+                    label-width="120px"
+                    label-position="right"
+                    size="small"
+                >
+                    <el-form-item prop="name" label="名称">
+                        <el-input v-model="formData.name" :disabled="!canEdit" style="width: 500px"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="pic" label="图片">
+                        <object-upload
+                            v-model="formData.pic[0]"
+                            :disabled="!canEdit"
+                            compress
+                            width="3000"
+                            height="3000"
+                        ></object-upload>
+                        <div class="tip">支持JPG、PNG、GIF、MP4,推荐长宽比1:1</div>
+                    </el-form-item>
+                    <!-- <el-form-item prop="model3d" label="3D模型">
+                        <model-upload
+                            :limit="1"
+                            v-model="formData.model3d"
+                            :customUrl="customUrl"
+                            accept="application/zip"
+                            format="json"
+                            single
+                        ></model-upload>
+                        <div class="tip">请将FBX文件与贴图打包成zip压缩包上传</div>
+                    </el-form-item> -->
+                    <el-form-item label="相机距离" v-if="formData.model3d">
+                        <el-input-number v-model="scale" :min="0.1" :step="0.1"></el-input-number>
+                    </el-form-item>
+                    <el-form-item label="Y轴偏移" v-if="formData.model3d">
+                        <el-input-number v-model="yOffset"></el-input-number>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="minterId" label="铸造者">
+                            <minter-select
+                                v-model="formData.minterId"
+                                @detail="onMinterDetail"
+                                :disabled="!canEdit"
+                            ></minter-select>
+                        </el-form-item>
+                        <el-form-item prop="category" label="分类">
+                            <el-select v-model="formData.category" :disabled="!canEdit">
+                                <el-option
+                                    v-for="item in cateogories"
+                                    :label="item"
+                                    :value="item"
+                                    :key="item"
+                                ></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="detail" label="详情" style="width: calc(100vw - 450px)">
+                        <rich-text v-model="formData.detail"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="properties" label="特性" style="width: calc(100vw - 450px)" size="mini">
+                        <el-table :data="formData.properties">
+                            <el-table-column prop="name" label="名称">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.name" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column prop="value" label="内容">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.value" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="80" align="center">
+                                <template v-slot="{ $index }">
+                                    <el-button type="danger" plain size="mini" @click="delProperty($index)">
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
+                    </el-form-item>
+                    <el-form-item label="特权" prop="privileges" style="width: calc(100vw - 450px)">
+                        <el-table :data="privilegeOptions">
+                            <el-table-column prop="name" label="可选特权" width="150"></el-table-column>
+                            <el-table-column prop="description"></el-table-column>
+                            <el-table-column width="155" align="right">
+                                <template v-slot="{ row, $index }">
+                                    <el-button size="mini" v-if="!row.added" @click="addPrivilege(row, $index)">
+                                        添加
+                                    </el-button>
+                                    <el-button size="mini" v-if="!!row.added" plain @click="editPrivilege(row, $index)">
+                                        编辑
+                                    </el-button>
+                                    <el-button
+                                        size="mini"
+                                        v-if="!!row.added"
+                                        type="danger"
+                                        plain
+                                        @click="delPrivilege(row, $index)"
+                                    >
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="price" label="价格">
+                            <el-input-number
+                                type="number"
+                                v-model="formData.price"
+                                :disabled="!canEdit"
+                            ></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="price" label="原价">
+                            <el-input-number
+                                type="number"
+                                v-model="formData.originalPrice"
+                                :disabled="!canEdit"
+                            ></el-input-number>
+                        </el-form-item>
+                    </div>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="royalties" label="版税(%)">
+                            <el-input-number v-model="formData.royalties" :min="0" :max="99" :disabled="!canEdit">
+                            </el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="serviceCharge" label="手续费(%)">
+                            <el-input-number v-model="formData.serviceCharge" :min="0" :max="99" :disabled="!canEdit">
+                            </el-input-number>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="total" label="发行数量">
+                        <el-input-number v-model="formData.total" :disabled="!canEdit"></el-input-number>
+                    </el-form-item>
+
+                    <el-form-item prop="scanCode" label="仅扫码可见">
+                        <el-radio v-model="formData.scanCode" :label="true">是</el-radio>
+                        <el-radio v-model="formData.scanCode" :label="false">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="onShelf" label="上架" v-if="!formData.scanCode">
+                        <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
+                        <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
+                    </el-form-item>
+
+                    <div class="inline-wrapper">
+                        <el-form-item prop="startTime" label="定时发布">
+                            <el-radio v-model="formData.scheduleSale" :label="true">是</el-radio>
+                            <el-radio v-model="formData.scheduleSale" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="startTime"
+                            label="发布时间"
+                            v-if="formData.scheduleSale"
+                            style="margin-left: 22px"
+                        >
+                            <el-date-picker
+                                v-model="formData.startTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="发布时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item
+                        prop="salable"
+                        label="可售"
+                        v-if="
+                            formData.onShelf === true || (formData.scanCode === true && formData.scheduleSale === false)
+                        "
+                    >
+                        <el-radio v-model="formData.salable" :label="true">是</el-radio>
+                        <el-radio v-model="formData.salable" :label="false">仅展示</el-radio>
+                    </el-form-item>
+
+                    <el-form-item prop="sort" label="排序">
+                        <el-input-number v-model="formData.sort" :min="0"></el-input-number>
+                        <div class="tip">数字越大排序越靠前,相同数值按创建时间倒序排列</div>
+                    </el-form-item>
+
+                    <div class="inline-wrapper">
+                        <el-form-item prop="maxCount" label="限购">
+                            <el-input-number v-model="formData.maxCount" :min="0" :step="1"></el-input-number>
+                            <div class="tip">0表示不限购</div>
+                        </el-form-item>
+                        <el-form-item prop="countId" label="限购识别码" v-if="formData.maxCount > 0">
+                            <el-input v-model="formData.countId" style="width: 300px"></el-input>
+                            <div class="tip">相同识别码的藏品共享限购数量</div>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item prop="holdDays" label="持有天数">
+                        <el-input-number type="number" :min="0" :step="1" :max="2147483647" v-model="formData.holdDays" style="width: 180px"></el-input-number>
+                        <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数。</div>
+                    </el-form-item>
+                    <el-form-item prop="noSoldOut" label="售罄">
+                        <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
+                        <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
+                    </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 prop="showroomBg" label="展厅背景">
+                        <single-upload v-model="formData.showroomBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item prop="maxCollection" label="最多可放数量">
+                        <el-input-number type="number" v-model="formData.maxCollection" :min="1"></el-input-number>
+                    </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>
+                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0">
+                        <el-radio v-model="formData.openQuota" :label="true">开启</el-radio>
+                        <el-radio v-model="formData.openQuota" :label="false">关闭</el-radio>
+                    </el-form-item>
+                    <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">
+                        <single-upload v-model="formData.shareBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item label="注册海报" v-if="formData.assignment > 0">
+                        <single-upload v-model="formData.registerBg"></single-upload>
+                    </el-form-item>
+
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+                        <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button> -->
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+
+        <el-dialog :visible.sync="showPrivilegeEditDialog" width="600px" :title="privilegeForm.name">
+            <el-form
+                ref="privilegeForm"
+                :model="privilegeForm"
+                label-position="right"
+                label-width="80px"
+                :rules="privelegeRules"
+            >
+                <el-form-item
+                    prop="detail"
+                    label="详细内容"
+                    v-if="privilegeForm.type === 'text' || privilegeForm.type === 'exchange'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.detail"></el-input>
+                </el-form-item>
+                <el-form-item prop="detail" label="二维码" v-if="privilegeForm.type === 'qrcode'">
+                    <single-upload v-model="privilegeForm.detail"></single-upload>
+                </el-form-item>
+                <el-form-item
+                    prop="remark"
+                    label="说明"
+                    v-if="privilegeForm.type === 'qrcode' || privilegeForm.type === 'code'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.remark"></el-input>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="showPrivilegeEditDialog = false">取消</el-button>
+                <el-button type="primary" @click="savePrivilege">保存</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import resolveUrl from 'resolve-url';
+import ModelUpload from '../components/ModelUpload.vue';
+import { format, parse, isBefore } from 'date-fns';
+import SingleUpload from '../components/SingleUpload.vue';
+export default {
+    name: 'CollectionEdit',
+    components: { ModelUpload, SingleUpload },
+    created() {
+        Promise.all([
+            new Promise((resolve, reject) => {
+                if (this.$route.query.id) {
+                    return this.$http
+                        .get('collection/get/' + this.$route.query.id)
+                        .then(res => {
+                            if (res.model3d) {
+                                let url = new URL(res.model3d.url);
+                                this.scale = Number(url.searchParams.get('scale')) || 1;
+                                this.yOffset = Number(url.searchParams.get('yOffset')) || 0;
+                                res.model3d.url = url.origin + url.pathname;
+                            }
+                            res.properties = res.properties || [];
+                            res.privileges = res.privileges || [];
+                            this.formData = res;
+                            if (res.totalQuota !== res.vipQuota) {
+                                this.editQuota = false;
+                            }
+                            resolve();
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.$message.error(e.error);
+                            resolve();
+                        });
+                }
+                return resolve();
+            }),
+            this.$http
+                .post('/privilegeOption/all', { size: 10000, query: { del: false } }, { body: 'json' })
+                .then(res => {
+                    this.privilegeOptions = res.content;
+                    return Promise.resolve();
+                })
+        ]).then(() => {
+            console.log(this.formData, this.privilegeOptions);
+            this.formData.privileges.forEach(p => {
+                let idx = this.privilegeOptions.findIndex(i => i.name === p.name);
+                if (idx > -1) {
+                    this.$set(this.privilegeOptions[idx], 'added', true);
+                }
+            });
+        });
+    },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+                onShelf: false,
+                salable: true,
+                properties: [],
+                type: 'SHOWROOM',
+                source: 'OFFICIAL',
+                pic: [],
+                scheduleSale: true,
+                sort: 0,
+                privileges: [],
+                maxCount: 0,
+                countId: null,
+                canResale: true,
+                scanCode: false,
+                noSoldOut: true,
+                assignment: 0,
+                couponPayment: false,
+                maxCollection: 20
+            },
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ],
+                pic: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('请上传内容'));
+                                    return;
+                                } else {
+                                    for (let f of value) {
+                                        if (!f.url) {
+                                            callback(new Error('请上传内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                                callback();
+                            } else {
+                                callback(new Error('请上传内容'));
+                            }
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                minter: [
+                    {
+                        required: true,
+                        message: '请输入铸造者',
+                        trigger: 'blur'
+                    }
+                ],
+                minterId: [
+                    {
+                        required: true,
+                        message: '请输入铸造者ID',
+                        trigger: 'blur'
+                    }
+                ],
+                minterAvatar: [
+                    {
+                        required: true,
+                        message: '请输入铸造者头像',
+                        trigger: 'blur'
+                    }
+                ],
+                detail: [
+                    {
+                        required: true,
+                        message: '请输入详情',
+                        trigger: 'blur'
+                    }
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入类型',
+                        trigger: 'blur'
+                    }
+                ],
+                source: [
+                    {
+                        required: true,
+                        message: '请输入来源',
+                        trigger: 'blur'
+                    }
+                ],
+                total: [
+                    {
+                        required: true,
+                        message: '请输入发行数量',
+                        trigger: 'blur'
+                    }
+                ],
+                onShelf: [
+                    {
+                        required: true,
+                        message: '请输入上架',
+                        trigger: 'blur'
+                    }
+                ],
+                price: [
+                    {
+                        required: true,
+                        message: '请输入价格',
+                        trigger: 'blur'
+                    }
+                ],
+                properties: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('properties must be array!'));
+                                    return;
+                                } else {
+                                    for (let i = 0; i < value.length; i++) {
+                                        if (value[i].name === '' || value[i].name === undefined) {
+                                            callback(new Error('请填写名称'));
+                                            return;
+                                        }
+                                        if (value[i].value === '' || value[i].value === undefined) {
+                                            callback(new Error('请填写内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                category: [{ required: true, message: '请填写分类' }],
+                royalties: [{ required: true, message: '请填写版税' }],
+                serviceCharge: [{ required: true, message: '请填手续费' }],
+                startTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.scheduleSale) {
+                                if (!value) {
+                                    callback(new Error('请填写发布时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('发布时间不能小于当前时间'));
+                                } else {
+                                    callback();
+                                }
+                            } else {
+                                callback();
+                            }
+                        },
+                        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'
+                    }
+                ],
+                openQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择开启/关闭白名单分享'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ]
+            },
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            cateogories: ['勋章', '收藏品', '数字艺术', '门票', '游戏', '音乐', '使用', '其他'],
+            privilegeOptions: [],
+            showPrivilegeEditDialog: false,
+            privilegeForm: {},
+            privelegeRules: {
+                detail: [{ required: true, message: '请填写内容' }],
+                remark: [{ required: true, message: '请填写说明' }]
+            },
+            customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel'),
+            scale: 1,
+            yOffset: 0,
+            editQuota: true
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+            if (data.model3d) {
+                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.$http
+                .post(this.formData.id ? '/collection/save' : '/collection/create', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        onMinterDetail(e) {
+            console.log(e);
+            this.$set(this.formData, 'minter', e.nickname);
+            this.$set(this.formData, 'minterAvatar', e.avatar);
+        },
+        addProperty() {
+            this.formData.properties.push({
+                name: '',
+                value: ''
+            });
+        },
+        delProperty(i) {
+            this.formData.properties.splice(i, 1);
+        },
+        addPrivilege(row, i) {
+            this.privilegeForm = { ...row };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        editPrivilege(row, i) {
+            this.privilegeForm = { ...(this.formData.privileges.find(e => e.name === row.name) || {}) };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        delPrivilege(row, i) {
+            let idx = this.formData.privileges.findIndex(e => e.name === row.name);
+            if (idx > -1) {
+                this.formData.privileges.splice(idx, 1);
+            }
+            this.$set(this.privilegeOptions[i], 'added', false);
+        },
+        savePrivilege() {
+            this.$refs.privilegeForm
+                .validate()
+                .then(() => {
+                    let i = this.formData.privileges.findIndex(e => e.name === this.privilegeForm.name);
+                    if (i > -1) {
+                        this.$set(this.formData.privileges, i, { ...this.privilegeForm });
+                    } else {
+                        this.formData.privileges.push({ ...this.privilegeForm });
+                    }
+                    let ii = this.privilegeOptions.findIndex(i => i.name === this.privilegeForm.name);
+                    console.log(ii);
+                    this.$set(this.privilegeOptions[ii], 'added', true);
+                    this.showPrivilegeEditDialog = false;
+                })
+                .catch(e => {
+                    console.log(e);
+                });
+        }
+    },
+    watch: {
+        'formData.scanCode'(val) {
+            if (val === true) {
+                this.$set(this.formData, 'onShelf', false);
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+/deep/ .el-switch__label--left {
+    width: 50px;
+    text-align: right;
+}
+.number-percent {
+    display: flex;
+    align-items: center;
+    .percent {
+        border: 1px solid #dcdfe6;
+        border-radius: 4px;
+        width: 30px;
+        text-align: center;
+        line-height: 30px;
+        color: @text2;
+        font-size: 13px;
+    }
+}
+.tip {
+    font-size: 12px;
+    color: @text3;
+    margin-top: 5px;
+}
+.inline-wrapper {
+    .el-form-item {
+        display: inline-block;
+    }
+}
+.right-margin {
+    margin-left: 50px;
+}
+</style>

+ 257 - 0
src/main/vue/src/views/ShowroomList.vue

@@ -0,0 +1,257 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button
+                @click="addRow"
+                type="primary"
+                icon="el-icon-plus"
+                :disabled="fetchingData || downloading"
+                class="filter-item"
+            >
+                新增
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <created-at-picker v-model="createdAt" @input="getData" class="filter-item"></created-at-picker>
+            <minter-filter v-model="minterId" @input="getData" class="filter-item"></minter-filter>
+            <el-input
+                placeholder="搜索..."
+                v-model="search"
+                clearable
+                class="filter-item search"
+                @keyup.enter.native="getData"
+            >
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+            v-loading="fetchingData"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="createdAt" label="创建时间" width="150">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="pic" label="作品内容" width="90" align="center">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px"
+                        :src="row.pic[0].thumb || row.pic[0].url"
+                        fit="cover"
+                        :preview-src-list="row.pic.map(i => i.thumb || i.url)"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="minter" label="铸造者"> </el-table-column>
+            <el-table-column prop="minterId" label="铸造者ID"> </el-table-column>
+            <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
+            <el-table-column prop="source" label="来源" :formatter="sourceFormatter"> </el-table-column>
+            <el-table-column prop="total" label="发行数量" width="105" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="stock" label="剩余库存" width="105" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="onShelf" label="上架" width="90" align="center">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="row.onShelf">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="salable" label="仅展示" width="90" align="center">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="!row.salable">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="price" label="价格" width="90"> </el-table-column>
+            <el-table-column prop="noSoldOut" label="售罄">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="!row.noSoldOut">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="sort" label="排序" width="90" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="150">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>查看</el-button>
+                    <!-- <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button> -->
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+import SortableHeader from '../components/SortableHeader.vue';
+import MinterSelect from '../components/MinterSelect.vue';
+
+export default {
+    components: { SortableHeader, MinterSelect },
+    name: 'ShowroomList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/collection/all',
+            downloading: false,
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' },
+                { label: '展厅', value: 'SHOWROOM' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            sort: { sort: 'desc' },
+            sortStr: 'sort,desc',
+            createdAt: '',
+            minterId: ''
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        typeFormatter(row, column, cellValue, index) {
+            let selectedOption = this.typeOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        sourceFormatter(row, column, cellValue, index) {
+            let selectedOption = this.sourceOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return {
+                search: this.search,
+                query: { del: false, source: 'OFFICIAL', type: 'SHOWROOM', createdAt: this.createdAt, minterId: this.minterId }
+            };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/showroomEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: 'showroomEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/collection/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 14 - 3
src/test/java/com/izouma/nineth/repo/UserRepoTest.java

@@ -9,10 +9,14 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import java.util.*;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
 
 @RunWith(SpringRunner.class)
 @SpringBootTest
@@ -54,6 +58,13 @@ public class UserRepoTest {
 
     @Test
     public void test() {
-        System.out.println(userRepo.findById(150843L).orElse(null));
+//        System.out.println(userRepo.findById(150843L).orElse(null));
+        LocalDate now = LocalDate.now();
+        int dayOfWeek = now.getDayOfWeek().getValue();
+        System.out.println(dayOfWeek);
+        LocalDate end = now.minusDays(dayOfWeek);
+        System.out.println(end);
+        LocalDate start = end.minusDays(6);
+        System.out.println(start);
     }
 }

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

@@ -242,4 +242,9 @@ class AssetServiceTest extends ApplicationTests {
     public void transferCDN() throws ExecutionException, InterruptedException {
         assetService.transferCDN();
     }
+
+    @Test
+    public void cancelPublicShow(){
+        assetService.cancelPublic(202159L);
+    }
 }

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

@@ -74,7 +74,6 @@ public class OrderServiceTest extends ApplicationTests {
 
     @Test
     public void refund() throws WxPayException {
-        orderService.refund(4627L);
     }
 
     @Test

+ 173 - 0
src/test/java/com/izouma/nineth/service/ShowroomServiceTest.java

@@ -0,0 +1,173 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.ApplicationTests;
+import com.izouma.nineth.domain.ShowCollection;
+import com.izouma.nineth.domain.Showroom;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class ShowroomServiceTest extends ApplicationTests {
+
+    @Autowired
+    private ShowroomService showroomService;
+
+    @Test
+    public void save() {
+        ShowCollection collection = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207013L)
+                .build();
+        ShowCollection collection1 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(206067L)
+                .build();
+        ShowCollection collection2 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207014L)
+                .build();
+        ShowCollection collection3 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207015L)
+                .build();
+        ShowCollection collection4 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207016L)
+                .build();
+        ShowCollection collection5 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207017L)
+                .build();
+        ShowCollection collection6 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207018L)
+                .build();
+        ShowCollection collection7 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207019L)
+                .build();
+        ShowCollection collection8 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207020L)
+                .build();
+        ShowCollection collection9 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207021L)
+                .build();
+        ShowCollection collection10 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207022L)
+                .build();
+        ShowCollection collection11 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207023L)
+                .build();
+        List<ShowCollection> collections = new ArrayList<>();
+        collections.add(collection);
+        collections.add(collection1);
+        collections.add(collection2);
+        collections.add(collection3);
+        collections.add(collection4);
+        collections.add(collection5);
+        collections.add(collection6);
+        collections.add(collection7);
+        collections.add(collection8);
+        collections.add(collection9);
+//        collections.add(collection10);
+//        collections.add(collection11);
+
+        Showroom showroom = Showroom.builder()
+                .assetId(207029L)
+                .pic("https://cdn.raex.vip/image/2022-03-16-11-19-11fAEkRVkS.jpg")
+                .collections(collections)
+                .build();
+        showroomService.save(9972L, showroom);
+    }
+
+    @Test
+    public void update() {
+        ShowCollection collection2 = ShowCollection.builder()
+                .sort(1)
+                .collectionId(207014L)
+                .showroomId(207035L)
+                .build();
+        collection2.setId(207038L);
+
+        ShowCollection collection3 = ShowCollection.builder()
+                .sort(2)
+                .collectionId(207015L)
+                .build();
+        collection3.setId(207039L);
+
+        ShowCollection collection4 = ShowCollection.builder()
+                .sort(3)
+                .collectionId(207016L)
+                .build();
+        collection4.setId(207040L);
+
+        ShowCollection collection5 = ShowCollection.builder()
+                .sort(4)
+                .collectionId(207017L)
+                .build();
+        collection5.setId(207041L);
+
+        ShowCollection collection6 = ShowCollection.builder()
+                .sort(5)
+                .collectionId(207018L)
+                .build();
+        collection6.setId(207042L);
+
+        ShowCollection collection7 = ShowCollection.builder()
+                .sort(6)
+                .collectionId(207019L)
+                .build();
+        collection7.setId(207043L);
+
+        ShowCollection collection8 = ShowCollection.builder()
+                .sort(7)
+                .collectionId(207020L)
+                .build();
+        collection8.setId(207044L);
+
+        ShowCollection collection9 = ShowCollection.builder()
+                .sort(8)
+                .collectionId(207021L)
+                .build();
+        collection9.setId(207045L);
+
+        ShowCollection collection10 = ShowCollection.builder()
+                .sort(9)
+                .collectionId(207022L)
+                .build();
+
+        ShowCollection collection11 = ShowCollection.builder()
+                .sort(10)
+                .collectionId(207023L)
+                .build();
+
+        List<ShowCollection> collections = new ArrayList<>();
+        collections.add(collection2);
+        collections.add(collection3);
+        collections.add(collection4);
+        collections.add(collection5);
+        collections.add(collection6);
+        collections.add(collection7);
+        collections.add(collection8);
+        collections.add(collection9);
+        collections.add(collection10);
+        collections.add(collection11);
+
+        Showroom showroom = Showroom.builder()
+                .assetId(207029L)
+                .pic("https://cdn.raex.vip/image/2022-03-16-11-19-11fAEkRVkS.jpg")
+                .collections(collections)
+                .introduction("这个一个展厅测试")
+                .build();
+        showroom.setId(207035L);
+        showroomService.update(showroom);
+
+    }
+}