AssetService.java 21 KB

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