diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameBaishunRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameBaishunRestController.java index 07fb4130..ca467d29 100644 --- a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameBaishunRestController.java +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameBaishunRestController.java @@ -1,6 +1,8 @@ package com.red.circle.other.adapter.app.game.party3rd; import com.red.circle.component.game.baishun.GameBaishunService; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd; +import com.red.circle.other.app.service.game.override.service.GameBaishunReportService; import com.red.circle.component.game.baishun.request.BaishunChangeCurrencyRequest; import com.red.circle.component.game.baishun.request.BaishunTokenRequest; import com.red.circle.component.game.baishun.request.BaishunTokenUpdateRequest; @@ -32,6 +34,7 @@ import org.springframework.web.bind.annotation.RestController; public class GameBaishunRestController extends BaseController { private final GameBaishunService gameBaishunService; + private final GameBaishunReportService gameBaishunReportService; /** * 获取 SSToken: BAISHUN 服务器端获取调⽤接⼝的⻓期令牌(ss_token. @@ -69,4 +72,13 @@ public class GameBaishunRestController extends BaseController { return gameBaishunService.changeBalance(request); } + /** + * 游戏状态上报(game_start / game_settle). + */ + @IgnoreResultResponse + @PostMapping("/report") + public BaishunResponse report(@RequestBody @Validated GameBaishunReportCmd cmd) { + return gameBaishunReportService.report(cmd); + } + } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/GameBaishunReportCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/GameBaishunReportCmdExe.java new file mode 100644 index 00000000..fbd58c57 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/GameBaishunReportCmdExe.java @@ -0,0 +1,165 @@ +package com.red.circle.other.app.command.game; + +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.red.circle.auth.inner.endpoint.AuthClient; +import com.red.circle.component.game.baishun.error.BaishunErrorCode; +import com.red.circle.component.game.baishun.props.GameBaishunProperties; +import com.red.circle.component.game.baishun.request.BaishunUserProfileRequest; +import com.red.circle.component.game.baishun.response.BaishunResponse; +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.framework.core.security.UserCredential; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd.GameSettleInfo; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd.GameStartInfo; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd.PlayerResultInfo; +import com.red.circle.tool.core.parse.DataTypeUtils; +import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient; +import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd; +import com.red.circle.wallet.inner.model.enums.GoldOrigin; +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * Baishun游戏状态上报处理. + * + * @author tf + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GameBaishunReportCmdExe { + + private static final String REPORT_TYPE_START = "game_start"; + private static final String REPORT_TYPE_SETTLE = "game_settle"; + + private final ObjectMapper objectMapper; + private final GameBaishunProperties gameBaishunProperties; + private final WalletGoldClient walletGoldClient; + private final AuthClient authClient; + + public BaishunResponse execute(GameBaishunReportCmd cmd) { + if (!checkTokenStrict(cmd)) { + log.error("BaishunReportCmdExe execute checkTokenStrict fail !"); + } + + + log.info("Baishun game report: type={}, userId={}", cmd.getReportType(), cmd.getUserId()); + log.error("Baishun game report: {}", JSONObject.toJSONString(cmd)); + + /*switch (cmd.getReportType()) { + case REPORT_TYPE_START: + return handleGameStart(cmd); + case REPORT_TYPE_SETTLE: + return handleGameSettle(cmd); + default: + log.warn("Unknown report_type: {}", cmd.getReportType()); + return BaishunResponse.fail(BaishunErrorCode.DATE_ERROR); + }*/ + return BaishunResponse.success(null); + } + + private BaishunResponse handleGameStart(GameBaishunReportCmd cmd) { + GameStartInfo startInfo = parseReportMsg(cmd.getReportMsg(), GameStartInfo.class); + if (startInfo == null) { + return BaishunResponse.fail(BaishunErrorCode.READ_DATE_ERROR); + } + + // 扣除开局 bet(非AI玩家) + if (startInfo.getPlayers() != null) { + for (GameBaishunReportCmd.PlayerStartInfo player : startInfo.getPlayers()) { + if (Objects.equals(player.getIsAi(), 1) || player.getBet() == null || player.getBet() <= 0) { + continue; + } + try { + Long userId = DataTypeUtils.toLong(player.getUserId()); + walletGoldClient.changeBalance(GoldReceiptCmd.builder() + .appExpenditure() + .userId(userId) + .sysOrigin(resolveTokenSysOrigin(cmd.getSsToken())) + .origin(GoldOrigin.BAISHUN_GAME.name()) + .originDescribe(GoldOrigin.BAISHUN_GAME.getDesc()) + .eventId(startInfo.getGameRoundId() + "_" + userId + "_start") + .amount(BigDecimal.valueOf(player.getBet())) + .build()); + } catch (Exception e) { + log.error("Baishun game start deduct failed: userId={}, roundId={}", player.getUserId(), startInfo.getGameRoundId(), e); + } + } + } + + return BaishunResponse.success(null); + } + + private BaishunResponse handleGameSettle(GameBaishunReportCmd cmd) { + GameSettleInfo settleInfo = parseReportMsg(cmd.getReportMsg(), GameSettleInfo.class); + if (settleInfo == null) { + return BaishunResponse.fail(BaishunErrorCode.READ_DATE_ERROR); + } + + List players = settleInfo.getPlayers(); + if (players == null || players.isEmpty()) { + return BaishunResponse.success(null); + } + + for (PlayerResultInfo player : players) { + if (Objects.equals(player.getIsAi(), 1) || player.getReward() == null || player.getReward() <= 0) { + continue; + } + try { + Long userId = DataTypeUtils.toLong(player.getUserId()); + walletGoldClient.changeBalance(GoldReceiptCmd.builder() + .appIncome() + .userId(userId) + .sysOrigin(resolveTokenSysOrigin(cmd.getSsToken())) + .origin(GoldOrigin.BAISHUN_GAME.name()) + .originDescribe(GoldOrigin.BAISHUN_GAME.getDesc()) + .eventId(settleInfo.getGameRoundId() + "_" + userId + "_settle") + .amount(BigDecimal.valueOf(player.getReward())) + .build()); + } catch (Exception e) { + log.error("Baishun game settle reward failed: userId={}, roundId={}", player.getUserId(), settleInfo.getGameRoundId(), e); + } + } + + return BaishunResponse.success(null); + } + + private T parseReportMsg(Object reportMsg, Class clazz) { + try { + return objectMapper.convertValue(reportMsg, clazz); + } catch (Exception e) { + log.error("Failed to parse report_msg to {}: {}", clazz.getSimpleName(), reportMsg, e); + return null; + } + } + + private String resolveTokenSysOrigin(String ssToken) { + try { + return com.red.circle.framework.core.security.UserCredential.parseToken(ssToken).getSysOrigin(); + } catch (Exception e) { + return "LIKEI"; + } + } + + private boolean checkTokenStrict(GameBaishunReportCmd request) { + UserCredential accessToken = ResponseAssert.requiredSuccess( + authClient.getUserCredential(DataTypeUtils.toLong(request.getUserId()))); + + if (Objects.isNull(accessToken)) { + return false; + } + + if (!Objects.equals(accessToken.getUserId(), DataTypeUtils.toLong(request.getUserId()))) { + return false; + } + + return Objects.equals(accessToken.token(), request.getSsToken()); + } + + +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunReportServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunReportServiceImpl.java new file mode 100644 index 00000000..717e8a40 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunReportServiceImpl.java @@ -0,0 +1,25 @@ +package com.red.circle.other.app.service.game; + +import com.red.circle.component.game.baishun.response.BaishunResponse; +import com.red.circle.other.app.command.game.GameBaishunReportCmdExe; +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd; +import com.red.circle.other.app.service.game.override.service.GameBaishunReportService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * @author tf + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class GameBaishunReportServiceImpl implements GameBaishunReportService { + + private final GameBaishunReportCmdExe gameBaishunReportCmdExe; + + @Override + public BaishunResponse report(GameBaishunReportCmd cmd) { + return gameBaishunReportCmdExe.execute(cmd); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/override/service/GameBaishunReportService.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/override/service/GameBaishunReportService.java new file mode 100644 index 00000000..454b8544 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/override/service/GameBaishunReportService.java @@ -0,0 +1,14 @@ +package com.red.circle.other.app.service.game.override.service; + +import com.red.circle.other.app.dto.cmd.game.GameBaishunReportCmd; +import com.red.circle.component.game.baishun.response.BaishunResponse; + +/** + * Baishun游戏状态上报服务. + * + * @author tf + */ +public interface GameBaishunReportService { + + BaishunResponse report(GameBaishunReportCmd cmd); +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/game/GameBaishunReportCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/game/GameBaishunReportCmd.java new file mode 100644 index 00000000..dbc13fa5 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/game/GameBaishunReportCmd.java @@ -0,0 +1,125 @@ +package com.red.circle.other.app.dto.cmd.game; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.Data; + +/** + * Baishun游戏状态上报. + * + * @author tf + */ +@Data +public class GameBaishunReportCmd { + + /** 上报类型: game_start / game_settle */ + @NotBlank + @JsonProperty("report_type") + private String reportType; + + /** 上报数据体(对应 game_start_info 或 game_settle_info) */ + @NotNull + @JsonProperty("report_msg") + private Object reportMsg; + + /** 用户ID */ + @NotBlank + @JsonProperty("user_id") + private String userId; + + /** 用户ss_token */ + @NotBlank + @JsonProperty("ss_token") + private String ssToken; + + // ------------------------------------------------------------------------- + // 内嵌数据结构 + // ------------------------------------------------------------------------- + + /** 游戏开始上报数据 */ + @Data + public static class GameStartInfo { + + @JsonProperty("game_id") + private Integer gameId; + + @JsonProperty("room_id") + private String roomId; + + @JsonProperty("game_round_id") + private String gameRoundId; + + /** 开始时间戳(毫秒) */ + @JsonProperty("start_at") + private Long startAt; + + private List players; + + /** 货币类型,app定义,非必填 */ + @JsonProperty("currency_type") + private Integer currencyType; + } + + /** 游戏结算上报数据 */ + @Data + public static class GameSettleInfo { + + @JsonProperty("game_id") + private Integer gameId; + + @JsonProperty("room_id") + private String roomId; + + @JsonProperty("game_round_id") + private String gameRoundId; + + /** 开始时间戳(毫秒) */ + @JsonProperty("start_at") + private Long startAt; + + /** 结束时间戳(毫秒) */ + @JsonProperty("end_at") + private Long endAt; + + private List players; + + /** 货币类型,app定义,非必填 */ + @JsonProperty("currency_type") + private Integer currencyType; + } + + /** 开局玩家信息 */ + @Data + public static class PlayerStartInfo { + + @JsonProperty("user_id") + private String userId; + + /** 0: 玩家, 1: 机器人 */ + @JsonProperty("is_ai") + private Integer isAi; + + /** 扣除数量 */ + private Integer bet; + } + + /** 结算玩家信息 */ + @Data + public static class PlayerResultInfo { + + @JsonProperty("user_id") + private String userId; + + /** 0: 玩家, 1: 机器人 */ + @JsonProperty("is_ai") + private Integer isAi; + + private Integer rank; + + private Integer reward; + + private Integer score; + } +}