AssetService.java 20 KB

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