From cb0da0ab2d387de98d157a028ff381b23fa7bad4 Mon Sep 17 00:00:00 2001
From: tianfeng <769204422@qq.com>
Date: Wed, 17 Sep 2025 15:02:47 +0800
Subject: [PATCH] =?UTF-8?q?=E7=99=BE=E9=A1=BA=E5=B9=B8=E8=BF=90=E7=A4=BC?=
=?UTF-8?q?=E7=89=A9=E8=B0=83=E9=80=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
接入第三方
---
.../other-application/pom.xml | 13 +
.../app/common/gift/GameLuckyGiftCommon.java | 316 ++++++++++++++++--
.../other-start/src/test/java/ApiTest.java | 12 +
3 files changed, 322 insertions(+), 19 deletions(-)
create mode 100644 rc-service/rc-service-other/other-start/src/test/java/ApiTest.java
diff --git a/rc-service/rc-service-other/other-application/pom.xml b/rc-service/rc-service-other/other-application/pom.xml
index 453bb3c0..80f1eead 100644
--- a/rc-service/rc-service-other/other-application/pom.xml
+++ b/rc-service/rc-service-other/other-application/pom.xml
@@ -42,6 +42,19 @@
telegrambots-client
8.0.0
+
+
+
+ cn.hutool
+ hutool-core
+ 5.8.22
+
+
+ cn.hutool
+ hutool-http
+ 5.8.22
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/GameLuckyGiftCommon.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/GameLuckyGiftCommon.java
index 190835b7..12b5871d 100644
--- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/GameLuckyGiftCommon.java
+++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/GameLuckyGiftCommon.java
@@ -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 makeMoneyInventory) {
+ List 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 orderIds) {
+ try {
+ Map 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 params) {
+ try {
+ // 按键名升序排列参数
+ TreeMap sortedParams = new TreeMap<>(params);
+
+ // 构建签名字符串
+ StringBuilder signBuilder = new StringBuilder();
+ for (Map.Entry 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 listInventory, GameLuckyGiftParam param) {
+
+ // 如果启用第三方API,则调用第三方接口
+ if (luckyGiftApiEnabled) {
+ return drawLuckyGiftWithThirdPartyApi(listInventory, param);
+ }
+
+ // 否则使用原有本地算法
+ return drawLuckyGiftLocal(listInventory);
+ }
+
+ /**
+ * 使用第三方API抽奖.
+ */
+ private GameLuckyGiftInventoryCache drawLuckyGiftWithThirdPartyApi(
+ List listInventory, GameLuckyGiftParam param) {
+
+ try {
+ // 生成订单ID列表(这里只有一个订单ID)
+ List 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 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 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; // 奖励数量
+ }
/**
* 是否开启了用户连续亏钱次数.
diff --git a/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java b/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java
new file mode 100644
index 00000000..3e2e1de8
--- /dev/null
+++ b/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java
@@ -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"));
+ }
+
+
+}