CollectionService.java 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. package com.izouma.nineth.service;
  2. import cn.hutool.core.convert.Convert;
  3. import com.izouma.nineth.TokenHistory;
  4. import com.izouma.nineth.annotations.Debounce;
  5. import com.izouma.nineth.config.Constants;
  6. import com.izouma.nineth.config.GeneralProperties;
  7. import com.izouma.nineth.config.RedisKeys;
  8. import com.izouma.nineth.converter.LongArrayConverter;
  9. import com.izouma.nineth.converter.StringArrayConverter;
  10. import com.izouma.nineth.domain.Collection;
  11. import com.izouma.nineth.domain.*;
  12. import com.izouma.nineth.domain.nftdomain.Cart;
  13. import com.izouma.nineth.dto.*;
  14. import com.izouma.nineth.enums.*;
  15. import com.izouma.nineth.exception.BusinessException;
  16. import com.izouma.nineth.repo.*;
  17. import com.izouma.nineth.repo.nftdomain.CartRepo;
  18. import com.izouma.nineth.utils.JpaUtils;
  19. import com.izouma.nineth.utils.SecurityUtils;
  20. import lombok.AllArgsConstructor;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.apache.commons.lang3.ObjectUtils;
  23. import org.apache.commons.lang3.RandomUtils;
  24. import org.apache.commons.lang3.Range;
  25. import org.apache.commons.lang3.StringUtils;
  26. import org.apache.rocketmq.spring.core.RocketMQTemplate;
  27. import org.springframework.beans.BeanUtils;
  28. import org.springframework.cache.annotation.Cacheable;
  29. import org.springframework.core.env.Environment;
  30. import org.springframework.data.domain.Page;
  31. import org.springframework.data.domain.PageImpl;
  32. import org.springframework.data.domain.PageRequest;
  33. import org.springframework.data.domain.Pageable;
  34. import org.springframework.data.jpa.domain.Specification;
  35. import org.springframework.data.redis.core.BoundHashOperations;
  36. import org.springframework.data.redis.core.BoundValueOperations;
  37. import org.springframework.data.redis.core.RedisTemplate;
  38. import org.springframework.scheduling.TaskScheduler;
  39. import org.springframework.stereotype.Service;
  40. import org.springframework.web.bind.annotation.GetMapping;
  41. import org.springframework.web.bind.annotation.RequestParam;
  42. import javax.annotation.PostConstruct;
  43. import javax.persistence.criteria.Join;
  44. import javax.persistence.criteria.Predicate;
  45. import javax.transaction.Transactional;
  46. import java.math.BigDecimal;
  47. import java.time.LocalDateTime;
  48. import java.time.ZoneId;
  49. import java.time.format.DateTimeFormatter;
  50. import java.util.*;
  51. import java.util.concurrent.ForkJoinPool;
  52. import java.util.concurrent.ScheduledFuture;
  53. import java.util.concurrent.TimeUnit;
  54. import java.util.concurrent.atomic.AtomicInteger;
  55. import java.util.stream.Collectors;
  56. @Service
  57. @AllArgsConstructor
  58. @Slf4j
  59. public class CollectionService {
  60. private CollectionRepo collectionRepo;
  61. private LikeRepo likeRepo;
  62. private BlindBoxItemRepo blindBoxItemRepo;
  63. private AppointmentRepo appointmentRepo;
  64. private UserRepo userRepo;
  65. private TaskScheduler taskScheduler;
  66. private CacheService cacheService;
  67. private RedisTemplate<String, Object> redisTemplate;
  68. private RocketMQTemplate rocketMQTemplate;
  69. private GeneralProperties generalProperties;
  70. private Environment env;
  71. private OrderRepo orderRepo;
  72. private TokenHistoryRepo tokenHistoryRepo;
  73. private TagRepo tagRepo;
  74. private UserBalanceService userBalanceService;
  75. private SysConfigService sysConfigService;
  76. private NewsRepo newsRepo;
  77. private AssetRepo assetRepo;
  78. private CartRepo cartRepo;
  79. private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();
  80. @PostConstruct
  81. public void init() {
  82. if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
  83. return;
  84. }
  85. List<Collection> collections = collectionRepo
  86. .findByScheduleSaleTrueAndOnShelfFalseAndStartTimeBeforeAndDelFalse(LocalDateTime.now());
  87. for (Collection collection : collections) {
  88. onShelfTask(collection);
  89. }
  90. }
  91. @Cacheable(value = "collectionList", key = "#pageQuery.hashCode()")
  92. public PageWrapper<Collection> all(PageQuery pageQuery) {
  93. Map<String, Object> query = pageQuery.getQuery();
  94. query.put("del", false);
  95. // String type = MapUtils.getString(pageQuery.getQuery(), "type", "DEFAULT");
  96. // pageQuery.getQuery().remove("type");
  97. Specification<Collection> specification = JpaUtils.toSpecification(pageQuery, Collection.class);
  98. PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery);
  99. //筛选最低价格
  100. if (query.containsKey("minPrice")) {
  101. BigDecimal minPrice = Convert.toBigDecimal(query.get("minPrice"));
  102. query.remove("minPrice");
  103. if (minPrice.compareTo(BigDecimal.ZERO) > 0) {
  104. specification = specification
  105. .and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  106. List<Predicate> and = new ArrayList<>();
  107. and.add(criteriaBuilder.greaterThanOrEqualTo(root.get("price"), minPrice));
  108. return criteriaBuilder.and(and.toArray(new Predicate[0]));
  109. });
  110. }
  111. }
  112. if (query.containsKey("distinct")) {
  113. query.remove("distinct");
  114. specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  115. criteriaQuery.groupBy(root.get("name"));
  116. return criteriaBuilder.and();
  117. });
  118. }
  119. if (query.containsKey("distinctPrefix")) {
  120. query.remove("distinctPrefix");
  121. specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  122. criteriaQuery.groupBy(root.get("prefixName"));
  123. return criteriaBuilder.and();
  124. });
  125. }
  126. if (query.containsKey("rarityLabel")) {
  127. String rarityLabel = String.valueOf(query.get("rarityLabel"));
  128. query.remove("rarityLabel");
  129. String not = null;
  130. if (StringUtils.isNotBlank(rarityLabel)) {
  131. if (Constants.Rarity.SSR.equals(rarityLabel)) {
  132. not = Constants.Rarity.U_LIKE;
  133. }
  134. if (Constants.Rarity.SR.equals(rarityLabel)) {
  135. not = Constants.Rarity.SSR_LIKE;
  136. }
  137. if (Constants.Rarity.U.equals(rarityLabel)) {
  138. not = Constants.Rarity.R_LIKE;
  139. }
  140. if (Constants.Rarity.R.equals(rarityLabel)) {
  141. not = Constants.Rarity.SR_LIKE;
  142. }
  143. String finalNotLike = not;
  144. String finalLike = "%" + rarityLabel + " #%";
  145. specification = specification
  146. .and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  147. List<Predicate> and = new ArrayList<>();
  148. and.add(criteriaBuilder.like(root.get("name"), finalLike));
  149. and.add(criteriaBuilder.notLike(root.get("name"), finalNotLike));
  150. return criteriaBuilder.and(and.toArray(new Predicate[0]));
  151. });
  152. }
  153. }
  154. // 筛选去除指定名称不展示
  155. if (query.containsKey("notLike")) {
  156. String notLike = Convert.toStr(query.get("notLike"));
  157. query.remove("notLike");
  158. StringArrayConverter converter = new StringArrayConverter();
  159. List<String> notLikes = converter.convertToEntityAttribute(notLike);
  160. specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  161. List<Predicate> and = new ArrayList<>();
  162. notLikes.forEach(str -> {
  163. and.add(criteriaBuilder.notLike(root.get("name"), "%" + str + "%"));
  164. });
  165. return criteriaBuilder.and(and.toArray(new Predicate[0]));
  166. });
  167. }
  168. // if (pageRequest.getSort().stream().noneMatch(order -> order.getProperty().equals("createdAt"))) {
  169. // pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageQuery.getSize(),
  170. // pageRequest.getSort().and(Sort.by("createdAt").descending()));
  171. // }
  172. // specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
  173. // List<Predicate> and = new ArrayList<>();
  174. //
  175. // if (StringUtils.isNotEmpty(type) && !"all".equalsIgnoreCase(type)) {
  176. // try {
  177. // if (type.contains(",")) {
  178. // and.add(root.get("type")
  179. // .in(Arrays.stream(type.split(",")).map(s -> Enum.valueOf(CollectionType.class, s))
  180. // .collect(Collectors.toList())));
  181. // } else {
  182. // and.add(criteriaBuilder.equal(root.get("type"), Enum.valueOf(CollectionType.class, type)));
  183. // }
  184. // } catch (Exception e) {
  185. //
  186. // }
  187. // }
  188. // return criteriaBuilder.and(and.toArray(new Predicate[0]));
  189. // });
  190. Page<Collection> page = collectionRepo.findAll(specification, pageRequest);
  191. return new PageWrapper<>(page.getContent(), page.getPageable().getPageNumber(),
  192. page.getPageable().getPageSize(), page.getTotalElements());
  193. }
  194. @Transactional
  195. public Collection create(Collection record) {
  196. User minter = userRepo.findById(record.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser());
  197. if (record.getCompanyId() != 1L) {
  198. BigDecimal price = sysConfigService.getBigDecimal("company_collection_price");
  199. BigDecimal totalPrice = new BigDecimal(record.getTotal()).multiply(price);
  200. if (!userBalanceService.checkBalance(record.getCompanyId(), totalPrice)) {
  201. throw new BusinessException("余额不足");
  202. }
  203. userBalanceService
  204. .modifyBalance(record.getCompanyId(), totalPrice.negate(), BalanceType.PAY, null, false, null);
  205. }
  206. if (record.getNewsId() != null) {
  207. News news = newsRepo.findById(record.getNewsId()).orElseThrow(new BusinessException("未找到"));
  208. record.setNewsId(news.getId());
  209. record.setNewsPic(news.getPic());
  210. record.setNewsTitle(news.getTitle());
  211. record.setNewsCreatedTime(news.getCreatedAt());
  212. }
  213. record.setMinter(minter.getNickname());
  214. record.setMinterId(minter.getId());
  215. record.setMinterAvatar(minter.getAvatar());
  216. record.setOwner(minter.getNickname());
  217. record.setOwnerId(minter.getId());
  218. record.setOwnerAvatar(minter.getAvatar());
  219. record.setStock(record.getTotal());
  220. record.setSale(0);
  221. record.setVipQuota(record.getTotalQuota());
  222. if (!record.getTags().isEmpty()) {
  223. record.setTags(new HashSet<>(tagRepo.findAllById(record.getTags().stream().map(Tag::getId)
  224. .collect(Collectors.toList()))));
  225. }
  226. if (record.isScheduleSale()) {
  227. if (record.getStartTime() == null) {
  228. throw new BusinessException("请填写定时发布时间");
  229. }
  230. if (record.getStartTime().isBefore(LocalDateTime.now())) {
  231. record.setOnShelf(true);
  232. record.setSalable(true);
  233. record.setStartTime(null);
  234. }
  235. }
  236. record = collectionRepo.save(record);
  237. onShelfTask(record);
  238. redisTemplate.opsForValue().set(RedisKeys.COLLECTION_STOCK + record.getId(), record.getStock());
  239. redisTemplate.opsForValue().set(RedisKeys.COLLECTION_SALE + record.getId(), record.getSale());
  240. return record;
  241. }
  242. public Collection update(Collection record) {
  243. // collectionRepo.update(record.getId(), record.isOnShelf(), record.isSalable(),
  244. // record.getStartTime(), record.isScheduleSale(), record.getSort(),
  245. // record.getDetail(), JSON.toJSONString(record.getPrivileges()),
  246. // JSON.toJSONString(record.getProperties()), JSON.toJSONString(record.getModel3d()),
  247. // record.getMaxCount(), record.getCountId(), record.isScanCode(), record.isNoSoldOut(),
  248. // record.getAssignment(), record.isCouponPayment(), record.getShareBg(), record.getRegisterBg(),
  249. // record.getVipQuota(), record.getTimeDelay(), record.getSaleTime(), record.getHoldDays(),
  250. // record.getOpenQuota(), record.getTotalQuota(), record.getMinimumCharge());
  251. Collection collection = collectionRepo.findDetailById(record.getId()).orElseThrow(new BusinessException("无记录"));
  252. collection.setOnShelf(record.isOnShelf());
  253. collection.setSalable(record.isSalable());
  254. collection.setStartTime(record.getStartTime());
  255. collection.setScheduleSale(record.isScheduleSale());
  256. collection.setSort(record.getSort());
  257. collection.setDetail(record.getDetail());
  258. collection.setPrivileges(record.getPrivileges());
  259. collection.setProperties(record.getProperties());
  260. collection.setModel3d(record.getModel3d());
  261. collection.setMaxCount(record.getMaxCount());
  262. collection.setCountId(record.getCountId());
  263. collection.setScanCode(record.isScanCode());
  264. collection.setNoSoldOut(record.isNoSoldOut());
  265. collection.setAssignment(record.getAssignment());
  266. collection.setCouponPayment(record.isCouponPayment());
  267. collection.setShareBg(record.getShareBg());
  268. collection.setRegisterBg(record.getRegisterBg());
  269. collection.setVipQuota(record.getVipQuota());
  270. collection.setTimeDelay(record.getTimeDelay());
  271. collection.setSaleTime(record.getSaleTime());
  272. collection.setHoldDays(record.getHoldDays());
  273. collection.setOpenQuota(record.getOpenQuota());
  274. collection.setTotalQuota(record.getTotalQuota());
  275. collection.setMinimumCharge(record.getMinimumCharge());
  276. collection.setRule(record.getRule());
  277. collection.setPrefixName(record.getPrefixName());
  278. if (record.getTags().isEmpty()) {
  279. collection.setTags(null);
  280. } else {
  281. collection.setTags(new HashSet<>(tagRepo.findAllById(record.getTags().stream().map(Tag::getId)
  282. .collect(Collectors.toList()))));
  283. }
  284. if (record.getNewsId() != null) {
  285. News news = newsRepo.findById(record.getNewsId()).orElseThrow(new BusinessException("未找到新闻"));
  286. collection.setNewsId(news.getId());
  287. collection.setNewsTitle(news.getTitle());
  288. collection.setNewsPic(news.getPic());
  289. collection.setNewsCreatedTime(news.getCreatedAt());
  290. }
  291. collection = collectionRepo.save(collection);
  292. onShelfTask(collection);
  293. return collection;
  294. }
  295. private void onShelfTask(Collection record) {
  296. ScheduledFuture<?> task = tasks.get(record.getId());
  297. if (task != null) {
  298. if (!task.cancel(true)) {
  299. return;
  300. }
  301. }
  302. if (record.isScheduleSale()) {
  303. if (record.getStartTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
  304. Date date = Date.from(record.getStartTime().atZone(ZoneId.systemDefault()).toInstant());
  305. ScheduledFuture<?> future = taskScheduler.schedule(() -> {
  306. collectionRepo.scheduleOnShelf(record.getId(), !record.isScanCode());
  307. tasks.remove(record.getId());
  308. }, date);
  309. tasks.put(record.getId(), future);
  310. } else {
  311. collectionRepo.scheduleOnShelf(record.getId(), !record.isScanCode());
  312. }
  313. }
  314. }
  315. public CollectionDTO toDTO(Collection collection) {
  316. return toDTO(collection, true, false);
  317. }
  318. public CollectionDTO toDTO(Collection collection, boolean join, boolean showVip) {
  319. CollectionDTO collectionDTO = new CollectionDTO();
  320. BeanUtils.copyProperties(collection, collectionDTO);
  321. if (join) {
  322. User user = SecurityUtils.getAuthenticatedUser();
  323. if (user != null) {
  324. List<Like> list = likeRepo.findByUserIdAndCollectionId(user.getId(),
  325. collection.getId());
  326. collectionDTO.setLiked(!list.isEmpty());
  327. if (collection.getType() == CollectionType.BLIND_BOX) {
  328. collectionDTO.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent());
  329. }
  330. if (showVip && collection.getAssignment() > 0 && user.getVipPurchase() > 0) {
  331. int purchase = orderRepo
  332. .countByUserIdAndCollectionIdAndVipTrueAndStatusIn(user.getId(), collection.getId(), Arrays
  333. .asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
  334. collectionDTO.setVipSurplus(user.getVipPurchase() - purchase);
  335. }
  336. }
  337. }
  338. return collectionDTO;
  339. }
  340. public Collection queryUserDetail(Collection collection, boolean join, boolean showVip) {
  341. if (join) {
  342. User user = SecurityUtils.getAuthenticatedUser();
  343. if (user != null) {
  344. List<Like> list = likeRepo.findByUserIdAndCollectionId(user.getId(),
  345. collection.getId());
  346. collection.setLiked(!list.isEmpty());
  347. if (collection.getType() == CollectionType.BLIND_BOX) {
  348. collection.setAppointment(appointmentRepo.findFirstByBlindBoxId(collection.getId()).isPresent());
  349. }
  350. Cart cart = cartRepo
  351. .findFirstByCollectionIdAndUserIdAndDel(collection.getId(), user.getId(), false);
  352. collection.setInCart(cart != null);
  353. // if (showVip && collection.getAssignment() > 0 && user.getVipPurchase() > 0) {
  354. // int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(user.getId(), collection.getId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING));
  355. collection.setVipSurplus(user.getVipPurchase());
  356. // }
  357. }
  358. }
  359. return collection;
  360. }
  361. public List<CollectionDTO> toDTO(List<Collection> collections) {
  362. List<Like> likes = new ArrayList<>();
  363. List<Appointment> appointments = new ArrayList<>();
  364. if (SecurityUtils.getAuthenticatedUser() != null) {
  365. likes.addAll(likeRepo.findByUserIdAndCollectionIdIsNotNull(SecurityUtils.getAuthenticatedUser().getId()));
  366. appointments.addAll(appointmentRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId()));
  367. }
  368. return collections.stream().parallel().map(collection -> {
  369. CollectionDTO dto = toDTO(collection, false, false);
  370. if (!likes.isEmpty()) {
  371. dto.setLiked(likes.stream().anyMatch(l -> l.getCollectionId().equals(collection.getId())));
  372. }
  373. if (!appointments.isEmpty()) {
  374. dto.setAppointment(appointments.stream().anyMatch(a -> a.getBlindBoxId().equals(collection.getId())));
  375. }
  376. return dto;
  377. }).collect(Collectors.toList());
  378. }
  379. public void queryUserDetail(List<Collection> collections) {
  380. List<Like> likes = new ArrayList<>();
  381. List<Appointment> appointments = new ArrayList<>();
  382. List<Cart> carts = new ArrayList<>();
  383. if (SecurityUtils.getAuthenticatedUser() != null) {
  384. likes.addAll(likeRepo.findByUserIdAndCollectionIdIsNotNull(SecurityUtils.getAuthenticatedUser().getId()));
  385. appointments.addAll(appointmentRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId()));
  386. carts.addAll(cartRepo.findByUserIdAndDel(SecurityUtils.getAuthenticatedUser().getId(), false));
  387. }
  388. collections.stream().parallel().forEach(collection -> {
  389. queryUserDetail(collection, false, false);
  390. if (!likes.isEmpty()) {
  391. collection.setLiked(likes.stream().anyMatch(l -> l.getCollectionId().equals(collection.getId())));
  392. }
  393. if (!appointments.isEmpty()) {
  394. collection.setAppointment(appointments.stream()
  395. .anyMatch(a -> a.getBlindBoxId().equals(collection.getId())));
  396. }
  397. if (!carts.isEmpty()) {
  398. collection.setInCart(carts.stream()
  399. .anyMatch(a -> a.getCollectionId().equals(collection.getId())));
  400. }
  401. });
  402. }
  403. public Page<CollectionDTO> toDTO(Page<Collection> collections) {
  404. List<CollectionDTO> userDTOS = toDTO(collections.getContent());
  405. return new PageImpl<>(userDTOS, collections.getPageable(), collections.getTotalElements());
  406. }
  407. @Transactional
  408. public Collection createBlindBox(CreateBlindBox createBlindBox) throws Exception {
  409. Collection blindBox = createBlindBox.getBlindBox();
  410. blindBox.setCompanyId(Optional.ofNullable(createBlindBox.getCompanyId()).orElse(1L));
  411. if (blindBox.getId() != null) {
  412. throw new BusinessException("无法完成此操作");
  413. }
  414. List<Collection> list =
  415. collectionRepo.findAllById(createBlindBox.getItems().stream().map(BlindBoxItem::getCollectionId)
  416. .collect(Collectors.toSet()));
  417. for (BlindBoxItem item : createBlindBox.getItems()) {
  418. Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId())).findAny()
  419. .orElseThrow(new BusinessException("所选藏品不存在"));
  420. if (item.getTotal() > collection.getStock()) {
  421. throw new BusinessException("所选藏品库存不足:" + collection.getName());
  422. }
  423. }
  424. User user = userRepo.findById(blindBox.getMinterId()).orElse(SecurityUtils.getAuthenticatedUser());
  425. blindBox.setMinter(user.getNickname());
  426. blindBox.setMinterId(user.getId());
  427. blindBox.setMinterAvatar(user.getAvatar());
  428. blindBox.setOwner(user.getNickname());
  429. blindBox.setOwnerId(user.getId());
  430. blindBox.setOwnerAvatar(user.getAvatar());
  431. blindBox.setTotal(createBlindBox.getItems().stream().mapToInt(BlindBoxItem::getTotal).sum());
  432. blindBox.setStock(blindBox.getTotal());
  433. blindBox.setSale(0);
  434. collectionRepo.save(blindBox);
  435. new ForkJoinPool(128).submit(() -> {
  436. createBlindBox.getItems().stream().parallel().forEach(item -> {
  437. Collection collection = list.stream().filter(i -> i.getId().equals(item.getCollectionId()))
  438. .findFirst().get();
  439. decreaseStock(collection.getId(), item.getTotal());
  440. BlindBoxItem blindBoxItem = new BlindBoxItem();
  441. BeanUtils.copyProperties(collection, blindBoxItem);
  442. blindBoxItem.setId(null);
  443. blindBoxItem.setOasisId(collection.getOasisId());
  444. blindBoxItem.setCollectionId(item.getCollectionId());
  445. blindBoxItem.setSale(0);
  446. blindBoxItem.setTotal(item.getTotal());
  447. blindBoxItem.setStock(item.getTotal());
  448. blindBoxItem.setRare(item.isRare());
  449. blindBoxItem.setRaceId(collection.getRaceId());
  450. blindBoxItem.setGroupId(collection.getGroupId());
  451. blindBoxItem.setItemId(collection.getItemId());
  452. blindBoxItem.setBlindBoxId(blindBox.getId());
  453. blindBoxItem.setCompanyId(collection.getCompanyId());
  454. blindBoxItemRepo.saveAndFlush(blindBoxItem);
  455. log.info("createBlindBoxItemSuccess" + blindBoxItem.getId());
  456. });
  457. }).get();
  458. return blindBox;
  459. }
  460. public void appointment(Long id, Long userId) {
  461. Collection collection = collectionRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  462. if (collection.getType() != CollectionType.BLIND_BOX) {
  463. throw new BusinessException("非盲盒,无需预约");
  464. }
  465. if (collection.getStartTime().isBefore(LocalDateTime.now())) {
  466. throw new BusinessException("盲盒已开售,无需预约");
  467. }
  468. appointmentRepo.save(Appointment.builder()
  469. .userId(userId)
  470. .blindBoxId(id)
  471. .build());
  472. }
  473. public BlindBoxItem draw(Long userId, Long collectionId) {
  474. log.info("blindBoxDraw, userId={}, collectionId={}", userId, collectionId);
  475. List<BlindBoxItem> items = blindBoxItemRepo.findByBlindBoxId(collectionId);
  476. BoundHashOperations<String, Object, Object> operations = redisTemplate
  477. .boundHashOps(RedisKeys.DRAW_BLIND_BOX + collectionId);
  478. Map<Object, Object> entries = operations.entries();
  479. Map<BlindBoxItem, Range<Integer>> randomRange = new HashMap<>();
  480. int c = 0, sum = 0;
  481. for (BlindBoxItem item : items) {
  482. if (item.getStock() > 0) {
  483. int stock = Optional.ofNullable(entries.get(item.getCollectionId() + "")).map(i -> (int) i)
  484. .orElse(item.getStock());
  485. randomRange.put(item, Range.between(c, c + item.getStock()));
  486. c += stock;
  487. sum += stock;
  488. }
  489. }
  490. int retry = 0;
  491. BlindBoxItem winItem = null;
  492. while (winItem == null) {
  493. retry++;
  494. if (userId == 3453161L || userId == 7194L || userId == 134613L) {
  495. winItem = items.stream().filter(i -> i.getName().contains("SSR") && i.getStock() > 0).findFirst()
  496. .orElse(null);
  497. }
  498. if (winItem == null) {
  499. int rand = RandomUtils.nextInt(0, sum + 1);
  500. for (Map.Entry<BlindBoxItem, Range<Integer>> entry : randomRange.entrySet()) {
  501. BlindBoxItem item = entry.getKey();
  502. Range<Integer> range = entry.getValue();
  503. if (rand >= range.getMinimum() && rand < range.getMaximum()) {
  504. int total = items.stream().filter(i -> !i.isRare())
  505. .mapToInt(BlindBoxItem::getTotal).sum();
  506. int stock = items.stream().filter(i -> !i.isRare())
  507. .mapToInt(BlindBoxItem::getStock).sum();
  508. if (item.isRare()) {
  509. double nRate = stock / (double) total;
  510. double rRate = (item.getStock() - 1) / (double) item.getTotal();
  511. if (Math.abs(nRate - rRate) < (1 / (double) item.getTotal()) || retry > 1 || rRate == 0) {
  512. if (!(nRate > 0.1 && item.getStock() == 1)) {
  513. winItem = item;
  514. }
  515. }
  516. } else {
  517. double nRate = (stock - 1) / (double) total;
  518. double rRate = item.getStock() / (double) item.getTotal();
  519. if (Math.abs(nRate - rRate) < 0.2 || retry > 1 || nRate == 0) {
  520. winItem = item;
  521. }
  522. }
  523. }
  524. }
  525. }
  526. if (retry > 100 && winItem == null) {
  527. throw new BusinessException("盲盒抽卡失败");
  528. }
  529. if (winItem != null) {
  530. operations.putIfAbsent(winItem.getCollectionId() + "", winItem.getStock());
  531. int stock = Math.toIntExact(operations.increment(winItem.getCollectionId() + "", -1));
  532. if (stock < 0) {
  533. log.info("over draw {} {}", stock, winItem.getCollectionId());
  534. operations.increment(winItem.getCollectionId() + "", 1);
  535. winItem = null;
  536. }
  537. }
  538. }
  539. // winItem.setStock(winItem.getStock() - 1);
  540. // winItem.setSale(winItem.getSale() + 1);
  541. // blindBoxItemRepo.saveAndFlush(winItem);
  542. blindBoxItemRepo.decreaseStockAndIncreaseSale(winItem.getId(), 1);
  543. blindBoxItemRepo.flush();
  544. return winItem;
  545. }
  546. public synchronized Integer getNextNumber(Long collectionId) {
  547. BoundValueOperations<String, Object> opt = redisTemplate
  548. .boundValueOps(RedisKeys.COLLECTION_NUMBER + collectionId);
  549. opt.setIfAbsent(collectionRepo.getCurrentNumber(collectionId).orElse(0));
  550. int num = Math.toIntExact(Optional.ofNullable(opt.increment()).orElse(1L));
  551. collectionRepo.setNumber(collectionId, num);
  552. return num;
  553. }
  554. public synchronized Integer getNextNumber(Collection collection) {
  555. return collection.getTotal() > 1 ? getNextNumber(collection.getId()) : null;
  556. }
  557. public synchronized Integer getNextNumber(BlindBoxItem collection) {
  558. return collection.getTotal() > 1 ? getNextNumber(collection.getId()) : null;
  559. }
  560. public void addStock(Long id, int number) {
  561. Collection collection = collectionRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  562. if (collection.getSource() != CollectionSource.OFFICIAL) {
  563. throw new BusinessException("用户转售无法增发");
  564. }
  565. if (collection.getType() == CollectionType.BLIND_BOX) {
  566. throw new BusinessException("盲盒无法增发");
  567. }
  568. increaseStock(id, number);
  569. collectionRepo.increaseTotal(id, number);
  570. }
  571. public synchronized Long increaseStock(Long id, int number) {
  572. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
  573. if (ops.get() == null) {
  574. Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id))
  575. .orElse(0), 7, TimeUnit.DAYS);
  576. log.info("创建redis库存:{}", success);
  577. }
  578. Long stock = ops.increment(number);
  579. rocketMQTemplate.convertAndSend(generalProperties.getUpdateStockTopic(), id);
  580. return stock;
  581. }
  582. public synchronized Integer getStock(Long id) {
  583. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_STOCK + id);
  584. Integer stock = (Integer) ops.get();
  585. if (stock == null) {
  586. Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getStock(id))
  587. .orElse(0), 7, TimeUnit.DAYS);
  588. log.info("创建redis库存:{}", success);
  589. return (Integer) ops.get();
  590. } else {
  591. return stock;
  592. }
  593. }
  594. public synchronized Long decreaseStock(Long id, int number) {
  595. return increaseStock(id, -number);
  596. }
  597. public synchronized Long increaseSale(Long id, int number) {
  598. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_SALE + id);
  599. if (ops.get() == null) {
  600. Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getSale(id))
  601. .orElse(0), 7, TimeUnit.DAYS);
  602. log.info("创建redis销量:{}", success);
  603. }
  604. Long sale = ops.increment(number);
  605. redisTemplate.opsForHash().increment(RedisKeys.UPDATE_SALE, id.toString(), 1);
  606. // rocketMQTemplate.convertAndSend(generalProperties.getUpdateSaleTopic(), id);
  607. return sale;
  608. }
  609. public synchronized Long decreaseSale(Long id, int number) {
  610. return increaseSale(id, -number);
  611. }
  612. // @Debounce(key = "#id", delay = 500)
  613. public void syncStock(Long id) {
  614. Integer stock = (Integer) redisTemplate.opsForValue().get(RedisKeys.COLLECTION_STOCK + id);
  615. if (stock != null) {
  616. log.info("同步库存信息{}", id);
  617. collectionRepo.updateStock(id, stock);
  618. cacheService.clearCollection(id);
  619. }
  620. }
  621. // @Debounce(key = "#id", delay = 500)
  622. public void syncSale(Long id) {
  623. Integer sale = (Integer) redisTemplate.opsForValue().get(RedisKeys.COLLECTION_SALE + id);
  624. if (sale != null) {
  625. log.info("同步销量信息{}", id);
  626. collectionRepo.updateSale(id, sale);
  627. cacheService.clearCollection(id);
  628. }
  629. }
  630. @Debounce(key = "#id", delay = 500)
  631. public void syncQuota(Long id) {
  632. Integer quota = (Integer) redisTemplate.opsForValue().get(RedisKeys.COLLECTION_QUOTA + id);
  633. if (quota != null) {
  634. log.info("同步额度信息{}", id);
  635. collectionRepo.updateVipQuota(id, quota);
  636. cacheService.clearCollection(id);
  637. }
  638. }
  639. public synchronized Long decreaseQuota(Long id, int number) {
  640. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.COLLECTION_QUOTA + id);
  641. if (ops.get() == null) {
  642. Boolean success = ops.setIfAbsent(Optional.ofNullable(collectionRepo.getVipQuota(id))
  643. .orElse(0), 7, TimeUnit.DAYS);
  644. log.info("创建redis额度:{}", success);
  645. }
  646. Long stock = ops.increment(-number);
  647. rocketMQTemplate.convertAndSend(generalProperties.getUpdateQuotaTopic(), id);
  648. return stock;
  649. }
  650. @Cacheable(value = "recommendLegacy", key = "#type")
  651. public List<CollectionDTO> recommendLegacy(@RequestParam String type, Long companyId) {
  652. return collectionRepo.recommend(type, companyId).stream().map(rc -> {
  653. if (StringUtils.isNotBlank(rc.getRecommend().getPic())) {
  654. rc.getCollection().setPic(Collections.singletonList(new FileObject(null, rc.getRecommend()
  655. .getPic(), null, null)));
  656. }
  657. CollectionDTO collectionDTO = new CollectionDTO();
  658. BeanUtils.copyProperties(rc.getCollection(), collectionDTO);
  659. return collectionDTO;
  660. }).collect(Collectors.toList());
  661. }
  662. public List<PointDTO> savePoint(Long collectionId, LocalDateTime time) {
  663. Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
  664. //库存
  665. // int stock = collection.getStock();
  666. //是否开启白名单
  667. int assignment = collection.getAssignment();
  668. if (assignment <= 0) {
  669. return null;
  670. }
  671. List<User> users = userRepo.findAllByCollectionId(collectionId);
  672. //邀请者
  673. Map<Long, List<User>> userMap = users.stream()
  674. .filter(user -> ObjectUtils.isNotEmpty(user.getCollectionInvitor()))
  675. .collect(Collectors.groupingBy(User::getCollectionInvitor));
  676. AtomicInteger sum = new AtomicInteger();
  677. AtomicInteger sum1 = new AtomicInteger();
  678. List<PointDTO> dtos = new ArrayList<>();
  679. Map<Long, BigDecimal> historyMap = tokenHistoryRepo.userBuy(userMap.keySet())
  680. .stream()
  681. .collect(Collectors
  682. .groupingBy(TokenHistory::getToUserId, Collectors
  683. .reducing(BigDecimal.ZERO,
  684. TokenHistory::getPrice,
  685. BigDecimal::add)));
  686. DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  687. userMap.forEach((key, value) -> {
  688. //邀请达到数量
  689. if (value.size() >= collection.getAssignment()) {
  690. value.sort(Comparator.comparing(User::getCreatedAt));
  691. BigDecimal buy = historyMap.get(key);
  692. //满足条件的时间
  693. User user = value.get(collection.getAssignment() - 1);
  694. //作弊得已屏蔽
  695. if ((ObjectUtils.isEmpty(buy) || buy.compareTo(BigDecimal.valueOf(500)) < 0) && user.getCreatedAt()
  696. .isBefore(time)) {
  697. sum1.getAndIncrement();
  698. System.out.println(key + "," + dft.format(user.getCreatedAt()) + "," + buy);
  699. } else {
  700. //实名数量
  701. long identitySum = value.stream().filter(u -> AuthStatus.SUCCESS.equals(u.getAuthStatus())).count();
  702. dtos.add(new PointDTO(key, user.getCreatedAt(), value.size(), (int) identitySum, buy));
  703. sum.getAndIncrement();
  704. }
  705. }
  706. });
  707. log.info("完成任务人数:{}", sum);
  708. log.info("作弊任务人数:{}", sum1);
  709. LongArrayConverter longArrayConverter = new LongArrayConverter();
  710. List<Long> collect = dtos.stream()
  711. .filter(dto -> time.isBefore(dto.getCreatedAt()))
  712. .map(PointDTO::getId)
  713. .collect(Collectors.toList());
  714. log.info(dft.format(time) + "前完成任务人数:{}", collect.size());
  715. // log.info("sql: update user set vip_point = 1 where id in ({})", longArrayConverter.convertToDatabaseColumn(collect));
  716. List<PointDTO> collect1 = dtos.stream()
  717. .filter(dto -> time.isAfter(dto.getCreatedAt()))
  718. .collect(Collectors.toList());
  719. log.info(dft.format(time) + "后完成任务人数:{}", collect1.size());
  720. List<Long> collect2 = dtos.stream()
  721. .filter(dto -> dto.getIdentitySum() > 0)
  722. .map(PointDTO::getId)
  723. .collect(Collectors.toList());
  724. log.info("邀请实名认证人量:{}", collect2.size());
  725. // log.info("sql: update user set vip_point = 1 where id in ({})", longArrayConverter.convertToDatabaseColumn(collect2));
  726. return dtos;
  727. }
  728. public Page<Collection> byTag(Long tagId, List<Long> excludeUserId, Pageable pageable) {
  729. if (excludeUserId.isEmpty()) {
  730. excludeUserId.add(0L);
  731. }
  732. return collectionRepo.findAll((Specification<Collection>) (root, query, criteriaBuilder) -> {
  733. Join join = root.join("tags");
  734. return criteriaBuilder.and(criteriaBuilder.equal(join.get("id"), tagId),
  735. criteriaBuilder.equal(root.get("source"), CollectionSource.TRANSFER),
  736. criteriaBuilder.equal(root.get("salable"), true),
  737. criteriaBuilder.equal(root.get("onShelf"), true),
  738. criteriaBuilder.not(root.get("ownerId").in(excludeUserId)));
  739. }, pageable);
  740. }
  741. public List<Collection> setOasisScancode(List<Long> oasisIds) {
  742. List<CollectionSource> collectionSources = new ArrayList<>();
  743. collectionSources.add(CollectionSource.COMPANY);
  744. collectionSources.add(CollectionSource.OFFICIAL);
  745. List<Collection> collections = collectionRepo
  746. .findAllByOasisIdInAndSourceInAndStockGreaterThan(oasisIds, collectionSources, 0);
  747. List<Collection> result = new ArrayList<>();
  748. collections.forEach(collection -> {
  749. collection.setOnShelf(false);
  750. collection.setScanCode(true);
  751. collection.setSalable(true);
  752. collectionRepo.save(collection);
  753. result.add(collection);
  754. });
  755. return result;
  756. }
  757. public List<Collection> setOasisOnShelf(List<Long> oasisIds) {
  758. List<CollectionSource> collectionSources = new ArrayList<>();
  759. collectionSources.add(CollectionSource.COMPANY);
  760. collectionSources.add(CollectionSource.OFFICIAL);
  761. List<Collection> collections = collectionRepo
  762. .findAllByOasisIdInAndSourceInAndStockGreaterThan(oasisIds, collectionSources, 0);
  763. List<Collection> result = new ArrayList<>();
  764. collections.forEach(collection -> {
  765. collection.setOnShelf(true);
  766. collection.setScanCode(false);
  767. collection.setSalable(true);
  768. collectionRepo.save(collection);
  769. result.add(collection);
  770. });
  771. return result;
  772. }
  773. public Long countDestroyAssets(String search) {
  774. return assetRepo.countDestroyed("%" + search + "%", AssetStatus.DESTROYED);
  775. }
  776. @Cacheable(value = "countByPrefix", key = "#prefixName")
  777. public Integer countNum(String prefixName) {
  778. return collectionRepo.countByPrefixNameAndOnShelfAndDelAndSalable(prefixName, true, false, true);
  779. }
  780. }