AssetService.java 21 KB

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