新增yomi游戏对接

This commit is contained in:
tianfeng 2026-01-20 14:52:36 +08:00
parent 8bb6f3941e
commit 14f3811c35
14 changed files with 836 additions and 1 deletions

View File

@ -564,6 +564,8 @@ public enum GoldOrigin {
*/
BAISHUN_GAME("Baishun game"),
YOMI_GAME("Yomi game"),
/**
* 房间贡献活动奖励.
*/

View File

@ -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());
}
}
}

View File

@ -316,7 +316,7 @@ public class HotGameServiceImpl implements HotGameService {
incGameRankingActivity(param);
//FruitParty
incGameRankingTmpActivity(param);
// incGameRankingTmpActivity(param);
//处理游戏消费任务
if (param.getType() == 1) {

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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");
// 生成随机IVNonce长度为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();
}
}
}