AssetService.java 19 KB

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