feat(game): 添加 SA 游戏接口实现

- 新增 SA游戏的登录、获取会员信息、创建会员等功能实现
- 添加 SA 游戏相关的配置常量和工具类
- 实现 SA 游戏的 XML 数据解析和加密解密逻辑
- 优化原有的 DES 加密工具类,支持 CBC 模式和 PKCS5 填充
main-sa
shi 2025-03-26 14:34:04 +08:00
parent 1fd1e10339
commit 9f6d1710b0
8 changed files with 340 additions and 107 deletions

View File

@ -16,6 +16,8 @@ public class ConfigConstants
public static final String VIEW_FILE_URL = "view.file.url";
/**
* nginx
*/
@ -26,6 +28,18 @@ public class ConfigConstants
*/
public static final String DOMAIN_NGINX_CONFIG_PARAM = "domain.nginx.config.param";
/**
*sa
*/
public static final String SA_API_LOGIN_URL = "sa.api.login.url";
/**
* sa-api
*/
public static final String SA_API_HALL_CODE = "sa.api.hall.code";
/**
* url
*/

View File

@ -3,9 +3,16 @@ package com.ff.base.utils;
import com.ff.base.exception.base.BaseException;
import lombok.Data;
import javax.crypto.Cipher;
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
@ -17,32 +24,61 @@ import java.util.Base64;
*/
public class DESEncryptUtils {
/**
*
*
* @param inString
* @param secretKey
* @param key
* @return {@link String }
* @throws Exception
*/
public static String DESEncrypt(String inString, String secretKey) {
public static String DESEncrypt(String inString, String key) {
try {
// 使用相同的密钥进行加密
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.US_ASCII), "DES");
// 使用DES加密算法
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptKey = key.getBytes(); // encryptKey
KeySpec keySpec = new DESKeySpec(encryptKey);
SecretKey myDesKey = SecretKeyFactory.getInstance("DES").generateSecret(keySpec);
IvParameterSpec iv = new IvParameterSpec(encryptKey);
// 加密输入字符串
byte[] encrypted = cipher.doFinal(inString.getBytes(StandardCharsets.UTF_8));
KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
Cipher desCipher;
// Create the cipher
desCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
// Initialize the cipher for encryption
desCipher.init(Cipher.ENCRYPT_MODE, myDesKey, iv);
//sensitive information
String source = inString;
byte[] text = source.getBytes();
System.out.println("Text [Byte Format] : " + text);
System.out.println("Text : " + new String(text));
// Encrypt the text
byte[] textEncrypted = desCipher.doFinal(text);
String t = Base64.getEncoder().encodeToString(textEncrypted);
System.out.println("Text Encryted [Byte Format] : " + textEncrypted);
System.out.println("Text Encryted : " + t);
// Initialize the same cipher for decryption
desCipher.init(Cipher.DECRYPT_MODE, myDesKey, iv);
// Decrypt the text
byte[] textDecrypted = desCipher.doFinal(textEncrypted);
System.out.println("Text Decryted : " + new String(textDecrypted));
return t;
// 返回Base64编码的密文
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new BaseException("加密失败");
throw new BaseException("解密失败");
}
}
}
}

View File

@ -0,0 +1,55 @@
package com.ff.base.utils;
import com.ff.base.exception.base.BaseException;
import org.apache.poi.ss.formula.functions.T;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.security.spec.KeySpec;
import java.util.Base64;
/**
*
*
* @author shi
* @date 2025/03/24
*/
public class XmlUtils {
/**
* xml
*
* @param xmlString xml
* @param clas
* @param <T>
* @return {@link T}
*/
public static <T> T xmlDecrypt(String xmlString, Class<T> clas) {
try {
// 创建 JAXBContext 对象,绑定目标类
JAXBContext jaxbContext = JAXBContext.newInstance(clas);
// 创建 Unmarshaller
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
// 将 XML 字符串转换为目标类对象
return clas.cast(unmarshaller.unmarshal(new StringReader(xmlString)));
} catch (JAXBException e) {
// 在异常中包含原始异常信息
throw new BaseException("XML 转化失败失败,错误信息:" + e.getMessage());
}
}
}

View File

