package com.izouma.nineth.service; import com.izouma.nineth.annotations.RedisLock; import com.izouma.nineth.config.Constants; import com.izouma.nineth.config.MetaConstants; import com.izouma.nineth.domain.*; import com.izouma.nineth.dto.MetaRestResult; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.MetaPropOperationType; import com.izouma.nineth.enums.MetaPropUsedType; import com.izouma.nineth.enums.MetaStoreCommodityType; import com.izouma.nineth.repo.MetaPropRepo; import com.izouma.nineth.repo.MetaStorePurchaseRecordRepo; import com.izouma.nineth.repo.MetaStoreRepo; import com.izouma.nineth.repo.MetaUserPropRepo; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.SecurityUtils; import lombok.AllArgsConstructor; import org.apache.commons.lang.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.time.Duration; import java.time.LocalDateTime; import java.util.Objects; @Service @AllArgsConstructor public class MetaStoreService { private RedisTemplate redisTemplate; private MetaStoreRepo metaStoreRepo; private MetaPropRepo metaPropRepo; private MetaUserPropRepo metaUserPropRepo; private MetaUserGoldService metaUserGoldService; private MetaUserPropRecordService metaUserPropRecordService; private MetaStorePurchaseRecordRepo metaStorePurchaseRecordRepo; public Page all(PageQuery pageQuery) { return metaStoreRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaStore.class), JpaUtils.toPageRequest(pageQuery)); } @Transactional public MetaRestResult purchase(Long id) { MetaStore metaStore = metaStoreRepo.findByIdAndDel(id, false); if (Objects.isNull(metaStore)) { return MetaRestResult.returnError("商品不存在"); } if (!metaStore.isOnShelf()) { return MetaRestResult.returnError("商品未上架"); } Long userId = SecurityUtils.getAuthenticatedUser().getId(); if (MetaStoreCommodityType.META_PROP.equals(metaStore.getCommodityType())) { return purchaseProp(metaStore, userId); } return purchaseNFT(metaStore, userId); } @Transactional public MetaRestResult purchaseProp(MetaStore metaStore, Long userId) { int price = metaStore.getPrice(); if (0 >= metaStore.getPrice()) { return MetaRestResult.returnError("道具价格不合法"); } MetaProp metaProp = metaPropRepo.findByIdAndDel(metaStore.getMetaPropId(), false); if (Objects.isNull(metaProp)) { return MetaRestResult.returnError("道具信息为空"); } MetaUserProp dbMetaUserProp = metaUserPropRepo.findByUserIdAndMetaPropIdAndDel(userId, metaProp.getId(), false); if (Objects.isNull(dbMetaUserProp)) { dbMetaUserProp = MetaUserProp.create(userId, metaProp, 1); MetaRestResult restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price)); if (restResult.getCode() != Constants.MetaRestCode.success) { return MetaRestResult.returnError(restResult.getMessage()); } metaUserPropRepo.save(dbMetaUserProp); metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1); metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now())); return MetaRestResult.returnSuccess("购买成功!"); } if (MetaPropUsedType.PERMANENT.equals(metaProp.getUsedType()) && dbMetaUserProp.getNum() >= 1) { return MetaRestResult.returnError("已拥有永久道具,不可购买"); } MetaRestResult restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price)); if (restResult.getCode() != Constants.MetaRestCode.success) { return MetaRestResult.returnError(restResult.getMessage()); } dbMetaUserProp.setNum(dbMetaUserProp.getNum() + 1); metaUserPropRepo.save(dbMetaUserProp); metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1); metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now())); return MetaRestResult.returnSuccess("购买成功!"); } @Transactional @RedisLock("'updateStockNumLock::'+#metaStore.getId()") public MetaRestResult purchaseNFT(MetaStore metaStore, Long userId) { int price = metaStore.getPrice(); if (price <= 0) { return MetaRestResult.returnError("购买失败,道具价格不合法"); } // 从缓存中获取库存 int stockNum = getStockNumFromCache(metaStore.getId()); if (stockNum <= 0) { return MetaRestResult.returnError("购买失败,库存不足"); } // 限购 if (isPurchaseLimitReached(metaStore.getId(), userId, metaStore.getPurchaseLimitNum())) { return MetaRestResult.returnError(String.format("购买失败,当前商品限购[%s]个", metaStore.getPurchaseLimitNum())); } // 扣减金币 MetaRestResult restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买NFT:[%s],消耗金币[%s]", metaStore.getName(), price)); if (restResult.getCode() != Constants.MetaRestCode.success) { return MetaRestResult.returnError(restResult.getMessage()); } // 更新库存 updateStockNum(metaStore.getId(), stockNum - 1); // 保存购买记录 metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now())); return MetaRestResult.returnSuccess("购买成功!"); } /** * 从 Redis 中获取库存信息 * * @param metaStoreId 商品id * @return 库存数量 */ private int getStockNumFromCache(Long metaStoreId) { String key = MetaConstants.REDIS_STOCK_PREFIX.concat(String.valueOf(metaStoreId)); String stockStr = redisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(stockStr)) { return Integer.parseInt(stockStr); } // 如果 Redis 中不存在,则从数据库中加载,并写入 Redis 缓存 MetaStore metaStore = metaStoreRepo.findByIdAndDel(metaStoreId, false); if (metaStore != null) { int stockNum = metaStore.getStockNum(); redisTemplate.opsForValue().set(key, String.valueOf(stockNum), Duration.ofMinutes(5)); // 设置过期时间 5 分钟 return stockNum; } return 0; } /** * 更新 Redis 中的库存信息,同时更新数据库中的库存 * * @param metaStoreId 商品id * @param stockNum 库存数量 */ @RedisLock("'updateStockNumLock::'+#metaStoreId") public void updateStockNum(Long metaStoreId, int stockNum) { String key = MetaConstants.REDIS_STOCK_PREFIX.concat(String.valueOf(metaStoreId)); redisTemplate.opsForValue().set(key, String.valueOf(stockNum), Duration.ofMinutes(5)); // 设置过期时间 5 分钟 metaStoreRepo.updateStockNum(metaStoreId, stockNum); } /** * 判断是否达到购买限制 * * @param metaStoreId 商品id * @param userId 用户id * @param purchaseLimitNum 最大购买数量 * @return 是否达到购买限制 */ private boolean isPurchaseLimitReached(Long metaStoreId, Long userId, Integer purchaseLimitNum) { if (purchaseLimitNum == null || purchaseLimitNum <= 0) { return false; } int count = metaStorePurchaseRecordRepo.countByMetaStoreIdAndUserId(metaStoreId, userId); return count >= purchaseLimitNum; } }