MetaStoreService.java 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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.MetaConstants;
  5. import com.izouma.nineth.domain.*;
  6. import com.izouma.nineth.dto.MetaRestResult;
  7. import com.izouma.nineth.dto.PageQuery;
  8. import com.izouma.nineth.enums.MetaPropOperationType;
  9. import com.izouma.nineth.enums.MetaPropUsedType;
  10. import com.izouma.nineth.enums.MetaStoreCommodityType;
  11. import com.izouma.nineth.repo.MetaPropRepo;
  12. import com.izouma.nineth.repo.MetaStorePurchaseRecordRepo;
  13. import com.izouma.nineth.repo.MetaStoreRepo;
  14. import com.izouma.nineth.repo.MetaUserPropRepo;
  15. import com.izouma.nineth.utils.JpaUtils;
  16. import com.izouma.nineth.utils.SecurityUtils;
  17. import lombok.AllArgsConstructor;
  18. import org.apache.commons.lang.StringUtils;
  19. import org.springframework.data.domain.Page;
  20. import org.springframework.data.redis.core.RedisTemplate;
  21. import org.springframework.stereotype.Service;
  22. import javax.transaction.Transactional;
  23. import java.time.Duration;
  24. import java.time.LocalDateTime;
  25. import java.util.Objects;
  26. @Service
  27. @AllArgsConstructor
  28. public class MetaStoreService {
  29. private RedisTemplate<String, String> redisTemplate;
  30. private MetaStoreRepo metaStoreRepo;
  31. private MetaPropRepo metaPropRepo;
  32. private MetaUserPropRepo metaUserPropRepo;
  33. private MetaUserGoldService metaUserGoldService;
  34. private MetaUserPropRecordService metaUserPropRecordService;
  35. private MetaStorePurchaseRecordRepo metaStorePurchaseRecordRepo;
  36. public Page<MetaStore> all(PageQuery pageQuery) {
  37. return metaStoreRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaStore.class), JpaUtils.toPageRequest(pageQuery));
  38. }
  39. @Transactional
  40. public MetaRestResult<Void> purchase(Long id) {
  41. MetaStore metaStore = metaStoreRepo.findByIdAndDel(id, false);
  42. if (Objects.isNull(metaStore)) {
  43. return MetaRestResult.returnError("商品不存在");
  44. }
  45. if (!metaStore.isOnShelf()) {
  46. return MetaRestResult.returnError("商品未上架");
  47. }
  48. Long userId = SecurityUtils.getAuthenticatedUser().getId();
  49. if (MetaStoreCommodityType.META_PROP.equals(metaStore.getCommodityType())) {
  50. return purchaseProp(metaStore, userId);
  51. }
  52. return purchaseNFT(metaStore, userId);
  53. }
  54. @Transactional
  55. public MetaRestResult<Void> purchaseProp(MetaStore metaStore, Long userId) {
  56. int price = metaStore.getPrice();
  57. if (0 >= metaStore.getPrice()) {
  58. return MetaRestResult.returnError("道具价格不合法");
  59. }
  60. MetaProp metaProp = metaPropRepo.findByIdAndDel(metaStore.getMetaPropId(), false);
  61. if (Objects.isNull(metaProp)) {
  62. return MetaRestResult.returnError("道具信息为空");
  63. }
  64. MetaUserProp dbMetaUserProp = metaUserPropRepo.findByUserIdAndMetaPropIdAndDel(userId, metaProp.getId(), false);
  65. if (Objects.isNull(dbMetaUserProp)) {
  66. dbMetaUserProp = MetaUserProp.create(userId, metaProp, 1);
  67. MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price));
  68. if (restResult.getCode() != Constants.MetaRestCode.success) {
  69. return MetaRestResult.returnError(restResult.getMessage());
  70. }
  71. metaUserPropRepo.save(dbMetaUserProp);
  72. metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1);
  73. metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
  74. return MetaRestResult.returnSuccess("购买成功!");
  75. }
  76. if (MetaPropUsedType.PERMANENT.equals(metaProp.getUsedType()) && dbMetaUserProp.getNum() >= 1) {
  77. return MetaRestResult.returnError("已拥有永久道具,不可购买");
  78. }
  79. MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price));
  80. if (restResult.getCode() != Constants.MetaRestCode.success) {
  81. return MetaRestResult.returnError(restResult.getMessage());
  82. }
  83. dbMetaUserProp.setNum(dbMetaUserProp.getNum() + 1);
  84. metaUserPropRepo.save(dbMetaUserProp);
  85. metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1);
  86. metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
  87. return MetaRestResult.returnSuccess("购买成功!");
  88. }
  89. @Transactional
  90. @RedisLock("'updateStockNumLock::'+#metaStore.getId()")
  91. public MetaRestResult<Void> purchaseNFT(MetaStore metaStore, Long userId) {
  92. int price = metaStore.getPrice();
  93. if (price <= 0) {
  94. return MetaRestResult.returnError("购买失败,道具价格不合法");
  95. }
  96. // 从缓存中获取库存
  97. int stockNum = getStockNumFromCache(metaStore.getId());
  98. if (stockNum <= 0) {
  99. return MetaRestResult.returnError("购买失败,库存不足");
  100. }
  101. // 限购
  102. if (isPurchaseLimitReached(metaStore.getId(), userId, metaStore.getPurchaseLimitNum())) {
  103. return MetaRestResult.returnError(String.format("购买失败,当前商品限购[%s]个", metaStore.getPurchaseLimitNum()));
  104. }
  105. // 扣减金币
  106. MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买NFT:[%s],消耗金币[%s]", metaStore.getName(), price));
  107. if (restResult.getCode() != Constants.MetaRestCode.success) {
  108. return MetaRestResult.returnError(restResult.getMessage());
  109. }
  110. // 更新库存
  111. updateStockNum(metaStore.getId(), stockNum - 1);
  112. // 保存购买记录
  113. metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
  114. return MetaRestResult.returnSuccess("购买成功!");
  115. }
  116. /**
  117. * 从 Redis 中获取库存信息
  118. *
  119. * @param metaStoreId 商品id
  120. * @return 库存数量
  121. */
  122. private int getStockNumFromCache(Long metaStoreId) {
  123. String key = MetaConstants.REDIS_STOCK_PREFIX.concat(String.valueOf(metaStoreId));
  124. String stockStr = redisTemplate.opsForValue().get(key);
  125. if (StringUtils.isNotBlank(stockStr)) {
  126. return Integer.parseInt(stockStr);
  127. }
  128. // 如果 Redis 中不存在,则从数据库中加载,并写入 Redis 缓存
  129. MetaStore metaStore = metaStoreRepo.findByIdAndDel(metaStoreId, false);
  130. if (metaStore != null) {
  131. int stockNum = metaStore.getStockNum();
  132. redisTemplate.opsForValue().set(key, String.valueOf(stockNum), Duration.ofMinutes(5)); // 设置过期时间 5 分钟
  133. return stockNum;
  134. }
  135. return 0;
  136. }
  137. /**
  138. * 更新 Redis 中的库存信息,异步更新数据库中的库存
  139. *
  140. * @param metaStoreId 商品id
  141. * @param stockNum 库存数量
  142. */
  143. @RedisLock("'updateStockNumLock::'+#metaStoreId")
  144. public void updateStockNum(Long metaStoreId, int stockNum) {
  145. String key = MetaConstants.REDIS_STOCK_PREFIX.concat(String.valueOf(metaStoreId));
  146. redisTemplate.opsForValue().set(key, String.valueOf(stockNum), Duration.ofMinutes(5)); // 设置过期时间 5 分钟
  147. metaStoreRepo.updateStockNum(metaStoreId, stockNum);
  148. }
  149. /**
  150. * 判断是否达到购买限制
  151. *
  152. * @param metaStoreId 商品id
  153. * @param userId 用户id
  154. * @param purchaseLimitNum 最大购买数量
  155. * @return 是否达到购买限制
  156. */
  157. private boolean isPurchaseLimitReached(Long metaStoreId, Long userId, Integer purchaseLimitNum) {
  158. if (purchaseLimitNum == null || purchaseLimitNum <= 0) {
  159. return false;
  160. }
  161. int count = metaStorePurchaseRecordRepo.countByMetaStoreIdAndUserId(metaStoreId, userId);
  162. return count >= purchaseLimitNum;
  163. }
  164. }