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.*; import com.izouma.nineth.event.TransferAssetEvent; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.utils.JpaUtils; 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; 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; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; @Service @AllArgsConstructor @Slf4j public class AssetService { private AssetRepo assetRepo; private UserRepo userRepo; private CollectionRepo collectionRepo; private ApplicationContext applicationContext; private OrderRepo orderRepo; private TokenHistoryRepo tokenHistoryRepo; private SysConfigService sysConfigService; private RocketMQTemplate rocketMQTemplate; private GeneralProperties generalProperties; private ShowroomRepo showroomRepo; private ShowCollectionRepo showCollectionRepo; private CollectionPrivilegeRepo collectionPrivilegeRepo; private PasswordEncoder passwordEncoder; private MintActivityRepo mintActivityRepo; public Page all(PageQuery pageQuery) { Page all = assetRepo .findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery)); Map query = pageQuery.getQuery(); if (query.containsKey("userId")) { List orderId = orderRepo .findAllByUserIdAndOpenedFalse(Convert.convert(Long.class, query.get("userId"))); return all.map(asset -> { if (orderId.contains(asset.getOrderId())) { asset.setOpened(false); } return asset; }); } return all; } public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type, Integer number) { Asset asset = Asset.create(collection, user); asset.setTokenId(TokenUtils.genTokenId()); asset.setNumber(number); asset.setOasisId(collection.getOasisId()); asset.setOrderId(orderId); asset.setPrice(price); assetRepo.saveAndFlush(asset); tokenHistoryRepo.save(TokenHistory.builder() .tokenId(asset.getTokenId()) .fromUser(collection.getMinter()) .fromUserId(collection.getMinterId()) .fromAvatar(collection.getMinterAvatar()) .toUser(user.getNickname()) .toUserId(user.getId()) .toAvatar(user.getAvatar()) .operation(type) .price(price) .build()); rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId()); return asset; } public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type, Integer number, Integer holdDays) { Collection blindBox = collectionRepo.findDetailById(winItem.getBlindBoxId()) .orElseThrow(new BusinessException("盲盒不存在")); Collection collection = collectionRepo.findDetailById(winItem.getCollectionId()) .orElseThrow(new BusinessException("藏品不存在")); Asset asset = Asset.create(winItem, user, holdDays); asset.setTokenId(TokenUtils.genTokenId()); asset.setNumber(number); asset.setOasisId(winItem.getOasisId()); asset.setOrderId(orderId); asset.setPrice(price); asset.setTags(new HashSet<>()); if (blindBox.getTags() != null) { asset.getTags().addAll(blindBox.getTags()); } if (collection.getTags() != null) { asset.getTags().addAll(collection.getTags()); } assetRepo.saveAndFlush(asset); tokenHistoryRepo.save(TokenHistory.builder() .tokenId(asset.getTokenId()) .fromUser(winItem.getMinter()) .fromUserId(winItem.getMinterId()) .fromAvatar(winItem.getMinterAvatar()) .toUser(user.getNickname()) .toUserId(user.getId()) .toAvatar(user.getAvatar()) .operation(type) .price(price) .build()); rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId()); return asset; } public void publicShow(Long id) { Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } if (asset.isPublicShow()) { return; } if (asset.getStatus() != AssetStatus.NORMAL) { throw new BusinessException("当前状态不可展示"); } User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在")); Collection collection = Collection.builder() .name(asset.getName()) .pic(asset.getPic()) .minter(asset.getMinter()) .minterId(asset.getMinterId()) .minterAvatar(asset.getMinterAvatar()) .owner(owner.getNickname()) .oasisId(asset.getOasisId()) .ownerId(owner.getId()) .ownerAvatar(owner.getAvatar()) .detail(asset.getDetail()) .type(CollectionType.DEFAULT) .source(CollectionSource.TRANSFER) .sale(0) .stock(1) .total(1) .onShelf(true) .salable(false) .price(BigDecimal.valueOf(0)) .properties(asset.getProperties()) .canResale(asset.isCanResale()) .royalties(asset.getRoyalties()) .serviceCharge(asset.getServiceCharge()) .assetId(id) .number(asset.getNumber()) .build(); collectionRepo.save(collection); asset.setPublicShow(true); asset.setPublicCollectionId(collection.getId()); assetRepo.save(asset); } public synchronized void consignment(Long id, BigDecimal price, String tradeCode) { Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } int holdDays; if (asset.getSource() == AssetSource.GIFT) { holdDays = sysConfigService.getInt("gift_days"); } else { if (ObjectUtils.isEmpty(asset.getHoldDays())) { holdDays = sysConfigService.getInt("hold_days"); } else { holdDays = asset.getHoldDays(); } } if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) { throw new BusinessException("需持有满" + holdDays + "天才能寄售上架"); } User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在")); if (!passwordEncoder.matches(tradeCode, owner.getTradeCode())) { throw new BusinessException("交易密码错误"); } // if (StringUtils.isBlank(owner.getSettleAccountId())) { // throw new BusinessException("请先绑定银行卡"); // } if (asset.isConsignment()) { throw new BusinessException("已寄售,请勿重新操作"); } if (asset.getStatus() != AssetStatus.NORMAL) { throw new BusinessException("当前状态不可寄售"); } 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()) .minter(asset.getMinter()) .minterId(asset.getMinterId()) .minterAvatar(asset.getMinterAvatar()) .owner(owner.getNickname()) .ownerId(owner.getId()) .oasisId(asset.getOasisId()) .ownerAvatar(owner.getAvatar()) .detail(asset.getDetail()) .type(CollectionType.DEFAULT) .source(CollectionSource.TRANSFER) .sale(0) .stock(1) .total(1) .onShelf(true) .salable(true) .price(price) .properties(asset.getProperties()) .canResale(asset.isCanResale()) .royalties(asset.getRoyalties()) .serviceCharge(asset.getServiceCharge()) .assetId(id) .number(asset.getNumber()) .build(); collectionRepo.save(collection); asset.setPublicShow(true); asset.setConsignment(true); asset.setPublicCollectionId(collection.getId()); asset.setSellPrice(price); assetRepo.save(asset); } public void cancelConsignment(Long id) { Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } cancelConsignment(asset); } public void cancelConsignment(Asset asset) { if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } if (asset.getPublicCollectionId() != null) { List orders = orderRepo.findByCollectionId(asset.getPublicCollectionId()); if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) { throw new BusinessException("已有订单不可取消"); } collectionRepo.findById(asset.getPublicCollectionId()) .ifPresent(collection -> { collection.setSalable(false); collectionRepo.save(collection); }); } asset.setConsignment(false); assetRepo.save(asset); } public void cancelPublic(Long id) { Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } cancelPublic(asset); } public void cancelPublic(Asset asset) { if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) { throw new BusinessException("此藏品不属于你"); } if (!asset.isPublicShow()) { return; } if (asset.isConsignment()) { cancelConsignment(asset); } Collection collection = collectionRepo.findById(asset.getPublicCollectionId()) .orElseThrow(new BusinessException("无展示记录")); collectionRepo.delete(collection); // 如果展厅有此藏品 showCollectionRepo.deleteAllByCollectionId(asset.getPublicCollectionId()); asset.setPublicShow(false); asset.setPublicCollectionId(null); assetRepo.save(asset); } public void usePrivilege(Long assetId, Long privilegeId) { Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("无记录")); asset.getPrivileges().stream().filter(p -> p.getId().equals(privilegeId)).forEach(p -> { if (!p.getName().equals("铸造")) { p.setOpened(true); p.setOpenTime(LocalDateTime.now()); p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId()); } }); assetRepo.save(asset); } public void transfer(Asset asset, BigDecimal price, User toUser, TransferReason reason, Long orderId) { Objects.requireNonNull(asset, "原藏品不能为空"); Objects.requireNonNull(toUser, "转让人不能为空"); Objects.requireNonNull(reason, "转让原因不能为空"); Asset newAsset = new Asset(); BeanUtils.copyProperties(asset, newAsset); newAsset.setId(null); newAsset.setUserId(toUser.getId()); newAsset.setOwner(toUser.getNickname()); newAsset.setOwnerId(toUser.getId()); newAsset.setOwnerAvatar(toUser.getAvatar()); newAsset.setPublicShow(false); newAsset.setConsignment(false); newAsset.setPublicCollectionId(null); newAsset.setStatus(AssetStatus.NORMAL); newAsset.setPrice(price); newAsset.setSellPrice(null); newAsset.setOrderId(orderId); newAsset.setOasisId(asset.getOasisId()); newAsset.setFromAssetId(asset.getId()); newAsset.setType(CollectionType.DEFAULT); newAsset.setSource(TransferReason.GIFT == reason ? AssetSource.GIFT : AssetSource.TRANSFER); newAsset.setTags(new HashSet<>(asset.getTags())); assetRepo.save(newAsset); tokenHistoryRepo.save(TokenHistory.builder() .tokenId(asset.getTokenId()) .fromUser(asset.getOwner()) .fromUserId(asset.getOwnerId()) .fromAvatar(asset.getOwnerAvatar()) .toUser(toUser.getNickname()) .toUserId(toUser.getId()) .toAvatar(toUser.getAvatar()) .operation(reason.getDescription()) .price(TransferReason.GIFT == reason ? null : price) .build()); asset.setPublicShow(false); asset.setConsignment(false); asset.setPublicCollectionId(null); switch (reason) { case GIFT: asset.setStatus(AssetStatus.GIFTED); break; case AUCTION: asset.setStatus(AssetStatus.AUCTIONED); break; case TRANSFER: asset.setStatus(AssetStatus.TRANSFERRED); } asset.setOwner(toUser.getNickname()); asset.setOwnerId(toUser.getId()); asset.setOwnerAvatar(toUser.getAvatar()); assetRepo.save(asset); //vip权限转让 CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo.findByCollectionId(asset.getCollectionId()); if (ObjectUtils.isNotEmpty(collectionPrivilege)) { if (collectionPrivilege.isVip()) { //更新vip信息 userRepo.updateVipPurchase(toUser.getId(), 1); userRepo.updateVipPurchase(asset.getUserId(), 0); } } } public List tokenHistory(String tokenId, Long assetId) { if (tokenId == null) { if (assetId == null) return new ArrayList<>(); tokenId = assetRepo.findById(assetId).map(Asset::getTokenId).orElse(null); } if (tokenId == null) return new ArrayList<>(); return tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(tokenId); } public void setHistory() { List assets = assetRepo.findByCreatedAtBefore(LocalDateTime.of(2021, 11, 22, 23, 59, 59)); assets.parallelStream().forEach(asset -> { try { User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("")); Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("")); TokenHistory t = TokenHistory.builder() .tokenId(asset.getTokenId()) .fromUser(asset.getMinter()) .fromUserId(asset.getMinterId()) .fromAvatar(asset.getMinterAvatar()) .toUser(owner.getNickname()) .toUserId(owner.getId()) .toAvatar(owner.getAvatar()) .operation("出售") .price(order.getPrice()) .build(); t.setCreatedAt(asset.getCreatedAt()); tokenHistoryRepo.save(t); } catch (Exception e) { } }); } public Page userHistory(Long userId, Long toUserId, Long fromUserId, Pageable pageable) { Page page; if (ObjectUtils.isNotEmpty(toUserId)) { page = tokenHistoryRepo.userHistoryTo(userId, toUserId, pageable); } else if (ObjectUtils.isNotEmpty(fromUserId)) { page = tokenHistoryRepo.userHistoryFrom(userId, fromUserId, pageable); } else { page = tokenHistoryRepo.userHistory(userId, pageable); } Set tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet()); List assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds); return page.map(tokenHistory -> { UserHistory userHistory = new UserHistory(); BeanUtils.copyProperties(tokenHistory, userHistory); Optional asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId())) .findAny(); userHistory.setAssetName(asset.map(Asset::getName).orElse(null)); userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>())); switch (tokenHistory.getOperation()) { case "出售": case "转让": userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出"); break; case "空投": userHistory.setDescription("空投"); break; case "转赠": userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送"); break; } return userHistory; }); } public Page userHistory(Long userId, PageQuery pageQuery) { Page page = tokenHistoryRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> { List and = JpaUtils .toPredicates(pageQuery, TokenHistory.class, root, criteriaQuery, criteriaBuilder); Map query = pageQuery.getQuery(); if (ObjectUtils.isEmpty(query.get("toUserId")) && ObjectUtils.isEmpty(query.get("fromUserId"))) { and.add(criteriaBuilder.or(criteriaBuilder.equal(root.get("toUserId"), userId), criteriaBuilder .equal(root.get("fromUserId"), userId))); } else { if (ObjectUtils.isNotEmpty(query.get("toUserId"))) { and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("toUserId"), userId))); } else { and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("fromUserId"), userId))); } } return criteriaBuilder.and(and.toArray(new Predicate[0])); }), JpaUtils.toPageRequest(pageQuery)); Set tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet()); List assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds); return page.map(tokenHistory -> { UserHistory userHistory = new UserHistory(); BeanUtils.copyProperties(tokenHistory, userHistory); Optional asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId())) .findAny(); userHistory.setAssetName(asset.map(Asset::getName).orElse(null)); userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>())); switch (tokenHistory.getOperation()) { case "出售": case "转让": userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出"); break; case "空投": userHistory.setDescription("空投"); break; case "转赠": userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送"); break; } return userHistory; }); } public String mint(LocalDateTime time) { if (time == null) { time = LocalDateTime.now(); } for (Asset asset : assetRepo.toMint(time)) { rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId()); } return "ok"; } @Cacheable(value = "userStat", key = "#userId") public Map breakdown(Long userId) { List page = tokenHistoryRepo.userHistory(userId); BigDecimal sale = page.stream() .filter(th -> th.getFromUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice())) .map(TokenHistory::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal buy = page.stream() .filter(th -> th.getToUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice())) .map(TokenHistory::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); Map map = new HashMap<>(); map.put("sale", sale); map.put("buy", buy); return map; } public void transferCDN() throws ExecutionException, InterruptedException { ForkJoinPool customThreadPool = new ForkJoinPool(100); customThreadPool.submit(() -> { collectionRepo.selectResource().parallelStream().forEach(list -> { for (int i = 0; i < list.size(); i++) { list.set(i, replaceCDN(list.get(i))); } collectionRepo.updateCDN(Long.parseLong(list.get(0)), list.get(1), list.get(2), list.get(3), list.get(4), list.get(5)); }); assetRepo.selectResource().parallelStream().forEach(list -> { for (int i = 0; i < list.size(); i++) { list.set(i, replaceCDN(list.get(i))); } assetRepo.updateCDN(Long.parseLong(list.get(0)), list.get(1), list.get(2), list.get(3), list.get(4), list.get(5)); }); }).get(); } public String replaceCDN(String url) { if (url == null) return null; return url.replaceAll("https://raex-meta\\.oss-cn-shenzhen\\.aliyuncs\\.com", "https://cdn.raex.vip"); } // @Scheduled(cron = "0 0 0/1 * * ?") // public void offTheShelf() { // LocalDateTime lastTime = LocalDateTime.now().minusHours(120); // Set assetIds = collectionRepo // .findResaleCollectionPriceOver20K(BigDecimal // .valueOf(20000L), CollectionSource.TRANSFER, lastTime, true); // assetIds.forEach(this::cancelConsignmentBySystem); // } @Scheduled(cron = "0 0 0/1 * * ?") public void offTheShelfAll() { LocalDateTime lastTime = LocalDateTime.now().minusHours(240); Set assetIds = collectionRepo .findResaleCollectionPriceOver20K(BigDecimal .valueOf(0L), CollectionSource.TRANSFER, lastTime, true); assetIds.forEach(this::cancelConsignmentBySystem); } public void cancelConsignmentBySystem(Long id) { Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (asset.getPublicCollectionId() != null) { List orders = orderRepo.findByCollectionId(asset.getPublicCollectionId()); if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) { throw new BusinessException("已有订单不可取消"); } collectionRepo.findById(asset.getPublicCollectionId()) .ifPresent(collection -> { collection.setSalable(false); collectionRepo.save(collection); }); } asset.setConsignment(false); assetRepo.save(asset); } // @Cacheable(cacheNames = "fmaa", key = "#userId+'#'+#mintActivityId+'#'+#pageable.hashCode()") public PageWrapper findMintActivityAssetsWrap(Long userId, Long mintActivityId, Pageable pageable) { return PageWrapper.of(findMintActivityAssets(userId, mintActivityId, pageable)); } public Page 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 tags = mintActivity.getRule().getTags(); if (tags.isEmpty()) return new PageImpl<>(Collections.emptyList()); return assetRepo.findAll((Specification) (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); } } }