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 01d8620..ca45741 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 @@ -137,8 +137,14 @@ public class CacheConstants { public static final String SV388_TIME_FROM= "sv388:time:from"; public static final String SV388_GAMES = "sv388_games:"; - - + /** + * pp游戏 + */ + public static final String PP_GAMES= "pp_games:"; + /** + * pp时间点 + */ + public static final String PP_TIME_POINT = "pp:time:point:"; } diff --git a/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java b/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java index a6152c1..a9cf778 100644 --- a/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java +++ b/ff-base/src/main/java/com/ff/base/enums/GamePlatforms.java @@ -19,6 +19,7 @@ public enum GamePlatforms { PGT("PGT", "PGT"), FBSports("FBSports", "FB体育"), SV388("SV388", "SV388真人"), + PP("PP", "PP"), DBSports("DBSports", "DB体育"); private final String code; diff --git a/ff-base/src/main/java/com/ff/base/enums/PPGameType.java b/ff-base/src/main/java/com/ff/base/enums/PPGameType.java new file mode 100644 index 0000000..adfbefc --- /dev/null +++ b/ff-base/src/main/java/com/ff/base/enums/PPGameType.java @@ -0,0 +1,64 @@ +package com.ff.base.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Optional; +import java.util.stream.Stream; + + +/** + * xkgame类型 + * + * @author shi + * @date 2024/11/13 + */ +@Getter +@AllArgsConstructor +public enum PPGameType { + + ELECTRONIC("vs", 1,"电子"), +// CARD_GAME("rl",2, "轮盘"), +// VIDEO("vp",6, "视频扑克"), +// SCRATCH_CARD("sc",7, "刮刮乐"), +// BLACK_JACK("bj",2, "21点"), +// CLASSIC_SLOTS("cs",1, "电子"), +// BACCARAT_NEW("bn",2, "百家乐新"), +// BACCARAT("bc",2, "百家乐"), +// LIVE_GAMES("lg",6, "现场游戏"), + ; + + private final String code; + private final Integer systemCode; + private final String info; + + + /** + * 按代码查找系统 + * + * @param code 代码 + * @return {@link String } + */ + public static Integer findSystemByCode(String code) { + Optional system = Stream.of(PPGameType.values()) + .filter(gameType -> gameType.getCode().equals(code)) + .map(PPGameType::getSystemCode) + .findFirst(); + return system.orElse(null); + } + + /** + * 按代码查找信息 + * + * @param code 代码 + * @return {@link String } + */ + public static String findInfoByCode(String code) { + Optional system = Stream.of(PPGameType.values()) + .filter(gameType -> gameType.getCode().equals(code)) + .map(PPGameType::getInfo) + .findFirst(); + return system.orElse(null); + } +} diff --git a/ff-base/src/main/java/com/ff/base/utils/DateUtils.java b/ff-base/src/main/java/com/ff/base/utils/DateUtils.java index a473e18..7448678 100644 --- a/ff-base/src/main/java/com/ff/base/utils/DateUtils.java +++ b/ff-base/src/main/java/com/ff/base/utils/DateUtils.java @@ -975,4 +975,23 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { } } + + /** + * 将自定义格式的时间字符串转换为毫秒级时间戳(UTC) + * + * @param dateStr 时间字符串 + * @param pattern 时间格式模式(如"yyyy-MM-dd HH:mm:ss") + * @return 毫秒级时间戳 + * @throws ParseException 如果解析失败 + */ + public static long toTimestamp(String dateStr, String pattern) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.parse(dateStr).getTime(); + } catch (Exception e) { + return 0; + } + + } } diff --git a/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java b/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java new file mode 100644 index 0000000..51ee44b --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/address/MyPPAddressSource.java @@ -0,0 +1,35 @@ +package com.ff.game.api.pp.address; + +import com.dtflys.forest.callback.AddressSource; +import com.dtflys.forest.http.ForestAddress; +import com.dtflys.forest.http.ForestRequest; +import com.ff.base.enums.GamePlatforms; +import com.ff.game.domain.Platform; +import com.ff.game.service.IPlatformService; +import com.sun.media.jfxmediaimpl.platform.PlatformManager; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + + +/** + * 我jili address来源 + * + * @author shi + * @date 2025/02/10 + */ +@Component +public class MyPPAddressSource implements AddressSource { + @Resource + private IPlatformService platformService; + + + @Override + public ForestAddress getAddress(ForestRequest request) { + Platform platform = platformService.get(GamePlatforms.PP.getCode()); + String apiBaseUrl = platform.getUrlInfo().getUrl(); + String host = platform.getUrlInfo().getHost(); + String https = platform.getUrlInfo().getHttps(); + return new ForestAddress(https, apiBaseUrl, Integer.parseInt(host), "/IntegrationService/v3"); + } +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java b/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java new file mode 100644 index 0000000..8473e17 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/client/PPClient.java @@ -0,0 +1,121 @@ +package com.ff.game.api.pp.client; + +import com.dtflys.forest.annotation.*; +import com.ff.game.api.pp.address.MyPPAddressSource; +import com.ff.game.api.pp.dto.*; +import com.ff.game.api.pp.forest.MySuccessCondition; + +import java.util.Map; + +/** + * pp 请求 + * + * @author shi + * @date 2025/02/10 + */ +@Address(source = MyPPAddressSource.class) +public interface PPClient { + /** + * 创建成员 + * + * @param params 参数 + * @return {@link PPUserAccountResponse } + */ + @Post( url ="/http/CasinoGameAPI/player/account/create/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + PPUserAccountResponse createMember(@Body Map params); + + + /** + * 获取会员信息 + * + * @param params 参数 + * @return {@link PPPlayerAccountResponse } + */ + @Post( url ="/http/CasinoGameAPI/balance/current/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + PPPlayerAccountResponse getMemberInfo(@Body Map params); + + + + /** + * 无重定向登录 + * + * @param params 参数 + * @return {@link PPGameLaunchResponse } + */ + @Post(url ="/http/CasinoGameAPI/game/start/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + PPGameLaunchResponse loginWithoutRedirect(@Body Map params); + + /** + * 获取游戏列表 + * + * @param params 参数 + * @return {@link PPGameResponseDTO } + */ + @Post( url ="/http/CasinoGameAPI/getCasinoGames/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + + PPGameResponseDTO getGameList(@Body Map params); + + /** + * 按代理id进行交换转账 + * + * @param params 参数 + * @return {@link PPTransactionResponse } + */ + @Post(url = "/http/CasinoGameAPI/balance/transfer/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + @Success(condition = MySuccessCondition.class) + PPTransactionResponse exchangeTransfer(@Body Map params); + + /** + * 汇兑转移状态 + * + * @param params 参数 + * @return {@link PPTransactionStatusResponse } + */ + @Post(url = "/http/CasinoGameAPI/balance/transfer/status/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + PPTransactionStatusResponse exchangeTransferStatus( @Body Map params); + + /** + * 按时间获取投注记录 + * + * @param params 参数 + * @return {@link String } + */ + @Get(url ="/DataFeeds/gamerounds/finished/?{params}") + String getBetRecordByTime(@Var("params") String params); + + + /** + * 踢出队员 + * + * @param params 参数 + * @return {@link PPResponse } + */ + @Post(url = "/http/CasinoGameAPI/game/session/terminate/", + headers = { + "Content-type: application/x-www-form-urlencoded" + }) + PPResponse kickMember(@Body Map params); + + + + + +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java new file mode 100644 index 0000000..2fa59c2 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGBetRecordResponseDTO.java @@ -0,0 +1,129 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * dgbet记录响应数据 + * + * @author shi + * @date 2025/03/27 + */ +@Data +public class DGBetRecordResponseDTO { + + /** 错误码 (参考文档定义) */ + @JsonProperty("codeId") + private Integer codeId; + + /** 错误信息 */ + @JsonProperty("msg") + private String msg; + + /** 注单报告 */ + @JsonProperty("list") + private List list; + + /** + * 注单报告类 + */ + @Data + public static class ReportDTO { + + /** 注单ID(唯一) */ + @JsonProperty("id") + private Long id; + + /** 游戏桌号(红包小费记录没有) */ + @JsonProperty("tableId") + private Integer tableId; + + /** 游戏靴号(红包小费记录没有) */ + @JsonProperty("shoeId") + private Long shoeId; + + /** 当靴局号(红包小费记录没有) */ + @JsonProperty("playId") + private Long playId; + + /** 游戏厅号(1:旗舰厅;2:亚洲厅;3,4:现场厅;5:性感厅;8,9:区块链厅) */ + @JsonProperty("lobbyId") + private Integer lobbyId; + + /** 注单类型(1:注单,2:红包小费) */ + @JsonProperty("gameType") + private Integer gameType; + + /** 游戏类型(百家乐,龙虎等) */ + @JsonProperty("gameId") + private Integer gameId; + + /** 下注时间 */ + @JsonProperty("betTime") + private Date betTime; + + /** 结算时间 */ + @JsonProperty("calTime") + private Date calTime; + + /** 派彩金额(含本金) */ + @JsonProperty("winOrLoss") + private BigDecimal winOrLoss; + + /** 下注前余额(仅作参考) */ + @JsonProperty("balanceBefore") + private BigDecimal balanceBefore; + + /** 下注金额(下注扣款金额) */ + @JsonProperty("betPoints") + private BigDecimal betPoints; + + /** 洗码金额(用于计算佣金) */ + @JsonProperty("availableBet") + private BigDecimal availableBet; + + /** 会员账号 */ + @JsonProperty("userName") + private String userName; + + /** 游戏结果 */ + @JsonProperty("result") + private String result; + + /** 注单详情 */ + @JsonProperty("betDetail") + private String betDetail; + + /** 客户端IP */ + @JsonProperty("ip") + private String ip; + + /** 游戏唯一局号 */ + @JsonProperty("ext") + private String ext; + + /** 结算状态(0:未结算,1:已结算,2:已撤销,3:冻结) */ + @JsonProperty("isRevocation") + private Integer isRevocation; + + /** 更改单时对应的注单记录 */ + @JsonProperty("parentBetId") + private Long parentBetId; + + /** 币种ID(请参考对应关系说明) */ + @JsonProperty("currencyId") + private Integer currencyId; + + /** 客户端平台ID(1: PC 2: 安卓 3: 苹果 5: H5) */ + @JsonProperty("deviceType") + private Integer deviceType; + + /** 钱包扣款记录(转账模式API没有) */ + @JsonProperty("transfers") + private String transfers; + } +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java new file mode 100644 index 0000000..576a000 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGLoginWithoutRedirectResponse.java @@ -0,0 +1,74 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 响应结果类 + */ +@Data +public class DGLoginWithoutRedirectResponse { + + /** + * 响应的代码ID + */ + @JsonProperty("codeId") + private int codeId; + + /** + * 响应的消息 + */ + @JsonProperty("msg") + private String msg; + + /** + * 响应的 token + */ + @JsonProperty("token") + private String token; + + /** + * 响应的域名信息 + */ + @JsonProperty("domains") + private String domains; + + /** + * 列表数据 + */ + @JsonProperty("list") + private List list; + + /** + * 限制组 + */ + @JsonProperty("limitGroup") + private String limitGroup; + + /** + * 限制区间 + */ + @JsonProperty("limits") + private List limits; + + /** + * 限制区间内部类 + */ + @Data + public static class Limit { + + /** + * 最小值 + */ + @JsonProperty("min") + private int min; + + /** + * 最大值 + */ + @JsonProperty("max") + private int max; + } +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java new file mode 100644 index 0000000..d7652d3 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGResponse.java @@ -0,0 +1,26 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 响应类,用于返回基本的状态信息。 + * + * @author shi + * @date 2025/03/26 + */ +@Data +public class DGResponse { + + /** + * 响应的代码ID。 + */ + @JsonProperty("codeId") + private int codeId; + + /** + * 响应的消息。 + */ + @JsonProperty("msg") + private String msg; +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java new file mode 100644 index 0000000..195e39e --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGTransactionResponseDTO.java @@ -0,0 +1,45 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 交易信息返回实体 + * + * @author shi + * @date 2025/03/27 + */ +@Data +public class DGTransactionResponseDTO { + + + /** 响应代码 */ + @JsonProperty("codeId") + private Integer codeId; + + /** 响应消息 */ + @JsonProperty("msg") + private String msg; + + /** 用户名 */ + @JsonProperty("username") + private String username; + + /** 交易金额 */ + @JsonProperty("amount") + private BigDecimal amount; + + /** 账户余额 */ + @JsonProperty("balance") + private BigDecimal balance; + + /** 交易流水号 */ + @JsonProperty("serial") + private String serial; + + /** 交易时间 */ + @JsonProperty("time") + private String time; +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java new file mode 100644 index 0000000..e5f6506 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserAccountResponse.java @@ -0,0 +1,40 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 用户账户信息响应类 + * + * @author shi + * @date 2025/03/26 + */ +@Data +public class DGUserAccountResponse { + + /** + * 响应的代码ID。 + */ + @JsonProperty("codeId") + private int codeId; + + /** + * 响应的消息。 + */ + @JsonProperty("msg") + private String msg; + + /** + * 用户名。 + */ + @JsonProperty("username") + private String username; + + /** + * 用户余额。 + */ + @JsonProperty("balance") + private BigDecimal balance; +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java new file mode 100644 index 0000000..13fe63d --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/DGUserListResponseDTO.java @@ -0,0 +1,70 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 用户列表响应类 + * + * @author shi + * @date 2025/03/27 + */ +@Data +public class DGUserListResponseDTO { + + /** + * 响应的代码ID。 + */ + @JsonProperty("codeId") + private int codeId; + + /** + * 响应的消息。 + */ + @JsonProperty("msg") + private String msg; + /** 用户列表 */ + @JsonProperty("list") + private List list; + + /** + * 用户信息类 + */ + @Data + public static class UserDTO { + + /** 用户名 */ + @JsonProperty("username") + private String username; + + /** 昵称 */ + @JsonProperty("nickname") + private String nickname; + + /** 币种名称 */ + @JsonProperty("currencyName") + private String currencyName; + + /** 用户IP */ + @JsonProperty("ip") + private String ip; + + /** 用户设备 */ + @JsonProperty("device") + private String device; + + /** 登录时间 */ + @JsonProperty("login") + private String login; + + /** 会员ID */ + @JsonProperty("memberId") + private Long memberId; + + /** 用户余额 */ + @JsonProperty("balance") + private Double balance; + } +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java new file mode 100644 index 0000000..60af0f4 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameLaunchResponse.java @@ -0,0 +1,35 @@ +package com.ff.game.api.pp.dto; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * ppgame发布响应 + * + * @author shi + * @date 2025/04/14 + */ +@Data +public class PPGameLaunchResponse { + /** + * 响应状态码 + * 示例: "0" 表示成功 + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 + * 示例: "OK" 表示成功 + */ + @JsonProperty("description") + private String description; + + /** + * 游戏启动URL + * 包含token、游戏符号、技术平台、语言、货币等参数 + */ + @JsonProperty("gameURL") + private String gameUrl; +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java new file mode 100644 index 0000000..bd767d6 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameResponseDTO.java @@ -0,0 +1,111 @@ +package com.ff.game.api.pp.dto; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 响应类 - 游戏信息列表 + * + * @author shi + * @date 2025/03/27 + */ +@Data +public class PPGameResponseDTO { + + /** 错误码 (参考文档定义) */ + @JsonProperty("error") + private String error; + + /** 错误信息 */ + @JsonProperty("description") + private String description; + + /** 游戏信息列表 */ + @JsonProperty("gameList") + private List gameList; + + /** + * 游戏信息类 + */ + @Data + public static class CasinoGame { + + /** 游戏唯一标识符 */ + @JsonProperty("gameID") + private String gameID; + + /** 游戏名称 */ + @JsonProperty("gameName") + private String gameName; + + /** 游戏类型标识符 */ + @JsonProperty("gameTypeID") + private String gameTypeID; + + /** 游戏类型描述 */ + @JsonProperty("typeDescription") + private String typeDescription; + + /** 游戏技术描述 */ + @JsonProperty("technology") + private String technology; + + /** 游戏支持的平台 */ + @JsonProperty("platform") + private String platform; + + /** 是否提供演示游戏 */ + @JsonProperty("demoGameAvailable") + private boolean demoGameAvailable; + + /** 游戏宽高比 */ + @JsonProperty("aspectRatio") + private String aspectRatio; + + /** 游戏技术标识符 */ + @JsonProperty("technologyID") + private String technologyID; + + /** 数值型游戏 ID */ + @JsonProperty("gameIdNumeric") + private Long gameIdNumeric; + + /** 是否提供免费回合奖励 */ + @JsonProperty("frbAvailable") + private boolean frbAvailable; + + /** 是否提供可变免费回合奖励 */ + @JsonProperty("variableFrbAvailable") + private boolean variableFrbAvailable; + + /** 游戏的投注线数 */ + @JsonProperty("lines") + private Integer lines; + + /** 游戏支持的功能列表 */ + @JsonProperty("features") + private List features; + + /** 游戏数据类型 */ + @JsonProperty("dataType") + private String dataType; + + /** 支持的司法管辖区列表 */ + @JsonProperty("jurisdictions") + private List jurisdictions; + + + /** + * 系统游戏id + */ + private String systemGameId; + + /** + * 系统游戏类型 + */ + private Integer systemPlatformType; + } +} \ No newline at end of file diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java new file mode 100644 index 0000000..2f7b7ad --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPGameRoundRecord.java @@ -0,0 +1,279 @@ +package com.ff.game.api.pp.dto; + + +import com.ff.base.utils.DateUtils; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * ppgame回合记录 + * + * @author shi + * @date 2025/04/15 + */ +@Data +public class PPGameRoundRecord { + private static final DateTimeFormatter DATE_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 时间戳(Unix毫秒时间戳) + *

记录生成时的系统时间戳,格式为 timepoint=1618298161139

+ */ + private Long timepoint; + + /** + * 玩家在Pragmatic Play系统中的唯一ID + * + * @example 531288 + */ + private String playerID; + + /** + * 娱乐场运营商系统中的玩家ID + * + * @example 889325 + */ + private String extPlayerID; + + /** + * 游戏唯一标识符 + * + * @example "vs20magicpot" + */ + private String gameID; + + /** + * 当前游戏会话ID + *

每个游戏回合的唯一编号

+ * + * @example 50994065 + */ + private String playSessionID; + + /** + * 父游戏会话ID + *

对于免费游戏,此值为触发免费游戏的母游戏ID

+ * + * @example 50994064 + */ + private String parentSessionID; + + /** + * 游戏回合开始时间 + *

格式:yyyy-MM-dd HH:mm:ss

+ * + * @example "2021-04-13 07:06:18" + */ + private Long startDate; + + /** + * 游戏回合结束时间 + *

未完成时为null,格式:yyyy-MM-dd HH:mm:ss

+ * + * @example "2021-04-13 07:06:18" + */ + private Long endDate; + + /** + * 游戏状态 + *

可能值: + *

    + *
  • C - 已完成
  • + *
  • F - 取消/最终确定
  • + *
+ *

+ * + * @example "C" + */ + private String status; + + /** + * 游戏类型 + *

可能值: + *

    + *
  • F - 免费旋转
  • + *
+ *

+ * + * @example "F" + */ + private String type; + + /** + * 投注金额(固定2位小数) + * + * @example 0.00 + */ + private BigDecimal bet; + + /** + * 赢取金额(固定2位小数) + * + * @example 0.00 + */ + private BigDecimal win; + + /** + * 货币代码(ISO 3字母) + * + * @example "EUR" + */ + private String currency; + + /** + * 累积奖金金额(固定2位小数) + * + * @example 0.00 + */ + private BigDecimal jackpot; + + /** + * 免费回合奖励ID + *

当值为"null"字符串时表示无奖励

+ * + * @example "null" + */ + private String bonusCode; + + /** + * 奖金投注金额(固定2位小数) + * + * @example 0.00 + */ + private BigDecimal bonusBet; + + /** + * 奖金赢取金额(固定2位小数) + * + * @example 0.00 + */ + private BigDecimal bonusWin; + + public static List parse(String data) { + List transactions = new ArrayList<>(); + + // 按行分割数据 + String[] lines = data.split("\n"); + + if (lines.length < 3) { + return new ArrayList<>(); + } + + + // 解析列名(第二行) + String[] headers = lines[1].trim().split(","); + + for (int j = 2; j < lines.length; j++) { + // 解析数据行(第三行开始) + String[] values = lines[j].trim().split(","); + + // 确保列数和值数匹配 + if (headers.length != values.length) { + continue; + } + + // 创建交易记录对象 + PPGameRoundRecord transaction = new PPGameRoundRecord(); + + // 根据列名设置对应的值 + for (int i = 0; i < headers.length; i++) { + String header = headers[i].trim(); + String value = values[i].trim(); + + switch (header) { + case "playerID": + transaction.setPlayerID(value); + break; + case "extPlayerID": + transaction.setExtPlayerID(value); + break; + case "gameID": + transaction.setGameID(value); + break; + case "playSessionID": + transaction.setPlaySessionID(value); + break; + case "parentSessionID": + transaction.setParentSessionID(value); + break; + case "startDate": + transaction.setStartDate(DateUtils.toTimestamp(value, DateUtils.YYYY_MM_DD_HH_MM_SS)); + break; + case "endDate": + transaction.setEndDate(DateUtils.toTimestamp(value, DateUtils.YYYY_MM_DD_HH_MM_SS)); + break; + case "status": + transaction.setStatus(value); + break; + case "type": + transaction.setType(value); + break; + case "bet": + transaction.setBet(new BigDecimal(value)); + break; + case "win": + transaction.setWin(new BigDecimal(value)); + break; + case "currency": + transaction.setCurrency(value); + break; + case "jackpot": + transaction.setJackpot(new BigDecimal(value)); + break; + case "bonusCode": + transaction.setBonusCode("null".equals(value) ? null : value); + break; + case "bonusBet": + transaction.setBonusBet(new BigDecimal(value)); + break; + case "bonusWin": + transaction.setBonusWin(new BigDecimal(value)); + break; + + } + } + + transactions.add(transaction); + } + + return transactions; + } + + + /** + * 解析长 + * + * @param s s + * @return {@link Long } + */// 辅助解析方法 + private static Long parseLong(String s) { + return s.isEmpty() ? null : Long.parseLong(s); + } + + /** + * 解析双精度 + * + * @param s s + * @return {@link BigDecimal } + */ + private static BigDecimal parseDouble(String s) { + return s.isEmpty() ? null : new BigDecimal(s); + } + + /** + * 解析日期时间 + * + * @param s s + * @return {@link LocalDateTime } + */ + private static Date parseDateTime(String s) { + return s.isEmpty() ? null : DateUtils.parseDate(s); + } +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java new file mode 100644 index 0000000..fec9362 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPPlayerAccountResponse.java @@ -0,0 +1,35 @@ +package com.ff.game.api.pp.dto; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 玩家账户信息响应实体 + */ +@Data +public class PPPlayerAccountResponse { + + /** + * 响应状态码 (必需) + * 示例: "0" 表示成功 + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 (必需) + * 示例: "OK" 表示成功 + */ + @JsonProperty("description") + private String description; + + /** + * 玩家余额 (必需) + * 示例: 999.99 + */ + @JsonProperty("balance") + private BigDecimal balance; +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java new file mode 100644 index 0000000..3903c35 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPResponse.java @@ -0,0 +1,30 @@ +package com.ff.game.api.pp.dto; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * ppgame发布响应 + * + * @author shi + * @date 2025/04/14 + */ +@Data +public class PPResponse { + /** + * 响应状态码 + * 示例: "0" 表示成功 + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 + * 示例: "OK" 表示成功 + */ + @JsonProperty("description") + private String description; + + +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java new file mode 100644 index 0000000..be0b8e2 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionResponse.java @@ -0,0 +1,43 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 回复 + * + * @author shi + * @date 2025/04/14 + */ +@Data +public class PPTransactionResponse { + /** + * 响应状态码 (必需) + * 示例: "0" 表示成功 + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 (必需) + * 示例: "OK" 表示成功 + */ + @JsonProperty("description") + private String description; + + /** + * 交易ID (必需) + * 示例: 1908759 + */ + @JsonProperty("transactionId") + private String transactionId; + + /** + * 账户余额 (必需) + * 示例: 999.99 + */ + @JsonProperty("balance") + private BigDecimal balance; +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java new file mode 100644 index 0000000..ac29195 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPTransactionStatusResponse.java @@ -0,0 +1,90 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 替换 + * + * @author shi + * @date 2025/04/14 + */ +@Data +public class PPTransactionStatusResponse { + /** + * 响应状态码 + *

+ * 遵循系统定义的错误码规范: + *

    + *
  • "0" - 表示成功
  • + *
  • 其他值 - 表示具体错误类型
  • + *
+ *

+ * + * @apiNote 必填字段 + * @example "0" + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 + *

人类可读的状态说明信息

+ * + * @apiNote 必填字段 + * @example "OK" + */ + @JsonProperty("description") + private String description; + + /** + * 交易唯一标识符 + *

系统生成的交易流水号,用于后续查询和跟踪

+ * + * @apiNote 必填字段 + * @example 1908759 + */ + @JsonProperty("transactionId") + private Long transactionId; + + /** + * 交易状态 + *

+ * 可能的值包括: + *

    + *
  • "Success" - 交易成功
  • + *
  • "Pending" - 处理中
  • + *
  • "Failed" - 交易失败
  • + *
+ *

+ * + * @apiNote 必填字段 + * @example "Success" + */ + @JsonProperty("status") + private String status; + + /** + * 交易金额 + *

+ * 字符串格式的金额数值,保留两位小数。 + *

+ * + * @apiNote 必填字段 + * @example "999.99" + */ + @JsonProperty("amount") + private BigDecimal amount; + + /** + * 账户余额 + *

交易完成后的实时余额,使用BigDecimal保证精度

+ * + * @apiNote 可选字段(某些查询接口可能返回null) + * @example 999.99 + */ + @JsonProperty("balance") + private BigDecimal balance; +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java new file mode 100644 index 0000000..fc9c1d6 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/dto/PPUserAccountResponse.java @@ -0,0 +1,33 @@ +package com.ff.game.api.pp.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 娱乐场运营商系统用户账户响应实体 + * 严格保密 - 仅用于预期目的和接收者! + */ +@Data +public class PPUserAccountResponse { + + /** + * 响应状态码 (必需) + * 示例: "0" 表示成功 + */ + @JsonProperty("error") + private String error; + + /** + * 响应状态描述 (必需) + * 示例: "OK" 表示成功 + */ + @JsonProperty("description") + private String description; + + /** + * 玩家唯一标识符 (必需) + * 示例: 6749178 + */ + @JsonProperty("playerId") + private Long playerId; +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java b/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java new file mode 100644 index 0000000..6cfc231 --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/forest/MySuccessCondition.java @@ -0,0 +1,31 @@ +package com.ff.game.api.pp.forest; + + +import com.dtflys.forest.callback.SuccessWhen; +import com.dtflys.forest.http.ForestRequest; +import com.dtflys.forest.http.ForestResponse; + +/** + * 我kmsuccess状态 + * + * @author shi + * @date 2025/04/02 + */ +public class MySuccessCondition implements SuccessWhen { + + /** + * 请求成功条件 + * @param req Forest请求对象 + * @param res Forest响应对象 + * @return 是否成功,true: 请求成功,false: 请求失败 + */ + @Override + public boolean successWhen(ForestRequest req, ForestResponse res) { + // req 为Forest请求对象,即 ForestRequest 类实例 + // res 为Forest响应对象,即 ForestResponse 类实例 + // 返回值为 ture 则表示请求成功,false 表示请求失败 + return res.noException(); + // 当然在这里也可以写其它条件,比如 通过 res.getResult() 或 res.getContent() 获取业务数据 + // 再根据业务数据判断是否成功 + } +} diff --git a/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java b/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java new file mode 100644 index 0000000..4df520d --- /dev/null +++ b/ff-game/src/main/java/com/ff/game/api/pp/impl/GamesPPServiceImpl.java @@ -0,0 +1,645 @@ +package com.ff.game.api.pp.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +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.utils.DateUtils; +import com.ff.base.utils.JsonUtil; +import com.ff.base.utils.StringUtils; +import com.ff.base.utils.sign.Md5Utils; +import com.ff.game.api.IGamesService; +import com.ff.game.api.pp.client.PPClient; +import com.ff.game.api.pp.dto.*; +import com.ff.game.api.request.*; +import com.ff.game.domain.*; +import com.ff.game.service.IGameBettingDetailsService; +import com.ff.game.service.IGameExchangeMoneyService; +import com.ff.game.service.IGameFreeRecordService; +import com.ff.game.service.IGameService; +import com.ff.member.domain.Member; +import com.ff.member.service.IMemberService; +import com.ff.utils.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +/** + * DG 游戏 impl + * + * @author shi + * @date 2024/11/12 + */ +@Service("PPService") +@Slf4j +public class GamesPPServiceImpl implements IGamesService { + + + @Resource + private RedisCache gRedisCache; + + @Resource + private IGameService gameService; + + @Resource + private IMemberService memberService; + + @Resource + private IGameFreeRecordService gameFreeRecordService; + + @Resource + private PPClient ppClient; + + @Resource + private IGameBettingDetailsService gameBettingDetailsService; + + + @Resource + private IGameExchangeMoneyService gameExchangeMoneyService; + + + + /** + * 获得就是成功 + * + * @param errorCode 错误代码 + * @return {@link Boolean } + */ + private Boolean getIsSuccess(String errorCode) { + ApiException.isTrue(!"7".equals(errorCode), ErrorCode.INSUFFICIENT_PLAYER_BALANCE.getCode()); + return "0".equals(errorCode); + } + + + /** + * 获取密钥 + * + * @param gamesBaseRequestDTO 游戏请求dto + * @return {@link String } + */ + private String getKey(GamesBaseRequestDTO gamesBaseRequestDTO) { + //取出对应的key跟密钥跟请求参数 + String agentKey = gamesBaseRequestDTO.getAgentKey(); + + return Md5Utils.md5New(gamesBaseRequestDTO.getQuery() + agentKey); + } + + /** + * 转换为ingress + * + * @param terminalTypes 终端类型 + * @return {@link Integer } + */ + public Integer convertToIngress(String terminalTypes) { + if (terminalTypes == null || terminalTypes.isEmpty()) { + return null; + } + + boolean hasMobile = terminalTypes.contains("MOBILE"); + boolean hasWeb = terminalTypes.contains("WEB"); + + if (hasMobile && hasWeb) { + return IngressType.PC_AND_MOBILE_WEB.getValue(); + } else if (hasMobile) { + return IngressType.MOBILE_WEB.getValue(); + } else if (hasWeb) { + return IngressType.PC_WEB.getValue(); + } else { + return null; + } + } + + + /** + * 创建成员 + * + * @param createMemberRequestDTO 创建成员请求dto + * @return {@link Boolean } + */ + @Override + public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) { + log.info("GamesPPServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO); + Map params = new TreeMap<>(); + params.put("externalPlayerId", createMemberRequestDTO.getAccount()); + params.put("secureLogin", createMemberRequestDTO.getAgentId()); + params.put("currency", createMemberRequestDTO.getCurrency()); + String query = JsonUtil.mapToQueryString(params); + createMemberRequestDTO.setQuery(query); + String key = this.getKey(createMemberRequestDTO); + params.put("hash", key); + PPUserAccountResponse member = ppClient.createMember(params); + if (this.getIsSuccess(member.getError())) { + return Boolean.TRUE; + } + //判断是否获取成功 + return Boolean.FALSE; + } + + + /** + * 获取会员信息 + * + * @param memberInfoRequestDTO 会员信息请求dto + * @return {@link MemberInfoResponseDTO } + */ + @Override + public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) { + log.info("GamesPPServiceImpl [loginWithoutRedirect] 请求参数 {}", memberInfoRequestDTO); + Map params = new TreeMap<>(); + params.put("secureLogin", memberInfoRequestDTO.getAgentId()); + params.put("externalPlayerId", memberInfoRequestDTO.getAccounts()); + String query = JsonUtil.mapToQueryString(params); + memberInfoRequestDTO.setQuery(query); + String key = this.getKey(memberInfoRequestDTO); + params.put("hash", key); + PPPlayerAccountResponse memberInfo = ppClient.getMemberInfo(params); + if (this.getIsSuccess(memberInfo.getError())) { + return MemberInfoResponseDTO.builder().account(memberInfoRequestDTO.getAccounts()) + .balance(memberInfo.getBalance()) + .status(GameMemberStatus.UNKNOWN.getCode()).build(); + } + throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode()); + } + + + /** + * 无重定向登录 + * + * @param gamesLogin 游戏登录 + * @return {@link String } + */ + @Override + public String loginWithoutRedirect(GamesLogin gamesLogin) { + log.info("GamesPPServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin); + + Map params = new TreeMap<>(); + params.put("secureLogin", gamesLogin.getAgentId()); + params.put("externalPlayerId", gamesLogin.getAccount()); + params.put("gameId", gamesLogin.getGameId()); + params.put("language", gamesLogin.getLang()); + if (!StringUtils.isEmpty(gamesLogin.getPlatform())) { + if ("web".equalsIgnoreCase(gamesLogin.getPlatform())) { + params.put("platform", "WEB"); + } else { + params.put("platform", "MOBILE"); + } + + } + params.put("lobbyURL", gamesLogin.getHomeUrl()); + String query = JsonUtil.mapToQueryString(params); + gamesLogin.setQuery(query); + String key = this.getKey(gamesLogin); + params.put("hash", key); + PPGameLaunchResponse ppGameLaunchResponse = ppClient.loginWithoutRedirect(params); + + + if (this.getIsSuccess(ppGameLaunchResponse.getError())) { + return ppGameLaunchResponse.getGameUrl(); + } else { + throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode()); + } + } + + + /** + * 获取游戏列表 + * + * @param gamesBaseRequestDTO 游戏请求dto + * @return {@link String } + */ + @Transactional + @Override + public String getGameList(GamesBaseRequestDTO gamesBaseRequestDTO) { + List gamesDatas = gRedisCache.getCacheList(CacheConstants.PP_GAMES); + if (!CollectionUtils.isEmpty(gamesDatas)) { + return CacheConstants.PP_GAMES; + } + + + Map params = new LinkedHashMap<>(); + params.put("secureLogin", gamesBaseRequestDTO.getAgentId()); + String query = JsonUtil.mapToQueryString(params); + gamesBaseRequestDTO.setQuery(query); + String key = this.getKey(gamesBaseRequestDTO); + params.put("hash", key); + + PPGameResponseDTO gameList = ppClient.getGameList(params); + if (this.getIsSuccess(gameList.getError())) { + for (PPGameResponseDTO.CasinoGame apiGameInfoResponseDTO : gameList.getGameList()) { + Integer platformType = PPGameType.findSystemByCode(apiGameInfoResponseDTO.getGameTypeID()); + //不支持的游戏跳过 + if (ObjectUtils.isEmpty(platformType)) { + continue; + } + Integer ingress = convertToIngress(apiGameInfoResponseDTO.getPlatform()); + //不需要的平台 + if (ObjectUtils.isEmpty(ingress)) { + continue; + } + + Game game = Game.builder() + .platformCode(GamePlatforms.PP.getCode()) + .platformType(platformType) + .gameCode(apiGameInfoResponseDTO.getGameID()) + .build(); + List games = gameService.selectGameList(game); + //不存在这个游戏 + if (CollectionUtils.isEmpty(games)) { + game.setIngress(ingress); + game.setGameSourceType(apiGameInfoResponseDTO.getGameTypeID()); + game.setFreespin(Boolean.FALSE); + game.setDemoStatus(Boolean.FALSE); + game.setSortNo(gameService.selectMaxSortNo(platformType, GamePlatforms.PP.getCode()) + 1); + game.setGameName(apiGameInfoResponseDTO.getGameName()); + game.setCreateBy("system"); + game.setPlatformCode(GamePlatforms.PP.getCode()); + game.setPlatformType(platformType); + game.setGameId(StringUtils.addSuffix(GamePlatforms.PP.getCode(), apiGameInfoResponseDTO.getGameID())); + List nameInfos = new ArrayList<>(); + nameInfos.add(new NameInfo(apiGameInfoResponseDTO.getGameName(), "en-US")); + game.setNameInfo(nameInfos); + gameService.insertGame(game); + } else { + game = games.get(0); + } + apiGameInfoResponseDTO.setSystemGameId(game.getGameId()); + apiGameInfoResponseDTO.setSystemPlatformType(platformType); + + } + + gRedisCache.deleteObject(CacheConstants.PP_GAMES); + gRedisCache.setCacheList(CacheConstants.PP_GAMES, gameList.getGameList()); + gRedisCache.expire(CacheConstants.PP_GAMES, 5L, TimeUnit.HOURS); + } else { + throw new BaseException(gameList.getDescription()); + } + + return CacheConstants.PP_GAMES; + } + + /** + * 获取交易id + * + * @param transactionIdRequestDTO 事务id请求dto + * @return {@link String } + */ + @Override + public String getTransactionId(TransactionIdRequestDTO transactionIdRequestDTO) { + return GamePlatforms.PP.getCode() + IdUtil.getSnowflakeNextIdStr(); + } + + /** + * 按代理id进行交换转账 + * + * @param exchangeTransferMoneyRequestDTO 外汇转账moeny dto + * @return {@link Long } + */ + @Override + @Transactional + public Long exchangeTransferByAgentId(ExchangeTransferMoneyRequestDTO exchangeTransferMoneyRequestDTO) { + log.info("GamesKMServiceImpl [exchangeTransferByAgentId] 请求参数 {}", exchangeTransferMoneyRequestDTO); + + + GameExchangeMoney exchangeMoney = gameExchangeMoneyService.selectGameExchangeMoneyById(exchangeTransferMoneyRequestDTO.getGameExchangeId()); + + + BigDecimal amount = exchangeTransferMoneyRequestDTO.getAmount(); + if (TransferType.ALL.getCode().equals(exchangeTransferMoneyRequestDTO.getTransferType())) { + // 获取第三方钱包余额 + MemberInfoRequestDTO gamesBaseRequestDTO = MemberInfoRequestDTO.builder() + .accounts(exchangeTransferMoneyRequestDTO.getAccount()) + .agentId(exchangeTransferMoneyRequestDTO.getAgentId()) + .currency(exchangeTransferMoneyRequestDTO.getCurrency()) + .agentKey(exchangeTransferMoneyRequestDTO.getAgentKey()) + .build(); + + amount = this.getMemberInfo(gamesBaseRequestDTO).getBalance().negate(); + } + + Map params = new TreeMap<>(); + params.put("secureLogin", exchangeTransferMoneyRequestDTO.getAgentId()); + params.put("externalPlayerId", exchangeTransferMoneyRequestDTO.getAccount()); + params.put("externalTransactionId", exchangeTransferMoneyRequestDTO.getTransactionId()); + params.put("amount", amount); + String query = JsonUtil.mapToQueryString(params); + exchangeTransferMoneyRequestDTO.setQuery(query); + String key = this.getKey(exchangeTransferMoneyRequestDTO); + params.put("hash", key); + + PPTransactionResponse transactionResponse = ppClient.exchangeTransfer(params); + //判断是否转移成功 + if (this.getIsSuccess(transactionResponse.getError())) { + amount = amount.abs(); + //更新数据 + exchangeMoney.setBalance(amount); + exchangeMoney.setCoinBefore(NumberUtil.sub(amount, transactionResponse.getBalance()).abs()); + exchangeMoney.setCoinAfter(transactionResponse.getBalance()); + exchangeMoney.setCurrencyBefore(exchangeMoney.getCoinBefore()); + exchangeMoney.setCurrencyAfter(exchangeMoney.getCoinAfter()); + exchangeMoney.setStep(GameExchangeStep.PLATFORM_TRANSACTION.getCode()); + exchangeMoney.setStepStatus(GameExchangeStepStatus.SUCCESS.getCode()); + exchangeMoney.setPartyTransactionId(transactionResponse.getTransactionId()); + gameExchangeMoneyService.updateGameExchangeMoney(exchangeMoney); + } else { + + exchangeMoney.setStep(GameExchangeStep.PLATFORM_TRANSACTION.getCode()); + exchangeMoney.setStepStatus(GameExchangeStepStatus.FAILURE.getCode()); + gameExchangeMoneyService.updateGameExchangeMoney(exchangeMoney); + log.error("GamesPPServiceImpl [exchangeTransferByAgentId] 金额转移失败,错误代码{},错误信息{}", transactionResponse.getError(), transactionResponse.getDescription()); + throw new ApiException(ErrorCode.BALANCE_TRANSFER_FAILED.getCode()); + } + + return exchangeMoney.getId(); + } + + /** + * 汇兑转移状态 + * + * @param exchangeTransferMoneyRequestDTO 兑换转账请求dto + * @return {@link Boolean } + */ + @Override + public ExchangeTransferStatusResponseDTO exchangeTransferStatus(ExchangeTransferStatusRequestDTO exchangeTransferMoneyRequestDTO) { + log.info("GamesPPServiceImpl [exchangeTransferStatus] 请求参数 {}", exchangeTransferMoneyRequestDTO); + GameExchangeMoney exchangeMoney = gameExchangeMoneyService.selectGameExchangeMoneyById(exchangeTransferMoneyRequestDTO.getGameExchangeMoneyId()); + + Map params = new TreeMap<>(); + params.put("secureLogin", exchangeTransferMoneyRequestDTO.getAgentId()); + params.put("externalTransactionId", StringUtils.isEmpty(exchangeMoney.getPartyTransactionId()) ? "0" : exchangeMoney.getPartyTransactionId()); + params.put("externalPlayerId", exchangeTransferMoneyRequestDTO.getOrderId()); + String query = JsonUtil.mapToQueryString(params); + exchangeTransferMoneyRequestDTO.setQuery(query); + String key = this.getKey(exchangeTransferMoneyRequestDTO); + params.put("hash", key); + + PPTransactionStatusResponse ppTransactionStatusResponse = ppClient.exchangeTransferStatus(params); + if (this.getIsSuccess(ppTransactionStatusResponse.getError())) { + Integer status = StatusType.IN_PROGRESS.getValue(); + if ("Success".equals(ppTransactionStatusResponse.getStatus())) { + status = StatusType.SUCCESS.getValue(); + } else { + status = StatusType.FAILURE.getValue(); + } + return ExchangeTransferStatusResponseDTO.builder() + .statusType(status) + .balance(ppTransactionStatusResponse.getAmount()) + .coinAfter(ppTransactionStatusResponse.getBalance()) + .build(); + } else { + log.error("GamesPPServiceImpl [exchangeTransferStatus] 金额转移确认失败,错误代码{},错误信息{}", ppTransactionStatusResponse.getError(), ppTransactionStatusResponse.getDescription()); + return ExchangeTransferStatusResponseDTO.builder() + .statusType(StatusType.FAILURE.getValue()) + .build(); + } + + } + + + /** + * 按时间获取投注记录 + * + * @param betRecordByTimeDTO 按时间dto投注记录 + * @return {@link List }<{@link GameBettingDetails }> + */ + @Override + public Boolean getBetRecordByTime(BetRecordByTimeDTO betRecordByTimeDTO) { + Long timepoint = gRedisCache.getCacheObject(CacheConstants.PP_TIME_POINT); + if (ObjectUtils.isEmpty(timepoint)) { + timepoint = betRecordByTimeDTO.getStartTime(); + } + + //请求参数 + log.info("GamesPPServiceImpl [getBetRecordByTime] 请求参数 {}", betRecordByTimeDTO); + Map params = new LinkedHashMap<>(); + params.put("login", betRecordByTimeDTO.getAgentId()); + params.put("password", betRecordByTimeDTO.getAgentKey()); + params.put("timepoint", timepoint); + String result = ppClient.getBetRecordByTime(JsonUtil.mapToQueryString(params)); + this.batchInsert(PPGameRoundRecord.parse(result), betRecordByTimeDTO); + //重新保存最新的时间 + String[] lines = result.split("\n"); + timepoint = Long.parseLong(lines[0].replace("timepoint=", "")); + gRedisCache.setCacheObject(CacheConstants.PP_TIME_POINT, timepoint); + + return Boolean.TRUE; + + } + + + /** + * 按历史时间获取投注记录 + * + * @param betRecordByTimeDTO 按时间dto投注记录 + * @return {@link Boolean } + */ + @Override + public Boolean getBetRecordByHistoryTime(BetRecordByTimeDTO betRecordByTimeDTO) { + + //请求参数 + log.info("GamesPPServiceImpl [getBetRecordByHistoryTime] 请求参数 {}", betRecordByTimeDTO); + long timepoint = betRecordByTimeDTO.getStartTime(); + while (timepoint < betRecordByTimeDTO.getEndTime()) { + Map params = new LinkedHashMap<>(); + params.put("login", betRecordByTimeDTO.getAgentId()); + params.put("password", betRecordByTimeDTO.getAgentKey()); + params.put("timepoint", timepoint); + String result = ppClient.getBetRecordByTime(JsonUtil.mapToQueryString(params)); + this.batchInsert(PPGameRoundRecord.parse(result), betRecordByTimeDTO); + //重新保存最新的时间 + String[] lines = result.split("\n"); + timepoint = Long.parseLong(lines[0].replace("timepoint=", "")); + } + + + return Boolean.TRUE; + } + + /** + * 赠送免费局数 + * + * @param createFreeSpinRequest 创建自由旋转请求 + * @return {@link Boolean } + */ + @Override + public Boolean createFreeSpin(CreateFreeSpinRequestDTO createFreeSpinRequest) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + /** + * 获取游戏详细信息 + * + * @param getGameDetailRequestDTO 获取游戏详细信息请求dto + * @return {@link GetGameDetailResponseDTO } + */ + @Override + public GetGameDetailResponseDTO getGameDetail(GetGameDetailRequestDTO getGameDetailRequestDTO) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + /** + * 强制会员从游戏注销 + * + * @param kickMemberRequestDTO 踢会员请求dto + * @return {@link Boolean } + */ + @Override + public Boolean kickMember(KickMemberRequestDTO kickMemberRequestDTO) { + log.info("GamesPPServiceImpl [kickMember] 请求参数 {}", kickMemberRequestDTO); + Map params = new TreeMap<>(); + params.put("secureLogin", kickMemberRequestDTO.getAgentId()); + params.put("externalPlayerId", kickMemberRequestDTO.getAccount()); + String query = JsonUtil.mapToQueryString(params); + kickMemberRequestDTO.setQuery(query); + String key = this.getKey(kickMemberRequestDTO); + params.put("hash", key); + PPResponse kickMember = ppClient.kickMember(params); + if (this.getIsSuccess(kickMember.getError())) { + return Boolean.TRUE; + } else { + throw new ApiException(ErrorCode.KICK_OUT_AILED.getCode()); + } + } + + /** + * 踢成员全部 + * + * @param kickMemberAllDTO 踢成员全部dto + * @return {@link Boolean } + */ + @Override + public Boolean kickMemberAll(KickMemberAllDTO kickMemberAllDTO) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + /** + * 免费游戏玩家使用的纪录 + * + * @param getFreeSpinDashflowRequestDTO 获取自由旋转dashflow请求dto + * @return {@link List }<{@link GameFreeRecord }> + */ + @Override + public List getFreeSpinDashflow(GetFreeSpinDashflowRequestDTO getFreeSpinDashflowRequestDTO) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + /** + * 取消赠送免费局数 + * + * @param cancelFreeSpinRequestDTO 取消免费旋转请求 + * @return {@link Boolean } + */ + @Override + public Boolean cancelFreeSpin(CancelFreeSpinRequestDTO cancelFreeSpinRequestDTO) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + @Override + public GameDemoLoginResponseDTO gameDemoLogin(GameDemoLoginRequestDTO gameDemoLoginRequestDTO) { + throw new ApiException(ErrorCode.PLATFORM_NOT_METHODS.getCode()); + } + + + /** + * 批量插入 + */ + private synchronized void batchInsert(List report, GamesBaseRequestDTO gamesBaseRequestDTO) { + List gameBettingDetails = new ArrayList<>(); + List wagersIds = new ArrayList<>(); + //数据转化 + for (PPGameRoundRecord bean : report) { + GameBettingDetails bettingDetails = this.dataBuild(GamesDataBuildDTO.builder().platform(gamesBaseRequestDTO.getVendor()).data(bean).build()); + if (!ObjectUtils.isEmpty(bettingDetails)) { + bettingDetails.setId(IdUtil.getSnowflakeNextId()); + gameBettingDetails.add(bettingDetails); + } + wagersIds.add(String.valueOf(bean.getPlaySessionID())); + } + if (!CollectionUtils.isEmpty(gameBettingDetails)) { + //查询重复数据id + List removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds, GamePlatforms.PP.getCode()); + //用steam流清除list中与wagersIds集合相同的数据 + gameBettingDetails = gameBettingDetails.stream() + .filter(detail -> !removeWagersIds.contains(detail.getWagersId())) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(gameBettingDetails)) { + gameBettingDetailsService.batchInsert(gameBettingDetails); + } + } + + } + + /** + * 数据构建 + * + * @param gamesDataBuildDTO 数据 + * @return {@link GameBettingDetails } + */ + @Override + public GameBettingDetails dataBuild(GamesDataBuildDTO gamesDataBuildDTO) { + //转化类 + PPGameRoundRecord resultBean = (PPGameRoundRecord) gamesDataBuildDTO.getData(); + + + Member member = memberService.selectMemberByGameAccount(resultBean.getExtPlayerID()); + if (ObjectUtils.isEmpty(member)) { + return null; + } + List gamesDatas = gRedisCache.getCacheList(CacheConstants.PP_GAMES); + Map dataDTOMap = gamesDatas.stream().collect(Collectors.toMap( + PPGameResponseDTO.CasinoGame::getGameID, + e -> e, + (existing, replacement) -> existing + )); + PPGameResponseDTO.CasinoGame casinoGame = dataDTOMap.get(resultBean.getGameID()); + + BigDecimal payoffAmount = NumberUtil.sub(resultBean.getWin(), resultBean.getBet()); + Integer gameStatus = GameStatus.FLAT.getCode(); + if (payoffAmount.compareTo(BigDecimal.ZERO) > 0) { + gameStatus = GameStatus.WIN.getCode(); + } else if (payoffAmount.compareTo(BigDecimal.ZERO) < 0) { + gameStatus = GameStatus.FAIL.getCode(); + } + + + //数据构造 + GameBettingDetails gameBettingDetails = GameBettingDetails.builder() + .tenantKey(member.getTenantKey()) + //保存我们的币种id + .currencyCode(gamesDataBuildDTO.getPlatform().getOurCurrency(resultBean.getCurrency())) + .memberId(member.getId()) + .gameCode(resultBean.getGameID()) + .gameType(casinoGame.getSystemPlatformType()) + .platformCode(GamePlatforms.PP.getInfo()) + .gameId(casinoGame.getSystemGameId()) + .gameName(casinoGame.getGameName()) + .gameStatus(gameStatus) + .gameStatusType("F".equals(resultBean.getType()) ? 19 : 1) + .gameCurrencyCode(resultBean.getCurrency()) + .account(resultBean.getExtPlayerID()) + .wagersId(resultBean.getPlaySessionID()) + .wagersTime(resultBean.getStartDate()) + .betAmount(resultBean.getBet()) + .payoffTime(resultBean.getEndDate()) + .payoffAmount(payoffAmount.abs()) + .settlementTime(resultBean.getEndDate()) + .turnover(resultBean.getBet()) + .settlementStatus(SettlementStatusEnum.COMPLETED.getCode()) + .build(); + gameBettingDetails.setCreateBy(Constants.SYSTEM); + gameBettingDetails.setCreateTime(DateUtils.getNowDate()); + return gameBettingDetails; + } +} diff --git a/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java b/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java index 336dd12..db296fe 100644 --- a/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java +++ b/ff-game/src/main/java/com/ff/game/domain/GameExchangeMoney.java @@ -99,4 +99,9 @@ public class GameExchangeMoney extends BaseEntity * 步骤状态 GameExchangeStepStatus 枚举 */ private Integer stepStatus; + + /** + * 部分平台使用 第三方平台交易id + */ + private String partyTransactionId; }