feat(ff-game): 添加创建成员接口的缓存锁功能

- 在 ApiMemberController 中添加 RedisCacheLock 依赖
- 在 createMember 方法中实现基于 Redis 的分布式锁- 新增 CacheLockConstants 类用于定义缓存锁的键常量
- 在 ff-base 中添加 Redisson 相关依赖
- 新增 RedisCacheLock 类用于实现 Redis 缓存锁功能
- 修改 application-druid.yml 配置,更新 Redis 连接信息
main-cf
shi 2025-03-18 14:10:34 +08:00
parent 170584861b
commit 59af0c07a7
6 changed files with 156 additions and 51 deletions

View File

@ -121,6 +121,13 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.0</version>
</dependency>
<!-- excel工具 --> <!-- excel工具 -->
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>

View File

@ -61,4 +61,6 @@ public class CacheConstants
* pg * pg
*/ */
public static final String PG_GAMES_BET_CURRENCY= "pg_games:bet:currency"; public static final String PG_GAMES_BET_CURRENCY= "pg_games:bet:currency";
} }

View File

@ -0,0 +1,17 @@
package com.ff.base.constant;
/**
* key
*
* @author ff
*/
public class CacheLockConstants
{
/**
* redis key
*/
public static final String CREATE_MEMBER = "CREATE:MEMBER:";
}

View File

@ -0,0 +1,71 @@
package com.ff.base.core.redis;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* redis
*
* @author shi
* @date 2025/03/18
*/
@Component
public class RedisCacheLock {
@Resource
private RedissonClient redissonClient;
/**
* Redis
*
* @param key Redis
* @return `RLock`
*
* `RLock` Redisson 线线
*/
public RLock getLock(String key) {
return redissonClient.getLock(key);
}
/**
*
*
* @param key Redis
* @param waitTime `false`
* @param leaseTime `leaseTime`
* @return `true` `false`
*
* 线`tryLock` 线
* 线线 `waitTime` `false`
*/
public boolean tryLock(String key, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(key);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复线程中断状态
return false;
}
}
/**
*
*
* @param key Redis
*
* 线 `RLock` `isHeldByCurrentThread()` `true`
* 线
*/
public void unlock(String key) {
RLock lock = redissonClient.getLock(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock(); // 释放锁
}
}
}

View File

