新增yomi游戏对接
This commit is contained in:
parent
8bb6f3941e
commit
14f3811c35
@ -564,6 +564,8 @@ public enum GoldOrigin {
|
||||
*/
|
||||
BAISHUN_GAME("Baishun game"),
|
||||
|
||||
YOMI_GAME("Yomi game"),
|
||||
|
||||
/**
|
||||
* 房间贡献活动奖励.
|
||||
*/
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
package com.red.circle.other.adapter.app.game.party3rd;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.red.circle.component.game.hotgame.response.HotGameCoin;
|
||||
import com.red.circle.component.game.hotgame.response.HotGameErrorEnum;
|
||||
import com.red.circle.component.game.hotgame.response.HotGameResponse;
|
||||
import com.red.circle.framework.core.exception.ResponseException;
|
||||
import com.red.circle.framework.web.controller.BaseController;
|
||||
import com.red.circle.other.app.dto.cmd.party3rd.*;
|
||||
import com.red.circle.other.app.service.game.override.service.YomiGameService;
|
||||
import com.red.circle.other.infra.config.YomiConfig;
|
||||
import com.red.circle.other.infra.utils.AesUtils;
|
||||
import com.red.circle.wallet.inner.error.WalletErrorCode;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Yomi游戏REST接口
|
||||
*
|
||||
* @eo.name Yomi游戏接口
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/game/yomi")
|
||||
@RequiredArgsConstructor
|
||||
public class YomiGameRestController extends BaseController {
|
||||
|
||||
private final YomiGameService yomiGameService;
|
||||
private final YomiConfig yomiConfig;
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*
|
||||
* @eo.name 获取token
|
||||
* @eo.url /api/gettoken
|
||||
* @eo.method post
|
||||
* @eo.request-type text/plain
|
||||
*/
|
||||
@PostMapping("/api/gettoken")
|
||||
public YomiResponseWrapper<YomiTokenCO> getToken(HttpServletRequest request) {
|
||||
try {
|
||||
String encryptedBody = readRequestBody(request);
|
||||
String decryptedJson = AesUtils.decryptAESWithBase64(encryptedBody, yomiConfig.getAesKey());
|
||||
|
||||
// 解析JSON为Cmd对象
|
||||
YomiGetTokenCmd cmd = JSON.parseObject(decryptedJson, YomiGetTokenCmd.class);
|
||||
|
||||
// 调用Service处理
|
||||
YomiTokenCO result = yomiGameService.getToken(cmd);
|
||||
|
||||
return YomiResponseWrapper.success(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("yomi getToken 业务异常: {}", e.getMessage());
|
||||
return YomiResponseWrapper.fail(10005, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @eo.name 获取用户信息
|
||||
* @eo.url /api/userinfo
|
||||
* @eo.method post
|
||||
* @eo.request-type text/plain
|
||||
*/
|
||||
@PostMapping("/api/userinfo")
|
||||
public YomiResponseWrapper<YomiUserInfoCO> getUserInfo(HttpServletRequest request) {
|
||||
try {
|
||||
String encryptedBody = readRequestBody(request);
|
||||
String decryptedJson = AesUtils.decryptAESWithBase64(encryptedBody, yomiConfig.getAesKey());
|
||||
|
||||
// 解析JSON为Cmd对象
|
||||
YomiUserInfoCmd cmd = JSON.parseObject(decryptedJson, YomiUserInfoCmd.class);
|
||||
|
||||
// 调用Service处理
|
||||
YomiUserInfoCO result = yomiGameService.getUserInfo(cmd);
|
||||
|
||||
return YomiResponseWrapper.success(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("yomi getUserInfo 业务异常: {}", e.getMessage());
|
||||
return YomiResponseWrapper.fail(10005, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户余额变动
|
||||
*
|
||||
* @eo.name 用户余额变动
|
||||
* @eo.url /api/change_balance
|
||||
* @eo.method post
|
||||
* @eo.request-type text/plain
|
||||
*/
|
||||
@PostMapping("/api/change_balance")
|
||||
public YomiResponseWrapper<YomiBalanceCO> changeBalance(HttpServletRequest request) {
|
||||
try {
|
||||
// 读取加密的请求body
|
||||
String encryptedBody = readRequestBody(request);
|
||||
log.info("yomi changeBalance 加密请求: {}", encryptedBody);
|
||||
|
||||
// AES解密
|
||||
String decryptedJson = AesUtils.decryptAESWithBase64(encryptedBody, yomiConfig.getAesKey());
|
||||
log.info("yomi changeBalance 解密后: {}", decryptedJson);
|
||||
|
||||
// 解析JSON为Cmd对象
|
||||
YomiChangeBalanceCmd cmd = JSON.parseObject(decryptedJson, YomiChangeBalanceCmd.class);
|
||||
|
||||
// 调用Service处理
|
||||
YomiBalanceCO result = yomiGameService.changeBalance(cmd);
|
||||
|
||||
return YomiResponseWrapper.success(result);
|
||||
|
||||
} catch (ResponseException e) {
|
||||
log.error("yomi changeBalance 业务异常: recordId={}, error={}",
|
||||
e.getMessage(), e.getMessage());
|
||||
|
||||
if (Objects.equals(e.getErrorCode(), WalletErrorCode.INSUFFICIENT_BALANCE.getCode())) {
|
||||
return YomiResponseWrapper.fail(10003, e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("yomi changeBalance 系统异常: {}", e.getMessage(), e);
|
||||
return YomiResponseWrapper.fail(9999, "系统错误,联系技术解决");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取请求body
|
||||
*/
|
||||
private String readRequestBody(HttpServletRequest request) {
|
||||
try {
|
||||
byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
return new String(bodyBytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("读取请求body失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -316,7 +316,7 @@ public class HotGameServiceImpl implements HotGameService {
|
||||
incGameRankingActivity(param);
|
||||
|
||||
//FruitParty
|
||||
incGameRankingTmpActivity(param);
|
||||
// incGameRankingTmpActivity(param);
|
||||
|
||||
//处理游戏消费任务
|
||||
if (param.getType() == 1) {
|
||||
|
||||
@ -0,0 +1,315 @@
|
||||
package com.red.circle.other.app.service.game;
|
||||
|
||||
import com.red.circle.auth.inner.endpoint.AuthClient;
|
||||
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
|
||||
import com.red.circle.component.game.hotgame.request.HotGameUserCoinUpdate;
|
||||
import com.red.circle.external.inner.endpoint.message.ImGroupClient;
|
||||
import com.red.circle.external.inner.model.cmd.message.BroadcastGroupMsgBodyCmd;
|
||||
import com.red.circle.external.inner.model.enums.message.GroupMessageTypeEnum;
|
||||
import com.red.circle.framework.core.dto.ReqSysOrigin;
|
||||
import com.red.circle.framework.core.security.UserCredential;
|
||||
import com.red.circle.framework.dto.ResultResponse;
|
||||
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
|
||||
import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd;
|
||||
import com.red.circle.other.app.dto.cmd.party3rd.*;
|
||||
import com.red.circle.other.app.enums.violation.GameOriginEnum;
|
||||
import com.red.circle.other.app.service.activity.RankingActivityService;
|
||||
import com.red.circle.other.app.service.game.override.service.YomiGameService;
|
||||
import com.red.circle.other.app.util.OfficialNoticeUtils;
|
||||
import com.red.circle.other.domain.game.GameRankingIncrementCmd;
|
||||
import com.red.circle.other.domain.gateway.game.GameRankingGateway;
|
||||
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
|
||||
import com.red.circle.other.domain.model.user.UserProfile;
|
||||
import com.red.circle.other.domain.ranking.RankingActivityType;
|
||||
import com.red.circle.other.domain.ranking.RankingCycleType;
|
||||
import com.red.circle.other.domain.ranking.RankingDimension;
|
||||
import com.red.circle.other.infra.config.YomiConfig;
|
||||
import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService;
|
||||
import com.red.circle.other.infra.database.rds.entity.sys.GameListConfig;
|
||||
import com.red.circle.other.infra.database.rds.service.sys.GameListConfigService;
|
||||
import com.red.circle.other.inner.enums.config.EnumConfigKey;
|
||||
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
|
||||
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||
import com.red.circle.tool.core.num.ArithmeticUtils;
|
||||
import com.red.circle.tool.core.parse.DataTypeUtils;
|
||||
import com.red.circle.tool.core.tuple.PennyAmount;
|
||||
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.dto.WalletReceiptResDTO;
|
||||
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Yomi游戏服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class YomiGameServiceImpl implements YomiGameService {
|
||||
|
||||
private final YomiConfig yomiConfig;
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
private final AuthClient authClient;
|
||||
private final UserProfileGateway userProfileGateway;
|
||||
private final WalletGoldClient walletGoldClient;
|
||||
private final GameListConfigService gameListConfigService;
|
||||
private final EnumConfigCacheService enumConfigCacheService;
|
||||
private final ImGroupClient imGroupClient;
|
||||
private final UserProfileAppConvertor userProfileAppConvertor;
|
||||
private final RankingActivityService rankingActivityService;
|
||||
private final GameActivityService gameActivityService;
|
||||
private final GameRankingGateway gameRankingGateway;
|
||||
|
||||
@Override
|
||||
public YomiTokenCO getToken(YomiGetTokenCmd cmd) {
|
||||
log.info("yomi获取token: userId={}, gameId={}", cmd.getUserId(), cmd.getGameId());
|
||||
|
||||
try {
|
||||
// 调用认证服务获取用户凭证
|
||||
ResultResponse<UserCredential> response = authClient.getUserCredential(Long.parseLong(cmd.getUserId()));
|
||||
UserCredential credential = response.getBody();
|
||||
|
||||
if (credential == null) {
|
||||
throw new RuntimeException("用户不存在");
|
||||
}
|
||||
|
||||
// 生成系统标准token
|
||||
String token = credential.token();
|
||||
|
||||
// 过期时间(24小时后)
|
||||
Long expireDate = System.currentTimeMillis() / 1000 + yomiConfig.getTokenExpireSeconds();
|
||||
|
||||
// 缓存token-userId映射
|
||||
String cacheKey = buildTokenCacheKey(token);
|
||||
redisTemplate.opsForValue().set(cacheKey, cmd.getUserId(),
|
||||
yomiConfig.getTokenExpireSeconds(), TimeUnit.SECONDS);
|
||||
|
||||
return new YomiTokenCO(token, expireDate);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("yomi获取token失败: userId={}, error={}", cmd.getUserId(), e.getMessage(), e);
|
||||
throw new RuntimeException("获取token失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public YomiUserInfoCO getUserInfo(YomiUserInfoCmd cmd) {
|
||||
String userId = cmd.getUserId();
|
||||
log.info("yomi获取用户信息: userId={}, gameId={}", userId, cmd.getGameId());
|
||||
|
||||
try {
|
||||
// 验证token
|
||||
if (!UserCredential.checkToken(cmd.getToken(), Long.parseLong(userId))) {
|
||||
throw new Exception("token与用户ID不匹配");
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
UserProfile profile = userProfileGateway.getByUserId(Long.parseLong(userId));
|
||||
|
||||
if (profile == null) {
|
||||
throw new Exception("用户信息不存在");
|
||||
}
|
||||
|
||||
// 获取用户余额
|
||||
ResultResponse<PennyAmount> response = walletGoldClient.getBalance(Long.parseLong(userId));
|
||||
|
||||
YomiUserInfoCO result = new YomiUserInfoCO(
|
||||
userId,
|
||||
profile.getUserNickname(),
|
||||
profile.getUserAvatar(),
|
||||
response.getBody().getDollarAmount().intValue()
|
||||
);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("yomi获取用户信息失败: userId={}, error={}", userId, e.getMessage(), e);
|
||||
}
|
||||
return new YomiUserInfoCO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public YomiBalanceCO changeBalance(YomiChangeBalanceCmd cmd) {
|
||||
Long userId = Long.parseLong(cmd.getUserId());
|
||||
Integer changeValue = cmd.getChangeValue();
|
||||
log.info("yomi余额变动: userId={}, gameId={}, changeValue={}, changeCause={}, recordId={}",
|
||||
userId, cmd.getGameId(), changeValue, cmd.getChangeCause(), cmd.getRecordId());
|
||||
|
||||
try {
|
||||
// 验证token
|
||||
String token = cmd.getToken();
|
||||
String gameId = cmd.getGameId();
|
||||
|
||||
if (!UserCredential.checkToken(token, userId)) {
|
||||
throw new Exception("token与用户ID不匹配");
|
||||
}
|
||||
|
||||
WalletReceiptResDTO res = walletGoldClient.changeBalance(createReceipt(cmd)).getBody();
|
||||
int currentBalance = res.getBalance().getDollarAmount().intValue();
|
||||
|
||||
// 获取游戏配置
|
||||
String sysOrigin = UserCredential.parseToken(token).getSysOrigin();
|
||||
GameListConfig gameConfig = gameListConfigService.getByGameId(gameId, GameOriginEnum.HOTGAME.name(), sysOrigin);
|
||||
if (gameConfig == null) {
|
||||
log.error("游戏配置不存在, gameId={}", gameId);
|
||||
return new YomiBalanceCO(currentBalance);
|
||||
}
|
||||
|
||||
// 发送广播通知
|
||||
if (!isConsume(cmd)) {
|
||||
sendBroadcast(userId, changeValue, gameConfig.getCover());
|
||||
|
||||
// 累计游戏排行榜
|
||||
incGameRankingRecord(changeValue, gameConfig);
|
||||
|
||||
// 游戏参与打榜活动
|
||||
incGameRankingActivity(userId, changeValue, gameConfig.getGameId());
|
||||
}
|
||||
|
||||
//处理游戏消费任务
|
||||
if (isConsume(cmd)) {
|
||||
gameActivityService.handleGameSpendTask(userId, changeValue.longValue());
|
||||
}
|
||||
|
||||
return new YomiBalanceCO(currentBalance);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("yomi余额变动失败: userId={}, recordId={}, error={}",
|
||||
userId, cmd.getRecordId(), e.getMessage(), e);
|
||||
throw new RuntimeException("余额变动失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isConsume(YomiChangeBalanceCmd cmd) {
|
||||
return "bet".equals(cmd.getChangeCause());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证token有效性
|
||||
*/
|
||||
private String validateToken(String token) {
|
||||
String cacheKey = buildTokenCacheKey(token);
|
||||
String userId = redisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (userId == null) {
|
||||
throw new RuntimeException("token已过期或无效");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建token缓存key
|
||||
*/
|
||||
private String buildTokenCacheKey(String token) {
|
||||
return "yomi:token:" + token;
|
||||
}
|
||||
|
||||
|
||||
private GoldReceiptCmd createReceipt(YomiChangeBalanceCmd cmd) {
|
||||
UserCredential userCredential = UserCredential.parseToken(cmd.getToken());
|
||||
return createGoldReceiptCmdBuilder(cmd.getChangeCause())
|
||||
.userId(Long.parseLong(cmd.getUserId()))
|
||||
.sysOrigin(userCredential.getSysOrigin())
|
||||
.origin(GoldOrigin.YOMI_GAME.name() + "_" + cmd.getGameId())
|
||||
.originDescribe(GoldOrigin.YOMI_GAME.getDesc() + "[" + cmd.getGameId() + "]")
|
||||
.eventId(cmd.getRoundId())
|
||||
.amount(ArithmeticUtils.toBigDecimal(cmd.getChangeValue()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private GoldReceiptCmd.GoldReceiptCmdBuilder createGoldReceiptCmdBuilder(String changeCause) {
|
||||
if ("bet".equals(changeCause)) {
|
||||
return GoldReceiptCmd.builder().appExpenditure();
|
||||
}
|
||||
|
||||
if ("awards".equals(changeCause)) {
|
||||
return GoldReceiptCmd.builder().appIncome();
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("yomi game param [changeCause] error: " + changeCause);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建游戏参数(用于广播和排行榜)
|
||||
*/
|
||||
private Map<String, Object> buildGameParam(YomiChangeBalanceCmd cmd, Long userId, GameListConfig gameConfig) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("userId", userId);
|
||||
param.put("gameId", cmd.getGameId());
|
||||
param.put("roomId", cmd.getRoomId());
|
||||
param.put("recordId", cmd.getRecordId());
|
||||
param.put("roundId", cmd.getRoundId());
|
||||
param.put("changeValue", cmd.getChangeValue());
|
||||
param.put("changeCause", cmd.getChangeCause());
|
||||
param.put("changeTime", cmd.getChangeTime());
|
||||
param.put("gameConfig", gameConfig);
|
||||
return param;
|
||||
}
|
||||
|
||||
private void sendBroadcast(Long userId, Integer rewardAmount, String gameUrl) {
|
||||
if (rewardAmount == null || rewardAmount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal cost = enumConfigCacheService.getValueBigDecimal(EnumConfigKey.GAME_BROADCAST_AMOUNT, SysOriginPlatformEnum.LIKEI.name()) ;
|
||||
int thresholdAmount = Optional.ofNullable(cost).orElse(new BigDecimal("5000")).intValue();
|
||||
if (rewardAmount < thresholdAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserProfileDTO userProfile = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(userId));
|
||||
|
||||
Map<Object, Object> build = OfficialNoticeUtils.buildUserProfile(userProfile);
|
||||
build.put("gameUrl", gameUrl);
|
||||
build.put("currencyDiff", rewardAmount);
|
||||
|
||||
imGroupClient.sendMessageBroadcast(
|
||||
BroadcastGroupMsgBodyCmd.builder()
|
||||
.toPlatform(SysOriginPlatformEnum.LIKEI)
|
||||
.type(GroupMessageTypeEnum.GAME_BAISHUN_WIN)
|
||||
.data(build)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private void incGameRankingRecord(Integer rewardAmount, GameListConfig gameListConfig) {
|
||||
|
||||
GameRankingIncrementCmd build = GameRankingIncrementCmd.builder()
|
||||
.gameOrigin(GameOriginEnum.HOTGAME.name())
|
||||
.gameId(gameListConfig.getGameId())
|
||||
.gameName(gameListConfig.getName())
|
||||
.gameCover(gameListConfig.getCover())
|
||||
.gameUrl(gameListConfig.getGameCode())
|
||||
.sysOrigin(gameListConfig.getSysOrigin())
|
||||
.amount(rewardAmount.longValue())
|
||||
.periodType("ALL")
|
||||
.build();
|
||||
gameRankingGateway.incrementPrizeAmount(build);
|
||||
}
|
||||
|
||||
|
||||
private void incGameRankingActivity(Long userId, Integer rewardAmount, String gameId) {
|
||||
RankingDataUpdateCmd receiverCmd = new RankingDataUpdateCmd();
|
||||
receiverCmd.setUserId(userId);
|
||||
receiverCmd.setUserSex(1);
|
||||
receiverCmd.setActivityType(RankingActivityType.KING_GAMES.getCode());
|
||||
receiverCmd.setCycleType(RankingCycleType.WEEKLY.getCode());
|
||||
receiverCmd.setCycleKey(ZonedDateTimeAsiaRiyadhUtils.nowWeekMondayToInt().toString());
|
||||
receiverCmd.setDimension(RankingDimension.GAME_WIN.getCode());
|
||||
receiverCmd.setIncrementQuantity(rewardAmount.longValue());
|
||||
receiverCmd.setBizNo(gameId);
|
||||
receiverCmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI"));
|
||||
rankingActivityService.accumulateRankingData(receiverCmd);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.red.circle.other.app.service.game.override.service;
|
||||
|
||||
import com.red.circle.other.app.dto.cmd.party3rd.*;
|
||||
|
||||
/**
|
||||
* Yomi游戏服务接口
|
||||
*/
|
||||
public interface YomiGameService {
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*/
|
||||
YomiTokenCO getToken(YomiGetTokenCmd cmd);
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
YomiUserInfoCO getUserInfo(YomiUserInfoCmd cmd);
|
||||
|
||||
/**
|
||||
* 用户余额变动
|
||||
*/
|
||||
YomiBalanceCO changeBalance(YomiChangeBalanceCmd cmd);
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Yomi余额响应
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class YomiBalanceCO {
|
||||
|
||||
/**
|
||||
* 当前余额
|
||||
*/
|
||||
private Integer currentBalance;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Yomi用户余额变动请求
|
||||
*/
|
||||
@Data
|
||||
public class YomiChangeBalanceCmd {
|
||||
|
||||
@NotBlank(message = "游戏ID不能为空")
|
||||
private String gameId;
|
||||
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "token不能为空")
|
||||
private String token;
|
||||
|
||||
@NotNull(message = "余额变化值不能为空")
|
||||
private Integer changeValue;
|
||||
|
||||
@NotBlank(message = "变化原因不能为空")
|
||||
private String changeCause;
|
||||
|
||||
@NotNull(message = "变化时间不能为空")
|
||||
private Long changeTime;
|
||||
|
||||
@NotBlank(message = "房间ID不能为空")
|
||||
private String roomId;
|
||||
|
||||
@NotBlank(message = "记录ID不能为空")
|
||||
private String recordId;
|
||||
|
||||
@NotBlank(message = "轮次ID不能为空")
|
||||
private String roundId;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Yomi获取token请求
|
||||
*/
|
||||
@Data
|
||||
public class YomiGetTokenCmd {
|
||||
|
||||
@NotBlank(message = "游戏ID不能为空")
|
||||
private String gameId;
|
||||
|
||||
@NotBlank(message = "房间ID不能为空")
|
||||
private String roomId;
|
||||
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String code;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Yomi统一响应包装
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class YomiResponseWrapper<T> {
|
||||
|
||||
/**
|
||||
* 状态码 200=成功
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
public static <T> YomiResponseWrapper<T> success(T data) {
|
||||
return new YomiResponseWrapper<>(200, "success", data);
|
||||
}
|
||||
|
||||
public static <T> YomiResponseWrapper<T> fail(Integer code, String message) {
|
||||
return new YomiResponseWrapper<>(code, message, null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Yomi Token响应
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class YomiTokenCO {
|
||||
|
||||
/**
|
||||
* token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 过期时间(秒级时间戳)
|
||||
*/
|
||||
private Long expireDate;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Yomi用户信息响应
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class YomiUserInfoCO {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 用户余额
|
||||
*/
|
||||
private Integer balance;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.red.circle.other.app.dto.cmd.party3rd;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Yomi获取用户信息请求
|
||||
*/
|
||||
@Data
|
||||
public class YomiUserInfoCmd {
|
||||
|
||||
@NotBlank(message = "游戏ID不能为空")
|
||||
private String gameId;
|
||||
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@NotBlank(message = "token不能为空")
|
||||
private String token;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.red.circle.other.infra.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Yomi游戏配置
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "yomi")
|
||||
public class YomiConfig {
|
||||
|
||||
/**
|
||||
* AES加密密钥
|
||||
*/
|
||||
private String aesKey;
|
||||
|
||||
/**
|
||||
* Token过期时间(秒) 默认24小时
|
||||
*/
|
||||
private Integer tokenExpireSeconds = 86400;
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
package com.red.circle.other.infra.utils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class AesUtils {
|
||||
private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder();
|
||||
private static final Base64.Decoder base64Decoder = Base64.getUrlDecoder();
|
||||
private static final int IV_LENGTH = 12;
|
||||
private static final int GCM_TAG_LENGTH = 16; // GCM tag length in bytes
|
||||
|
||||
public static String generateAESKey() throws Exception {
|
||||
final int keyLength = 32; // 256-bit key (AES-256)
|
||||
final String charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
final int charSetLength = charSet.length();
|
||||
|
||||
SecureRandom random = new SecureRandom();
|
||||
StringBuilder keyBuilder = new StringBuilder(keyLength);
|
||||
for (int i = 0; i < keyLength; i++) {
|
||||
int index = random.nextInt(charSetLength);
|
||||
keyBuilder.append(charSet.charAt(index));
|
||||
}
|
||||
|
||||
return keyBuilder.toString();
|
||||
}
|
||||
|
||||
// 验证并转换字符串密钥为字节数组
|
||||
private static SecretKeySpec validateKey(String key) throws Exception {
|
||||
int keyLength = key.length();
|
||||
if (keyLength != 16 && keyLength != 24 && keyLength != 32) {
|
||||
throw new IllegalArgumentException("Invalid AES key size (must be 16, 24, or 32 bytes)");
|
||||
}
|
||||
return new SecretKeySpec(key.getBytes(), "AES");
|
||||
}
|
||||
|
||||
// 使用AES加密并返回Base64编码结果
|
||||
public static String encryptAESWithBase64(byte[] data, String key) throws Exception {
|
||||
// 验证并转换密钥
|
||||
SecretKeySpec keyBytes = validateKey(key);
|
||||
|
||||
// 创建AES加密块
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
// 生成随机IV(Nonce),长度为GCM推荐的NonceSize
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(iv);
|
||||
|
||||
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
|
||||
|
||||
// 初始化加密器
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyBytes, gcmParameterSpec);
|
||||
|
||||
// 加密数据
|
||||
byte[] ciphertext = cipher.doFinal(data);
|
||||
|
||||
// 拼接IV和密文
|
||||
byte[] finalData = new byte[iv.length + ciphertext.length];
|
||||
System.arraycopy(iv, 0, finalData, 0, iv.length);
|
||||
System.arraycopy(ciphertext, 0, finalData, iv.length, ciphertext.length);
|
||||
|
||||
// 返回Base64编码字符串
|
||||
return base64Encoder.encodeToString(finalData);
|
||||
}
|
||||
|
||||
// 使用AES解密Base64编码数据
|
||||
public static String decryptAESWithBase64(String base64Data, String key) throws Exception {
|
||||
System.out.println("解码原数据:"+ base64Data);
|
||||
// 验证并转换密钥
|
||||
SecretKeySpec keyBytes = validateKey(key);
|
||||
|
||||
// 解码Base64字符串
|
||||
byte[] encryptedData = base64Decoder.decode(base64Data);
|
||||
|
||||
// 创建AES加密块
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
// 检查数据长度是否足够(至少包含一个Nonce)
|
||||
if (encryptedData.length < GCM_TAG_LENGTH) {
|
||||
throw new IllegalArgumentException("Encrypted data too short");
|
||||
}
|
||||
|
||||
// 提取IV和密文
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
System.arraycopy(encryptedData, 0, iv, 0, iv.length);
|
||||
byte[] ciphertext = new byte[encryptedData.length - iv.length];
|
||||
System.arraycopy(encryptedData, iv.length, ciphertext, 0, ciphertext.length);
|
||||
|
||||
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
|
||||
|
||||
// 初始化解密器
|
||||
cipher.init(Cipher.DECRYPT_MODE, keyBytes, gcmParameterSpec);
|
||||
|
||||
// 解密数据
|
||||
String decryptStr = new String(cipher.doFinal(ciphertext));
|
||||
System.out.println("解密后数据:"+ decryptStr);
|
||||
return decryptStr;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 生成AES密钥
|
||||
String key = "bDyeXhfYPAadK7DzxuMpZDHsPBZjbEzS";
|
||||
// 密文
|
||||
String encryptedData = "ni-P7cw9mPhm8SndkwvxAl_6o1I_jLc930uHJLXuYNe2On1vmKz1--5dvWFpJoMlKnXOrhax03DU5wrR82l_ukRo9ZMqk8AX3kENM816doDcGIS2xcotqrjaN202Lcj2_IFCy-TZdP4WjA==";
|
||||
|
||||
// 解密
|
||||
String decryptedData = decryptAESWithBase64(encryptedData, key);
|
||||
System.out.println("Decrypted Data: " + decryptedData);
|
||||
|
||||
System.out.println();
|
||||
|
||||
String encryptedData2 = encryptAESWithBase64(decryptedData.getBytes(), key);
|
||||
System.out.println("Encrypt Data: " + encryptedData2);
|
||||
|
||||
// 解密2
|
||||
String decryptedData2 = decryptAESWithBase64(encryptedData2, key);
|
||||
System.out.println("Decrypted Data2: " + decryptedData2);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user