From 3853150e9d21d69597c11d26feaf1984faba905f Mon Sep 17 00:00:00 2001 From: shi Date: Mon, 24 Mar 2025 20:05:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(game):=20=E6=B7=BB=E5=8A=A0=20FC=20?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E5=B9=B3=E5=8F=B0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 FCClient 接口和 GamesFCServiceImpl 实现类 - 添加与 FC游戏平台交互所需的 DTO 类- 实现了创建成员、获取会员信息、登录、游戏列表获取等功能 - 集成了兑换转账和状态查询功能 -支持按时间获取和处理投注记录 --- .../java/com/ff/base/constant/Constants.java | 5 + .../api/fc/address/MyFCAddressSource.java | 24 + .../com/ff/game/api/fc/client/FCClient.java | 114 +++ .../com/ff/game/api/fc/dto/ApiFCResult.java | 19 + .../com/ff/game/api/fc/dto/ApiKeyRequest.java | 68 ++ .../game/api/fc/impl/GamesFCServiceImpl.java | 864 ++++++++++++++++++ 6 files changed, 1094 insertions(+) create mode 100644 ff-game/src/main/java/com/ff/game/api/fc/address/MyFCAddressSource.java create mode 100644 ff-game/src/main/java/com/ff/game/api/fc/client/FCClient.java create mode 100644 ff-game/src/main/java/com/ff/game/api/fc/dto/ApiFCResult.java create mode 100644 ff-game/src/main/java/com/ff/game/api/fc/dto/ApiKeyRequest.java create mode 100644 ff-game/src/main/java/com/ff/game/api/fc/impl/GamesFCServiceImpl.java diff --git a/ff-base/src/main/java/com/ff/base/constant/Constants.java b/ff-base/src/main/java/com/ff/base/constant/Constants.java index a41b811..1a3a390 100644 --- a/ff-base/src/main/java/com/ff/base/constant/Constants.java +++ b/ff-base/src/main/java/com/ff/base/constant/Constants.java @@ -188,6 +188,11 @@ public class Constants { */ public static final String NG_API_BASE_URL = "ng.api.base.url"; + /** + * fc-api基本url + */ + public static final String FC_API_BASE_URL = "fc.api.base.url"; + /** * 吉利测试地址 */ diff --git a/ff-game/src/main/java/com/ff/game/api/fc/address/MyFCAddressSource.java b/ff-game/src/main/java/com/ff/game/api/fc/address/MyFCAddressSource.java new file mode 100644 index 0000000..cc1fe63 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/fc/address/MyFCAddressSource.java @@ -0,0 +1,24 @@ +package com.ff.game.api.fc.address; + +import com.dtflys.forest.callback.AddressSource; +import com.dtflys.forest.http.ForestAddress; +import com.dtflys.forest.http.ForestRequest; +import com.ff.base.constant.Constants; +import com.ff.base.system.service.ISysConfigService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +public class MyFCAddressSource implements AddressSource { + + @Resource + private ISysConfigService configService; + + + @Override + public ForestAddress getAddress(ForestRequest request) { + String apiBaseUrl = configService.selectConfigByKey(Constants.NG_API_BASE_URL); + return new ForestAddress("https",apiBaseUrl, 443,""); + } +} diff --git a/ff-game/src/main/java/com/ff/game/api/fc/client/FCClient.java b/ff-game/src/main/java/com/ff/game/api/fc/client/FCClient.java new file mode 100644 index 0000000..d74c4a0 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/fc/client/FCClient.java @@ -0,0 +1,114 @@ +package com.ff.game.api.fc.client; + + +import com.dtflys.forest.annotation.Address; +import com.dtflys.forest.annotation.Header; +import com.dtflys.forest.annotation.JSONBody; +import com.dtflys.forest.annotation.Post; +import com.ff.game.api.fc.dto.ApiFCResult; +import com.ff.game.api.fc.address.MyFCAddressSource; +import com.ff.game.api.fc.dto.ApiKeyRequest; +import com.ff.game.api.ng.dto.*; + +import java.util.List; +import java.util.Map; + +/** + * ng客户端 + * + * @author shi + * @date 2025/03/11 + */ +@Address(source = MyFCAddressSource.class) +public interface FCClient { + + @Post("/Key") + ApiKeyRequest key(@JSONBody Map parameters); + + + /** + * 创建成员 + * + * @param parameters 参数 + * @return {@link String } + */ + @Post("/AddMember") + ApiFCResult createMember(@JSONBody Map parameters); + + /** + * 获取会员信息 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link String }> + */ + @Post("/server/balance") + ApiNGResponseDTO getMemberInfo(@JSONBody Map parameters, @Header Map headerMap); + + + /** + * 获取游戏列表 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link List }> + */ + @Post("/server/gameCode") + ApiNGResponseDTO> getGameList(@JSONBody Map parameters, @Header Map headerMap); + + + /** + * 无重定向登录 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link ApiLoginResponseDTO }> + */ + @Post("/server/gameUrl") + ApiNGResponseDTO loginWithoutRedirect(@JSONBody Map parameters, @Header Map headerMap); + + + /** + * 按代理id进行交换转账 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link ApiLoginResponseDTO }> + */ + @Post(url = "/server/transfer",connectTimeout = 70000) + ApiNGResponseDTO exchangeTransferByAgentId(@JSONBody Map parameters, @Header Map headerMap); + + + /** + * 汇兑转移状态 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link ApiExchangeTransferStatusResponseDTO }> + */ + @Post(url = "/server/transferStatus") + ApiNGResponseDTO exchangeTransferStatus(@JSONBody Map parameters, @Header Map headerMap); + + + /** + * 按时间获取投注记录 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link ApiGameBetRecordPageResponseDTO }> + */ + @Post(url = "server/recordAll") + ApiNGResponseDTO getBetRecordByTime(@JSONBody Map parameters, @Header Map headerMap); + + /** + * 按历史时间获取投注记录 + * + * @param parameters 范围 + * @param headerMap 标题映射 + * @return {@link ApiNGResponseDTO }<{@link ApiGameBetRecordPageResponseDTO }> + */ + @Post(url = "server/recordHistory") + ApiNGResponseDTO getBetRecordByHistoryTime(@JSONBody Map parameters, @Header Map headerMap); + + +} diff --git a/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiFCResult.java b/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiFCResult.java new file mode 100644 index 0000000..646206b --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiFCResult.java @@ -0,0 +1,19 @@ +package com.ff.game.api.fc.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 红蜜蜂 + * + * @author shi + * @date 2025/03/24 + */ +@Data +public class ApiFCResult { + /** + * 操作结果,0 表示成功,其他值表示失败 + */ + @JsonProperty("Result") + private int result; +} diff --git a/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiKeyRequest.java b/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiKeyRequest.java new file mode 100644 index 0000000..9b96e39 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/fc/dto/ApiKeyRequest.java @@ -0,0 +1,68 @@ +package com.ff.game.api.fc.dto; + +import lombok.Data; +import java.util.List; + +/** + * 通用 API 请求对象 + */ +@Data +public class ApiKeyRequest { + + /** + * 加密参数 + */ + private String params; + + /** + * 请求参数对象,包含多个子字段 + */ + private ReParams reParams; + + /** + * 签名 + */ + private String sign; + + /** + * 内部类,表示嵌套的 ReParams 参数 + */ + @Data + public static class ReParams { + + /** + * 用户账户 + */ + private String memberAccount; + + /** + * 游戏ID + */ + private int gameID; + + /** + * 语言ID + */ + private int languageID; + + /** + * 首页链接 + */ + private String homeUrl; + + /** + * 奖池状态 + */ + private boolean jackpotStatus; + + /** + * 是否登录游戏大厅 + */ + private boolean loginGameHall; + + /** + * 游戏大厅的游戏类型列表 + */ + private List gameHallGameType; + } +} diff --git a/ff-game/src/main/java/com/ff/game/api/fc/impl/GamesFCServiceImpl.java b/ff-game/src/main/java/com/ff/game/api/fc/impl/GamesFCServiceImpl.java new file mode 100644 index 0000000..0d12e23 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/fc/impl/GamesFCServiceImpl.java @@ -0,0 +1,864 @@ +package com.ff.game.api.fc.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +import com.alibaba.druid.support.json.JSONUtils; +import com.ff.base.constant.CacheConstants; +import com.ff.base.constant.Constants; +import com.ff.base.core.redis.RedisCache; +import com.ff.base.enums.*; +import com.ff.base.exception.base.ApiException; +import com.ff.base.exception.base.BaseException; +import com.ff.base.system.service.ISysConfigService; +import com.ff.base.utils.DateUtils; +import com.ff.base.utils.SleepUtil; +import com.ff.base.utils.StringUtils; +import com.ff.base.utils.sign.Md5Utils; +import com.ff.config.KeyConfig; +import com.ff.game.api.IGamesService; +import com.ff.game.api.fc.dto.ApiFCResult; +import com.ff.game.api.fc.client.FCClient; +import com.ff.game.api.ng.dto.*; +import com.ff.game.api.request.*; +import com.ff.game.domain.*; +import com.ff.game.dto.GameBettingDetailsDTO; +import com.ff.game.dto.GameDTO; +import com.ff.game.dto.GameSecretKeyCurrencyDTO; +import com.ff.game.dto.GameSecretKeyLangDTO; +import com.ff.game.service.*; +import com.ff.member.domain.Member; +import com.ff.member.service.IMemberService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import javax.annotation.Resource; +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + + +/** + * 游戏数据解析服务 + * + * @author shi + * @date 2024/10/21 + */ +@Service("FCService") +@Slf4j +public class GamesFCServiceImpl implements IGamesService { + + + @Resource + private ISysConfigService configService; + + @Resource + private RedisCache redisCache; + + @Resource + private IGameExchangeMoneyService gameExchangeMoneyService; + + + @Resource + private IGamePlatformService gamePlatformService; + + + @Resource + private IGameService gameService; + + + @Resource + private IMemberService memberService; + + @Resource + private IGameFreeRecordService gameFreeRecordService; + + @Resource + private IGameSecretKeyService gameSecretKeyService; + + @Resource + private FCClient FCClient; + + + @Resource + private KeyConfig keyConfig; + + @Resource + private IGameBettingDetailsService gameBettingDetailsService; + + @Resource + private IGameSecretKeyCurrencyService gameSecretKeyCurrencyService; + + @Resource + private IGameNameService gameNameService; + + + @Resource + private IGameSecretKeyLangService gameSecretKeyLangService; + + + @Autowired + @Qualifier("threadPoolTaskExecutor") + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + /** + * 获得就是成功 + * + * @param errorCode 错误代码 + * @return {@link Boolean } + */ + private Boolean getIsSuccess(Integer errorCode) { + ApiException.isTrue(10009 != errorCode, ErrorCode.FREQUENT_INTERFACE_REQUESTS.getCode()); + + return 10000 == errorCode; + } + + + /** + * 获取密钥 + * + * @param paramsMap 参数图 + * @param agentKey 代理密钥 + * @return {@link String } + */ + private static String getKey(Map paramsMap, String agentKey) { + try { + Base64.Encoder encoder = Base64.getEncoder(); + SecretKeySpec keySpec = new SecretKeySpec(agentKey.getBytes(StandardCharsets.UTF_8), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + return encoder.encodeToString(cipher.doFinal(JSONUtils.toJSONString(paramsMap).getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new BaseException("加密失败"); + } + + } + + public static void main(String[] args) { + Map paramsMap =new HashMap<>(); + paramsMap.put("MemberAccount","5hajptaA"); + System.out.printf(getKey(paramsMap,"pySJ3DRpW2Zj2A9C")); + } + + /** + * 获取密钥映射 + * + * @param paramsMap 参数图 + * @param agentKey 代理密钥 + * @param currency 货币 + * @return {@link Map }<{@link String },{@link Object }> + */ + private Map getKeyMap(Map paramsMap,String agentKey,String currency){ + Map keyMap = new HashMap<>(); + String key = getKey(paramsMap, agentKey); + keyMap.put("Sign", Md5Utils.md5New(JSONUtils.toJSONString(paramsMap))); + keyMap.put("AgentCode", "FCXTU3042"); + keyMap.put("Currency", currency); + keyMap.put("Params",key); + return keyMap; + + } + + /** + * 解密密钥 + * + * @param gamesBaseRequestDTO 游戏基础请求dto + * @return {@link String } + */ + private String decryptKey(GamesBaseRequestDTO gamesBaseRequestDTO) { + try { + String val="SBF6G5gFvW5n4aFSFCar9Im0CHgBMyU7OxFTuuB6InmRkm6Qt4K2pfktU="; + Base64.Decoder decoder = Base64.getDecoder(); + SecretKeySpec keySpec = new SecretKeySpec("gamesBaseRequestDTO.getAgentKey()".getBytes(), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec); + return new String(cipher.doFinal(decoder.decode(val))); + } catch (Exception e) { + throw new BaseException("解密失败"); + } + + } + + /** + * 创建成员 + * + * @param createMemberRequestDTO 创建成员请求dto + * @return {@link Boolean } + */ + @Override + public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) { + log.info("GamesNGServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO); + Map paramsMap = new HashMap<>(); + paramsMap.put("MemberAccount", createMemberRequestDTO.getAccount()); + paramsMap.putAll(getKeyMap(paramsMap, createMemberRequestDTO.getAgentKey(), createMemberRequestDTO.getCurrency())); + ApiFCResult apiFCResult = FCClient.createMember(paramsMap); + int errorCode = apiFCResult.getResult(); + if (10000 == errorCode) { + return Boolean.TRUE; + } + if (10002 == errorCode) { + throw new ApiException(ErrorCode.GAME_ACCOUNT_CREATION_FAILED.getCode()); + } + //判断是否获取成功 + return Boolean.FALSE; + } + + + /** + * 获取会员信息 + * + * @param memberInfoRequestDTO 会员信息请求dto + * @return {@link MemberInfoResponseDTO } + */ + @Override + public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) { + log.info("GamesNGServiceImpl [getMemberInfo] 请求参数 {}", memberInfoRequestDTO); + Map paramsMap = new HashMap<>(); + paramsMap.put("playerId", memberInfoRequestDTO.getAccounts()); + paramsMap.put("platType", NGPlatforms.PG.getCode()); + paramsMap.put("currency", memberInfoRequestDTO.getCurrency()); + //这个接口请求稍微重复一次就报错 + SleepUtil.sleep(500); + ApiNGResponseDTO apiNGResponseDTO = FCClient.getMemberInfo(paramsMap, null); + int errorCode = apiNGResponseDTO.getCode(); + if (this.getIsSuccess(errorCode)) { + return MemberInfoResponseDTO.builder().account(memberInfoRequestDTO.getAccounts()).balance(apiNGResponseDTO.getData().getBalance()).status(GameMemberStatus.UNKNOWN.getCode()).build(); + } else { + throw new BaseException(apiNGResponseDTO.getMsg()); + } + } + + /** + * 无重定向登录 + * + * @param gamesLogin 游戏登录 + * @return {@link String } + */ + @Override + public String loginWithoutRedirect(GamesLogin gamesLogin) { + log.info("GamesNGServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin); + Map paramsMap = new HashMap<>(); + paramsMap.put("playerId", gamesLogin.getAccount()); + paramsMap.put("platType", NGPlatforms.PG.getCode()); + paramsMap.put("currency", gamesLogin.getCurrency()); + paramsMap.put("gameType", gamesLogin.getGameType()); + paramsMap.put("lang", gamesLogin.getLang()); + paramsMap.put("gameCode", gamesLogin.getGameId()); + paramsMap.put("returnUrl", gamesLogin.getHomeUrl()); + paramsMap.put("ingress", PlatformHomeType.WEB.getValue().equals(gamesLogin.getPlatform()) ? IngressType.PC_WEB.getValue() : IngressType.MOBILE_WEB.getValue()); + Map headerMap =new HashMap<>(); + ApiNGResponseDTO apiLoginResponseDTOApiNGResponseDTO = FCClient.loginWithoutRedirect(paramsMap, headerMap); + if (this.getIsSuccess(apiLoginResponseDTOApiNGResponseDTO.getCode())) { + return apiLoginResponseDTOApiNGResponseDTO.getData().getUrl(); + } else { + throw new BaseException(apiLoginResponseDTOApiNGResponseDTO.getMsg()); + } + } + + + /** + * 获取游戏列表 + * + * @param gamesBaseRequestDTO 游戏请求dto + * @return {@link String } + */ + @Transactional + @Override + public String getGameList(GamesBaseRequestDTO gamesBaseRequestDTO) { + List apiGameInfoResponseDTOS = redisCache.getCacheList(CacheConstants.PG_GAMES); + if (!CollectionUtils.isEmpty(apiGameInfoResponseDTOS)) { + return CacheConstants.PG_GAMES; + } + + + log.info("GamesNGServiceImpl [getGameList] 请求参数 {}", gamesBaseRequestDTO); + Map paramsMap = new HashMap<>(); + paramsMap.put("platType", NGPlatforms.PG.getCode()); + Map headerMap =new HashMap<>(); + ApiNGResponseDTO> gameList = FCClient.getGameList(paramsMap, headerMap); + if (this.getIsSuccess(gameList.getCode())) { + for (ApiGameInfoResponseDTO apiGameInfoResponseDTO : gameList.getData()) { + GamePlatform gamePlatform = GamePlatform.builder() + .platformType(NGGameType.findSystemByCode(apiGameInfoResponseDTO.getGameType())) + .platformCode(GamePlatforms.PG.getCode()) + .build(); + List gamePlatforms = gamePlatformService.selectGamePlatformList(gamePlatform); + //没有此平台就新增一个平台 + if (CollectionUtils.isEmpty(gamePlatforms)) { + gamePlatform.setPlatformName(GamePlatforms.PG.getInfo() + NGGameType.findInfoByCode(apiGameInfoResponseDTO.getGameType())); + gamePlatform.setSortNo(gamePlatformService.selectMaxSortNo() + 1); + gamePlatform.setCreateBy(Constants.SYSTEM); + gamePlatformService.insertGamePlatform(gamePlatform); + } else { + gamePlatform = gamePlatforms.get(0); + } + Game game = Game.builder() + .platformId(gamePlatform.getId()) + .gameCode(apiGameInfoResponseDTO.getGameCode()) + .build(); + List games = gameService.selectGameList(game); + //不存在这个游戏 + if (CollectionUtils.isEmpty(games)) { + game.setGameSourceType(apiGameInfoResponseDTO.getGameType()); + game.setFreespin(Boolean.FALSE); + game.setDemoStatus(Boolean.TRUE); + game.setSortNo(gameService.selectMaxSortNoByPlatformId(gamePlatform.getId()) + 1); + game.setGameName(apiGameInfoResponseDTO.getGameName().get("zh-hans")); + game.setCreateBy(Constants.SYSTEM); + gameService.insertGame(game); + } else { + game = games.get(0); + } + apiGameInfoResponseDTO.setSystemGameId(game.getId()); + Map gameName = apiGameInfoResponseDTO.getGameName(); + for (String key : gameName.keySet()) { + String name = gameName.get(key); + List gameNames = gameNameService.selectGameNameList(GameName.builder().gameId(game.getId()).gameName(name).build()); + if (CollectionUtils.isEmpty(gameNames)) { + if ("zh-hans".equals(key)) { + gameNameService.insertGameName(GameName.builder() + .gameId(game.getId()) + .gameName(name) + .langCode("zh-CN") + .createBy(Constants.SYSTEM) + .build()); + } else if (!"zh-hant".equals(key)) { + GameSecretKeyLangDTO gameSecretKeyLangDTO = gameSecretKeyLangService.findGameSecretKeyLangDTO(GameSecretKeyLangDTO.builder() + .platformCode(GamePlatforms.PG.getCode()) + .lang(key) + .build()); + gameNameService.insertGameName(GameName.builder() + .gameId(game.getId()) + .gameName(name) + .langCode(gameSecretKeyLangDTO.getSystemLangCode()) + .createBy(Constants.SYSTEM) + .build()); + } + } + } + + + } + + redisCache.deleteObject(CacheConstants.PG_GAMES); + redisCache.setCacheList(CacheConstants.PG_GAMES, gameList.getData()); + redisCache.expire(CacheConstants.PG_GAMES, 5L, TimeUnit.HOURS); + } else { + throw new BaseException(gameList.getMsg()); + } + return CacheConstants.PG_GAMES; + } + + /** + * 按代理id进行交换转账 + * + * @param exchangeTransferMoneyRequestDTO 外汇转账moeny dto + * @return {@link Long } + */ + @Override + @Transactional + public Long exchangeTransferByAgentId(ExchangeTransferMoneyRequestDTO exchangeTransferMoneyRequestDTO) { + log.info("GamesNGServiceImpl [exchangeTransferByAgentId] 请求参数 {}", exchangeTransferMoneyRequestDTO); + + GameSecretKeyCurrency currencyDTO = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder() + .platformCode(GamePlatforms.PG.getCode()) + .code(exchangeTransferMoneyRequestDTO.getAgentId()) + .currency(exchangeTransferMoneyRequestDTO.getCurrency()) + .build()); + + Member member = memberService.selectMemberByGameAccount(exchangeTransferMoneyRequestDTO.getAccount()); + String transactionId = gameExchangeMoneyService.getTransactionId(GamePlatforms.PG.getCode(), 32); + List gameExchangeMonies = gameExchangeMoneyService.selectGameExchangeMoneyList( + GameExchangeMoney.builder() + .tenantKey(exchangeTransferMoneyRequestDTO.getTenantKey()) + .orderId(exchangeTransferMoneyRequestDTO.getOrderId()) + .build() + ); + Assert.isTrue(CollectionUtils.isEmpty(gameExchangeMonies), "订单号重复"); + + //获取下一个自增id + GameExchangeMoney exchangeMoney = GameExchangeMoney + .builder() + .tenantKey(exchangeTransferMoneyRequestDTO.getTenantKey()) + .orderId(exchangeTransferMoneyRequestDTO.getOrderId()) + .quota(exchangeTransferMoneyRequestDTO.getQuota()) + .balance(exchangeTransferMoneyRequestDTO.getAmount()) + .exchangeType(exchangeTransferMoneyRequestDTO.getTransferType()) + .currencyCode(currencyDTO.getSystemCurrency()) + .memberId(member.getId()) + .transactionId(transactionId) + .platformCode(GamePlatforms.PG.getCode()) + .build(); + exchangeMoney.setCreateBy(Constants.SYSTEM); + + //获取余额 + String type = TransferType.ALL.getCode().equals(exchangeTransferMoneyRequestDTO.getTransferType()) ? NGTransferType.TRANSFER_OUT.getValue() : NGTransferType.TRANSFER_IN.getValue(); + //获取当前游戏币 + MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder() + .accounts(member.getGameAccount()) + .agentId(exchangeTransferMoneyRequestDTO.getAgentId()) + .agentKey(exchangeTransferMoneyRequestDTO.getAgentKey()) + .currency(currencyDTO.getCurrency()) + .build(); + MemberInfoResponseDTO memberInfo = this.getMemberInfo(gamesBaseRequestDTO); + //判断是不是转出 + if (NGTransferType.TRANSFER_OUT.getValue().equals(type)) { + exchangeTransferMoneyRequestDTO.setAmount(memberInfo.getBalance()); + } + + + Map paramsMap = new HashMap<>(); + paramsMap.put("platType", NGPlatforms.PG.getCode()); + paramsMap.put("playerId", exchangeTransferMoneyRequestDTO.getAccount()); + paramsMap.put("currency", currencyDTO.getCurrency()); + paramsMap.put("type", type); + paramsMap.put("amount", exchangeTransferMoneyRequestDTO.getAmount()); + paramsMap.put("orderId", transactionId); + + + Map key =new HashMap<>(); + ApiNGResponseDTO apiNGResponseDTO = FCClient.exchangeTransferByAgentId(paramsMap, key); + if (this.getIsSuccess(apiNGResponseDTO.getCode())) { + //更新数据 + exchangeMoney.setBalance(exchangeTransferMoneyRequestDTO.getAmount()); + exchangeMoney.setStatus(StatusType.IN_PROGRESS.getValue()); + gameExchangeMoneyService.insertGameExchangeMoney(exchangeMoney); + ExchangeTransferStatusRequestDTO exchangeTransferStatusRequestDTO = new ExchangeTransferStatusRequestDTO(); + exchangeTransferStatusRequestDTO.setAccount(exchangeTransferMoneyRequestDTO.getAccount()); + exchangeTransferStatusRequestDTO.setCurrency(currencyDTO.getCurrency()); + exchangeTransferStatusRequestDTO.setOrderId(transactionId); + exchangeTransferStatusRequestDTO.setAgentId(exchangeTransferMoneyRequestDTO.getAgentId()); + exchangeTransferStatusRequestDTO.setAgentKey(exchangeTransferMoneyRequestDTO.getAgentKey()); + this.exchangeTransferStatus(exchangeTransferStatusRequestDTO); + } else { + log.error("GamesPGServiceImpl [exchangeTransferByAgentId] 金额转移失败,错误代码{},错误信息{}", apiNGResponseDTO.getCode(), apiNGResponseDTO.getMsg()); + throw new BaseException(apiNGResponseDTO.getMsg()); + } + return exchangeMoney.getId(); + } + + /** + * 汇兑转移状态 + * + * @param exchangeTransferMoneyRequestDTO 兑换转账请求dto + * @return {@link Boolean } + */ + @Override + public Boolean exchangeTransferStatus(ExchangeTransferStatusRequestDTO exchangeTransferMoneyRequestDTO) { + + + Map paramsMap = new HashMap<>(); + paramsMap.put("playerId", exchangeTransferMoneyRequestDTO.getAccount()); + paramsMap.put("currency", exchangeTransferMoneyRequestDTO.getCurrency()); + paramsMap.put("orderId", exchangeTransferMoneyRequestDTO.getOrderId()); + Map key =new HashMap<>(); + ApiNGResponseDTO apiNGResponseDTO = FCClient.exchangeTransferStatus(paramsMap, key); + if (this.getIsSuccess(apiNGResponseDTO.getCode())) { + ApiExchangeTransferStatusResponseDTO apiNGResponseDTOData = apiNGResponseDTO.getData(); + List gameExchangeMonies = gameExchangeMoneyService.selectGameExchangeMoneyList( + GameExchangeMoney.builder() + .platformCode(GamePlatforms.PG.getCode()) + .transactionId(exchangeTransferMoneyRequestDTO.getOrderId()) + .build() + ); + + for (GameExchangeMoney exchangeMoney : gameExchangeMonies) { + //更新数据 + exchangeMoney.setBalance(apiNGResponseDTOData.getAmount().abs()); + exchangeMoney.setCoinBefore(NumberUtil.sub(apiNGResponseDTOData.getAfterBalance(), apiNGResponseDTOData.getAmount())); + exchangeMoney.setCoinAfter(apiNGResponseDTOData.getAfterBalance()); + exchangeMoney.setCurrencyBefore(exchangeMoney.getCoinBefore()); + exchangeMoney.setCurrencyAfter(exchangeMoney.getCoinAfter()); + exchangeMoney.setStatus(apiNGResponseDTOData.getStatus()); + gameExchangeMoneyService.updateGameExchangeMoney(exchangeMoney); + } + return Boolean.TRUE; + } else { + log.error("GamesPGServiceImpl [exchangeTransferStatus]错误代码{},错误信息{}", apiNGResponseDTO.getCode(), apiNGResponseDTO.getMsg()); + return Boolean.FALSE; + } + + + } + + + /** + * 按时间获取投注记录 + * + * @param betRecordByTimeDTO 按时间dto投注记录 + * @return {@link Boolean } + */ + @Override + public Boolean getBetRecordByTime(BetRecordByTimeDTO betRecordByTimeDTO) { + + GameSecretKeyCurrencyDTO gameSecretKeyDTO = new GameSecretKeyCurrencyDTO(); + gameSecretKeyDTO.setPlatformCodes(NGPlatforms.getAllPlatforms()); + List currencyDTOList = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTOList(gameSecretKeyDTO); + List currencys = currencyDTOList.stream() + .map(GameSecretKeyCurrencyDTO::getCurrency) + .distinct() + .collect(Collectors.toList()); + + + Set cacheSet = redisCache.getCacheSet(CacheConstants.PG_GAMES_BET_CURRENCY); + if (CollectionUtils.isEmpty(cacheSet)) { + cacheSet = new HashSet<>(); + } + //如果长度一致则清空缓存循环币种 + if (cacheSet.size() >= currencys.size()) { + cacheSet = new HashSet<>(); + redisCache.deleteObject(CacheConstants.PG_GAMES_BET_CURRENCY); + } + //去掉重复的 + currencys.removeAll(cacheSet); + + String firstCurrency = currencys.get(0); + + GameSecretKeyCurrencyDTO currencyDTO = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder() + .platformCode(GamePlatforms.PG.getCode()) + .currency(firstCurrency) + .build()); + + + betRecordByTimeDTO.setAgentId(currencyDTO.getCode()); + betRecordByTimeDTO.setAgentKey(currencyDTO.getKey()); + int pageNo = 1; + int pageSize = 2000; + Map paramsMap = new HashMap<>(); + paramsMap.put("currency", currencyDTO.getCurrency()); + paramsMap.put("pageNo", pageNo); + paramsMap.put("pageSize", pageSize); + Map key = new HashMap<>(); + + + ApiNGResponseDTO betRecordByTime = FCClient.getBetRecordByTime(paramsMap, key); + + if (this.getIsSuccess(betRecordByTime.getCode())) { + cacheSet.add(firstCurrency); + redisCache.setCacheSet(CacheConstants.PG_GAMES_BET_CURRENCY, cacheSet); + + ApiNGResponseDTO result = betRecordByTime; + AtomicInteger pageNoAtomic = new AtomicInteger(pageNo); + + + ApiGameBetRecordPageResponseDTO data = result.getData(); + //数据组装 + this.batchInsert(data); + + //总页数 + // 计算总页数,确保不会遗漏 + int totalPage = (int) Math.ceil((double) data.getTotal() / pageSize); + + // 获取下一页数据 + while (pageNoAtomic.get() < totalPage && data.getTotal() > 0) { + pageNoAtomic.incrementAndGet(); + //请求参数 + Map paramMap = new HashMap<>(); + paramMap.put("currency", currencyDTO.getCurrency()); + paramMap.put("pageNo", pageNoAtomic.get()); + paramMap.put("pageSize", pageSize); + SleepUtil.sleep(10000); + ApiNGResponseDTO betRecordByTimePage = FCClient.getBetRecordByTime(paramMap, key); + data = betRecordByTimePage.getData(); + //数据组装 + this.batchInsert(data); + } + + + } + getBetRecordByHistoryTime(betRecordByTimeDTO, currencyDTO); + return Boolean.TRUE; + } + + /** + * 按历史时间获取投注记录 + * + * @param betRecordByTimeDTO 按时间dto投注记录 + * @param currencyDTO 货币dto + */ + private void getBetRecordByHistoryTime(BetRecordByTimeDTO betRecordByTimeDTO, GameSecretKeyCurrencyDTO currencyDTO) { + + //捞取指定30分钟前的数据 + Long startTimes = DateUtils.addOrSubtractMinutes(DateUtils.getNowDate(), -30); + Long endTimes = DateUtils.getNowDate(); + betRecordByTimeDTO.setStartTime(startTimes); + betRecordByTimeDTO.setEndTime(endTimes); + + + int currentMinute = java.time.LocalTime.now().getMinute(); + // 判断当前分钟是否是 20 分、40 分或 0 分,如果不是,跳过当前执行 + if (currentMinute != 20 && currentMinute != 40 && currentMinute != 0) { + // 当前时间不是 20 分、40 分、0 分,跳过执行 + return; + } + + + String startTime = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss") + .withZone(ZoneId.of("Asia/Shanghai")) + .format(Instant.ofEpochMilli(betRecordByTimeDTO.getStartTime())); + + String endTime = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss") + .withZone(ZoneId.of("Asia/Shanghai")) + .format(Instant.ofEpochMilli(betRecordByTimeDTO.getEndTime())); + + + betRecordByTimeDTO.setAgentId(currencyDTO.getCode()); + betRecordByTimeDTO.setAgentKey(currencyDTO.getKey()); + int pageNo = 1; + int pageSize = 2000; + Map paramsMap = new HashMap<>(); + paramsMap.put("currency", currencyDTO.getCurrency()); + paramsMap.put("pageNo", pageNo); + paramsMap.put("pageSize", pageSize); + paramsMap.put("startTime", startTime); + paramsMap.put("endTime", endTime); + Map key =new HashMap<>(); + + + ApiNGResponseDTO betRecordByTime = FCClient.getBetRecordByHistoryTime(paramsMap, key); + + if (this.getIsSuccess(betRecordByTime.getCode())) { + ApiNGResponseDTO result = betRecordByTime; + AtomicInteger pageNoAtomic = new AtomicInteger(pageNo); + + threadPoolTaskExecutor.execute(() -> { + + ApiGameBetRecordPageResponseDTO data = result.getData(); + //数据组装 + this.batchInsert(data); + + //总页数 + // 计算总页数,确保不会遗漏 + int totalPage = (int) Math.ceil((double) data.getTotal() / pageSize); + + // 获取下一页数据 + while (pageNoAtomic.get() < totalPage && data.getTotal() > 0) { + pageNoAtomic.incrementAndGet(); + //请求参数 + Map paramMap = new HashMap<>(); + paramMap.put("currency", currencyDTO.getCurrency()); + paramMap.put("pageNo", pageNoAtomic.get()); + paramMap.put("pageSize", pageSize); + paramMap.put("startTime", startTime); + paramMap.put("endTime", endTime); + SleepUtil.sleep(10000); + ApiNGResponseDTO betRecordByTimePage = FCClient.getBetRecordByTime(paramMap, key); + data = betRecordByTimePage.getData(); + //数据组装 + this.batchInsert(data); + } + }); + + + } + } + + + /** + * 赠送免费局数 + * + * @param createFreeSpinRequest 创建自由旋转请求 + * @return {@link Boolean } + */ + @Override + public Boolean createFreeSpin(CreateFreeSpinRequestDTO createFreeSpinRequest) { + + + return null; + } + + /** + * 获取游戏详细信息 + * + * @param getGameDetailRequestDTO 获取游戏详细信息请求dto + * @return {@link GetGameDetailResponseDTO } + */ + @Override + public GetGameDetailResponseDTO getGameDetail(GetGameDetailRequestDTO getGameDetailRequestDTO) { + + List gameBettingDetails = gameBettingDetailsService.selectGameBettingDetailsList(GameBettingDetailsDTO.builder().wagersId(getGameDetailRequestDTO.getWagersId()).build()); + if (!CollectionUtils.isEmpty(gameBettingDetails)) { + GetGameDetailResponseDTO getGameDetailResponseDTO = new GetGameDetailResponseDTO(); + getGameDetailResponseDTO.setUrl(gameBettingDetails.get(0).getBetContent()); + return getGameDetailResponseDTO; + } + return null; + } + + /** + * 强制会员从游戏注销 + * + * @param kickMemberRequestDTO 踢会员请求dto + * @return {@link Boolean } + */ + @Override + public Boolean kickMember(KickMemberRequestDTO kickMemberRequestDTO) { + + return null; + } + + /** + * 踢成员全部 + * + * @param kickMemberAllDTO 踢成员全部dto + * @return {@link Boolean } + */ + @Override + public Boolean kickMemberAll(KickMemberAllDTO kickMemberAllDTO) { + + return null; + } + + /** + * 免费游戏玩家使用的纪录 + * + * @param getFreeSpinDashflowRequestDTO 获取自由旋转dashflow请求dto + * @return {@link List }<{@link GameFreeRecord }> + */ + @Override + public List getFreeSpinDashflow(GetFreeSpinDashflowRequestDTO getFreeSpinDashflowRequestDTO) { + + + return Collections.emptyList(); + } + + /** + * 取消赠送免费局数 + * + * @param cancelFreeSpinRequestDTO 取消免费旋转请求 + * @return {@link Boolean } + */ + @Override + public Boolean cancelFreeSpin(CancelFreeSpinRequestDTO cancelFreeSpinRequestDTO) { + + return null; + } + + + /** + * 数据构建 + * + * @param gamesDataBuildDTO 数据 + * @return {@link GameBettingDetails } + */ + @Override + public GameBettingDetails dataBuild(GamesDataBuildDTO gamesDataBuildDTO) { + + //转化类 + ApiGameBetRecordPageResponseDTO.GameBetRecord resultBean = (ApiGameBetRecordPageResponseDTO.GameBetRecord) gamesDataBuildDTO.getData(); + + NGPlatforms ngPlatforms = NGPlatforms.getByCode(resultBean.getPlatType()); + if (ObjectUtils.isEmpty(ngPlatforms)) { + return null; + } + + String platform = ngPlatforms.getPlatform(); + List games = gameService.selectGameDTOList(GameDTO.builder().gameName(resultBean.getGameName()).platformCode(platform).build()); + if (CollectionUtils.isEmpty(games)) { + return null; + } + Game gamesDataDTO = games.get(0); + GameSecretKeyCurrency currencyDTO = gameSecretKeyCurrencyService.findByGameSecretKeyCurrencyDTO(GameSecretKeyCurrencyDTO.builder() + .platformCodes(NGPlatforms.getAllPlatforms()) + .currency(resultBean.getCurrency()) + .build()); + + Member member = memberService.selectMemberByGameAccount(resultBean.getPlayerId()); + if (ObjectUtils.isEmpty(member)) { + return null; + } + GameDTO gameDTO = new GameDTO(); + gameDTO.setPlatformCodes(NGPlatforms.getAllPlatforms()); + + int gameStatus = GameStatus.FLAT.getCode(); + if (BigDecimal.ZERO.compareTo(resultBean.getSettledAmount()) > 0) { + gameStatus = GameStatus.FAIL.getCode(); + } else if (BigDecimal.ZERO.compareTo(resultBean.getSettledAmount()) < 0) { + gameStatus = GameStatus.WIN.getCode(); + } + + //数据构造 + GameBettingDetails gameBettingDetails = GameBettingDetails.builder() + .tenantKey(member.getTenantKey()) + //保存我们的币种id + .currencyCode(currencyDTO.getSystemCurrency()) + .memberId(member.getId()) + .gameCode(gamesDataDTO.getGameCode()) + .gameType(NGGameType.findSystemByCode(resultBean.getGameType())) + .platformCode(NGPlatforms.getByCode(resultBean.getPlatType()).getPlatform()) + .gameId(gamesDataDTO.getId()) + .gameName(gamesDataDTO.getGameName()) + .gameStatus(gameStatus) + .gameStatusType(1) + .gameCurrencyCode(resultBean.getCurrency()) + .account(resultBean.getPlayerId()) + .wagersId(resultBean.getGameOrderId()) + .wagersTime(resultBean.getBetTime().getTime()) + .betAmount(resultBean.getBetAmount().abs()) + .payoffTime(resultBean.getLastUpdateTime().getTime()) + .payoffAmount(resultBean.getSettledAmount().abs()) + .settlementTime(resultBean.getLastUpdateTime().getTime()) + .turnover(resultBean.getValidAmount()) + .orderNo(StringUtils.isNotEmpty(resultBean.getRound()) ? String.valueOf(resultBean.getRound()) : null) + .settlementStatus(NGSettlementStatusEnum.fromStatus(resultBean.getStatus()).getCode()) + .round(resultBean.getRound()) + .table(resultBean.getTable()) + .seat(resultBean.getSeat()) + .betContent(resultBean.getBetContent()) + .build(); + gameBettingDetails.setCreateBy(Constants.SYSTEM); + gameBettingDetails.setCreateTime(DateUtils.getNowDate()); + return gameBettingDetails; + + } + + /** + * 批量插入 + * + * @param data 数据 + */ + private synchronized void batchInsert(ApiGameBetRecordPageResponseDTO data) { + List gameBettingDetails = new ArrayList<>(); + List wagersIds = new ArrayList<>(); + //数据转化 + for (ApiGameBetRecordPageResponseDTO.GameBetRecord bean : data.getList()) { + GameBettingDetails bettingDetails = this.dataBuild(GamesDataBuildDTO.builder().data(bean).build()); + if (!ObjectUtils.isEmpty(bettingDetails)) { + bettingDetails.setId(IdUtil.getSnowflakeNextId()); + gameBettingDetails.add(bettingDetails); + } + wagersIds.add(bean.getGameOrderId()); + } + if (!CollectionUtils.isEmpty(gameBettingDetails)) { + //查询重复数据id + List removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds); + //用steam流清除list中与wagersIds集合相同的数据 + gameBettingDetails = gameBettingDetails.stream() + .filter(detail -> !removeWagersIds.contains(detail.getWagersId())) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(gameBettingDetails)) { + List wagersId = gameBettingDetails.stream().map(GameBettingDetails::getWagersId).collect(Collectors.toList()); + gameBettingDetailsService.deleteGameBettingDetailsByWagersId(wagersId); + gameBettingDetailsService.batchInsert(gameBettingDetails); + } + } + + } + +}