@ -7,9 +7,11 @@ import com.ff.api.request.MemberInfoAllApiRequest;
import com.ff.api.request.MemberInfoApiRequest; import com.ff.api.request.MemberInfoApiRequest;
import com.ff.api.response.MemberInfoAllResponse; import com.ff.api.response.MemberInfoAllResponse;
import com.ff.api.response.MemberInfoResponse; import com.ff.api.response.MemberInfoResponse;
import com.ff.base.constant.CacheLockConstants;
import com.ff.base.constant.Constants; import com.ff.base.constant.Constants;
import com.ff.base.core.controller.BaseController; import com.ff.base.core.controller.BaseController;
import com.ff.base.core.domain.AjaxResult; import com.ff.base.core.domain.AjaxResult;
import com.ff.base.core.redis.RedisCacheLock;
import com.ff.base.enums.ErrorCode; import com.ff.base.enums.ErrorCode;
import com.ff.base.exception.base.ApiException; import com.ff.base.exception.base.ApiException;
import com.ff.base.exception.base.BaseException; import com.ff.base.exception.base.BaseException;
@ -18,7 +20,6 @@ import com.ff.base.system.domain.TenantSecretKey;
import com.ff.config.KeyConfig; import com.ff.config.KeyConfig;
import com.ff.game.api.IGamesService; import com.ff.game.api.IGamesService;
import com.ff.game.api.request.*; import com.ff.game.api.request.*;
import com.ff.game.domain.GameSecretKey;
import com.ff.game.dto.GameSecretKeyCurrencyDTO; import com.ff.game.dto.GameSecretKeyCurrencyDTO;
import com.ff.game.service.IGameSecretKeyCurrencyService; import com.ff.game.service.IGameSecretKeyCurrencyService;
import com.ff.game.service.IGameSecretKeyService; import com.ff.game.service.IGameSecretKeyService;
@ -30,9 +31,9 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -84,6 +85,10 @@ public class ApiMemberController extends BaseController {
@Resource @Resource
private IGameSecretKeyCurrencyService gameSecretKeyCurrencyService; private IGameSecretKeyCurrencyService gameSecretKeyCurrencyService;
@Resource
private RedisCacheLock redisCacheLock;
/** /**
* *
* *
@ -91,53 +96,60 @@ public class ApiMemberController extends BaseController {
* @return {@link AjaxResult } * @return {@link AjaxResult }
*/ */
@PostMapping("/create") @PostMapping("/create")
@Transactional @Transactional(isolation = Isolation.READ_COMMITTED)
public synchronized AjaxResult createMember(@Validated @RequestBody MemberCreateApiRequest memberCreateApiRequest) { public AjaxResult createMember(@Validated @RequestBody MemberCreateApiRequest memberCreateApiRequest) {
IGamesService iGamesService = gamesService.get(memberCreateApiRequest.getPlatformCode() + Constants.SERVICE);
ApiException.notNull(iGamesService, ErrorCode.PLATFORM_NOT_EXIST.getCode());
TenantSecretKey tenantSecretKey = keyConfig.get(); TenantSecretKey tenantSecretKey = keyConfig.get();
GameSecretKeyCurrencyDTO gameSecretKey = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder() String lockName = CacheLockConstants.CREATE_MEMBER + memberCreateApiRequest.getAccount() + memberCreateApiRequest.getCurrencyCode() + memberCreateApiRequest.getPlatformCode() + tenantSecretKey.getTenantSn();
.platformCode(memberCreateApiRequest.getPlatformCode())
.systemCurrency(memberCreateApiRequest.getCurrencyCode()).build());
ApiException.notNull(gameSecretKey, ErrorCode.CURRENCY_NOT_EXIST.getCode()); //加锁防止重复
boolean tryLock = redisCacheLock.tryLock(lockName, 10, 10);
String gameAccount = StringUtils.addSuffix(memberService.getMemberGameAccount(), tenantSecretKey.getTenantSn()); try {
if (tryLock) {
IGamesService iGamesService = gamesService.get(memberCreateApiRequest.getPlatformCode() + Constants.SERVICE);
ApiException.notNull(iGamesService, ErrorCode.PLATFORM_NOT_EXIST.getCode());
// 获取用户信息 GameSecretKeyCurrencyDTO gameSecretKey = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder()
Member gameMember = memberService.selectMemberByAccount(memberCreateApiRequest.getAccount(), memberCreateApiRequest.getCurrencyCode(), memberCreateApiRequest.getPlatformCode()); .platformCode(memberCreateApiRequest.getPlatformCode())
if (!ObjectUtils.isEmpty(gameMember)){ .systemCurrency(memberCreateApiRequest.getCurrencyCode()).build());
throw new ApiException(ErrorCode.GAME_ACCOUNT_CREATION_FAILED.getCode());
ApiException.notNull(gameSecretKey, ErrorCode.CURRENCY_NOT_EXIST.getCode());
String gameAccount = StringUtils.addSuffix(memberService.getMemberGameAccount(), tenantSecretKey.getTenantSn());
// 获取用户信息
Member gameMember = memberService.selectMemberByAccount(memberCreateApiRequest.getAccount(), memberCreateApiRequest.getCurrencyCode(), memberCreateApiRequest.getPlatformCode());
if (!ObjectUtils.isEmpty(gameMember)) {
throw new ApiException(ErrorCode.GAME_ACCOUNT_CREATION_FAILED.getCode());
}
//注册本地账号
Member member = Member.builder()
.tenantKey(tenantSecretKey.getTenantKey())
.memberAccount(memberCreateApiRequest.getAccount())
.gameAccount(gameAccount)
.platformCode(memberCreateApiRequest.getPlatformCode())
.currencyCode(memberCreateApiRequest.getCurrencyCode())
.build();
int insertMember = memberService.insertMember(member);
Assert.isTrue(insertMember > 0, "建立游戏账号失败");
//向第三方注册账号
CreateMemberRequestDTO gamesBaseRequestDTO = CreateMemberRequestDTO.builder()
.account(gameAccount)
.agentId(gameSecretKey.getCode())
.agentKey(gameSecretKey.getKey())
.currency(gameSecretKey.getCurrency())
.build();
Boolean result = iGamesService.createMember(gamesBaseRequestDTO);
Assert.isTrue(result, "建立游戏账号失败");
} else {
throw new ApiException(ErrorCode.FREQUENT_INTERFACE_REQUESTS.getCode());
}
return toAjax(Boolean.TRUE);
} finally {
redisCacheLock.unlock(lockName);
} }
//注册本地账号
Member member = Member.builder()
.tenantKey(tenantSecretKey.getTenantKey())
.memberAccount(memberCreateApiRequest.getAccount())
.gameAccount(gameAccount)
.platformCode(memberCreateApiRequest.getPlatformCode())
.currencyCode(memberCreateApiRequest.getCurrencyCode())
.build();
int insertMember = memberService.insertMember(member);
Assert.isTrue(insertMember > 0, "建立游戏账号失败");
//向第三方注册账号
CreateMemberRequestDTO gamesBaseRequestDTO = CreateMemberRequestDTO.builder()
.account(gameAccount)
.agentId(gameSecretKey.getCode())
.agentKey(gameSecretKey.getKey())
.currency(gameSecretKey.getCurrency())
.build();
Boolean result = iGamesService.createMember(gamesBaseRequestDTO);
Assert.isTrue(result, "建立游戏账号失败");
return toAjax(Boolean.TRUE);
} }
@ -166,7 +178,6 @@ public class ApiMemberController extends BaseController {
ApiException.notNull(member, ErrorCode.ACCOUNT_NOT_EXIST.getCode()); ApiException.notNull(member, ErrorCode.ACCOUNT_NOT_EXIST.getCode());
//向第三方查询账号 //向第三方查询账号
MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder() MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder()
.accounts(member.getGameAccount()) .accounts(member.getGameAccount())
@ -191,9 +202,6 @@ public class ApiMemberController extends BaseController {
public AjaxResult infoAll(@Validated @RequestBody MemberInfoAllApiRequest memberInfoAllApiRequest) { public AjaxResult infoAll(@Validated @RequestBody MemberInfoAllApiRequest memberInfoAllApiRequest) {
List<GameSecretKeyCurrencyDTO> gameSecretKeys = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTOList(GameSecretKeyCurrencyDTO.builder() List<GameSecretKeyCurrencyDTO> gameSecretKeys = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTOList(GameSecretKeyCurrencyDTO.builder()
.systemCurrency(memberInfoAllApiRequest.getCurrencyCode()).build()); .systemCurrency(memberInfoAllApiRequest.getCurrencyCode()).build());

View File

@ -3,13 +3,13 @@ spring:
# redis 配置 # redis 配置
redis: redis:
# 地址 # 地址
host: 127.0.0.1 host: 192.168.50.11
# 端口默认为6379 # 端口默认为6379
port: 6379 port: 26379
# 数据库索引 # 数据库索引
database: 1 database: 10
# 密码 # 密码
password: password: reAa123456
# 连接超时时间 # 连接超时时间
timeout: 10s timeout: 10s
lettuce: lettuce: