百顺幸运礼物调通

接入第三方
This commit is contained in:
tianfeng 2025-09-17 15:02:47 +08:00
parent cbc8e1fc9e
commit cb0da0ab2d
3 changed files with 322 additions and 19 deletions

View File

@ -42,6 +42,19 @@
<artifactId>telegrambots-client</artifactId>
<version>8.0.0</version>
</dependency>
<!-- Hutool HTTP 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -1,6 +1,11 @@
package com.red.circle.other.app.common.gift;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
@ -56,17 +61,22 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.TreeMap;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
@ -93,6 +103,22 @@ public class GameLuckyGiftCommon {
private final GameLuckyGiftRuleConfigService gameLuckyGiftRuleConfigService;
private final LuckyGiftProbabilityDetailsService luckyGiftProbabilityDetailsService;
// 第三方幸运礼物API配置
@Value("${lucky.gift.api.url:https://game-cn-test.jieyou.shop/lucky_gift/start_game}")
private String luckyGiftApiUrl;
@Value("${lucky.gift.api.app_key:w0GoEN4hg6GEjaYeB6mSG6VHld0LhdqT}")
private String luckyGiftApiAppKey;
@Value("${lucky.gift.api.app_id:9291621826}")
private String luckyGiftApiAppId;
@Value("${lucky.gift.api.app_channel:likei}")
private String luckyGiftApiAppChannel;
@Value("${lucky.gift.api.enabled:true}")
private boolean luckyGiftApiEnabled;
/**
* 发送幸运礼物抽奖mq.
*/
@ -303,7 +329,7 @@ public class GameLuckyGiftCommon {
param.getQuantity(), inventory);
if (CollectionUtils.isNotEmpty(makeMoneyInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.TRUE, Boolean.TRUE,
Boolean.TRUE, makeMoneyInventory);
Boolean.TRUE, makeMoneyInventory, param);
}
// true - 有大于0倍的签
@ -311,12 +337,12 @@ public class GameLuckyGiftCommon {
if (CollectionUtils.isNotEmpty(awardedInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.TRUE, Boolean.FALSE,
Boolean.TRUE, awardedInventory);
Boolean.TRUE, awardedInventory, param);
}
// true - 没有大于0倍的签, 给1倍,且不变更库存
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.FALSE, Boolean.FALSE,
Boolean.TRUE, null);
Boolean.TRUE, null, param);
}
// 不满足 - 累计送礼物次数逻辑
@ -326,12 +352,12 @@ public class GameLuckyGiftCommon {
param.getQuantity(), inventory);
if (CollectionUtils.isNotEmpty(makeMoneyInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.TRUE, Boolean.TRUE,
Boolean.FALSE, makeMoneyInventory);
Boolean.FALSE, makeMoneyInventory, param);
}
// true - 没有赚钱签
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.TRUE, Boolean.FALSE,
Boolean.FALSE, inventory);
Boolean.FALSE, inventory, param);
}
// 不满足 - 连续亏钱逻辑
@ -344,7 +370,7 @@ public class GameLuckyGiftCommon {
if (CollectionUtils.isNotEmpty(awardedInventory)) {
GameLuckyGiftRewardResult rewardResult = buildRewardResult(GameLuckyGiftModeEnum.ALL,
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE, awardedInventory);
Boolean.TRUE, Boolean.TRUE, Boolean.TRUE, awardedInventory, param);
// 如果为赚钱签则需要重置连续亏损
rewardResult.setResetComboLossCount(
rewardResult.getReward().getMultiple() > param.getQuantity());
@ -353,12 +379,12 @@ public class GameLuckyGiftCommon {
// false - 无大于0倍的签
return buildRewardResult(GameLuckyGiftModeEnum.ALL, Boolean.FALSE, Boolean.FALSE,
Boolean.TRUE, null);
Boolean.TRUE, null, param);
}
// 不满足 - 累计送礼物次数要求
GameLuckyGiftRewardResult rewardResult = buildRewardResult(GameLuckyGiftModeEnum.ALL,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE, inventory);
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE, inventory, param);
// 如果为赚钱签则需要重置连续亏损
rewardResult.setResetComboLossCount(
rewardResult.getReward().getMultiple() > param.getQuantity());
@ -393,17 +419,17 @@ public class GameLuckyGiftCommon {
if (CollectionUtils.isNotEmpty(makeMoneyInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.LOSS, Boolean.TRUE, Boolean.TRUE,
Boolean.FALSE, makeMoneyInventory);
Boolean.FALSE, makeMoneyInventory, param);
}
// true - 没有赚钱签
return buildRewardResult(GameLuckyGiftModeEnum.LOSS, Boolean.TRUE, Boolean.FALSE,
Boolean.FALSE, inventory);
Boolean.FALSE, inventory, param);
}
// 不满足 - 连续亏钱逻辑
GameLuckyGiftRewardResult rewardResult = buildRewardResult(GameLuckyGiftModeEnum.LOSS,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE, inventory);
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE, inventory, param);
rewardResult.setResetComboLossCount(
rewardResult.getReward().getMultiple() > param.getQuantity());
@ -436,12 +462,12 @@ public class GameLuckyGiftCommon {
if (CollectionUtils.isNotEmpty(awardedInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.GIFT, Boolean.TRUE, Boolean.TRUE,
Boolean.TRUE, awardedInventory);
Boolean.TRUE, awardedInventory, param);
}
// true - 给1倍,且不变更库存
return buildRewardResult(GameLuckyGiftModeEnum.GIFT, Boolean.FALSE, Boolean.FALSE,
Boolean.TRUE, null);
Boolean.TRUE, null, param);
}
// 不满足 - 累计送礼物次数逻辑
@ -451,12 +477,12 @@ public class GameLuckyGiftCommon {
param.getQuantity(), inventory);
if (CollectionUtils.isNotEmpty(makeMoneyInventory)) {
return buildRewardResult(GameLuckyGiftModeEnum.GIFT, Boolean.TRUE, Boolean.TRUE,
Boolean.FALSE, makeMoneyInventory);
Boolean.FALSE, makeMoneyInventory, param);
}
// true - 没有赚钱签
return buildRewardResult(GameLuckyGiftModeEnum.GIFT, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE,
inventory);
inventory, param);
}
@ -467,7 +493,7 @@ public class GameLuckyGiftCommon {
log.info("runRandomMode param:{}", JSON.toJSONString(param));
return buildRewardResult(GameLuckyGiftModeEnum.NONE, Boolean.TRUE, Boolean.TRUE, Boolean.TRUE,
listRemainInventory(param));
listRemainInventory(param), param);
}
/**
@ -478,11 +504,12 @@ public class GameLuckyGiftCommon {
* @param resetComboLossCount 是否重置连续亏损次数
* @param resetGiftsCount 是否重置连续送礼累计
* @param makeMoneyInventory 签库存
* @param param 游戏参数
* @return 抽奖结果
*/
private GameLuckyGiftRewardResult buildRewardResult(GameLuckyGiftModeEnum mode,
Boolean changeInventory, Boolean resetComboLossCount, Boolean resetGiftsCount,
List<GameLuckyGiftInventoryCache> makeMoneyInventory) {
List<GameLuckyGiftInventoryCache> makeMoneyInventory, GameLuckyGiftParam param) {
return new GameLuckyGiftRewardResult()
.setMode(mode)
@ -493,7 +520,7 @@ public class GameLuckyGiftCommon {
.setReward(makeMoneyInventory == null ? new GameLuckyGiftInventoryCache()
.setMultiple(1)
.setQuantity(1)
: drawLuckyGift(makeMoneyInventory)
: drawLuckyGift(makeMoneyInventory, param)
);
}
@ -594,6 +621,158 @@ public class GameLuckyGiftCommon {
.getPropsResources()).orElse(new UserPropsResourcesDTO());
}
/**
* 调用第三方幸运礼物API.
*/
private ThirdPartyLotteryResult callThirdPartyLotteryApi(GameLuckyGiftParam param, List<String> orderIds) {
try {
Map<String, Object> requestData = new HashMap<>();
requestData.put("room_id", param.getRoomId().toString());
requestData.put("user_id", param.getUserId().toString());
requestData.put("order_id", orderIds);
requestData.put("app_id", luckyGiftApiAppId);
requestData.put("app_channel", luckyGiftApiAppChannel);
requestData.put("gift_id", param.getGiftId().intValue());
requestData.put("gift_price", param.getGift().getGiftCandy().intValue());
requestData.put("gift_num", param.getQuantity());
requestData.put("gift_is_free", 0); // 0收费 1免费
requestData.put("timestamp", System.currentTimeMillis() / 1000);
// 生成签名
String signature = generateSignature(requestData);
requestData.put("signature", signature);
log.info("调用第三方幸运礼物API请求参数{}", JSON.toJSONString(requestData));
// 特别记录order_id的编码情况
log.debug("order_id原始值: {}", JSON.toJSONString(orderIds));
log.debug("order_id URL编码后: {}", URLEncoder.encode(JSON.toJSONString(orderIds), StandardCharsets.UTF_8));
// 发送HTTP请求 - 使用Hutool的HttpUtil
String requestBody = JSON.toJSONString(requestData);
String response = HttpUtil.createPost(luckyGiftApiUrl)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.timeout(10000)
.body(requestBody)
.execute()
.body();
if (StringUtils.isBlank(response)) {
log.error("第三方幸运礼物API返回空内容");
return createFailedResult();
}
log.info("第三方幸运礼物API响应{}", response);
// 解析响应
JSONObject responseJson = JSON.parseObject(response);
if (responseJson.getInteger("code") == 200) {
JSONObject data = responseJson.getJSONObject("data");
if (data == null) {
log.error("第三方幸运礼物API返回data为空");
return createFailedResult();
}
JSONArray orderList = data.getJSONArray("OrderList");
if (orderList == null || orderList.isEmpty()) {
log.error("第三方幸运礼物API返回订单列表为空");
return createFailedResult();
}
ThirdPartyLotteryResult result = new ThirdPartyLotteryResult();
result.setSuccess(true);
result.setOrderResults(new ArrayList<>());
for (int i = 0; i < orderList.size(); i++) {
JSONObject order = orderList.getJSONObject(i);
OrderResult orderResult = new OrderResult();
orderResult.setOrderId(order.getString("order_id"));
orderResult.setIsWin(order.getInteger("is_win"));
orderResult.setRewardNum(order.getInteger("reward_num"));
result.getOrderResults().add(orderResult);
}
return result;
} else {
log.error("第三方幸运礼物API返回错误code{}docs{}",
responseJson.getInteger("code"), responseJson.getString("docs"));
return createFailedResult();
}
} catch (Exception e) {
log.error("调用第三方幸运礼物API异常", e);
return createFailedResult();
}
}
/**
* 生成API签名.
*/
private String generateSignature(Map<String, Object> params) {
try {
// 按键名升序排列参数
TreeMap<String, Object> sortedParams = new TreeMap<>(params);
// 构建签名字符串
StringBuilder signBuilder = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (!"signature".equals(entry.getKey())) {
if (!signBuilder.isEmpty()) {
signBuilder.append("&");
}
String key = entry.getKey();
Object value = entry.getValue();
String valueStr;
// 特殊处理order_id字段需要URL编码
if ("order_id".equals(key) && value instanceof List) {
// 将List转为JSON字符串然后URL编码
String jsonStr = JSON.toJSONString(value);
valueStr = URLEncoder.encode(jsonStr, StandardCharsets.UTF_8);
} else {
valueStr = String.valueOf(value);
}
signBuilder.append(key).append("=").append(valueStr);
}
}
signBuilder.append("&app_key=").append(luckyGiftApiAppKey);
log.debug("签名原始字符串: {}", signBuilder.toString());
// MD5加密
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(signBuilder.toString().getBytes("UTF-8"));
// 转换为16进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
String signature = hexString.toString();
log.debug("生成的签名: {}", signature);
return signature;
} catch (Exception e) {
log.error("生成签名异常", e);
return "";
}
}
/**
* 创建失败的结果.
*/
private ThirdPartyLotteryResult createFailedResult() {
ThirdPartyLotteryResult result = new ThirdPartyLotteryResult();
result.setSuccess(false);
return result;
}
/**
* 获得中奖倍数.
*/
@ -640,9 +819,81 @@ public class GameLuckyGiftCommon {
}
/**
* 用户抽奖根据剩余数量与剩余总数的比例确定概率.
* 用户抽奖使用第三方API或本地算法.
*/
private GameLuckyGiftInventoryCache drawLuckyGift(
List<GameLuckyGiftInventoryCache> listInventory, GameLuckyGiftParam param) {
// 如果启用第三方API则调用第三方接口
if (luckyGiftApiEnabled) {
return drawLuckyGiftWithThirdPartyApi(listInventory, param);
}
// 否则使用原有本地算法
return drawLuckyGiftLocal(listInventory);
}
/**
* 使用第三方API抽奖.
*/
private GameLuckyGiftInventoryCache drawLuckyGiftWithThirdPartyApi(
List<GameLuckyGiftInventoryCache> listInventory, GameLuckyGiftParam param) {
try {
// 生成订单ID列表这里只有一个订单ID
List<String> orderIds = List.of(param.getId().toString());
// 调用第三方API
ThirdPartyLotteryResult apiResult = callThirdPartyLotteryApi(param, orderIds);
if (apiResult.isSuccess() && !apiResult.getOrderResults().isEmpty()) {
OrderResult firstOrder = apiResult.getOrderResults().get(0);
// 根据第三方API返回的结果确定倍数
int rewardMultiple = 1; // 默认1倍
if (firstOrder.getIsWin() == 1 && firstOrder.getRewardNum() > 0) {
// 第三方返回的奖励数量转换为倍数
// 根据礼物价格计算倍数奖励金额 / 礼物价格
rewardMultiple = firstOrder.getRewardNum() / param.getGift().getGiftCandy().intValue();
if (rewardMultiple <= 0) {
rewardMultiple = 1;
}
}
// 查找对应倍数的库存如果没有则返回最接近的
GameLuckyGiftInventoryCache selectedInventory = null;
for (GameLuckyGiftInventoryCache inventory : listInventory) {
if (inventory.getMultiple().equals(rewardMultiple)) {
selectedInventory = inventory;
break;
}
}
// 如果没找到完全匹配的倍数选择最接近的
if (selectedInventory == null) {
int finalRewardMultiple = rewardMultiple;
selectedInventory = listInventory.stream()
.min(Comparator.comparing(inv -> Math.abs(inv.getMultiple() - finalRewardMultiple)))
.orElse(listInventory.get(0));
}
// 创建新的库存对象使用API返回的倍数
return new GameLuckyGiftInventoryCache()
.setMultiple(rewardMultiple)
.setQuantity(selectedInventory.getQuantity());
}
} catch (Exception e) {
log.error("第三方API抽奖异常降级使用本地算法", e);
}
// API调用失败降级使用本地算法
return drawLuckyGiftLocal(listInventory);
}
/**
* 本地抽奖算法原有逻辑.
*/
private GameLuckyGiftInventoryCache drawLuckyGiftLocal(
List<GameLuckyGiftInventoryCache> listInventory) {
long totalQuantity = listInventory.stream().mapToLong(GameLuckyGiftInventoryCache::getQuantity)
@ -795,6 +1046,33 @@ public class GameLuckyGiftCommon {
return Objects.nonNull(probabilityConfig.getGiftGiveTotal())
&& probabilityConfig.getGiftGiveTotal() > 0;
}
/**
* 第三方抽奖API结果.
*/
@Data
@Accessors(chain = true)
static class ThirdPartyLotteryResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private boolean success;
private List<OrderResult> orderResults;
}
/**
* 订单结果.
*/
@Data
@Accessors(chain = true)
static class OrderResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String orderId;
private Integer isWin; // 0未中 1中奖
private Integer rewardNum; // 奖励数量
}
/**
* 是否开启了用户连续亏钱次数.

View File

@ -0,0 +1,12 @@
import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class ApiTest {
@Test
public void test() {
System.out.println(new BCryptPasswordEncoder().encode("8826681"));
}
}