AuctionOrderService.java 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. package com.izouma.nineth.service;
  2. import com.izouma.nineth.annotations.RedisLock;
  3. import com.izouma.nineth.config.Constants;
  4. import com.izouma.nineth.config.RedisKeys;
  5. import com.izouma.nineth.domain.*;
  6. import com.izouma.nineth.dto.PageQuery;
  7. import com.izouma.nineth.enums.*;
  8. import com.izouma.nineth.exception.BusinessException;
  9. import com.izouma.nineth.repo.*;
  10. import com.izouma.nineth.service.sms.SmsService;
  11. import com.izouma.nineth.utils.JpaUtils;
  12. import com.izouma.nineth.utils.SecurityUtils;
  13. import com.izouma.nineth.utils.SnowflakeIdWorker;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.apache.commons.lang3.ObjectUtils;
  16. import org.apache.commons.lang3.StringUtils;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.context.annotation.Lazy;
  19. import org.springframework.data.domain.Page;
  20. import org.springframework.data.redis.core.BoundValueOperations;
  21. import org.springframework.data.redis.core.RedisTemplate;
  22. import org.springframework.scheduling.annotation.Scheduled;
  23. import org.springframework.stereotype.Service;
  24. import javax.persistence.Transient;
  25. import java.math.BigDecimal;
  26. import java.math.RoundingMode;
  27. import java.time.LocalDateTime;
  28. import java.util.Arrays;
  29. import java.util.List;
  30. import java.util.Optional;
  31. import java.util.concurrent.TimeUnit;
  32. @Slf4j
  33. @Service
  34. public class AuctionOrderService {
  35. @Autowired
  36. private AuctionOrderRepo auctionOrderRepo;
  37. @Autowired
  38. private SysConfigService sysConfigService;
  39. @Autowired
  40. private UserRepo userRepo;
  41. @Autowired
  42. private AssetService assetService;
  43. @Autowired
  44. private AuctionActivityRepo auctionActivityRepo;
  45. @Autowired
  46. private AuctionRecordRepo auctionRecordRepo;
  47. @Autowired
  48. private AssetRepo assetRepo;
  49. @Autowired
  50. private UserAddressRepo userAddressRepo;
  51. @Autowired
  52. private AuctionActivityService auctionActivityService;
  53. @Autowired
  54. private RedisTemplate<String, Object> redisTemplate;
  55. @Autowired
  56. private SnowflakeIdWorker snowflakeIdWorker;
  57. @Autowired
  58. private AuctionPassRecordRepo auctionPassRecordRepo;
  59. @Lazy
  60. @Autowired
  61. private OrderPayService orderPayService;
  62. @Autowired
  63. private SmsService smsService;
  64. @Autowired
  65. private UserBalanceService userBalanceService;
  66. @Autowired
  67. private ShowroomService showroomService;
  68. @Autowired
  69. private CollectionRepo collectionRepo;
  70. @Autowired
  71. private ShowroomRepo showroomRepo;
  72. @Autowired
  73. private UserBalanceRepo userBalanceRepo;
  74. public Page<AuctionOrder> all(PageQuery pageQuery) {
  75. return auctionOrderRepo
  76. .findAll(JpaUtils.toSpecification(pageQuery, AuctionOrder.class), JpaUtils.toPageRequest(pageQuery));
  77. }
  78. @RedisLock("'createAuctionOrder::'+#auctionId")
  79. public AuctionOrder create(Long userId, Long auctionId, Long addressId, Long auctionRecordId, AuctionPaymentType type) {
  80. User user = userRepo.findById(userId).orElseThrow(new BusinessException("无用户"));
  81. AuctionActivity auction = auctionActivityRepo.findById(auctionId)
  82. .orElseThrow(new BusinessException("无拍卖信息"));
  83. if (!auction.isOnShelf()) {
  84. throw new BusinessException("拍卖已结束");
  85. }
  86. String status = auctionActivityRepo.getStatus(auctionId);
  87. switch (AuctionStatus.valueOf(status)) {
  88. case NOTSTARTED:
  89. throw new BusinessException("拍卖还未开始");
  90. // case PURCHASED:
  91. // throw new BusinessException("拍卖成交中");
  92. case PASS:
  93. throw new BusinessException("已经流拍");
  94. case FINISH:
  95. throw new BusinessException("拍卖已结束");
  96. case FIXED_PRICE_PURCHASED:
  97. if (AuctionPaymentType.FIXED_PRICE.equals(type)) {
  98. throw new BusinessException("一口价成交中");
  99. }
  100. }
  101. if (user.getId().equals(auction.getSellerId())) {
  102. throw new BusinessException("不可自己出价自己");
  103. }
  104. if (AuctionPaymentType.PURCHASE_PRICE.equals(type)) {
  105. if (auction.getEndTime().isAfter(LocalDateTime.now())) {
  106. throw new BusinessException("拍卖还未结束");
  107. }
  108. AuctionRecord record = auctionRecordRepo.findTopByAuctionIdAndUserIdOrderByIdDesc(auctionId, userId);
  109. if (ObjectUtils.isEmpty(record) || !record.isPayDeposit()) {
  110. throw new BusinessException("未支付保证金");
  111. }
  112. if (record.getBidderPrice().compareTo(auction.getPurchasePrice()) != 0) {
  113. throw new BusinessException("与成交价不否");
  114. }
  115. int time = sysConfigService.getInt("auction_cancel_time");
  116. if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
  117. throw new BusinessException("超过支付时长");
  118. }
  119. } else {
  120. if (auction.getEndTime().isBefore(LocalDateTime.now())) {
  121. throw new BusinessException("拍卖已结束");
  122. }
  123. if (AuctionPaymentType.DEPOSIT.equals(type)) {
  124. return this.createDeposit(user, auction);
  125. }
  126. }
  127. UserAddress userAddress = null;
  128. if (addressId != null) {
  129. userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
  130. }
  131. try {
  132. auctionActivityRepo.updateStatus(auctionId, AuctionPaymentType.FIXED_PRICE
  133. .equals(type) ? AuctionStatus.FIXED_PRICE_PURCHASED : AuctionStatus.PURCHASED);
  134. if (AuctionSource.TRANSFER.equals(auction.getSource())) {
  135. Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  136. asset.setStatus(AssetStatus.AUCTION_TRADING);
  137. assetRepo.save(asset);
  138. }
  139. BigDecimal price = AuctionPaymentType.FIXED_PRICE.equals(type) ? auction.getFixedPrice() : auction
  140. .getPurchasePrice();
  141. AuctionOrder order = AuctionOrder.builder()
  142. .id(snowflakeIdWorker.nextId())
  143. .auctionId(auction.getId())
  144. .userId(user.getId())
  145. .nickname(user.getNickname())
  146. .paymentType(type)
  147. .name(auction.getName())
  148. .pic(auction.getPic())
  149. .serviceCharge(auction.getServiceCharge())
  150. .royalties(auction.getRoyalties())
  151. .source(auction.getSource())
  152. .price(price)
  153. .totalPrice(price)
  154. .auctionRecordId(auctionRecordId)
  155. .status(AuctionOrderStatus.NOT_PAID)
  156. .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
  157. .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
  158. .address(Optional.ofNullable(userAddress).map(UserAddress::getDetail).orElse(null))
  159. .build();
  160. return auctionOrderRepo.save(order);
  161. } catch (Exception e) {
  162. auctionActivityRepo.updateStatus(auctionId, AuctionStatus.ONGOING);
  163. throw e;
  164. }
  165. }
  166. public AuctionOrder createDeposit(User user, AuctionActivity auction) {
  167. if (user.getId().equals(auction.getSellerId())) {
  168. throw new BusinessException("不可自己出价自己的");
  169. }
  170. //竞拍人绿魔卡余额限制
  171. UserBalance userBalance = userBalanceRepo.findByUserId(user.getId()).orElse(null);
  172. BigDecimal minAmount = sysConfigService.getBigDecimal("auction_min_amount");
  173. if (userBalance == null) {
  174. throw new BusinessException("请开通绿魔卡,并充值" + minAmount + "元");
  175. } else if (minAmount.compareTo(userBalance.getBalance()) > 0) {
  176. throw new BusinessException("绿魔卡余额不足" + minAmount + "元,请先充值");
  177. }
  178. //保证金
  179. // BigDecimal deposit = sysConfigService.getBigDecimal("deposit");
  180. AuctionOrder order = auctionOrderRepo
  181. .findByUserIdAndAuctionIdAndPaymentTypeAndStatusIn(user.getId(), auction.getId(),
  182. AuctionPaymentType.DEPOSIT, Arrays
  183. .asList(AuctionOrderStatus.NOT_PAID, AuctionOrderStatus.FINISH));
  184. if (ObjectUtils.isNotEmpty(order)) {
  185. if (AuctionOrderStatus.FINISH.equals(order.getStatus())) {
  186. throw new BusinessException("保证金已交过,无需再交");
  187. }
  188. throw new BusinessException("保证金未支付,取消后再重新出价");
  189. }
  190. AuctionRecord auctionRecord = AuctionRecord.builder()
  191. .auctionId(auction.getId())
  192. .type(AuctionRecordType.DEPOSIT)
  193. .bidderPrice(auction.getDeposit())
  194. .auctionPic(null)
  195. .userId(SecurityUtils.getAuthenticatedUser().getId())
  196. .avatar(SecurityUtils.getAuthenticatedUser().getAvatar())
  197. .name(auction.getName())
  198. .purchased(false)
  199. .auctionType(auction.getAuctionType())
  200. .build();
  201. AuctionRecord record = auctionRecordRepo.save(auctionRecord);
  202. order = AuctionOrder.builder()
  203. .id(snowflakeIdWorker.nextId())
  204. .auctionId(auction.getId())
  205. .userId(user.getId())
  206. .nickname(user.getNickname())
  207. .paymentType(AuctionPaymentType.DEPOSIT)
  208. .name(auction.getName())
  209. .pic(auction.getPic())
  210. .serviceCharge(auction.getServiceCharge())
  211. .royalties(auction.getRoyalties())
  212. .source(auction.getSource())
  213. .price(auction.getDeposit())
  214. .totalPrice(auction.getDeposit())
  215. .auctionRecordId(record.getId())
  216. .status(AuctionOrderStatus.NOT_PAID)
  217. .build();
  218. return auctionOrderRepo.save(order);
  219. }
  220. @Transient
  221. public void notify(Long id, PayMethod payMethod, String transactionId) {
  222. AuctionOrder order = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  223. if (!order.getStatus().equals(AuctionOrderStatus.NOT_PAID)) {
  224. throw new BusinessException("订单已处理");
  225. }
  226. AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
  227. .orElseThrow(new BusinessException("无拍卖活动"));
  228. if (auction.getAuctionType().equals(AuctionType.ENTITY)) {
  229. order.setStatus(AuctionOrderStatus.DELIVERY);
  230. } else {
  231. order.setStatus(AuctionOrderStatus.FINISH);
  232. }
  233. order.setPayMethod(payMethod);
  234. order.setTransactionId(transactionId);
  235. order.setPayTime(LocalDateTime.now());
  236. //存订单
  237. auctionOrderRepo.save(order);
  238. if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
  239. //改出价记录表
  240. AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
  241. .orElseThrow(new BusinessException("无出价记录"));
  242. record.setPayDeposit(true);
  243. auctionRecordRepo.save(record);
  244. return;
  245. }
  246. //此拍卖结束
  247. //修改买家和成交价
  248. auction.setStatus(AuctionStatus.FINISH);
  249. auction.setPurchaserId(order.getUserId());
  250. auction.setPurchasePrice(order.getTotalPrice());
  251. auctionActivityRepo.save(auction);
  252. log.info("拍卖结束:{}", order.getAuctionId());
  253. if (AuctionSource.TRANSFER.equals(order.getSource())) {
  254. Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  255. if (asset.isPublicShow()) {
  256. //取消公开展示
  257. assetService.cancelPublic(asset);
  258. }
  259. User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("无用户"));
  260. //转让流程
  261. assetService.transfer(asset, order.getTotalPrice(), user, TransferReason.AUCTION, order.getId());
  262. // 发送短信提醒用户转让成功
  263. if (asset.getUserId() != null) {
  264. smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
  265. }
  266. //用户冲余额
  267. BigDecimal amount = order.getTotalPrice()
  268. .multiply(BigDecimal.valueOf(100 - order.getRoyalties() - order.getServiceCharge()))
  269. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  270. userBalanceService.addBalance(asset.getUserId(), amount, id, BalanceType.AUCTION);
  271. }
  272. //改出价记录表为竞得(一口价无出价表)
  273. if (ObjectUtils.isNotEmpty(order.getAuctionRecordId())) {
  274. AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
  275. .orElseThrow(new BusinessException("无出价记录"));
  276. record.setPurchased(true);
  277. auctionRecordRepo.save(record);
  278. }
  279. //退保证金
  280. List<AuctionOrder> orders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
  281. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  282. //退款
  283. orders.forEach(this::refund);
  284. }
  285. public void cancel(AuctionOrder order) {
  286. if (!getOrderLock(order.getId())) {
  287. log.error("订单取消失败 {}, redis锁了", order.getId());
  288. return;
  289. }
  290. boolean isRefund = false;
  291. try {
  292. AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
  293. .orElseThrow(new BusinessException("无记录"));
  294. if (AuctionPaymentType.PURCHASE_PRICE.equals(order.getPaymentType())) {
  295. //如果是拍卖,需获取取消订单的时长
  296. int time = sysConfigService.getInt("auction_cancel_time");
  297. if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
  298. //超过支付时长
  299. log.info("取消订单流拍:{}", auction.getId());
  300. auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PASS);
  301. //添加到流拍记录表里
  302. auctionPassRecordRepo.save(AuctionPassRecord.builder()
  303. .auctionId(auction.getId())
  304. .userId(auction.getPurchaserId())
  305. .purchasePrice(auction.getPurchasePrice())
  306. .build());
  307. //流拍不退自己的保证金
  308. isRefund = true;
  309. if (AuctionSource.TRANSFER.equals(order.getSource())) {
  310. //改回资产状态
  311. Asset asset = assetRepo.findById(auction.getAssetId())
  312. .orElseThrow(new BusinessException("资产不存在"));
  313. asset.setStatus(AssetStatus.NORMAL);
  314. assetRepo.save(asset);
  315. }
  316. }
  317. } else if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
  318. //删除出价记录
  319. auctionRecordRepo.softDelete(order.getAuctionRecordId());
  320. } else {
  321. //拍卖是否结束
  322. if (LocalDateTime.now().isBefore(auction.getEndTime())) {
  323. //返回拍卖状态
  324. auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.ONGOING);
  325. } else {
  326. //最后一个出价的人得
  327. auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PURCHASED);
  328. }
  329. }
  330. order.setStatus(AuctionOrderStatus.CANCELLED);
  331. order.setCancelTime(LocalDateTime.now());
  332. auctionOrderRepo.save(order);
  333. log.info("取消订单{}", order.getId());
  334. } catch (Exception e) {
  335. log.error("订单取消错误 orderId: " + order.getId(), e);
  336. }
  337. if (isRefund) {
  338. //退其余保证金
  339. List<AuctionOrder> orders = auctionOrderRepo
  340. .findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
  341. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  342. //退款
  343. orders.stream()
  344. .filter(o -> !order.getUserId().equals(o.getUserId()))
  345. .forEach(this::refund);
  346. }
  347. releaseOrderLock(order.getId());
  348. }
  349. /**
  350. * 退款方法
  351. *
  352. * @param order 订单
  353. */
  354. private void refund(AuctionOrder order) {
  355. log.info("退款拍卖保证金订单{}", order.getId());
  356. PayMethod payMethod = order.getPayMethod();
  357. if (PayMethod.ALIPAY == payMethod) {
  358. if (StringUtils.length(order.getTransactionId()) == 28) {
  359. payMethod = PayMethod.HMPAY;
  360. } else if (StringUtils.length(order.getTransactionId()) == 30) {
  361. payMethod = PayMethod.SANDPAY;
  362. }
  363. }
  364. try {
  365. switch (payMethod) {
  366. case HMPAY:
  367. orderPayService.refund(order.getId()
  368. .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.HM);
  369. log.info("退款成功{}", order.getId());
  370. break;
  371. case SANDPAY:
  372. orderPayService.refund(order.getId()
  373. .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.SAND);
  374. log.info("退款成功{}", order.getId());
  375. break;
  376. case PAYEASE:
  377. orderPayService.refund(order.getId()
  378. .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.PE);
  379. log.info("退款成功{}", order.getId());
  380. break;
  381. case BALANCE:
  382. userBalanceService.addBalance(order.getUserId(), order.getTotalPrice(), order.getId(), BalanceType.AUCTION_RETURN);
  383. log.info("退款成功{}", order.getId());
  384. break;
  385. }
  386. order.setRefundTime(LocalDateTime.now());
  387. order.setStatus(AuctionOrderStatus.REFUNDED);
  388. auctionOrderRepo.save(order);
  389. } catch (Exception e) {
  390. log.error("拍卖保证金订单退款失败 {} ", order.getId(), e);
  391. order.setRefundTime(LocalDateTime.now());
  392. order.setStatus(AuctionOrderStatus.REFUNDING);
  393. auctionOrderRepo.save(order);
  394. }
  395. }
  396. public boolean getOrderLock(Long orderId) {
  397. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_ORDER_LOCK + orderId);
  398. Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS);
  399. return Boolean.TRUE.equals(flag);
  400. }
  401. public void releaseOrderLock(Long orderId) {
  402. redisTemplate.delete(RedisKeys.AUCTION_ORDER_LOCK + orderId);
  403. }
  404. @Scheduled(cron = "0 0/10 * * * ?")
  405. public void passOverTimeAuction() {
  406. List<AuctionActivity> purchased = auctionActivityRepo.findAllByStatus(AuctionStatus.PURCHASED);
  407. if (purchased != null) {
  408. int time = sysConfigService.getInt("auction_cancel_time");
  409. purchased.forEach(act -> {
  410. if (LocalDateTime.now().isAfter(act.getEndTime().plusMinutes(time))) {
  411. List<AuctionOrder> auctionOrders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(act
  412. .getId(), AuctionPaymentType.PURCHASE_PRICE, AuctionOrderStatus.NOT_PAID);
  413. // if (CollUtil.isNotEmpty(auctionOrders)) {
  414. auctionOrders.forEach(this::cancel);
  415. // return;
  416. // }
  417. auctionActivityRepo.updateStatus(act.getId(), AuctionStatus.PASS);
  418. log.info("拍卖定时任务流拍{}", act.getId());
  419. if (AuctionSource.TRANSFER.equals(act.getSource())) {
  420. //改回资产状态
  421. Asset asset = assetRepo.findById(act.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  422. asset.setStatus(AssetStatus.NORMAL);
  423. assetRepo.save(asset);
  424. }
  425. //退其余保证金
  426. List<AuctionOrder> orders = auctionOrderRepo
  427. .findAllByAuctionIdAndPaymentTypeAndStatus(act.getId(),
  428. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  429. //退款
  430. orders.stream()
  431. .filter(o -> !act.getPurchaserId().equals(o.getUserId()))
  432. .forEach(this::refund);
  433. //添加到流拍记录表里
  434. auctionPassRecordRepo.save(AuctionPassRecord.builder()
  435. .auctionId(act.getId())
  436. .userId(act.getPurchaserId())
  437. .purchasePrice(act.getPurchasePrice())
  438. .build());
  439. }
  440. });
  441. }
  442. }
  443. /**
  444. * 发货
  445. *
  446. * @param id 编号
  447. * @param courierId 快递单号
  448. */
  449. public void dispatch(Long id, String courierId) {
  450. AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
  451. auctionOrder.setStatus(AuctionOrderStatus.RECEIVE);
  452. auctionOrder.setCourierId(courierId);
  453. auctionOrderRepo.save(auctionOrder);
  454. }
  455. /**
  456. * 订单
  457. *
  458. * @param id 编号
  459. */
  460. public void finish(Long id) {
  461. AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
  462. auctionOrder.setStatus(AuctionOrderStatus.FINISH);
  463. auctionOrderRepo.save(auctionOrder);
  464. }
  465. public void privilege(AuctionOrder order, User user) {
  466. if (showroomRepo.findByUserIdAndType(order.getUserId(), "AUCTION").isEmpty()) {
  467. //Bidder特殊拍卖展厅服务 创建一个bidder展厅藏品
  468. Long collectionId = (long) sysConfigService.getInt("bidder_collection_id");
  469. List<Asset> assets = assetRepo.findAllByUserIdAndCollectionIdAndStatus(order.getUserId(), collectionId, AssetStatus.NORMAL);
  470. Asset asset;
  471. if (assets.isEmpty()) {
  472. Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
  473. if (!CollectionType.SHOWROOM.equals(collection.getType())) {
  474. throw new BusinessException("不是展厅藏品");
  475. }
  476. //创建资产
  477. asset = assetService.createAsset(collection, user, order.getId(), BigDecimal.ZERO, "拍卖赠送",
  478. null, false);
  479. } else {
  480. asset = assets.get(0);
  481. }
  482. //创建展厅
  483. showroomService.save(asset, "AUCTION");
  484. }
  485. //一个月的优先拍卖权
  486. PurchaserPrivilege.builder()
  487. .userId(order.getUserId())
  488. .title("元宇宙Bidder")
  489. .priority(true)
  490. .priorityExpireAt(LocalDateTime.now().plusMonths(1))
  491. .build();
  492. //前20名分钱
  493. BigDecimal totalPrice = order.getTotalPrice();
  494. //手续费
  495. int auctionServiceCharge = sysConfigService.getInt("auction_service_charge");
  496. BigDecimal serviceCharge = totalPrice.multiply(new BigDecimal(auctionServiceCharge))
  497. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  498. //奖励费用
  499. BigDecimal subtract = totalPrice.subtract(serviceCharge);
  500. int auctionReward = sysConfigService.getInt("auction_reward");
  501. BigDecimal reward = subtract.multiply(new BigDecimal(auctionReward))
  502. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  503. List<Long> records = auctionRecordRepo.findByAuctionId(order.getAuctionId(), 20);
  504. BigDecimal everyReward = reward.divide(new BigDecimal(records.size()), 2, RoundingMode.HALF_UP);
  505. //分奖励
  506. records.forEach(userId -> userBalanceService.addBalance(userId, everyReward, order.getId(), BalanceType.REWARD));
  507. //拍卖者所得
  508. BigDecimal amount = totalPrice.subtract(serviceCharge).subtract(reward);
  509. userBalanceService.addBalance(order.getUserId(), amount, order.getId(), BalanceType.AUCTION);
  510. }
  511. }