AuctionActivityService.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package com.izouma.nineth.service;
  2. import cn.hutool.core.collection.CollUtil;
  3. import com.izouma.nineth.config.RedisKeys;
  4. import com.izouma.nineth.domain.Asset;
  5. import com.izouma.nineth.domain.AuctionActivity;
  6. import com.izouma.nineth.domain.User;
  7. import com.izouma.nineth.dto.PageQuery;
  8. import com.izouma.nineth.dto.auction.AuctionInputDTO;
  9. import com.izouma.nineth.enums.*;
  10. import com.izouma.nineth.exception.BusinessException;
  11. import com.izouma.nineth.repo.*;
  12. import com.izouma.nineth.utils.JpaUtils;
  13. import com.izouma.nineth.utils.SecurityUtils;
  14. import lombok.AllArgsConstructor;
  15. import lombok.extern.slf4j.Slf4j;
  16. import org.apache.commons.lang3.ObjectUtils;
  17. import org.springframework.core.env.Environment;
  18. import org.springframework.data.domain.Page;
  19. import org.springframework.data.redis.core.BoundValueOperations;
  20. import org.springframework.data.redis.core.RedisTemplate;
  21. import org.springframework.scheduling.TaskScheduler;
  22. import org.springframework.scheduling.annotation.Scheduled;
  23. import org.springframework.security.crypto.password.PasswordEncoder;
  24. import org.springframework.stereotype.Service;
  25. import javax.annotation.PostConstruct;
  26. import java.math.BigDecimal;
  27. import java.time.LocalDateTime;
  28. import java.time.ZoneId;
  29. import java.time.temporal.ChronoUnit;
  30. import java.util.*;
  31. import java.util.concurrent.ScheduledFuture;
  32. import java.util.concurrent.TimeUnit;
  33. @Slf4j
  34. @Service
  35. @AllArgsConstructor
  36. public class AuctionActivityService {
  37. private final AuctionActivityRepo auctionActivityRepo;
  38. private final AssetRepo assetRepo;
  39. private final UserRepo userRepo;
  40. private final PasswordEncoder passwordEncoder;
  41. private final RedisTemplate<String, Object> redisTemplate;
  42. private final CacheService cacheService;
  43. private final TaskScheduler taskScheduler;
  44. private final Environment env;
  45. private final SysConfigService sysConfigService;
  46. private final RockRecordService rockRecordService;
  47. private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();
  48. @PostConstruct
  49. public void init() {
  50. if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
  51. return;
  52. }
  53. List<AuctionActivity> activities = auctionActivityRepo.findByStartTimeBeforeAndStatus(LocalDateTime.now(),
  54. AuctionStatus.NOTSTARTED);
  55. for (AuctionActivity activity : activities) {
  56. onShelfTask(activity);
  57. }
  58. }
  59. public Page<AuctionActivity> all(PageQuery pageQuery) {
  60. return auctionActivityRepo
  61. .findAll(JpaUtils.toSpecification(pageQuery, AuctionActivity.class), JpaUtils.toPageRequest(pageQuery));
  62. }
  63. public AuctionActivity createFromAsset(AuctionInputDTO dto) {
  64. Asset asset = assetRepo.findById(dto.getAssetId()).orElseThrow(new BusinessException("暂无"));
  65. //拍卖周期
  66. BigDecimal auctionCycle = sysConfigService.getBigDecimal("auction_cycle");
  67. AuctionActivity auctionActivity = new AuctionActivity();
  68. auctionActivity.setAuctionType(AuctionType.NFT);
  69. auctionActivity.setAssetId(dto.getAssetId());
  70. auctionActivity.setStatus(AuctionStatus.ONGOING);
  71. auctionActivity.setBids(0);
  72. auctionActivity.setCategory(asset.getCategory());
  73. auctionActivity.setEndTime(LocalDateTime.now()
  74. .plusHours(auctionCycle.multiply(new BigDecimal("24")).intValue()));
  75. auctionActivity.setDeposit(dto.getDeposit());
  76. if (Arrays.asList(env.getActiveProfiles()).contains("staging")) {
  77. auctionActivity.setEndTime(LocalDateTime.now().plusMinutes(8));
  78. }
  79. auctionActivity.setDetail(asset.getDetail());
  80. auctionActivity.setFixedPrice(dto.getFixedPrice());
  81. auctionActivity.setIncrement(dto.getIncrement());
  82. auctionActivity.setMinter(asset.getMinter());
  83. auctionActivity.setPic(asset.getPic());
  84. auctionActivity.setModel3d(asset.getModel3d());
  85. auctionActivity.setName(asset.getName());
  86. auctionActivity.setSeller(asset.getOwner());
  87. auctionActivity.setSellerId(asset.getOwnerId());
  88. auctionActivity.setStartTime(LocalDateTime.now());
  89. auctionActivity.setSource(AuctionSource.TRANSFER);
  90. //固定值 or 资产值
  91. auctionActivity.setServiceCharge(asset.getServiceCharge());
  92. auctionActivity.setRoyalties(asset.getRoyalties());
  93. auctionActivity.setStartingPrice(dto.getStartingPrice());
  94. auctionActivity.setHasFixedPrice(auctionActivity.getFixedPrice() != null);
  95. return save(auctionActivity, dto.getTradeCode());
  96. }
  97. public AuctionActivity save(AuctionActivity record, String tradeCode) {
  98. if (record.getSource().equals(AuctionSource.OFFICIAL)) {
  99. record.setStatus(AuctionStatus.NOTSTARTED);
  100. }
  101. if (record.getSource().equals(AuctionSource.TRANSFER) && !record.getSellerId().equals(9859L)) {
  102. User user = userRepo.findById(record.getSellerId()).orElseThrow(new BusinessException("无用户信息"));
  103. if (!passwordEncoder.matches(tradeCode, user.getTradeCode())) {
  104. throw new BusinessException("交易密码错误");
  105. }
  106. if (!AuthStatus.SUCCESS.equals(user.getAuthStatus())) {
  107. throw new BusinessException("未实名或实名未通过");
  108. }
  109. BigDecimal userBuy = rockRecordService.getRock(user.getId()).getRecord();
  110. BigDecimal num = sysConfigService.getBigDecimal("auction_lvzhoushi_num");
  111. if (userBuy.compareTo(num) < 0) {
  112. throw new BusinessException("绿洲石不足");
  113. }
  114. Asset asset = assetRepo.findById(record.getAssetId()).orElseThrow(new BusinessException("未找到该藏品"));
  115. if (!asset.getOwnerId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
  116. throw new BusinessException("非本人藏品,无法操作.");
  117. }
  118. int holdDays;
  119. if (ObjectUtils.isEmpty(asset.getHoldDays())) {
  120. holdDays = sysConfigService.getInt("hold_days");
  121. } else {
  122. holdDays = asset.getHoldDays();
  123. }
  124. if (holdDays == 0 && AssetSource.OFFICIAL.equals(asset.getSource())) {
  125. BigDecimal officialConsignment = sysConfigService.getBigDecimal("OFFICIAL_CONSIGNMENT");
  126. //天转小时
  127. int hour = officialConsignment.multiply(new BigDecimal("24")).intValue();
  128. if (ChronoUnit.HOURS.between(asset.getCreatedAt(), LocalDateTime.now()) < hour) {
  129. throw new BusinessException("需持有满" + hour + "小时后才能进行拍卖");
  130. }
  131. }
  132. if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
  133. throw new BusinessException("需持有满" + holdDays + "天才能进行拍卖");
  134. }
  135. if (!asset.getStatus().equals(AssetStatus.NORMAL)) {
  136. throw new BusinessException("藏品状态异常,无法操作.");
  137. }
  138. if (asset.isPublicShow() || asset.isConsignment()) {
  139. // throw new BusinessException("藏品已寄售,取消寄售后再申请拍卖。");
  140. throw new BusinessException("请先下架藏品");
  141. }
  142. //是否二次拍卖
  143. List<AuctionActivity> activity = auctionActivityRepo.findByAssetId(asset.getId());
  144. if (CollUtil.isNotEmpty(activity)) {
  145. if (activity.stream().anyMatch(ac -> !AuctionStatus.PASS.equals(ac.getStatus()))) {
  146. throw new BusinessException("已有拍卖");
  147. }
  148. log.info("下架流拍拍卖:assetId-{}", asset.getId());
  149. auctionActivityRepo.updateOnShelf(asset.getId(), false);
  150. }
  151. asset.setStatus(AssetStatus.AUCTIONING);
  152. assetRepo.save(asset);
  153. }
  154. //上架
  155. record.setOnShelf(true);
  156. AuctionActivity saved = auctionActivityRepo.save(record);
  157. if (saved.getStatus().equals(AuctionStatus.NOTSTARTED)) {
  158. onShelfTask(saved);
  159. }
  160. return saved;
  161. }
  162. public synchronized String changeStatus(Long id, AuctionStatus status) {
  163. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_STATUS + id);
  164. if (ops.get() == null) {
  165. Boolean success = ops.setIfAbsent(Optional.ofNullable(auctionActivityRepo.getStatus(id))
  166. .orElse(AuctionStatus.NOTSTARTED.toString()), 7, TimeUnit.DAYS);
  167. log.info("创建redis拍卖活动状态:{}", success);
  168. }
  169. String stock = (String) ops.getAndSet(status.toString());
  170. syncStatus(id);
  171. return stock;
  172. }
  173. // @Debounce(key = "#id", delay = 500)
  174. public void syncStatus(Long id) {
  175. String stock = (String) redisTemplate.opsForValue().get(RedisKeys.AUCTION_STATUS + id);
  176. if (stock != null) {
  177. log.info("同步拍卖活动状态信息{},{}", id, stock);
  178. auctionActivityRepo.updateStatus(id, AuctionStatus.valueOf(stock));
  179. cacheService.clearAuction(id);
  180. }
  181. }
  182. public synchronized String getStatus(Long id) {
  183. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_STATUS + id);
  184. String stock = (String) ops.get();
  185. if (stock == null) {
  186. Boolean success = ops.setIfAbsent(Optional.ofNullable(auctionActivityRepo.getStatus(id))
  187. .orElse(AuctionStatus.NOTSTARTED.toString()), 7, TimeUnit.DAYS);
  188. log.info("创建redis拍卖活动状态:{}", success);
  189. return (String) ops.get();
  190. }
  191. return stock;
  192. }
  193. private void onShelfTask(AuctionActivity record) {
  194. ScheduledFuture<?> task = tasks.get(record.getId());
  195. if (task != null) {
  196. if (!task.cancel(true)) {
  197. return;
  198. }
  199. }
  200. if (record.getStatus().equals(AuctionStatus.NOTSTARTED)) {
  201. if (record.getStartTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
  202. Date date = Date.from(record.getStartTime().atZone(ZoneId.systemDefault()).toInstant());
  203. ScheduledFuture<?> future = taskScheduler.schedule(() -> {
  204. // this.changeStatus(record.getId(), AuctionStatus.ONGOING);
  205. auctionActivityRepo.updateStatus(record.getId(), AuctionStatus.ONGOING);
  206. tasks.remove(record.getId());
  207. }, date);
  208. tasks.put(record.getId(), future);
  209. } else {
  210. // this.changeStatus(record.getId(), AuctionStatus.ONGOING);
  211. auctionActivityRepo.updateStatus(record.getId(), AuctionStatus.ONGOING);
  212. }
  213. }
  214. }
  215. public void offShelfTask(AuctionActivity record) {
  216. Long id = record.getId();
  217. ScheduledFuture<?> task = tasks.get(id);
  218. if (task != null) {
  219. if (!task.cancel(true)) {
  220. return;
  221. }
  222. }
  223. // AuctionActivity recordNew = auctionActivityRepo.findById(id)
  224. // .orElseThrow(new BusinessException("无数据"));
  225. if (record.getStatus().equals(AuctionStatus.ONGOING)) {
  226. if (record.getEndTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
  227. Date date = Date.from(record.getEndTime().atZone(ZoneId.systemDefault()).toInstant());
  228. ScheduledFuture<?> future = taskScheduler.schedule(() -> {
  229. AuctionActivity recordNew1 = auctionActivityRepo.findById(id)
  230. .orElseThrow(new BusinessException("无数据"));
  231. if (ObjectUtils.isNotEmpty(recordNew1.getPurchasePrice())) {
  232. log.info("拍卖成交{}", recordNew1.getId());
  233. // this.changeStatus(recordNew1.getId(), AuctionStatus.PURCHASED);
  234. auctionActivityRepo.updateStatus(recordNew1.getId(), AuctionStatus.PURCHASED);
  235. } else {
  236. //没有成交价,无人出价过
  237. log.info("拍卖流拍Task-else{}", recordNew1.getId());
  238. auctionActivityRepo.scheduleOffShelf(recordNew1.getId(), AuctionStatus.PASS);
  239. if (AuctionSource.TRANSFER.equals(recordNew1.getSource())) {
  240. Asset asset = assetRepo.findById(recordNew1.getAssetId())
  241. .orElseThrow(new BusinessException("暂无"));
  242. asset.setStatus(AssetStatus.NORMAL);
  243. asset.setConsignment(false);
  244. asset.setPublicShow(false);
  245. assetRepo.save(asset);
  246. }
  247. }
  248. tasks.remove(id);
  249. }, date);
  250. tasks.put(id, future);
  251. } else {
  252. AuctionActivity recordNew1 = auctionActivityRepo.findById(id)
  253. .orElseThrow(new BusinessException("无数据"));
  254. if (ObjectUtils.isNotEmpty(recordNew1.getPurchasePrice())) {
  255. log.info("拍卖成交{}", id);
  256. auctionActivityRepo.scheduleOffShelf(id, AuctionStatus.PURCHASED);
  257. } else {
  258. log.info("拍卖流拍Task-else-else{}", id);
  259. auctionActivityRepo.scheduleOffShelf(id, AuctionStatus.PASS);
  260. if (AuctionSource.TRANSFER.equals(recordNew1.getSource())) {
  261. Asset asset = assetRepo.findById(recordNew1.getAssetId())
  262. .orElseThrow(new BusinessException("暂无"));
  263. asset.setStatus(AssetStatus.NORMAL);
  264. asset.setConsignment(false);
  265. asset.setPublicShow(false);
  266. assetRepo.save(asset);
  267. }
  268. }
  269. }
  270. }
  271. }
  272. /**
  273. * 定时下架拍卖
  274. * (每隔1分钟执行一次)
  275. */
  276. @Scheduled(cron = "0 */1 * * * ?")
  277. public void passAuction() {
  278. List<AuctionActivity> activities = auctionActivityRepo.findAllByStatus(AuctionStatus.ONGOING);
  279. activities.forEach(activity -> {
  280. if (activity.getEndTime().isBefore(LocalDateTime.now())) {
  281. if (ObjectUtils.isNotEmpty(activity.getPurchasePrice())) {
  282. log.info("拍卖成交{}", activity.getId());
  283. // this.changeStatus(activity.getId(), AuctionStatus.PURCHASED);
  284. auctionActivityRepo.updateStatus(activity.getId(), AuctionStatus.PURCHASED);
  285. } else {
  286. //没有成交价,无人出价过
  287. log.info("拍卖流拍Task-else-else{}", activity.getId());
  288. // this.changeStatus(activity.getId(), AuctionStatus.PASS);
  289. auctionActivityRepo.updateStatus(activity.getId(), AuctionStatus.PASS);
  290. if (AuctionSource.TRANSFER.equals(activity.getSource())) {
  291. Asset asset = assetRepo.findById(activity.getAssetId())
  292. .orElseThrow(new BusinessException("暂无"));
  293. asset.setStatus(AssetStatus.NORMAL);
  294. asset.setConsignment(false);
  295. asset.setPublicShow(false);
  296. assetRepo.save(asset);
  297. }
  298. }
  299. }
  300. });
  301. }
  302. }