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"; public static final String VIEW_FILE_URL = "view.file.url";
/** /**
* nginx * nginx
*/ */
@ -26,6 +28,18 @@ public class ConfigConstants
*/ */
public static final String DOMAIN_NGINX_CONFIG_PARAM = "domain.nginx.config.param"; 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 * url
*/ */

View File

@ -3,9 +3,16 @@ package com.ff.base.utils;
import com.ff.base.exception.base.BaseException; import com.ff.base.exception.base.BaseException;
import lombok.Data; 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 javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets; 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; import java.util.Base64;
@ -17,32 +24,61 @@ import java.util.Base64;
*/ */
public class DESEncryptUtils { public class DESEncryptUtils {
/** /**
* *
* *
* @param inString * @param inString
* @param secretKey * @param key
* @return {@link String } * @return {@link String }
* @throws Exception * @throws Exception
*/ */
public static String DESEncrypt(String inString, String secretKey) { public static String DESEncrypt(String inString, String key) {
try { try {
// 使用相同的密钥进行加密
SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.US_ASCII), "DES");
// 使用DES加密算法 byte[] encryptKey = key.getBytes(); // encryptKey
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); KeySpec keySpec = new DESKeySpec(encryptKey);
cipher.init(Cipher.ENCRYPT_MODE, key); SecretKey myDesKey = SecretKeyFactory.getInstance("DES").generateSecret(keySpec);
IvParameterSpec iv = new IvParameterSpec(encryptKey);
// 加密输入字符串 KeyGenerator keygenerator = KeyGenerator.getInstance("DES");
byte[] encrypted = cipher.doFinal(inString.getBytes(StandardCharsets.UTF_8));
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) { } 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.dtflys.forest.annotation.*;
import com.ff.game.api.jili.dto.*; import com.ff.game.api.jili.dto.*;
import com.ff.game.api.sa.address.MySAAddressSource; import com.ff.game.api.sa.address.MySAAddressSource;
import com.ff.game.api.sa.dto.SARegUserInfoResponse;
import com.ff.game.api.xk.dto.*; import com.ff.game.api.xk.dto.*;
import java.util.Map; import java.util.Map;
@ -22,29 +21,33 @@ public interface SAClient {
* @param parameters * @param parameters
* @return {@link String } * @return {@link String }
*/ */
@Post( url ="/api.aspx/RegUserInfo?${parameters}", @Post( url ="/api.aspx/RegUserInfo",
headers = { headers = {
"Content-type: application/x-www-form-urlencoded" "Content-type: application/x-www-form-urlencoded"
}) })
SARegUserInfoResponse createMember(@Var("parameters") String parameters); String createMember(@Body String parameters);
/** /**
* *
* *
* @param params * @return {@link String }
* @return {@link XKMemberInfoDTO }
*/ */
@Post("/getMemberInfo") @Post( url ="/api.aspx/GetUserStatusDV",
XKMemberInfoDTO getMemberInfo(@JSONBody Map<String, Object> params); headers = {
"Content-type: application/x-www-form-urlencoded"
})
String getMemberInfo(@Body String params);
/** /**
* *
* *
* @param params * @param params
* @return {@link JILILoginWithoutRedirectResponseDTO }
*/ */
@Post("/loginWithoutRedirect") @Post( url ="/api.aspx/LoginRequest",
XKLoginWithoutRedirectResponseDTO loginWithoutRedirect(@JSONBody Map<String, Object> params); 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; package com.ff.game.api.sa.dto;
import lombok.Data;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
@ -10,37 +12,16 @@ import javax.xml.bind.annotation.XmlRootElement;
* @author shi * @author shi
* @date 2025/03/25 * @date 2025/03/25
*/ */
@Data
@XmlRootElement(name = "RegUserInfoResponse") @XmlRootElement(name = "RegUserInfoResponse")
public class SARegUserInfoResponse { public class SARegUserInfoResponse {
@XmlElement(name = "ErrorMsgId")
private String errorMsgId; private Integer errorMsgId;
@XmlElement(name = "ErrorMsg")
private String errorMsg; private String errorMsg;
@XmlElement(name = "Username")
private String 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.IdUtil;
import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.NumberUtil;
import com.ff.base.constant.CacheConstants; import com.ff.base.constant.CacheConstants;
import com.ff.base.constant.ConfigConstants;
import com.ff.base.constant.Constants; import com.ff.base.constant.Constants;
import com.ff.base.core.redis.RedisCache; import com.ff.base.core.redis.RedisCache;
import com.ff.base.enums.*; import com.ff.base.enums.*;
import com.ff.base.exception.base.ApiException; import com.ff.base.exception.base.ApiException;
import com.ff.base.exception.base.BaseException; import com.ff.base.exception.base.BaseException;
import com.ff.base.system.service.ISysConfigService; import com.ff.base.system.service.ISysConfigService;
import com.ff.base.utils.DESEncryptUtils; import com.ff.base.utils.*;
import com.ff.base.utils.DateUtils;
import com.ff.base.utils.JsonUtil;
import com.ff.base.utils.sign.Md5Utils; import com.ff.base.utils.sign.Md5Utils;
import com.ff.base.utils.uuid.IdUtils; import com.ff.base.utils.uuid.IdUtils;
import com.ff.config.KeyConfig; import com.ff.config.KeyConfig;
import com.ff.game.api.IGamesService; import com.ff.game.api.IGamesService;
import com.ff.game.api.request.*; import com.ff.game.api.request.*;
import com.ff.game.api.sa.client.SAClient; 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.SARegUserInfoResponse;
import com.ff.game.api.sa.dto.SAGetUserStatusResponse;
import com.ff.game.api.xk.dto.*; import com.ff.game.api.xk.dto.*;
import com.ff.game.domain.*; import com.ff.game.domain.*;
import com.ff.game.dto.GameSecretKeyCurrencyDTO; import com.ff.game.dto.GameSecretKeyCurrencyDTO;
@ -100,6 +101,8 @@ public class GamesSAServiceImpl implements IGamesService {
* @return {@link Boolean } * @return {@link Boolean }
*/ */
private Boolean getIsSuccess(Integer errorCode) { 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; return 0 == errorCode;
} }
@ -119,7 +122,6 @@ public class GamesSAServiceImpl implements IGamesService {
} }
/** /**
* *
* *
@ -128,7 +130,7 @@ public class GamesSAServiceImpl implements IGamesService {
*/ */
@Override @Override
public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) { public Boolean createMember(CreateMemberRequestDTO createMemberRequestDTO) {
log.info("GamesXKServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO); log.info("GamesSAServiceImpl [createMember] 请求参数 {}", createMemberRequestDTO);
Map<String, Object> params = new LinkedHashMap<>(); Map<String, Object> params = new LinkedHashMap<>();
params.put("Username", createMemberRequestDTO.getAccount()); params.put("Username", createMemberRequestDTO.getAccount());
@ -136,8 +138,12 @@ public class GamesSAServiceImpl implements IGamesService {
String query = JsonUtil.mapToQueryString(params); String query = JsonUtil.mapToQueryString(params);
createMemberRequestDTO.setQuery(query); createMemberRequestDTO.setQuery(query);
String key = this.getKey(createMemberRequestDTO, "RegUserInfo"); String key = this.getKey(createMemberRequestDTO, "RegUserInfo");
SARegUserInfoResponse saRegUserInfoResponse = SAClient.createMember(key); String result = SAClient.createMember(key);
String errorCode = saRegUserInfoResponse.getErrorMsgId(); SARegUserInfoResponse saRegUserInfoResponse = XmlUtils.xmlDecrypt(result, SARegUserInfoResponse.class);
Integer errorCode = saRegUserInfoResponse.getErrorMsgId();
if (this.getIsSuccess(errorCode)) {
return Boolean.TRUE;
}
//判断是否获取成功 //判断是否获取成功
return Boolean.FALSE; return Boolean.FALSE;
} }
@ -151,28 +157,25 @@ public class GamesSAServiceImpl implements IGamesService {
*/ */
@Override @Override
public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) { public MemberInfoResponseDTO getMemberInfo(MemberInfoRequestDTO memberInfoRequestDTO) {
log.info("GamesXKServiceImpl [getMemberInfo] 请求参数 {}", memberInfoRequestDTO); log.info("GamesSAServiceImpl [getMemberInfo] 请求参数 {}", memberInfoRequestDTO);
Map<String, Object> params = new LinkedHashMap<>(); Map<String, Object> params = new LinkedHashMap<>();
params.put("accounts", memberInfoRequestDTO.getAccounts()); params.put("Username", memberInfoRequestDTO.getAccounts());
params.put("agentId", memberInfoRequestDTO.getAgentId());
String query = JsonUtil.mapToQueryString(params); String query = JsonUtil.mapToQueryString(params);
memberInfoRequestDTO.setQuery(query); memberInfoRequestDTO.setQuery(query);
String key = this.getKey(memberInfoRequestDTO,null); String key = this.getKey(memberInfoRequestDTO, "GetUserStatusDV");
params.put("key", key); String result = SAClient.getMemberInfo(key);
XKMemberInfoDTO xkMemberInfoDTO = SAClient.getMemberInfo(params); SAGetUserStatusResponse saGetUserStatusResponse = XmlUtils.xmlDecrypt(result, SAGetUserStatusResponse.class);
//判断是否获取成功 Integer errorCode = saGetUserStatusResponse.getErrorMsgId();
if (this.getIsSuccess(xkMemberInfoDTO.getCode())) { if (this.getIsSuccess(errorCode)) {
List<MemberInfoResponseDTO> memberInfoResponseDTOS = new ArrayList<>();
xkMemberInfoDTO.getData().forEach(e -> { return MemberInfoResponseDTO.builder()
memberInfoResponseDTOS.add(MemberInfoResponseDTO.builder() .account(memberInfoRequestDTO.getAccounts())
.status(e.getStatus()) .balance(saGetUserStatusResponse.getBalance())
.balance(e.getBalance()) .status(saGetUserStatusResponse.isOnline() ? GameMemberStatus.ONLINE.getCode() : GameMemberStatus.OFFLINE.getCode())
.account(e.getAccount()) .build();
.build()); }
}); else {
return memberInfoResponseDTOS.get(0); throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
} else {
throw new BaseException(xkMemberInfoDTO.getMsg());
} }
} }
@ -187,23 +190,32 @@ public class GamesSAServiceImpl implements IGamesService {
log.info("GamesXKServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin); log.info("GamesXKServiceImpl [loginWithoutRedirect] 请求参数 {}", gamesLogin);
Map<String, Object> params = new LinkedHashMap<>(); Map<String, Object> params = new LinkedHashMap<>();
params.put("account", gamesLogin.getAccount()); params.put("Username", gamesLogin.getAccount());
params.put("gameId", Integer.valueOf(gamesLogin.getGameId())); params.put("CurrencyType", gamesLogin.getCurrency());
params.put("lang", gamesLogin.getLang());
params.put("agentId", gamesLogin.getAgentId());
String query = JsonUtil.mapToQueryString(params); String query = JsonUtil.mapToQueryString(params);
gamesLogin.setQuery(query); gamesLogin.setQuery(query);
String key = this.getKey(gamesLogin,null); String key = this.getKey(gamesLogin, "LoginRequest");
params.put("key", key); String result = SAClient.loginWithoutRedirect(key);
params.put("disableFullScreen", gamesLogin.getDisableFullScreen()); SALoginRequestResponse saLoginRequestResponse = XmlUtils.xmlDecrypt(result, SALoginRequestResponse.class);
params.put("homeUrl", gamesLogin.getHomeUrl()); Integer errorCode = saLoginRequestResponse.getErrorMsgId();
params.put("platform", gamesLogin.getPlatform());
XKLoginWithoutRedirectResponseDTO xkLoginWithoutRedirectResponseDTO = SAClient.loginWithoutRedirect(params);
//判断是否获取成功 //判断是否获取成功
if (this.getIsSuccess(xkLoginWithoutRedirectResponseDTO.getCode())) { if (this.getIsSuccess(errorCode)) {
return xkLoginWithoutRedirectResponseDTO.getData();
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 { } else {
throw new BaseException(xkLoginWithoutRedirectResponseDTO.getMsg()); throw new ApiException(ErrorCode.ACCOUNT_NOT_EXIST.getCode());
} }
} }
@ -431,6 +443,17 @@ public class GamesSAServiceImpl implements IGamesService {
} }
/**
*
*
* @param betRecordByTimeDTO dto
* @return {@link Boolean }
*/
@Override
public Boolean getBetRecordByHistoryTime(BetRecordByTimeDTO betRecordByTimeDTO) {
return Boolean.TRUE;
}
/** /**
* *
* *
@ -519,7 +542,7 @@ public class GamesSAServiceImpl implements IGamesService {
*/ */
private void batchInsert(XKBetRecordResponseDTO xkBetRecordResponseDTO) { private void batchInsert(XKBetRecordResponseDTO xkBetRecordResponseDTO) {
List<GameBettingDetails> gameBettingDetails = new ArrayList<>(); List<GameBettingDetails> gameBettingDetails = new ArrayList<>();
List<Long> wagersIds = new ArrayList<>(); List<String> wagersIds = new ArrayList<>();
//数据组装 //数据组装
XKBetRecordResponseDTO.DataBean dataBean = xkBetRecordResponseDTO.getData(); XKBetRecordResponseDTO.DataBean dataBean = xkBetRecordResponseDTO.getData();
//数据转化 //数据转化
@ -529,11 +552,11 @@ public class GamesSAServiceImpl implements IGamesService {
bettingDetails.setId(IdUtil.getSnowflakeNextId()); bettingDetails.setId(IdUtil.getSnowflakeNextId());
gameBettingDetails.add(bettingDetails); gameBettingDetails.add(bettingDetails);
} }
wagersIds.add(bean.getWagersId()); wagersIds.add(String.valueOf(bean.getWagersId()));
} }
if (!CollectionUtils.isEmpty(gameBettingDetails)) { if (!CollectionUtils.isEmpty(gameBettingDetails)) {
//查询重复数据id //查询重复数据id
List<Long> removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds); List<String> removeWagersIds = gameBettingDetailsService.selectGameBettingDetailsByWagersId(wagersIds);
//用steam流清除list中与wagersIds集合相同的数据 //用steam流清除list中与wagersIds集合相同的数据
gameBettingDetails = gameBettingDetails.stream() gameBettingDetails = gameBettingDetails.stream()
.filter(detail -> !removeWagersIds.contains(detail.getWagersId())) .filter(detail -> !removeWagersIds.contains(detail.getWagersId()))
@ -590,7 +613,7 @@ public class GamesSAServiceImpl implements IGamesService {
.gameStatusType(resultBean.getType()) .gameStatusType(resultBean.getType())
.gameCurrencyCode(resultBean.getAgentId()) .gameCurrencyCode(resultBean.getAgentId())
.account(String.valueOf(resultBean.getAccount())) .account(String.valueOf(resultBean.getAccount()))
.wagersId(resultBean.getWagersId()) .wagersId(String.valueOf(resultBean.getWagersId()))
.wagersTime(resultBean.getWagersTime()) .wagersTime(resultBean.getWagersTime())
.betAmount(resultBean.getBetAmount().abs()) .betAmount(resultBean.getBetAmount().abs())
.payoffTime(resultBean.getPayoffTime()) .payoffTime(resultBean.getPayoffTime())