AssetService.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. package com.izouma.nineth.service;
  2. import cn.hutool.core.convert.Convert;
  3. import com.izouma.nineth.TokenHistory;
  4. import com.izouma.nineth.config.GeneralProperties;
  5. import com.izouma.nineth.domain.Collection;
  6. import com.izouma.nineth.domain.*;
  7. import com.izouma.nineth.dto.PageQuery;
  8. import com.izouma.nineth.dto.UserHistory;
  9. import com.izouma.nineth.enums.AssetStatus;
  10. import com.izouma.nineth.enums.CollectionSource;
  11. import com.izouma.nineth.enums.CollectionType;
  12. import com.izouma.nineth.enums.OrderStatus;
  13. import com.izouma.nineth.event.TransferAssetEvent;
  14. import com.izouma.nineth.exception.BusinessException;
  15. import com.izouma.nineth.repo.*;
  16. import com.izouma.nineth.utils.JpaUtils;
  17. import com.izouma.nineth.utils.SecurityUtils;
  18. import com.izouma.nineth.utils.TokenUtils;
  19. import lombok.AllArgsConstructor;
  20. import lombok.extern.slf4j.Slf4j;
  21. import org.apache.commons.lang3.ObjectUtils;
  22. import org.apache.commons.lang3.StringUtils;
  23. import org.apache.rocketmq.spring.core.RocketMQTemplate;
  24. import org.springframework.beans.BeanUtils;
  25. import org.springframework.cache.annotation.Cacheable;
  26. import org.springframework.context.ApplicationContext;
  27. import org.springframework.data.domain.Page;
  28. import org.springframework.data.domain.Pageable;
  29. import org.springframework.scheduling.annotation.Async;
  30. import org.springframework.stereotype.Service;
  31. import javax.persistence.criteria.Predicate;
  32. import java.math.BigDecimal;
  33. import java.time.LocalDateTime;
  34. import java.time.temporal.ChronoUnit;
  35. import java.util.*;
  36. import java.util.concurrent.ExecutionException;
  37. import java.util.concurrent.ForkJoinPool;
  38. import java.util.stream.Collectors;
  39. @Service
  40. @AllArgsConstructor
  41. @Slf4j
  42. public class AssetService {
  43. private AssetRepo assetRepo;
  44. private UserRepo userRepo;
  45. private CollectionRepo collectionRepo;
  46. private ApplicationContext applicationContext;
  47. private OrderRepo orderRepo;
  48. private TokenHistoryRepo tokenHistoryRepo;
  49. private SysConfigService sysConfigService;
  50. private RocketMQTemplate rocketMQTemplate;
  51. private GeneralProperties generalProperties;
  52. private ShowroomRepo showroomRepo;
  53. private ShowCollectionRepo showCollectionRepo;
  54. public Page<Asset> all(PageQuery pageQuery) {
  55. Page<Asset> all = assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
  56. Map<String, Object> query = pageQuery.getQuery();
  57. if (query.containsKey("userId")) {
  58. List<Long> orderId = orderRepo.findAllByUserIdAndOpenedFalse(Convert.convert(Long.class, query.get("userId")));
  59. return all.map(asset -> {
  60. if (orderId.contains(asset.getOrderId())) {
  61. asset.setOpened(false);
  62. }
  63. return asset;
  64. });
  65. }
  66. return all;
  67. }
  68. public Asset createAsset(Collection collection, User user, Long orderId, BigDecimal price, String type, Integer number) {
  69. Asset asset = Asset.create(collection, user);
  70. asset.setTokenId(TokenUtils.genTokenId());
  71. asset.setNumber(number);
  72. asset.setOrderId(orderId);
  73. asset.setPrice(price);
  74. assetRepo.saveAndFlush(asset);
  75. tokenHistoryRepo.save(TokenHistory.builder()
  76. .tokenId(asset.getTokenId())
  77. .fromUser(collection.getMinter())
  78. .fromUserId(collection.getMinterId())
  79. .fromAvatar(collection.getMinterAvatar())
  80. .toUser(user.getNickname())
  81. .toUserId(user.getId())
  82. .toAvatar(user.getAvatar())
  83. .operation(type)
  84. .price(price)
  85. .build());
  86. rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
  87. return asset;
  88. }
  89. public Asset createAsset(BlindBoxItem winItem, User user, Long orderId, BigDecimal price, String type,
  90. Integer number, Integer holdDays) {
  91. Asset asset = Asset.create(winItem, user, holdDays);
  92. asset.setTokenId(TokenUtils.genTokenId());
  93. asset.setNumber(number);
  94. asset.setOrderId(orderId);
  95. asset.setPrice(price);
  96. assetRepo.saveAndFlush(asset);
  97. tokenHistoryRepo.save(TokenHistory.builder()
  98. .tokenId(asset.getTokenId())
  99. .fromUser(winItem.getMinter())
  100. .fromUserId(winItem.getMinterId())
  101. .fromAvatar(winItem.getMinterAvatar())
  102. .toUser(user.getNickname())
  103. .toUserId(user.getId())
  104. .toAvatar(user.getAvatar())
  105. .operation(type)
  106. .price(price)
  107. .build());
  108. rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
  109. return asset;
  110. }
  111. public void publicShow(Long id) {
  112. Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  113. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  114. throw new BusinessException("此藏品不属于你");
  115. }
  116. if (asset.isPublicShow()) {
  117. return;
  118. }
  119. if (asset.getStatus() != AssetStatus.NORMAL) {
  120. throw new BusinessException("当前状态不可展示");
  121. }
  122. User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
  123. Collection collection = Collection.builder()
  124. .name(asset.getName())
  125. .pic(asset.getPic())
  126. .minter(asset.getMinter())
  127. .minterId(asset.getMinterId())
  128. .minterAvatar(asset.getMinterAvatar())
  129. .owner(owner.getNickname())
  130. .ownerId(owner.getId())
  131. .ownerAvatar(owner.getAvatar())
  132. .detail(asset.getDetail())
  133. .type(CollectionType.DEFAULT)
  134. .source(CollectionSource.TRANSFER)
  135. .sale(0)
  136. .stock(1)
  137. .total(1)
  138. .onShelf(true)
  139. .salable(false)
  140. .price(BigDecimal.valueOf(0))
  141. .properties(asset.getProperties())
  142. .canResale(asset.isCanResale())
  143. .royalties(asset.getRoyalties())
  144. .serviceCharge(asset.getServiceCharge())
  145. .assetId(id)
  146. .number(asset.getNumber())
  147. .build();
  148. collectionRepo.save(collection);
  149. asset.setPublicShow(true);
  150. asset.setPublicCollectionId(collection.getId());
  151. assetRepo.save(asset);
  152. }
  153. public synchronized void consignment(Long id, BigDecimal price) {
  154. Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  155. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  156. throw new BusinessException("此藏品不属于你");
  157. }
  158. int holdDays;
  159. if (ObjectUtils.isEmpty(asset.getHoldDays())) {
  160. holdDays = sysConfigService.getInt("hold_days");
  161. } else {
  162. holdDays = asset.getHoldDays();
  163. }
  164. if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
  165. throw new BusinessException("需持有满" + holdDays + "天才能寄售上架");
  166. }
  167. User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
  168. if (StringUtils.isBlank(owner.getSettleAccountId())) {
  169. throw new BusinessException("请先绑定银行卡");
  170. }
  171. if (asset.isConsignment()) {
  172. throw new BusinessException("已寄售,请勿重新操作");
  173. }
  174. if (asset.getStatus() != AssetStatus.NORMAL) {
  175. throw new BusinessException("当前状态不可寄售");
  176. }
  177. if (asset.isPublicShow()) {
  178. cancelPublic(asset);
  179. }
  180. //寄售中的展厅需要先删除展厅
  181. if (CollectionType.SHOWROOM.equals(asset.getType())) {
  182. if (showroomRepo.findByAssetId(id).isPresent()) {
  183. throw new BusinessException("请先删除展厅");
  184. }
  185. }
  186. Collection collection = Collection.builder()
  187. .name(asset.getName())
  188. .pic(asset.getPic())
  189. .minter(asset.getMinter())
  190. .minterId(asset.getMinterId())
  191. .minterAvatar(asset.getMinterAvatar())
  192. .owner(owner.getNickname())
  193. .ownerId(owner.getId())
  194. .ownerAvatar(owner.getAvatar())
  195. .detail(asset.getDetail())
  196. .type(CollectionType.DEFAULT)
  197. .source(CollectionSource.TRANSFER)
  198. .sale(0)
  199. .stock(1)
  200. .total(1)
  201. .onShelf(true)
  202. .salable(true)
  203. .price(price)
  204. .properties(asset.getProperties())
  205. .canResale(asset.isCanResale())
  206. .royalties(asset.getRoyalties())
  207. .serviceCharge(asset.getServiceCharge())
  208. .assetId(id)
  209. .number(asset.getNumber())
  210. .build();
  211. collectionRepo.save(collection);
  212. asset.setPublicShow(true);
  213. asset.setConsignment(true);
  214. asset.setPublicCollectionId(collection.getId());
  215. asset.setSellPrice(price);
  216. assetRepo.save(asset);
  217. }
  218. public void cancelConsignment(Long id) {
  219. Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  220. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  221. throw new BusinessException("此藏品不属于你");
  222. }
  223. cancelConsignment(asset);
  224. }
  225. public void cancelConsignment(Asset asset) {
  226. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  227. throw new BusinessException("此藏品不属于你");
  228. }
  229. if (asset.getPublicCollectionId() != null) {
  230. List<Order> orders = orderRepo.findByCollectionId(asset.getPublicCollectionId());
  231. if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) {
  232. throw new BusinessException("已有订单不可取消");
  233. }
  234. collectionRepo.findById(asset.getPublicCollectionId())
  235. .ifPresent(collection -> {
  236. collection.setSalable(false);
  237. collectionRepo.save(collection);
  238. });
  239. }
  240. asset.setConsignment(false);
  241. assetRepo.save(asset);
  242. }
  243. public void cancelPublic(Long id) {
  244. Asset asset = assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  245. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  246. throw new BusinessException("此藏品不属于你");
  247. }
  248. cancelPublic(asset);
  249. }
  250. public void cancelPublic(Asset asset) {
  251. if (!asset.getUserId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  252. throw new BusinessException("此藏品不属于你");
  253. }
  254. if (!asset.isPublicShow()) {
  255. return;
  256. }
  257. if (asset.isConsignment()) {
  258. cancelConsignment(asset);
  259. }
  260. Collection collection = collectionRepo.findById(asset.getPublicCollectionId())
  261. .orElseThrow(new BusinessException("无展示记录"));
  262. collectionRepo.delete(collection);
  263. // 如果展厅有此藏品
  264. showCollectionRepo.softDeleteCollection(asset.getPublicCollectionId());
  265. asset.setPublicShow(false);
  266. asset.setPublicCollectionId(null);
  267. assetRepo.save(asset);
  268. }
  269. public void usePrivilege(Long assetId, Long privilegeId) {
  270. Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("无记录"));
  271. asset.getPrivileges().stream().filter(p -> p.getId().equals(privilegeId)).forEach(p -> {
  272. p.setOpened(true);
  273. p.setOpenTime(LocalDateTime.now());
  274. p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId());
  275. });
  276. assetRepo.save(asset);
  277. }
  278. @Async
  279. public void transfer(Asset asset, BigDecimal price, User toUser, String reason, Long orderId) {
  280. Asset newAsset = new Asset();
  281. BeanUtils.copyProperties(asset, newAsset);
  282. newAsset.setId(null);
  283. newAsset.setUserId(toUser.getId());
  284. newAsset.setOwner(toUser.getNickname());
  285. newAsset.setOwnerId(toUser.getId());
  286. newAsset.setOwnerAvatar(toUser.getAvatar());
  287. newAsset.setPublicShow(false);
  288. newAsset.setConsignment(false);
  289. newAsset.setPublicCollectionId(null);
  290. newAsset.setStatus(AssetStatus.NORMAL);
  291. newAsset.setPrice(price);
  292. newAsset.setSellPrice(null);
  293. newAsset.setOrderId(orderId);
  294. newAsset.setFromAssetId(asset.getId());
  295. newAsset.setType(CollectionType.DEFAULT);
  296. assetRepo.save(newAsset);
  297. tokenHistoryRepo.save(TokenHistory.builder()
  298. .tokenId(asset.getTokenId())
  299. .fromUser(asset.getOwner())
  300. .fromUserId(asset.getOwnerId())
  301. .fromAvatar(asset.getOwnerAvatar())
  302. .toUser(toUser.getNickname())
  303. .toUserId(toUser.getId())
  304. .toAvatar(toUser.getAvatar())
  305. .operation(reason)
  306. .price("转赠".equals(reason) ? null : price)
  307. .build());
  308. asset.setPublicShow(false);
  309. asset.setConsignment(false);
  310. asset.setPublicCollectionId(null);
  311. asset.setStatus("转赠".equals(reason) ? AssetStatus.GIFTED : AssetStatus.TRANSFERRED);
  312. asset.setOwner(toUser.getNickname());
  313. asset.setOwnerId(toUser.getId());
  314. asset.setOwnerAvatar(toUser.getAvatar());
  315. assetRepo.save(asset);
  316. if (orderId != null) {
  317. applicationContext.publishEvent(new TransferAssetEvent(this, true, newAsset));
  318. }
  319. }
  320. public List<TokenHistory> tokenHistory(String tokenId, Long assetId) {
  321. if (tokenId == null) {
  322. if (assetId == null) return new ArrayList<>();
  323. tokenId = assetRepo.findById(assetId).map(Asset::getTokenId).orElse(null);
  324. }
  325. if (tokenId == null) return new ArrayList<>();
  326. return tokenHistoryRepo.findByTokenIdOrderByCreatedAtDesc(tokenId);
  327. }
  328. public void setHistory() {
  329. List<Asset> assets = assetRepo.findByCreatedAtBefore(LocalDateTime.of(2021, 11, 22, 23, 59, 59));
  330. assets.parallelStream().forEach(asset -> {
  331. try {
  332. User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException(""));
  333. Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException(""));
  334. TokenHistory t = TokenHistory.builder()
  335. .tokenId(asset.getTokenId())
  336. .fromUser(asset.getMinter())
  337. .fromUserId(asset.getMinterId())
  338. .fromAvatar(asset.getMinterAvatar())
  339. .toUser(owner.getNickname())
  340. .toUserId(owner.getId())
  341. .toAvatar(owner.getAvatar())
  342. .operation("出售")
  343. .price(order.getPrice())
  344. .build();
  345. t.setCreatedAt(asset.getCreatedAt());
  346. tokenHistoryRepo.save(t);
  347. } catch (Exception e) {
  348. }
  349. });
  350. }
  351. public Page<UserHistory> userHistory(Long userId, Long toUserId, Long fromUserId, Pageable pageable) {
  352. Page<TokenHistory> page;
  353. if (ObjectUtils.isNotEmpty(toUserId)) {
  354. page = tokenHistoryRepo.userHistoryTo(userId, toUserId, pageable);
  355. } else if (ObjectUtils.isNotEmpty(fromUserId)) {
  356. page = tokenHistoryRepo.userHistoryFrom(userId, fromUserId, pageable);
  357. } else {
  358. page = tokenHistoryRepo.userHistory(userId, pageable);
  359. }
  360. Set<String> tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet());
  361. List<Asset> assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds);
  362. return page.map(tokenHistory -> {
  363. UserHistory userHistory = new UserHistory();
  364. BeanUtils.copyProperties(tokenHistory, userHistory);
  365. Optional<Asset> asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId()))
  366. .findAny();
  367. userHistory.setAssetName(asset.map(Asset::getName).orElse(null));
  368. userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>()));
  369. switch (tokenHistory.getOperation()) {
  370. case "出售":
  371. case "转让":
  372. userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出");
  373. break;
  374. case "空投":
  375. userHistory.setDescription("空投");
  376. break;
  377. case "转赠":
  378. userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
  379. break;
  380. }
  381. return userHistory;
  382. });
  383. }
  384. public Page<UserHistory> userHistory(Long userId, PageQuery pageQuery) {
  385. Page<TokenHistory> page = tokenHistoryRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
  386. List<Predicate> and = JpaUtils.toPredicates(pageQuery, TokenHistory.class, root, criteriaQuery, criteriaBuilder);
  387. Map<String, Object> query = pageQuery.getQuery();
  388. if (ObjectUtils.isEmpty(query.get("toUserId")) && ObjectUtils.isEmpty(query.get("fromUserId"))) {
  389. and.add(criteriaBuilder.or(criteriaBuilder.equal(root.get("toUserId"), userId), criteriaBuilder.equal(root.get("fromUserId"), userId)));
  390. } else {
  391. if (ObjectUtils.isNotEmpty(query.get("toUserId"))) {
  392. and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("toUserId"), userId)));
  393. } else {
  394. and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("fromUserId"), userId)));
  395. }
  396. }
  397. return criteriaBuilder.and(and.toArray(new Predicate[0]));
  398. }), JpaUtils.toPageRequest(pageQuery));
  399. Set<String> tokenIds = page.stream().map(TokenHistory::getTokenId).collect(Collectors.toSet());
  400. List<Asset> assets = tokenIds.isEmpty() ? new ArrayList<>() : assetRepo.findByTokenIdIn(tokenIds);
  401. return page.map(tokenHistory -> {
  402. UserHistory userHistory = new UserHistory();
  403. BeanUtils.copyProperties(tokenHistory, userHistory);
  404. Optional<Asset> asset = assets.stream().filter(a -> a.getTokenId().equals(tokenHistory.getTokenId()))
  405. .findAny();
  406. userHistory.setAssetName(asset.map(Asset::getName).orElse(null));
  407. userHistory.setPic(asset.map(Asset::getPic).orElse(new ArrayList<>()));
  408. switch (tokenHistory.getOperation()) {
  409. case "出售":
  410. case "转让":
  411. userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "作品交易——买入" : "作品交易——售出");
  412. break;
  413. case "空投":
  414. userHistory.setDescription("空投");
  415. break;
  416. case "转赠":
  417. userHistory.setDescription(tokenHistory.getToUserId().equals(userId) ? "他人赠送" : "作品赠送");
  418. break;
  419. }
  420. return userHistory;
  421. });
  422. }
  423. public String mint(LocalDateTime time) {
  424. if (time == null) {
  425. time = LocalDateTime.now();
  426. }
  427. for (Asset asset : assetRepo.toMint(time)) {
  428. rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
  429. }
  430. return "ok";
  431. }
  432. @Cacheable(value = "userStat", key = "#userId")
  433. public Map<String, BigDecimal> breakdown(Long userId) {
  434. List<TokenHistory> page = tokenHistoryRepo.userHistory(userId);
  435. BigDecimal sale = page.stream()
  436. .filter(th -> th.getFromUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice()))
  437. .map(TokenHistory::getPrice)
  438. .reduce(BigDecimal.ZERO, BigDecimal::add);
  439. BigDecimal buy = page.stream()
  440. .filter(th -> th.getToUserId().equals(userId) && ObjectUtils.isNotEmpty(th.getPrice()))
  441. .map(TokenHistory::getPrice)
  442. .reduce(BigDecimal.ZERO, BigDecimal::add);
  443. Map<String, BigDecimal> map = new HashMap<>();
  444. map.put("sale", sale);
  445. map.put("buy", buy);
  446. return map;
  447. }
  448. public void transferCDN() throws ExecutionException, InterruptedException {
  449. ForkJoinPool customThreadPool = new ForkJoinPool(100);
  450. customThreadPool.submit(() -> {
  451. collectionRepo.selectResource().parallelStream().forEach(list -> {
  452. for (int i = 0; i < list.size(); i++) {
  453. list.set(i, replaceCDN(list.get(i)));
  454. }
  455. collectionRepo.updateCDN(Long.parseLong(list.get(0)),
  456. list.get(1),
  457. list.get(2),
  458. list.get(3),
  459. list.get(4),
  460. list.get(5));
  461. });
  462. assetRepo.selectResource().parallelStream().forEach(list -> {
  463. for (int i = 0; i < list.size(); i++) {
  464. list.set(i, replaceCDN(list.get(i)));
  465. }
  466. assetRepo.updateCDN(Long.parseLong(list.get(0)),
  467. list.get(1),
  468. list.get(2),
  469. list.get(3),
  470. list.get(4),
  471. list.get(5));
  472. });
  473. }).get();
  474. }
  475. public String replaceCDN(String url) {
  476. if (url == null) return null;
  477. return url.replaceAll("https://raex-meta\\.oss-cn-shenzhen\\.aliyuncs\\.com",
  478. "https://cdn.raex.vip");
  479. }
  480. }