@ -3,7 +3,6 @@ package com.ff.game.api.sa.client;
import com.dtflys.forest.annotation.*;
import com.ff.game.api.jili.dto.*;
import com.ff.game.api.sa.address.MySAAddressSource;
import com.ff.game.api.sa.dto.SARegUserInfoResponse;
import com.ff.game.api.xk.dto.*;
import java.util.Map;
@ -22,29 +21,33 @@ public interface SAClient {
* @param parameters
* @return {@link String }
*/
@Post( url ="/api.aspx/RegUserInfo?${parameters}",
@Post( url ="/api.aspx/RegUserInfo",
headers = {
"Content-type: application/x-www-form-urlencoded"
})
SARegUserInfoResponse createMember(@Var("parameters") String parameters);
String createMember(@Body String parameters);
/**
*
*
* @param params
* @return {@link XKMemberInfoDTO }
* @return {@link String }
*/
@Post("/getMemberInfo")
XKMemberInfoDTO getMemberInfo(@JSONBody Map<String, Object> params);
@Post( url ="/api.aspx/GetUserStatusDV",
headers = {
"Content-type: application/x-www-form-urlencoded"
})
String getMemberInfo(@Body String params);
/**
*
*
* @param params
* @return {@link JILILoginWithoutRedirectResponseDTO }
*/
@Post("/loginWithoutRedirect")
XKLoginWithoutRedirectResponseDTO loginWithoutRedirect(@JSONBody Map<String, Object> params);
@Post( url ="/api.aspx/LoginRequest",
headers = {
"Content-type: application/x-www-form-urlencoded"
})
String loginWithoutRedirect(@Body String params);
/**
*

View File

@ -0,0 +1,77 @@
package com.ff.game.api.sa.dto;
import lombok.Data;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.math.BigDecimal;
/**
* sa
*
* @author shi
* @date 2025/03/26
*/
@Data
@XmlRootElement(name = "GetUserStatusResponse")
public class SAGetUserStatusResponse {
/**
* ID
*/
@XmlElement(name = "ErrorMsgId")
private int errorMsgId;
/**
*
*/
@XmlElement(name = "ErrorMsg")
private String errorMsg;
/**
*
*/
@XmlElement(name = "IsSuccess")
private boolean isSuccess;
/**
*
*/
@XmlElement(name = "Username")
private String username;
/**
*
*/
@XmlElement(name = "Balance")
private BigDecimal balance;
/**
* 线
*/
@XmlElement(name = "Online")
private boolean online;
/**
*
*/
@XmlElement(name = "Betted")
private boolean betted;
/**
*
*/
@XmlElement(name = "BettedAmount")
private double bettedAmount;
/**
*
*/
@XmlElement(name = "MaxBalance")
private double maxBalance;
/**
*
*/
@XmlElement(name = "MaxWinning")
private double maxWinning;
}

View File

@ -0,0 +1,44 @@
package com.ff.game.api.sa.dto;
import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* salogin
*
* @author shi
* @date 2025/03/26
*/
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "LoginRequestResponse")
public class SALoginRequestResponse {
/**
* ID
*/
@XmlElement(name = "ErrorMsgId")
private int errorMsgId;
/**
*
*/
@XmlElement(name = "ErrorMsg")
private String errorMsg;
/**
*
*/
@XmlElement(name = "Token")
private String token;
/**
*
*/
@XmlElement(name = "DisplayName")
private String displayName;
}

View File

@ -1,5 +1,7 @@
package com.ff.game.api.sa.dto;
import lombok.Data;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@ -10,37 +12,16 @@ import javax.xml.bind.annotation.XmlRootElement;
* @author shi
* @date 2025/03/25
*/
@Data
@XmlRootElement(name = "RegUserInfoResponse")
public class SARegUserInfoResponse {
private String errorMsgId;
@XmlElement(name = "ErrorMsgId")
private Integer errorMsgId;
@XmlElement(name = "ErrorMsg")
private String errorMsg;
@XmlElement(name = "Username")
private String username;
@XmlElement(name = "ErrorMsgId")
public String getErrorMsgId() {
return errorMsgId;
}
public void setErrorMsgId(String errorMsgId) {
this.errorMsgId = errorMsgId;
}
@XmlElement(name = "ErrorMsg")
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
@XmlElement(name = "Username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -3,22 +3,23 @@ package com.ff.game.api.sa.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import com.ff.base.constant.CacheConstants;
import com.ff.base.constant.ConfigConstants;
import com.ff.base.constant.Constants;
import com.ff.base.core.redis.RedisCache;
import com.ff.base.enums.*;
import com.ff.base.exception.base.ApiException;
import com.ff.base.exception.base.BaseException;
import com.ff.base.system.service.ISysConfigService;
import com.ff.base.utils.DESEncryptUtils;
import com.ff.base.utils.DateUtils;
import com.ff.base.utils.JsonUtil;
import com.ff.base.utils.*;
import com.ff.base.utils.sign.Md5Utils;
import com.ff.base.utils.uuid.IdUtils;
import com.ff.config.KeyConfig;
import com.ff.game.api.IGamesService;
import com.ff.game.api.request.*;
import com.ff.game.api.sa.client.SAClient;
import com.ff.game.api.sa.dto.SALoginRequestResponse;
import com.ff.game.api.sa.dto.SARegUserInfoResponse;
import com.ff.game.api.sa.dto.SAGetUserStatusResponse;
import com.ff.game.api.xk.dto.*;
import com.ff.game.domain.*;
import com.ff.game.dto.GameSecretKeyCurrencyDTO;
@ -100,6 +101,8 @@ public class GamesSAServiceImpl implements IGamesService {
* @return {@link Boolean }
*/
private Boolean getIsSuccess(Integer errorCode) {
ApiException.isTrue(113 != errorCode, ErrorCode.GAME_ACCOUNT_CREATION_FAILED.getCode());
ApiException.isTrue(116 != errorCode, ErrorCode.ACCOUNT_NOT_EXIST.getCode());
return 0 == errorCode;
}
@ -110,16 +113,15 @@ public class GamesSAServiceImpl implements IGamesService {
* @param gamesBaseRequestDTO dto
* @return {@link String }
*/
private String getKey(GamesBaseRequestDTO gamesBaseRequestDTO,String method) {
private String getKey(GamesBaseRequestDTO gamesBaseRequestDTO, String method) {
String dateTimeNow = DateUtils.dateTimeNow();
String query="method="+method+"&Key="+gamesBaseRequestDTO.getAgentKey()+"&Time="+dateTimeNow+"&"+gamesBaseRequestDTO.getQuery();
String query = "method=" + method + "&Key=" + gamesBaseRequestDTO.getAgentKey() + "&Time=" + dateTimeNow + "&" + gamesBaseRequestDTO.getQuery();
String q = DESEncryptUtils.DESEncrypt(query, "g9G16nTs");
String s=Md5Utils.md5New(query+gamesBaseRequestDTO.getAgentId()+dateTimeNow+gamesBaseRequestDTO.getAgentKey());
return "q="+q+"&s="+s;
String s = Md5Utils.md5New(query + gamesBaseRequestDTO.getAgentId() + dateTimeNow + gamesBaseRequestDTO.getAgentKey());
return "q=" + q + "&s=" + s;
}
/**
*
*
@ -128,16 +130,20 @@ public class GamesSAServiceImpl implements IGamesService {
*/
@Override
public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) {
log.info("GamesXKServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO);
log.info("GamesSAServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO);
Map<String, Object> params = new LinkedHashMap<>();
params.put("Username", createMemberRequestDTO.getAccount());
params.put("CurrencyType", createMemberRequestDTO.getCurrency());
String query = JsonUtil.mapToQueryString(params);
createMemberRequestDTO.setQuery(query);
String key = this.getKey(createMemberRequestDTO,"RegUserInfo");
SARegUserInfoResponse saRegUserInfoResponse = SAClient.createMember(key);
String errorCode = saRegUserInfoResponse.getErrorMsgId();
String key = this.getKey(createMemberRequestDTO, "RegUserInfo");
String result = SAClient.createMember(key);
SARegUserInfoResponse saRegUserInfoResponse = XmlUtils.xmlDecrypt(result, SARegUserInfoResponse.class);
Integer errorCode = saRegUserInfoResponse.getErrorMsgId();
if (this.getIsSuccess(errorCode)) {
return Boolean.TRUE;
}
//判断是否获取成功
return Boolean.FALSE;
}
@ -151,28 +157,25 @@ public class GamesSAServiceImpl implements IGamesService {
*/
@Override
public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) {
log.info("GamesXKServiceImpl [getMemberInfo] 请求参数 {}", memberInfoRequestDTO);
log.info("GamesSAServiceImpl [getMemberInfo] 请求参数 {}", memberInfoRequestDTO);
Map<String, Object> params = new LinkedHashMap<>();
params.put("accounts", memberInfoRequestDTO.getAccounts());
params.put("agentId", memberInfoRequestDTO.getAgentId());
params.put("Username", memberInfoRequestDTO.getAccounts());
String query = JsonUtil.mapToQueryString(params);
memberInfoRequestDTO.setQuery(query);
String key = this.getKey(memberInfoRequestDTO,null);
params.put("key", key);
XKMemberInfoDTO xkMemberInfoDTO = SAClient.getMemberInfo(params);
//判断是否获取成功
if (this.getIsSuccess(xkMemberInfoDTO.getCode())) {
List<MemberInfoResponseDTO> memberInfoResponseDTOS = new ArrayList<>();
xkMemberInfoDTO.getData().forEach(e -> {
memberInfoResponseDTOS.add(MemberInfoResponseDTO.builder()
.status(e.getStatus())
.balance(e.getBalance())
.account(e.getAccount())
.build());
});
return memberInfoResponseDTOS.get(0);
} else {
throw new BaseException(xkMemberInfoDTO.getMsg());
String key = this.getKey(memberInfoRequestDTO, "GetUserStatusDV");
String result = SAClient.getMemberInfo(key);
SAGetUserStatusResponse saGetUserStatusResponse = XmlUtils.xmlDecrypt(result, SAGetUserStatusResponse.class);
Integer errorCode = saGetUserStatusResponse.getErrorMsgId();
if (this.getIsSuccess(errorCode)) {
return MemberInfoResponseDTO.builder()
.account(memberInfoRequestDTO.getAccounts())
.balance(saGetUserStatusResponse.getBalance())
.status(saGetUserStatusResponse.isOnline() ? GameMemberStatus.ONLINE.getCode() : GameMemberStatus.OFFLINE.getCode())
.build();
}
else {
throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
}
}
@ -187,23 +190,32 @@ public class GamesSAServiceImpl implements IGamesService {
log.info("GamesXKServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin);
Map<String, Object> params = new LinkedHashMap<>();
params.put("account", gamesLogin.getAccount());
params.put("gameId", Integer.valueOf(gamesLogin.getGameId()));
params.put("lang", gamesLogin.getLang());
params.put("agentId", gamesLogin.getAgentId());
params.put("Username", gamesLogin.getAccount());
params.put("CurrencyType", gamesLogin.getCurrency());
String query = JsonUtil.mapToQueryString(params);
gamesLogin.setQuery(query);
String key = this.getKey(gamesLogin,null);
params.put("key", key);
params.put("disableFullScreen", gamesLogin.getDisableFullScreen());
params.put("homeUrl", gamesLogin.getHomeUrl());
params.put("platform", gamesLogin.getPlatform());
XKLoginWithoutRedirectResponseDTO xkLoginWithoutRedirectResponseDTO = SAClient.loginWithoutRedirect(params);
String key = this.getKey(gamesLogin, "LoginRequest");
String result = SAClient.loginWithoutRedirect(key);
SALoginRequestResponse saLoginRequestResponse = XmlUtils.xmlDecrypt(result, SALoginRequestResponse.class);
Integer errorCode = saLoginRequestResponse.getErrorMsgId();
//判断是否获取成功
if (this.getIsSuccess(xkLoginWithoutRedirectResponseDTO.getCode())) {
return xkLoginWithoutRedirectResponseDTO.getData();
if (this.getIsSuccess(errorCode)) {
String loginUrl = configService.selectConfigByKey(ConfigConstants.SA_API_LOGIN_URL);
String hallCode = configService.selectConfigByKey(ConfigConstants.SA_API_HALL_CODE);
params = new LinkedHashMap<>();
params.put("username", gamesLogin.getAccount());
params.put("token", saLoginRequestResponse.getToken());
params.put("lobby", hallCode);
params.put("lang", gamesLogin.getLang());
if (StringUtils.isEmpty(gamesLogin.getHomeUrl())){
params.put("returnurl", gamesLogin.getHomeUrl());
}
return loginUrl+"?"+JsonUtil.mapToQueryString(params);
} else {
throw new BaseException(xkLoginWithoutRedirectResponseDTO.getMsg());
throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
}
}
@ -228,7 +240,7 @@ public class GamesSAServiceImpl implements IGamesService {
params.put("agentId", gamesBaseRequestDTO.getAgentId());
String query = JsonUtil.mapToQueryString(params);
gamesBaseRequestDTO.setQuery(query);
String key = this.getKey(gamesBaseRequestDTO,null);
String key = this.getKey(gamesBaseRequestDTO, null);
params.put("key", key);
XKGamesDTO xkGamesDTO = SAClient.getGameList(params);
@ -268,7 +280,7 @@ public class GamesSAServiceImpl implements IGamesService {
}
gamesDataDTO.setSystemGameId(game.getId());
List<GameName> gameNames = gameNameService.selectGameNameList(GameName.builder().gameId(game.getId()).gameName(game.getGameName()).build());
if (CollectionUtils.isEmpty(gameNames)){
if (CollectionUtils.isEmpty(gameNames)) {
gameNameService.insertGameName(GameName.builder()
.gameId(game.getId())
.gameName(game.getGameName())
@ -339,7 +351,7 @@ public class GamesSAServiceImpl implements IGamesService {
params.put("agentId", exchangeTransferMoneyRequestDTO.getAgentId());
String query = JsonUtil.mapToQueryString(params);
exchangeTransferMoneyRequestDTO.setQuery(query);
String key = this.getKey(exchangeTransferMoneyRequestDTO,null);
String key = this.getKey(exchangeTransferMoneyRequestDTO, null);
params.put("key", key);
XKExchangeMoneyResponseDTO exchangeMoneyResponse = SAClient.exchangeTransferByAgentId(params);
//判断是否转移成功
@ -394,7 +406,7 @@ public class GamesSAServiceImpl implements IGamesService {
params.put("agentId", betRecordByTimeDTO.getAgentId());
String query = JsonUtil.mapToQueryString(params);
betRecordByTimeDTO.setQuery(query);
String key = this.getKey(betRecordByTimeDTO,null);
String key = this.getKey(betRecordByTimeDTO, null);
params.put("key", key);
XKBetRecordResponseDTO xkBetRecordResponseDTO = SAClient.getBetRecordByTime(params);
@ -416,7 +428,7 @@ public class GamesSAServiceImpl implements IGamesService {
params.put("agentId", betRecordByTimeDTO.getAgentId());
query = JsonUtil.mapToQueryString(params);
betRecordByTimeDTO.setQuery(query);
key = this.getKey(betRecordByTimeDTO,null);
key = this.getKey(betRecordByTimeDTO, null);
params.put("key", key);
xkBetRecordResponseDTO = SAClient.getBetRecordByTime(params);
this.batchInsert(xkBetRecordResponseDTO);
@ -431,6 +443,17 @@ public class GamesSAServiceImpl implements IGamesService {
}
/**
*
*
* @param betRecordByTimeDTO dto
* @return {@link Boolean }
*/
@Override
public Boolean getBetRecordByHistoryTime(BetRecordByTimeDTO betRecordByTimeDTO) {
return Boolean.TRUE;
}
/**
*
*
@ -467,7 +490,7 @@ public class GamesSAServiceImpl implements IGamesService {
params.put("agentId", kickMemberRequestDTO.getAgentId());
String query = JsonUtil.mapToQueryString(params);
kickMemberRequestDTO.setQuery(query);
String key = this.getKey(kickMemberRequestDTO,null);
String key = this.getKey(kickMemberRequestDTO, null);
params.put("key", key);
XKKickMemberDTO xkKickMemberDTO = SAClient.kickMember(params);
//判断是否获取成功
@ -519,7 +542,7 @@ public class GamesSAServiceImpl implements IGamesService {
*/
private void batchInsert(XKBetRecordResponseDTO xkBetRecordResponseDTO) {
List<GameBettingDetails> gameBettingDetails = new ArrayList<>();
List<Long> wagersIds = new ArrayList<>();
List<String> wagersIds = new ArrayList<>();
//数据组装
XKBetRecordResponseDTO.DataBean dataBean = xkBetRecordResponseDTO.getData();
//数据转化
@ -529,11 +552,11 @@ public class GamesSAServiceImpl implements IGamesService {
bettingDetails.setId(IdUtil.getSnowflakeNextId());
gameBettingDetails.add(bettingDetails);
}
wagersIds.add(bean.getWagersId());
wagersIds.add(String.valueOf(bean.getWagersId()));
}
if (!CollectionUtils.isEmpty(gameBettingDetails)) {
//查询重复数据id
List<Long> removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds);
List<String> removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds);
//用steam流清除list中与wagersIds集合相同的数据
gameBettingDetails = gameBettingDetails.stream()
.filter(detail -> !removeWagersIds.contains(detail.getWagersId()))
@ -590,7 +613,7 @@ public class GamesSAServiceImpl implements IGamesService {
.gameStatusType(resultBean.getType())
.gameCurrencyCode(resultBean.getAgentId())
.account(String.valueOf(resultBean.getAccount()))
.wagersId(resultBean.getWagersId())
.wagersId(String.valueOf(resultBean.getWagersId()))
.wagersTime(resultBean.getWagersTime())
.betAmount(resultBean.getBetAmount().abs())
.payoffTime(resultBean.getPayoffTime())