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")); + } + + +}