diff --git a/ff-base/pom.xml b/ff-base/pom.xml
index dbf57ac..db88db5 100644
--- a/ff-base/pom.xml
+++ b/ff-base/pom.xml
@@ -121,6 +121,13 @@
commons-io
+
+ org.redisson
+ redisson-spring-boot-starter
+ 3.23.0
+
+
+
org.apache.poi
diff --git a/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java b/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
index 3d96357..57d0452 100644
--- a/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
+++ b/ff-base/src/main/java/com/ff/base/constant/CacheConstants.java
@@ -61,4 +61,6 @@ public class CacheConstants
* pg游戏投注货币
*/
public static final String PG_GAMES_BET_CURRENCY= "pg_games:bet:currency";
+
+
}
diff --git a/ff-base/src/main/java/com/ff/base/constant/CacheLockConstants.java b/ff-base/src/main/java/com/ff/base/constant/CacheLockConstants.java
new file mode 100644
index 0000000..e0f1df7
--- /dev/null
+++ b/ff-base/src/main/java/com/ff/base/constant/CacheLockConstants.java
@@ -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:";
+
+
+
+}
diff --git a/ff-base/src/main/java/com/ff/base/core/redis/RedisCacheLock.java b/ff-base/src/main/java/com/ff/base/core/redis/RedisCacheLock.java
new file mode 100644
index 0000000..aba978b
--- /dev/null
+++ b/ff-base/src/main/java/com/ff/base/core/redis/RedisCacheLock.java
@@ -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(); // 释放锁
+ }
+ }
+
+}
diff --git a/ff-game/src/main/java/com/ff/api/controller/ApiMemberController.java b/ff-game/src/main/java/com/ff/api/controller/ApiMemberController.java
index 2d42f5e..8b7432f 100644
--- a/ff-game/src/main/java/com/ff/api/controller/ApiMemberController.java
+++ b/ff-game/src/main/java/com/ff/api/controller/ApiMemberController.java
@@ -7,9 +7,11 @@ import com.ff.api.request.MemberInfoAllApiRequest;
import com.ff.api.request.MemberInfoApiRequest;
import com.ff.api.response.MemberInfoAllResponse;
import com.ff.api.response.MemberInfoResponse;
+import com.ff.base.constant.CacheLockConstants;
import com.ff.base.constant.Constants;
import com.ff.base.core.controller.BaseController;
import com.ff.base.core.domain.AjaxResult;
+import com.ff.base.core.redis.RedisCacheLock;
import com.ff.base.enums.ErrorCode;
import com.ff.base.exception.base.ApiException;
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.game.api.IGamesService;
import com.ff.game.api.request.*;
-import com.ff.game.domain.GameSecretKey;
import com.ff.game.dto.GameSecretKeyCurrencyDTO;
import com.ff.game.service.IGameSecretKeyCurrencyService;
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.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -84,6 +85,10 @@ public class ApiMemberController extends BaseController {
@Resource
private IGameSecretKeyCurrencyService gameSecretKeyCurrencyService;
+ @Resource
+ private RedisCacheLock redisCacheLock;
+
+
/**
* 创建成员
*
@@ -91,53 +96,60 @@ public class ApiMemberController extends BaseController {
* @return {@link AjaxResult }
*/
@PostMapping("/create")
- @Transactional
- public synchronized AjaxResult createMember(@Validated @RequestBody MemberCreateApiRequest memberCreateApiRequest) {
-
- IGamesService iGamesService = gamesService.get(memberCreateApiRequest.getPlatformCode() + Constants.SERVICE);
- ApiException.notNull(iGamesService, ErrorCode.PLATFORM_NOT_EXIST.getCode());
-
+ @Transactional(isolation = Isolation.READ_COMMITTED)
+ public AjaxResult createMember(@Validated @RequestBody MemberCreateApiRequest memberCreateApiRequest) {
TenantSecretKey tenantSecretKey = keyConfig.get();
- GameSecretKeyCurrencyDTO gameSecretKey = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder()
- .platformCode(memberCreateApiRequest.getPlatformCode())
- .systemCurrency(memberCreateApiRequest.getCurrencyCode()).build());
+ String lockName = CacheLockConstants.CREATE_MEMBER + memberCreateApiRequest.getAccount() + memberCreateApiRequest.getCurrencyCode() + memberCreateApiRequest.getPlatformCode() + tenantSecretKey.getTenantSn();
- 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());
- // 获取用户信息
- Member gameMember = memberService.selectMemberByAccount(memberCreateApiRequest.getAccount(), memberCreateApiRequest.getCurrencyCode(), memberCreateApiRequest.getPlatformCode());
- if (!ObjectUtils.isEmpty(gameMember)){
- throw new ApiException(ErrorCode.GAME_ACCOUNT_CREATION_FAILED.getCode());
+ GameSecretKeyCurrencyDTO gameSecretKey = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder()
+ .platformCode(memberCreateApiRequest.getPlatformCode())
+ .systemCurrency(memberCreateApiRequest.getCurrencyCode()).build());
+
+ 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());
-
//向第三方查询账号
MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder()
.accounts(member.getGameAccount())
@@ -191,9 +202,6 @@ public class ApiMemberController extends BaseController {
public AjaxResult infoAll(@Validated @RequestBody MemberInfoAllApiRequest memberInfoAllApiRequest) {
-
-
-
List gameSecretKeys = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTOList(GameSecretKeyCurrencyDTO.builder()
.systemCurrency(memberInfoAllApiRequest.getCurrencyCode()).build());
diff --git a/ff-game/src/main/resources/application-druid.yml b/ff-game/src/main/resources/application-druid.yml
index 89db033..dd61f2a 100644
--- a/ff-game/src/main/resources/application-druid.yml
+++ b/ff-game/src/main/resources/application-druid.yml
@@ -3,13 +3,13 @@ spring:
# redis 配置
redis:
# 地址
- host: 127.0.0.1
+ host: 192.168.50.11
# 端口,默认为6379
- port: 6379
+ port: 26379
# 数据库索引
- database: 1
+ database: 10
# 密码
- password:
+ password: reAa123456
# 连接超时时间
timeout: 10s
lettuce: