Sfoglia il codice sorgente

自动铸造空投

xiongzhu 3 anni fa
parent
commit
a8805cede5
25 ha cambiato i file con 826 aggiunte e 51 eliminazioni
  1. 13 0
      src/main/java/com/izouma/nineth/Application.java
  2. 8 0
      src/main/java/com/izouma/nineth/config/CacheConfig.java
  3. 4 0
      src/main/java/com/izouma/nineth/config/WebMvcConfig.java
  4. 8 0
      src/main/java/com/izouma/nineth/domain/MintActivity.java
  5. 29 0
      src/main/java/com/izouma/nineth/dto/MintActivityRule.java
  6. 4 0
      src/main/java/com/izouma/nineth/dto/MintActivityRuleDetail.java
  7. 4 0
      src/main/java/com/izouma/nineth/repo/AssetRepo.java
  8. 4 0
      src/main/java/com/izouma/nineth/repo/UserRepo.java
  9. 1 1
      src/main/java/com/izouma/nineth/security/JwtUserDetailsService.java
  10. 36 3
      src/main/java/com/izouma/nineth/service/AssetService.java
  11. 4 0
      src/main/java/com/izouma/nineth/service/CacheService.java
  12. 29 11
      src/main/java/com/izouma/nineth/service/MintOrderService.java
  13. 35 0
      src/main/java/com/izouma/nineth/utils/PageDeserializer.java
  14. 199 0
      src/main/java/com/izouma/nineth/utils/PageJacksonModule.java
  15. 30 0
      src/main/java/com/izouma/nineth/utils/PageSerializer.java
  16. 39 0
      src/main/java/com/izouma/nineth/utils/SortJacksonModule.java
  17. 89 0
      src/main/java/com/izouma/nineth/utils/SortJsonComponent.java
  18. 16 1
      src/main/java/com/izouma/nineth/web/AssetController.java
  19. 29 5
      src/main/java/com/izouma/nineth/web/MintActivityController.java
  20. 6 1
      src/main/vue/src/views/CollectionEdit.vue
  21. 111 16
      src/main/vue/src/views/MintActivityEdit.vue
  22. 10 11
      src/main/vue/src/views/MintActivityList.vue
  23. 10 2
      src/main/vue/src/views/MintOrderList.vue
  24. 6 0
      src/test/java/com/izouma/nineth/service/AssetServiceTest.java
  25. 102 0
      批量加标签.sql

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

@@ -1,8 +1,15 @@
 package com.izouma.nineth;
 
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.Module;
+import com.izouma.nineth.utils.PageDeserializer;
+import com.izouma.nineth.utils.PageSerializer;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.domain.PageImpl;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.retry.annotation.EnableRetry;
 import org.springframework.scheduling.annotation.EnableAsync;
@@ -20,4 +27,10 @@ public class Application {
         SpringApplication.run(Application.class, args);
     }
 
+    @Bean
+    public Module jacksonPageWithJsonViewModule() {
+        SimpleModule module = new SimpleModule("jackson-page-with-jsonview", Version.unknownVersion());
+        module.addSerializer(PageImpl.class, new PageSerializer());
+        return module;
+    }
 }

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

@@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.izouma.nineth.domain.User;
+import com.izouma.nineth.utils.PageJacksonModule;
+import com.izouma.nineth.utils.SortJacksonModule;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
 import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -59,6 +61,8 @@ public class CacheConfig {
         simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
         simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
         mapper.registerModule(simpleModule);
+//        mapper.registerModule(new PageJacksonModule());
+//        mapper.registerModule(new SortJacksonModule());
 
         serializer.setObjectMapper(mapper);
 
@@ -133,6 +137,10 @@ public class CacheConfig {
                 .entryTtl(Duration.ofHours(1))
                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
 
+        cacheNamesConfigurationMap.put("fmaa", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+
 
         RedisCacheManager redisCacheManager = RedisCacheManager.builder()
                 .cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory()))

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

@@ -3,6 +3,8 @@ package com.izouma.nineth.config;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.izouma.nineth.utils.PageJacksonModule;
+import com.izouma.nineth.utils.SortJacksonModule;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -65,6 +67,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
                     simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
                     simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
                     objectMapper.registerModule(simpleModule);
+//                    objectMapper.registerModule(new PageJacksonModule());
+//                    objectMapper.registerModule(new SortJacksonModule());
                     ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper);
                 });
         System.out.println(converters);

+ 8 - 0
src/main/java/com/izouma/nineth/domain/MintActivity.java

@@ -10,6 +10,7 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.hibernate.annotations.DynamicUpdate;
 
 import javax.persistence.Column;
 import javax.persistence.Convert;
@@ -20,6 +21,7 @@ import java.util.List;
 
 @Data
 @Entity
+@DynamicUpdate
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
@@ -68,6 +70,9 @@ public class MintActivity extends BaseEntity {
     @ApiModelProperty("空投/实物")
     private boolean airDrop;
 
+    @ApiModelProperty("自动空投")
+    private boolean autoDrop;
+
     @ApiModelProperty("定时发布")
     private boolean scheduleSale;
 
@@ -85,4 +90,7 @@ public class MintActivity extends BaseEntity {
     @Convert(converter = MintRuleConverter.class)
     @Column(columnDefinition = "TEXT")
     private MintActivityRule rule;
+
+    @ApiModelProperty("空投藏品ID")
+    private Long airDropCollectionId;
 }

+ 29 - 0
src/main/java/com/izouma/nineth/dto/MintActivityRule.java

@@ -1,10 +1,20 @@
 package com.izouma.nineth.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.izouma.nineth.domain.Tag;
 import lombok.Data;
+import org.apache.commons.collections.CollectionUtils;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 @Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
 public class MintActivityRule {
 
     private MintActivityRuleDetail detail;
@@ -12,4 +22,23 @@ public class MintActivityRule {
     private List<MintActivityRule> and;
 
     private List<MintActivityRule> or;
+
+    public Set<Tag> getTags() {
+        return getTags(this);
+    }
+
+    public static Set<Tag> getTags(MintActivityRule rule) {
+        if (rule == null) return Collections.emptySet();
+        Set<Tag> tags = new HashSet<>();
+        if (rule.getDetail() != null) {
+            tags.add(rule.getDetail().getTag());
+        }
+        if (CollectionUtils.isNotEmpty(rule.getAnd())) {
+            tags.addAll(rule.getAnd().stream().flatMap(child -> getTags(child).stream()).collect(Collectors.toList()));
+        }
+        if (CollectionUtils.isNotEmpty(rule.getOr())) {
+            tags.addAll(rule.getOr().stream().flatMap(child -> getTags(child).stream()).collect(Collectors.toList()));
+        }
+        return tags;
+    }
 }

+ 4 - 0
src/main/java/com/izouma/nineth/dto/MintActivityRuleDetail.java

@@ -1,9 +1,13 @@
 package com.izouma.nineth.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.izouma.nineth.domain.Tag;
 import lombok.Data;
 
 @Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
 public class MintActivityRuleDetail {
 
     private Tag tag;

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

@@ -2,6 +2,8 @@ package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.Asset;
 import com.izouma.nineth.enums.AssetStatus;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
@@ -71,4 +73,6 @@ public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationE
             "c.owner_avatar = ?5, c.detail = ?6 where c.id = ?1", nativeQuery = true)
     int updateCDN(Long id, String pic, String model3d, String minterAvatar,
                   String ownerAvatar, String detail);
+
+    Page<Asset> findByUserIdAndStatusAndNameLike(Long userId, AssetStatus status, String name, Pageable pageable);
 }

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

@@ -32,6 +32,10 @@ public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExe
     @Cacheable(value = "user", key = "#userId")
     Optional<User> findByIdAndDelFalse(Long userId);
 
+    @Cacheable(value = "user", key = "#userId")
+    @Query("select u from User u where u.id = ?1 and u.del = false")
+    Optional<User> jwtFindUserCache(Long userId);
+
     @Transactional
     @Modifying
     @Query("update User u set u.followers = u.followers + ?1 where u.id = ?1")

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

@@ -16,7 +16,7 @@ public class JwtUserDetailsService implements UserDetailsService {
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         Long userId = Long.parseLong(username);
-        User user = userRepo.findById(userId).orElseThrow(null);
+        User user = userRepo.jwtFindUserCache(userId).orElseThrow(null);
 
         if (user == null) {
             throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));

+ 36 - 3
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -1,11 +1,13 @@
 package com.izouma.nineth.service;
 
 import cn.hutool.core.convert.Convert;
+import com.fasterxml.jackson.annotation.JsonView;
 import com.izouma.nineth.TokenHistory;
 import com.izouma.nineth.config.GeneralProperties;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.PageWrapper;
 import com.izouma.nineth.dto.UserHistory;
 import com.izouma.nineth.enums.AssetStatus;
 import com.izouma.nineth.enums.CollectionSource;
@@ -19,6 +21,7 @@ import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.TokenUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
@@ -26,13 +29,18 @@ 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.PageImpl;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
@@ -59,7 +67,7 @@ public class AssetService {
     private ShowCollectionRepo      showCollectionRepo;
     private CollectionPrivilegeRepo collectionPrivilegeRepo;
     private PasswordEncoder         passwordEncoder;
-
+    private MintActivityRepo        mintActivityRepo;
 
     public Page<Asset> all(PageQuery pageQuery) {
         Page<Asset> all = assetRepo
@@ -103,8 +111,10 @@ public class AssetService {
 
     public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type,
                              Integer number, Integer holdDays) {
-        Collection blindBox = collectionRepo.findById(winItem.getBlindBoxId()).orElseThrow(new BusinessException("盲盒不存在"));
-        Collection collection = collectionRepo.findById(winItem.getCollectionId()).orElseThrow(new BusinessException("藏品不存在"));
+        Collection blindBox = collectionRepo.findById(winItem.getBlindBoxId())
+                .orElseThrow(new BusinessException("盲盒不存在"));
+        Collection collection = collectionRepo.findById(winItem.getCollectionId())
+                .orElseThrow(new BusinessException("藏品不存在"));
         Asset asset = Asset.create(winItem, user, holdDays);
         asset.setTokenId(TokenUtils.genTokenId());
         asset.setNumber(number);
@@ -571,4 +581,27 @@ public class AssetService {
         asset.setConsignment(false);
         assetRepo.save(asset);
     }
+
+    @Cacheable(cacheNames = "fmaa", key = "#userId+'#'+#mintActivityId+'#'+#pageable.hashCode()")
+    public PageWrapper<Asset> findMintActivityAssetsWrap(Long userId, Long mintActivityId, Pageable pageable) {
+        return PageWrapper.of(findMintActivityAssets(userId, mintActivityId, pageable));
+    }
+
+    public Page<Asset> findMintActivityAssets(Long userId, Long mintActivityId, Pageable pageable) {
+        MintActivity mintActivity = mintActivityRepo.findById(mintActivityId).orElse(null);
+        if (mintActivity == null) return new PageImpl<>(Collections.emptyList());
+
+        if (!mintActivity.isAudit()) {
+            Set<Tag> tags = mintActivity.getRule().getTags();
+            if (tags.isEmpty()) return new PageImpl<>(Collections.emptyList());
+            return assetRepo.findAll((Specification<Asset>) (root, query, criteriaBuilder) ->
+                    query.distinct(true).where(criteriaBuilder.equal(root.get("userId"), userId),
+                                    criteriaBuilder.equal(root.get("status"), AssetStatus.NORMAL),
+                                    root.join("tags").get("id").in(tags.stream().map(Tag::getId).toArray()))
+                            .getRestriction(), pageable);
+        } else {
+            return assetRepo.findByUserIdAndStatusAndNameLike(userId, AssetStatus.NORMAL,
+                    "%" + mintActivity.getCollectionName() + "%", pageable);
+        }
+    }
 }

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

@@ -90,4 +90,8 @@ public class CacheService {
     @CacheEvict(value = {"showroom"}, allEntries = true)
     public void clearShowroom() {
     }
+
+    @CacheEvict(value = "fmaa", allEntries = true)
+    public void clearFmaa() {
+    }
 }

+ 29 - 11
src/main/java/com/izouma/nineth/service/MintOrderService.java

@@ -19,10 +19,7 @@ import com.izouma.nineth.config.WxPayProperties;
 import com.izouma.nineth.domain.*;
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.dto.PageQuery;
-import com.izouma.nineth.enums.AssetStatus;
-import com.izouma.nineth.enums.MintOrderStatus;
-import com.izouma.nineth.enums.OrderStatus;
-import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.enums.*;
 import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.*;
@@ -77,6 +74,7 @@ public class MintOrderService {
     private CollectionRepo                collectionRepo;
     private OrderRepo                     orderRepo;
     private ShowCollectionRepo            showCollectionRepo;
+    private AirDropService                airDropService;
 
     public Page<MintOrder> all(PageQuery pageQuery) {
         return mintOrderRepo.findAll(JpaUtils.toSpecification(pageQuery, MintOrder.class), JpaUtils.toPageRequest(pageQuery));
@@ -191,15 +189,23 @@ public class MintOrderService {
 
 
             List<Asset> assets = assetRepo.findAllByIdInAndUserId(assetId, user.getId());
-            // 资产产品是否符合铸造活动的名称
-            assets = assets.stream()
-                    .filter(asset -> asset.getName()
-                            .contains(mintActivity.getCollectionName()) && AssetStatus.NORMAL.equals(asset.getStatus()))
-                    .collect(Collectors.toList());
-            if (mintActivity.getNum() > 0 && (assets.size() != mintActivity.getNum())) {
-                throw new BusinessException("有藏品不符合,请重新选择");
+
+            if (!mintActivity.isAudit()) {
+                if (!mintActivityService.matchRule(assets, mintActivity.getRule())) {
+                    throw new BusinessException("所选藏品不符和规则,请重新选择");
+                }
+            } else {
+                // 资产产品是否符合铸造活动的名称
+                assets = assets.stream()
+                        .filter(asset -> asset.getName()
+                                .contains(mintActivity.getCollectionName()) && AssetStatus.NORMAL.equals(asset.getStatus()))
+                        .collect(Collectors.toList());
+                if (mintActivity.getNum() > 0 && (assets.size() != mintActivity.getNum())) {
+                    throw new BusinessException("有藏品不符合,请重新选择");
+                }
             }
 
+
             Map<Long, Long> privilegeIds = new HashMap<>();
             // 铸造特权
             if (!mintActivity.isConsume()) {
@@ -482,6 +488,18 @@ public class MintOrderService {
 
             if (mintOrder.isAirDrop()) {
                 mintOrder.setStatus(MintOrderStatus.AIR_DROP);
+                MintActivity mintActivity = mintActivityRepo.findById(mintOrder.getMintActivityId()).orElse(null);
+                if (mintActivity != null && mintActivity.isAutoDrop()) {
+                    airDropService.create(AirDrop.builder()
+                            .name("铸造活动[" + mintActivity.getName() + "]空投")
+                            .remark(mintOrder.getId().toString())
+                            .type(AirDropType.asset)
+                            .userIds(Collections.singletonList(mintOrder.getUserId()))
+                            .collectionId(mintActivity.getAirDropCollectionId())
+                            .build());
+                    mintOrder.setStatus(MintOrderStatus.FINISH);
+                    mintOrderRepo.save(mintOrder);
+                }
             } else {
                 mintOrder.setStatus(MintOrderStatus.DELIVERY);
             }

+ 35 - 0
src/main/java/com/izouma/nineth/utils/PageDeserializer.java

@@ -0,0 +1,35 @@
+package com.izouma.nineth.utils;
+
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+
+import java.io.IOException;
+import java.util.List;
+
+public class PageDeserializer extends StdDeserializer<PageImpl> {
+
+    public PageDeserializer() {
+        super(PageImpl.class);
+    }
+
+    @Override
+    public PageImpl deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JacksonException {
+        int number = ctx.readValue(p, int.class);
+        int numberOfElements = ctx.readValue(p, int.class);
+        long totalElements = ctx.readValue(p, long.class);
+        long totalPages = ctx.readValue(p, long.class);
+        int size = ctx.readValue(p, int.class);
+        List content = ctx.readValue(p, List.class);
+        new PageImpl<>(content, PageRequest.of(number, size), totalElements);
+        return null;
+    }
+
+
+}

+ 199 - 0
src/main/java/com/izouma/nineth/utils/PageJacksonModule.java

@@ -0,0 +1,199 @@
+package com.izouma.nineth.utils;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+/**
+ * This Jackson module provides support to deserialize Spring {@link Page} objects.
+ *
+ * @author Pascal Büttiker
+ * @author Olga Maciaszek-Sharma
+ * @author Pedro Mendes
+ * @author Nikita Konev
+ */
+public class PageJacksonModule extends Module {
+
+    @Override
+    public String getModuleName() {
+        return "PageJacksonModule";
+    }
+
+    @Override
+    public Version version() {
+        return new Version(0, 1, 0, "", null, null);
+    }
+
+    @Override
+    public void setupModule(SetupContext context) {
+        context.setMixInAnnotations(Page.class, PageMixIn.class);
+    }
+
+    @JsonDeserialize(as = SimplePageImpl.class)
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    private interface PageMixIn {
+
+    }
+
+    static class SimplePageImpl<T> extends PageImpl<T> {
+
+        private final Page<T> delegate;
+
+        SimplePageImpl(@JsonProperty("content") List<T> content, @JsonProperty("number") int number,
+                       @JsonProperty("size") int size, @JsonProperty("totalElements") @JsonAlias({"total-elements",
+                "total_elements", "totalelements", "TotalElements"}) long totalElements,
+                       @JsonProperty("sort") Sort sort) {
+            super(content, sort == null ? PageRequest.of(number, size) : PageRequest.of(number, size, sort), totalElements);
+            if (size > 0) {
+                PageRequest pageRequest;
+                if (sort != null) {
+                    pageRequest = PageRequest.of(number, size, sort);
+                } else {
+                    pageRequest = PageRequest.of(number, size);
+                }
+                delegate = new PageImpl<>(content, pageRequest, totalElements);
+            } else {
+                delegate = new PageImpl<>(content);
+            }
+        }
+
+        @JsonProperty
+        @Override
+        public int getTotalPages() {
+            return delegate.getTotalPages();
+        }
+
+        @JsonProperty
+        @Override
+        public long getTotalElements() {
+            return delegate.getTotalElements();
+        }
+
+        @JsonProperty
+        @Override
+        public int getNumber() {
+            return delegate.getNumber();
+        }
+
+        @JsonProperty
+        @Override
+        public int getSize() {
+            return delegate.getSize();
+        }
+
+        @JsonProperty
+        @Override
+        public int getNumberOfElements() {
+            return delegate.getNumberOfElements();
+        }
+
+        @JsonProperty
+        @Override
+        public List<T> getContent() {
+            return delegate.getContent();
+        }
+
+        @JsonProperty
+        @Override
+        public boolean hasContent() {
+            return delegate.hasContent();
+        }
+
+        @JsonIgnore
+        @Override
+        public Sort getSort() {
+            return delegate.getSort();
+        }
+
+        @JsonProperty
+        @Override
+        public boolean isFirst() {
+            return delegate.isFirst();
+        }
+
+        @JsonProperty
+        @Override
+        public boolean isLast() {
+            return delegate.isLast();
+        }
+
+        @JsonIgnore
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+
+        @JsonIgnore
+        @Override
+        public boolean hasPrevious() {
+            return delegate.hasPrevious();
+        }
+
+        @JsonIgnore
+        @Override
+        public Pageable nextPageable() {
+            return delegate.nextPageable();
+        }
+
+        @JsonIgnore
+        @Override
+        public Pageable previousPageable() {
+            return delegate.previousPageable();
+        }
+
+        @JsonIgnore
+        @Override
+        public <S> Page<S> map(Function<? super T, ? extends S> converter) {
+            return delegate.map(converter);
+        }
+
+        @JsonIgnore
+        @Override
+        public Iterator<T> iterator() {
+            return delegate.iterator();
+        }
+
+        @JsonIgnore
+        @Override
+        public Pageable getPageable() {
+            return delegate.getPageable();
+        }
+
+        @JsonIgnore
+        @Override
+        public boolean isEmpty() {
+            return delegate.isEmpty();
+        }
+
+        @Override
+        public int hashCode() {
+            return delegate.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return delegate.equals(obj);
+        }
+
+        @Override
+        public String toString() {
+            return delegate.toString();
+        }
+
+    }
+
+}

+ 30 - 0
src/main/java/com/izouma/nineth/utils/PageSerializer.java

@@ -0,0 +1,30 @@
+package com.izouma.nineth.utils;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.springframework.data.domain.PageImpl;
+
+import java.io.IOException;
+
+public class PageSerializer extends StdSerializer<PageImpl> {
+
+    public PageSerializer() {
+        super(PageImpl.class);
+    }
+
+    @Override
+    public void serialize(PageImpl value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        gen.writeStartObject();
+        gen.writeNumberField("number", value.getNumber());
+        gen.writeNumberField("numberOfElements", value.getNumberOfElements());
+        gen.writeNumberField("totalElements", value.getTotalElements());
+        gen.writeNumberField("totalPages", value.getTotalPages());
+        gen.writeNumberField("size", value.getSize());
+        gen.writeFieldName("content");
+        provider.defaultSerializeValue(value.getContent(), gen);
+        gen.writeEndObject();
+    }
+
+
+}

+ 39 - 0
src/main/java/com/izouma/nineth/utils/SortJacksonModule.java

@@ -0,0 +1,39 @@
+package com.izouma.nineth.utils;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.module.SimpleDeserializers;
+import com.fasterxml.jackson.databind.module.SimpleSerializers;
+
+import org.springframework.data.domain.Sort;
+
+/**
+ * This Jackson module provides support for serializing and deserializing for Spring
+ * {@link Sort} object.
+ *
+ * @author Can Bezmen
+ */
+public class SortJacksonModule extends Module {
+
+    @Override
+    public String getModuleName() {
+        return "SortModule";
+    }
+
+    @Override
+    public Version version() {
+        return new Version(0, 1, 0, "", null, null);
+    }
+
+    @Override
+    public void setupModule(SetupContext context) {
+        SimpleSerializers serializers = new SimpleSerializers();
+        serializers.addSerializer(Sort.class, new SortJsonComponent.SortSerializer());
+        context.addSerializers(serializers);
+
+        SimpleDeserializers deserializers = new SimpleDeserializers();
+        deserializers.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer());
+        context.addDeserializers(deserializers);
+    }
+
+}

+ 89 - 0
src/main/java/com/izouma/nineth/utils/SortJsonComponent.java

@@ -0,0 +1,89 @@
+package com.izouma.nineth.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import org.springframework.data.domain.Sort;
+
+/**
+ * This class provides provides support for serializing and deserializing for Spring
+ * {@link Sort} object.
+ *
+ * @author Can Bezmen
+ */
+public class SortJsonComponent {
+
+    public static class SortSerializer extends JsonSerializer<Sort> {
+
+        @Override
+        public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+            gen.writeStartArray();
+            value.iterator().forEachRemaining(v -> {
+                try {
+                    gen.writeObject(v);
+                } catch (IOException e) {
+                    throw new RuntimeException("Couldn't serialize object " + v);
+                }
+            });
+            gen.writeEndArray();
+        }
+
+        public void serializeWithType(Sort value, JsonGenerator gen, SerializerProvider serializers,
+                                      TypeSerializer typeSer) throws IOException {
+            gen.writeStartArray();
+            value.iterator().forEachRemaining(v -> {
+                try {
+                    gen.writeObject(v);
+                } catch (IOException e) {
+                    throw new RuntimeException("Couldn't serialize object " + v);
+                }
+            });
+            gen.writeEndArray();
+        }
+
+        @Override
+        public Class<Sort> handledType() {
+            return Sort.class;
+        }
+
+    }
+
+    public static class SortDeserializer extends JsonDeserializer<Sort> {
+
+        @Override
+        public Sort deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
+                throws IOException {
+            TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
+            if (treeNode.isArray()) {
+                ArrayNode arrayNode = (ArrayNode) treeNode;
+                List<Sort.Order> orders = new ArrayList<>();
+                for (JsonNode jsonNode : arrayNode) {
+                    Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
+                            jsonNode.get("property").textValue());
+                    orders.add(order);
+                }
+                return Sort.by(orders);
+            }
+            return null;
+        }
+
+        @Override
+        public Class<Sort> handledType() {
+            return Sort.class;
+        }
+
+    }
+
+}

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

@@ -10,12 +10,14 @@ import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.AssetRepo;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.service.AssetService;
+import com.izouma.nineth.service.CacheService;
 import com.izouma.nineth.service.GiftOrderService;
 import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -25,6 +27,7 @@ import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
 @RestController
@@ -35,6 +38,7 @@ public class AssetController extends BaseController {
     private AssetRepo        assetRepo;
     private GiftOrderService giftOrderService;
     private OrderRepo        orderRepo;
+    private CacheService     cacheService;
 
     //@PreAuthorize("hasRole('ADMIN')")
 //    @PostMapping("/save")
@@ -116,7 +120,8 @@ public class AssetController extends BaseController {
     @PostMapping("/giftWithoutGasFee")
     @ApiOperation("转赠(无gas费)")
     public GiftOrder giftWithoutGasFee(@RequestParam Long assetId, @RequestParam Long toUserId, @RequestParam String tradeCode) {
-        return giftOrderService.giftWithoutGasFee(SecurityUtils.getAuthenticatedUser().getId(), assetId, toUserId, tradeCode);
+        return giftOrderService.giftWithoutGasFee(SecurityUtils.getAuthenticatedUser()
+                .getId(), assetId, toUserId, tradeCode);
     }
 
     @GetMapping("/tokenHistory")
@@ -154,6 +159,16 @@ public class AssetController extends BaseController {
         }).start();
         return "ok";
     }
+
+    @GetMapping("/assetsForMint")
+    @JsonView(Asset.View.Basic.class)
+    public Page<Asset> assetsForMint(@RequestParam Long mintActivityId, Boolean refresh, Pageable pageable) {
+        if (Objects.equals(refresh, true)) {
+            cacheService.clearFmaa();
+        }
+        return assetService.findMintActivityAssetsWrap(SecurityUtils.getAuthenticatedUser().getId(),
+                mintActivityId, pageable).toPage();
+    }
 }
 
 

+ 29 - 5
src/main/java/com/izouma/nineth/web/MintActivityController.java

@@ -8,6 +8,7 @@ import com.izouma.nineth.repo.MintActivityRepo;
 import com.izouma.nineth.utils.ObjUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
+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.*;
@@ -26,17 +27,38 @@ public class MintActivityController extends BaseController {
     @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     public MintActivity save(@RequestBody MintActivity record) {
+        if (record.getRule() != null) mintActivityService.checkRule(record.getRule());
         if (record.getId() != null) {
             MintActivity orig = mintActivityRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, record);
+            orig.setName(record.getName());
+            orig.setCover(record.getCover());
+            orig.setMinterId(record.getMinterId());
+            orig.setMinter(record.getMinter());
+            orig.setMinterAvatar(record.getMinterAvatar());
+            orig.setDetail(record.getDetail());
+            orig.setAudit(record.isAudit());
+            orig.setNum(record.getNum());
+            orig.setCollectionName(record.getCollectionName());
+            orig.setRule(record.getRule());
+            orig.setConsume(record.isConsume());
+            orig.setGasPrice(record.getGasPrice());
+            orig.setAirDrop(record.isAirDrop());
+            orig.setAutoDrop(record.isAutoDrop());
+            orig.setOnShelf(record.isOnShelf());
+            orig.setScheduleSale(record.isScheduleSale());
+            orig.setStartTime(record.getStartTime());
+            orig.setAutoDrop(record.isAutoDrop());
+            orig.setAirDropCollectionId(record.getAirDropCollectionId());
+
             orig = mintActivityRepo.save(orig);
             mintActivityService.syncStock(record.getId());
             return orig;
+        } else {
+            record.setStock(record.getTotal());
+            record = mintActivityRepo.save(record);
+            mintActivityService.syncStock(record.getId());
+            return record;
         }
-        record.setStock(record.getTotal());
-        record = mintActivityRepo.save(record);
-        mintActivityService.syncStock(record.getId());
-        return record;
     }
 
 
@@ -47,10 +69,12 @@ public class MintActivityController extends BaseController {
     }
 
     @GetMapping("/get/{id}")
+//    @Cacheable(cacheNames = "mintActivity", key = "#id")
     public MintActivity get(@PathVariable Long id) {
         return mintActivityRepo.findById(id).orElseThrow(new BusinessException("无记录"));
     }
 
+    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/del/{id}")
     public void del(@PathVariable Long id) {
         mintActivityRepo.softDelete(id);

+ 6 - 1
src/main/vue/src/views/CollectionEdit.vue

@@ -426,7 +426,7 @@ export default {
             saving: false,
             formData: {
                 onShelf: false,
-                salable: true,
+                salable: false,
                 properties: [],
                 type: 'DEFAULT',
                 source: 'OFFICIAL',
@@ -787,6 +787,11 @@ export default {
             if (val === true) {
                 this.$set(this.formData, 'onShelf', false);
             }
+        },
+        'formData.onShelf'(val) {
+            if (val === false && this.formData.scanCode === false) {
+                this.$set(this.formData, 'salable', false);
+            }
         }
     }
 };

+ 111 - 16
src/main/vue/src/views/MintActivityEdit.vue

@@ -32,9 +32,32 @@
                     <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="collectionName" label="藏品名称">
+                    <el-form-item prop="audit" label="是否需要审核">
+                        <el-radio-group v-model="formData.audit">
+                            <el-radio :label="true">人工审核</el-radio>
+                            <el-radio :label="false">自动匹配</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                    <el-form-item prop="collectionName" label="藏品名称" v-if="formData.audit === true">
                         <el-input v-model="formData.collectionName" :disabled="!canEdit" class="width"></el-input>
                     </el-form-item>
+                    <el-form-item prop="rule" label="匹配规则设置" v-if="formData.audit === false">
+                        <template v-if="formData.rule && formData.rule.and">
+                            <div v-for="(item, i) in formData.rule.and" class="rule-item">
+                                <el-select v-model="item.detail.tag" value-key="id" size="mini">
+                                    <el-option
+                                        v-for="item in tags"
+                                        :key="item.id"
+                                        :value="item"
+                                        :label="item.name"
+                                    ></el-option>
+                                </el-select>
+                                <span style="padding: 0 10px; color: #606266; font-weight: bold">×&nbsp;1</span>
+                                <i @click="delRule(i)" class="el-icon-delete icon-del"></i>
+                            </div>
+                        </template>
+                        <el-button size="mini" @click="addRule">添加</el-button>
+                    </el-form-item>
                     <el-form-item prop="num" label="藏品数量">
                         <el-input-number
                             type="number"
@@ -52,12 +75,6 @@
                             <el-radio :label="false">否</el-radio>
                         </el-radio-group>
                     </el-form-item>
-                    <el-form-item prop="audit" label="是否需要审核">
-                        <el-radio-group v-model="formData.audit">
-                            <el-radio :label="true">是</el-radio>
-                            <el-radio :label="false">否</el-radio>
-                        </el-radio-group>
-                    </el-form-item>
                     <!-- <div class="inline-wrapper"> -->
                     <el-form-item prop="total" label="发行数量">
                         <el-input-number
@@ -70,23 +87,28 @@
                         ></el-input-number>
                     </el-form-item>
                     <el-form-item prop="gasPrice" label="铸造gas费">
-                        <el-input-number
-                            v-model="formData.gasPrice"
-                            :disabled="!canEdit"
-                            :min="0"
-                            class="width1"
-                        ></el-input-number>
+                        <el-input-number v-model="formData.gasPrice" :min="0" class="width1"></el-input-number>
                     </el-form-item>
                     <!-- </div> -->
                     <el-form-item prop="airDrop" label="类型">
                         <el-radio v-model="formData.airDrop" :label="true" :disabled="!canEdit">空投铸造</el-radio>
                         <el-radio v-model="formData.airDrop" :label="false" :disabled="!canEdit">实物铸造</el-radio>
                     </el-form-item>
+                    <el-form-item prop="autoDrop" label="自动空投" v-if="formData.airDrop === true">
+                        <el-radio v-model="formData.autoDrop" :label="true">是</el-radio>
+                        <el-radio v-model="formData.autoDrop" :label="false">否</el-radio>
+                    </el-form-item>
+                    <el-form-item
+                        prop="airDropCollectionId"
+                        label="空投藏品"
+                        v-if="formData.airDrop === true && formData.autoDrop === true"
+                    >
+                        <collection-search v-model="formData.airDropCollectionId"></collection-search>
+                    </el-form-item>
                     <el-form-item prop="onShelf" label="上架">
                         <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
                         <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
                     </el-form-item>
-
                     <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>
@@ -114,6 +136,7 @@
 </template>
 <script>
 import { format, parse, isBefore } from 'date-fns';
+import TagSelect from '@/components/TagSelect';
 export default {
     name: 'MintActivityEdit',
     created() {
@@ -128,13 +151,20 @@ export default {
                     this.$message.error(e.error);
                 });
         }
+        this.$http.post('/tag/all', { size: 10000 }, { body: 'json' }).then(res => {
+            this.tags = res.content;
+        });
     },
     data() {
         return {
             saving: false,
             formData: {
                 onShelf: false,
-                scheduleSale: true
+                scheduleSale: true,
+                rule: {
+                    and: []
+                },
+                autoDrop: false
             },
             rules: {
                 name: [
@@ -210,8 +240,54 @@ export default {
                         },
                         trigger: 'blur'
                     }
+                ],
+                rule: [
+                    { required: true, message: '请选择规则', trigger: 'blur' },
+                    {
+                        validator: (rule, value, callback) => {
+                            if (!this.formData.audit) {
+                                if (!this.formData.rule) {
+                                    callback(new Error('请填写规则'));
+                                } else if (!this.formData.rule.and) {
+                                    callback(new Error('请填写规则'));
+                                } else if (!this.formData.rule.and.length) {
+                                    callback(new Error('请填写规则'));
+                                } else {
+                                    for (let i = 0; i < this.formData.rule.and.length; i++) {
+                                        if (
+                                            !(this.formData.rule.and[i].detail && this.formData.rule.and[i].detail.tag)
+                                        ) {
+                                            callback(new Error('请选择'));
+                                            callback = null;
+                                            break;
+                                        }
+                                    }
+                                    if (callback) {
+                                        callback();
+                                    }
+                                }
+                            } else {
+                                callback();
+                            }
+                        }
+                    }
+                ],
+                autoDrop: [
+                    {
+                        required: true,
+                        message: '请选择是否自动空投',
+                        trigger: 'blur'
+                    }
+                ],
+                airDropCollectionId: [
+                    {
+                        required: true,
+                        message: '请选择空投藏品',
+                        trigger: 'blur'
+                    }
                 ]
-            }
+            },
+            tags: []
         };
     },
     computed: {
@@ -266,6 +342,15 @@ export default {
             console.log(e);
             this.$set(this.formData, 'minter', e.nickname);
             this.$set(this.formData, 'minterAvatar', e.avatar);
+        },
+        addRule() {
+            if (!(this.formData.rule && this.formData.rule.and)) {
+                this.$set(this.formData, 'rule', { and: [] });
+            }
+            this.formData.rule.and.push({ detail: { tag: null, num: 1 } });
+        },
+        delRule(i) {
+            this.formData.rule.and.splice(i, 1);
         }
     }
 };
@@ -294,4 +379,14 @@ export default {
 /deep/.el-radio__input.is-disabled + span.el-radio__label {
     color: #7c7e7e;
 }
+.rule-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+    .icon-del {
+        color: #f56c6c;
+        cursor: pointer;
+        font-size: 18px;
+    }
+}
 </style>

+ 10 - 11
src/main/vue/src/views/MintActivityList.vue

@@ -38,7 +38,7 @@
         >
             <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="createdAt" label="创建时间" min-width="135"></el-table-column>
+            <el-table-column prop="createdAt" label="创建时间" width="135"></el-table-column>
             <el-table-column prop="name" label="活动名称" min-width="135" show-overflow-tooltip> </el-table-column>
             <el-table-column prop="minter" label="铸造者" min-width="100"> </el-table-column>
             <el-table-column prop="cover" label="封面" width="60">
@@ -51,29 +51,28 @@
                     ></el-image>
                 </template>
             </el-table-column>
-            <el-table-column prop="detail" label="活动详情"> </el-table-column>
             <el-table-column prop="collectionName" label="藏品名称"> </el-table-column>
 
-            <el-table-column prop="num" label="藏品数量"> </el-table-column>
-            <el-table-column prop="consume" label="消耗藏品">
+            <el-table-column prop="num" label="藏品数量" width="80" align="center"> </el-table-column>
+            <el-table-column prop="consume" label="消耗藏品" width="80" align="center">
                 <template v-slot="{ row }">
                     <el-tag type="success" v-if="row.consume">是</el-tag>
                     <el-tag type="info" v-else>否</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="total" label="发行数量"> </el-table-column>
-            <el-table-column prop="stock" label="剩余数量"> </el-table-column>
-            <el-table-column prop="gasPrice" label="GAS费"></el-table-column>
-            <el-table-column prop="onShelf" label="上架">
+            <el-table-column prop="total" label="发行数量" width="80" align="center"> </el-table-column>
+            <el-table-column prop="stock" label="剩余数量" width="80" align="center"> </el-table-column>
+            <el-table-column prop="gasPrice" label="GAS费" width="80" align="center"></el-table-column>
+            <el-table-column prop="onShelf" label="上架" width="80" 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="audit" label="审核">
+            <el-table-column prop="audit" label="审核" width="80" align="center">
                 <template v-slot="{ row }">
-                    <el-tag type="success" v-if="row.audit">是</el-tag>
-                    <el-tag type="info" v-else>否</el-tag>
+                    <el-tag type="warning" v-if="row.audit">人工</el-tag>
+                    <el-tag type="success" v-else>自动</el-tag>
                 </template>
             </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" width="120">

+ 10 - 2
src/main/vue/src/views/MintOrderList.vue

@@ -78,9 +78,17 @@
             <el-table-column prop="mintActivityId" label="铸造活动ID" min-width="80"> </el-table-column>
             <el-table-column prop="mintActivity" label="活动名称" min-width="120"></el-table-column>
             <!-- <el-table-column prop="createdAt" label="铸造时间" min-width="120"> </el-table-column> -->
-            <el-table-column prop="material" label="铸造材料详情" min-width="120">
+            <el-table-column prop="material" label="铸造材料详情" width="150">
                 <template slot-scope="{ row }">
-                    <span v-for="item in row.material" :key="item.assestId">{{ item.name }}<br /></span>
+                    <el-popover placement="top-start" width="300" trigger="hover">
+                        <span
+                            slot="reference"
+                            style="max-width: 130px; overflow: hidden; display: inline-block; text-overflow: ellipsis"
+                        >
+                            <span v-for="item in row.material" :key="item.assestId">{{ item.name }}</span>
+                        </span>
+                        <div v-for="item in row.material" :key="item.assestId">{{ item.name }}</div>
+                    </el-popover>
                 </template>
             </el-table-column>
             <!-- <el-table-column prop="consume" label="消耗藏品" width="90" align="center">

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

@@ -17,7 +17,12 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.domain.Specification;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -255,4 +260,5 @@ class AssetServiceTest extends ApplicationTests {
         User user = userRepo.findById(9972L).orElse(null);
         assetService.transfer(asset, BigDecimal.ZERO, user, "转赠", null);
     }
+
 }

+ 102 - 0
批量加标签.sql

@@ -0,0 +1,102 @@
+insert into asset_tag (asset_id, tag_id)
+select id, 7336269
+from asset
+where name like '%ape%';
+
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336117
+from asset
+where name like '%big3%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336267
+from asset
+where name like '%MAYBEMAN%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336118
+from asset
+where name like '%OASISPUNK%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336268
+from asset
+where name like '%OASISPUNK.EVO%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336272
+from asset
+where name like '%创世%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336273
+from asset
+where name like '%DOGKING%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336270
+from asset
+where name like '%科比%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336271
+from asset
+where name like '%袁隆平%';
+
+insert into asset_tag (asset_id, tag_id)
+select id, 7336274
+from asset
+where name like '%MUGEN%';
+
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336269
+from collection_info
+where name like '%ape%';
+
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336117
+from collection_info
+where name like '%big3%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336267
+from collection_info
+where name like '%MAYBEMAN%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336118
+from collection_info
+where name like '%OASISPUNK%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336268
+from collection_info
+where name like '%OASISPUNK.EVO%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336272
+from collection_info
+where name like '%创世%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336273
+from collection_info
+where name like '%DOGKING%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336270
+from collection_info
+where name like '%科比%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336271
+from collection_info
+where name like '%袁隆平%';
+
+insert into collection_tag (collection_id, tag_id)
+select id, 7336274
+from collection_info
+where name like '%MUGEN%';