package com.izouma.nineth.service; import com.izouma.nineth.domain.*; import com.izouma.nineth.domain.Collection; import com.izouma.nineth.dto.CollectionDTO; import com.izouma.nineth.dto.CreateBlindBox; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.CollectionType; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.SecurityUtils; import lombok.AllArgsConstructor; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.Range; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import javax.persistence.criteria.Predicate; import javax.transaction.Transactional; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service @AllArgsConstructor public class CollectionService { private CollectionRepo collectionRepo; private LikeRepo likeRepo; private BlindBoxItemRepo blindBoxItemRepo; private AppointmentRepo appointmentRepo; private UserRepo userRepo; private AssetService assetService; private RedisTemplate redisTemplate; public Page all(PageQuery pageQuery) { pageQuery.getQuery().put("del", false); String type = MapUtils.getString(pageQuery.getQuery(), "type", "DEFAULT"); pageQuery.getQuery().remove("type"); Specification specification = JpaUtils.toSpecification(pageQuery, Collection.class); PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery); if (pageRequest.getSort().stream().noneMatch(order -> order.getProperty().equals("createdAt"))) { pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageQuery.getSize(), pageRequest.getSort().and(Sort.by("createdAt").descending())); } specification = specification.and((Specification) (root, criteriaQuery, criteriaBuilder) -> { List and = new ArrayList<>(); if (StringUtils.isNotEmpty(type) && !"all".equalsIgnoreCase(type)) { try { if (type.contains(",")) { and.add(root.get("type") .in(Arrays.stream(type.split(",")).map(s -> Enum.valueOf(CollectionType.class, s)) .collect(Collectors.toList()))); } else { and.add(criteriaBuilder.equal(root.get("type"), Enum.valueOf(CollectionType.class, type))); } } catch (Exception e) { } } return criteriaBuilder.and(and.toArray(new Predicate[0])); }); return collectionRepo.findAll(specification, pageRequest); } public Collection create(Collection record) { User minter = userRepo.findById(record.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser()); record.setMinter(minter.getNickname()); record.setMinterId(minter.getId()); record.setMinterAvatar(minter.getAvatar()); record.setOwner(minter.getNickname()); record.setOwnerId(minter.getId()); record.setOwnerAvatar(minter.getAvatar()); record.setStock(record.getTotal()); record.setSale(0); if (record.isScheduleSale()) { if (record.getStartTime() == null) { throw new BusinessException("请填写定时发布时间"); } record.setOnShelf(record.getStartTime().isBefore(LocalDateTime.now())); } return collectionRepo.save(record); } public CollectionDTO toDTO(Collection collection) { return toDTO(collection, true); } public CollectionDTO toDTO(Collection collection, boolean join) { CollectionDTO collectionDTO = new CollectionDTO(); BeanUtils.copyProperties(collection, collectionDTO); if (join) { if (SecurityUtils.getAuthenticatedUser() != null) { List list = likeRepo.findByUserIdAndCollectionId(SecurityUtils.getAuthenticatedUser().getId(), collection.getId()); collectionDTO.setLiked(!list.isEmpty()); if (collection.getType() == CollectionType.BLIND_BOX) { collectionDTO.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent()); } } } return collectionDTO; } public List toDTO(List collections) { List likes = new ArrayList<>(); List appointments = new ArrayList<>(); if (SecurityUtils.getAuthenticatedUser() != null) { likes.addAll(likeRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId())); appointments.addAll(appointmentRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId())); } return collections.stream().parallel().map(collection -> { CollectionDTO dto = toDTO(collection, false); if (!likes.isEmpty()) { dto.setLiked(likes.stream().anyMatch(l -> l.getCollectionId().equals(collection.getId()))); } if (!appointments.isEmpty()) { dto.setAppointment(appointments.stream().anyMatch(a -> a.getBlindBoxId().equals(collection.getId()))); } return dto; }).collect(Collectors.toList()); } public Page toDTO(Page collections) { List userDTOS = toDTO(collections.getContent()); return new PageImpl<>(userDTOS, collections.getPageable(), collections.getTotalElements()); } @Transactional public Collection createBlindBox(CreateBlindBox createBlindBox) { Collection blindBox = createBlindBox.getBlindBox(); List list = collectionRepo.findAllById(createBlindBox.getItems().stream().map(BlindBoxItem::getCollectionId) .collect(Collectors.toSet())); for (BlindBoxItem item : createBlindBox.getItems()) { Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny() .orElseThrow(new BusinessException("所选藏品不存在")); if (item.getTotal() > collection.getStock()) { throw new BusinessException("所选藏品库存不足:" + collection.getName()); } } User user = userRepo.findById(blindBox.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser()); blindBox.setMinter(user.getNickname()); blindBox.setMinterId(user.getId()); blindBox.setMinterAvatar(user.getAvatar()); blindBox.setOwner(user.getNickname()); blindBox.setOwnerId(user.getId()); blindBox.setOwnerAvatar(user.getAvatar()); blindBox.setStock(blindBox.getTotal()); blindBox.setSale(0); collectionRepo.save(blindBox); for (BlindBoxItem item : createBlindBox.getItems()) { Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny() .orElseThrow(new BusinessException("所选藏品不存在")); collection.setStock(collection.getStock() - item.getTotal()); collectionRepo.save(collection); BlindBoxItem blindBoxItem = new BlindBoxItem(); BeanUtils.copyProperties(collection, blindBoxItem); blindBoxItem.setId(null); blindBoxItem.setCollectionId(item.getCollectionId()); blindBoxItem.setSale(0); blindBoxItem.setTotal(item.getTotal()); blindBoxItem.setStock(item.getTotal()); blindBoxItem.setRare(item.isRare()); blindBoxItem.setBlindBoxId(blindBox.getId()); blindBoxItemRepo.save(blindBoxItem); } return blindBox; } public void appointment(Long id, Long userId) { Collection collection = collectionRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (collection.getType() != CollectionType.BLIND_BOX) { throw new BusinessException("非盲盒,无需预约"); } if (collection.getStartTime().isBefore(LocalDateTime.now())) { throw new BusinessException("盲盒已开售,无需预约"); } appointmentRepo.save(Appointment.builder() .userId(userId) .blindBoxId(id) .build()); } @Scheduled(fixedRate = 60000) public void scheduleOnShelf() { List collections = collectionRepo.findByScheduleSaleTrueAndOnShelfFalseAndStartTimeBeforeAndDelFalse(LocalDateTime.now()); for (Collection collection : collections) { collection.setOnShelf(true); } collectionRepo.saveAll(collections); } public BlindBoxItem draw(Long collectionId) { List items = blindBoxItemRepo.findByBlindBoxId(collectionId); Map> randomRange = new HashMap<>(); int c = 0, sum = 0; for (BlindBoxItem item : items) { randomRange.put(item, Range.between(c, c + item.getStock())); c += item.getStock(); sum += item.getStock(); } int retry = 0; BlindBoxItem winItem = null; while (winItem == null) { retry++; int rand = RandomUtils.nextInt(0, sum + 1); for (Map.Entry> entry : randomRange.entrySet()) { BlindBoxItem item = entry.getKey(); Range range = entry.getValue(); if (rand >= range.getMinimum() && rand < range.getMaximum()) { int total = items.stream().filter(i -> !i.isRare()) .mapToInt(BlindBoxItem::getTotal).sum(); int stock = items.stream().filter(i -> !i.isRare()) .mapToInt(BlindBoxItem::getStock).sum(); if (item.isRare()) { double nRate = stock / (double) total; double rRate = (item.getStock() - 1) / (double) item.getTotal(); if (Math.abs(nRate - rRate) < (1 / (double) item.getTotal()) || retry > 1 || rRate == 0) { if (!(nRate > 0.1 && item.getStock() == 1)) { winItem = item; } } } else { double nRate = (stock - 1) / (double) total; double rRate = item.getStock() / (double) item.getTotal(); if (Math.abs(nRate - rRate) < 0.2 || retry > 1 || nRate == 0) { winItem = item; } } } } if (retry > 100 && winItem == null) { throw new BusinessException("盲盒抽卡失败"); } } winItem.setStock(winItem.getStock() - 1); winItem.setSale(winItem.getSale() + 1); blindBoxItemRepo.save(winItem); return winItem; } public synchronized Integer getNextNumber(Long collectionId) { Collection collection = collectionRepo.findById(collectionId).orElse(null); if (collection == null) return 0; if (collection.getCurrentNumber() == null) { collection.setCurrentNumber(0); } collection.setCurrentNumber(collection.getCurrentNumber() + 1); collectionRepo.save(collection); return collection.getCurrentNumber(); } }