From 2fd0976d0dfb1e0cdd8119177171d1e524cc5c55 Mon Sep 17 00:00:00 2001 From: hy001 Date: Mon, 20 Apr 2026 22:07:10 +0800 Subject: [PATCH] feat: update mifapay and region config flows --- .../rc-service-external/application.yml | 4 +- nacos_config/rc-service-order/application.yml | 1 + nacos_config/rc-service-other/application.yml | 2 +- .../core/util/CountryCodeAliasUtils.java | 119 ++++++++++++ .../model/cmd/sys/SysCountryCodeCmd.java | 22 ++- .../model/dto/sys/SysCountryCodeDTO.java | 22 ++- .../src/main/resources/application.yml | 16 +- .../adapter/app/WebPayRestController.java | 45 +++-- ...FaPayServerNoticeReceivePaymentCmdExe.java | 177 +++++++++--------- .../pay/web/PayWebOrderStatusQueryExe.java | 51 +++++ .../MiFaPayWebPayPlaceAnOrderStrategy.java | 33 ++-- .../web/strategy/PayPlaceAnOrderService.java | 62 +++--- .../order/app/common/InAppPurchaseCommon.java | 72 ++++--- .../app/common/MiFaPayMysqlOrderSupport.java | 168 +++++++++++++++++ .../InAppPurchaseProductServiceImpl.java | 57 +++--- .../order/app/service/MiFaPayService.java | 119 +++++++----- .../clientobject/pay/PayOrderStatusCO.java | 76 ++++++++ .../order/app/dto/cmd/PayOrderStatusCmd.java | 26 +++ .../service/InAppPurchaseProductService.java | 31 +-- .../order/infra/config/MiFaPayProperties.java | 15 +- ...pPurchaseCollectionReceiptServiceImpl.java | 112 +++++------ .../impl/InAppPurchaseDetailsServiceImpl.java | 150 +++++++-------- .../dao/order/OrderUserPurchasePayDAO.java | 10 + .../order/OrderUserPurchasePayNoticeDAO.java | 10 + .../entity/order/OrderUserPurchasePay.java | 113 +++++++++++ .../order/OrderUserPurchasePayNotice.java | 37 ++++ .../OrderUserPurchasePayNoticeService.java | 16 ++ .../order/OrderUserPurchasePayService.java | 20 ++ ...OrderUserPurchasePayNoticeServiceImpl.java | 31 +++ .../impl/OrderUserPurchasePayServiceImpl.java | 40 ++++ .../gift/query/ActivityGiftListQryExe.java | 41 ++-- .../room/query/RoomGiftListQueryExe.java | 33 ++-- .../room/query/RoomVoiceDiscoverQryExe.java | 37 ++-- .../app/command/sys/query/BannerQryExe.java | 33 ++-- .../sys/query/SysCountryOpenTopQryExe.java | 29 +-- .../app/common/gift/GameLuckyGiftCommon.java | 15 +- .../gift/LuckyGiftMultipleCalculator.java | 22 +++ .../common/gift/LuckyGiftResultProcessor.java | 17 +- .../common/room/RoomVoiceProfileCommon.java | 32 ++-- .../convertor/sys/SysCountryAppConvertor.java | 38 ++-- .../app/scheduler/EmptyRoomCleanTask.java | 75 -------- .../RoomOnlineQuantityReconcileTask.java | 92 +++++++++ .../trtc/EnterRoomCallbackStrategyTest.java | 43 +++++ .../gift/LuckyGiftResultProcessorTest.java | 126 +++++++++++++ .../room/RoomVoiceProfileCommonTest.java | 105 +++++++++++ .../dto/clientobject/sys/CountryCodeCO.java | 22 ++- .../common/sys/CountryCodeAliasSupport.java | 82 ++++++++ .../sys/CountryCodeInnerConvertor.java | 59 +++--- .../live/impl/ActiveVoiceRoomServiceImpl.java | 68 +++---- .../impl/RoomProfileManagerServiceImpl.java | 30 +-- .../impl/TeamPolicyManagerServiceImpl.java | 28 +-- .../rds/entity/sys/SysCountryCode.java | 22 ++- .../sys/impl/BannerConfigServiceImpl.java | 80 +++++--- .../sys/impl/SysCountryCodeServiceImpl.java | 56 ++++-- .../gateway/user/UserRegionGatewayImpl.java | 56 +++--- sql/20260420_mifapay_order_mysql.sql | 64 +++++++ sql/20260420_sys_country_code_alias_codes.sql | 6 + 57 files changed, 2198 insertions(+), 770 deletions(-) create mode 100644 rc-common-business/common-business-core/src/main/java/com/red/circle/common/business/core/util/CountryCodeAliasUtils.java create mode 100644 rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebOrderStatusQueryExe.java create mode 100644 rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/MiFaPayMysqlOrderSupport.java create mode 100644 rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/clientobject/pay/PayOrderStatusCO.java create mode 100644 rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/cmd/PayOrderStatusCmd.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayDAO.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayNoticeDAO.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePay.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePayNotice.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayNoticeService.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayService.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayNoticeServiceImpl.java create mode 100644 rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayServiceImpl.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftMultipleCalculator.java delete mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/EmptyRoomCleanTask.java create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomOnlineQuantityReconcileTask.java create mode 100644 rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/party3rd/callback/trtc/EnterRoomCallbackStrategyTest.java create mode 100644 rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessorTest.java create mode 100644 rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommonTest.java create mode 100644 rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/common/sys/CountryCodeAliasSupport.java create mode 100644 sql/20260420_mifapay_order_mysql.sql create mode 100644 sql/20260420_sys_country_code_alias_codes.sql diff --git a/nacos_config/rc-service-external/application.yml b/nacos_config/rc-service-external/application.yml index 3ef8ee2..beeb392 100644 --- a/nacos_config/rc-service-external/application.yml +++ b/nacos_config/rc-service-external/application.yml @@ -34,8 +34,8 @@ red-circle: access-url: ${LIKEI_TENCENT_COS_ACCESS_URL} instant-message: broadcast-group: - LOTFUN: ${LIKEI_IM_BROADCAST_GROUP_LOTFUN} - LIKEI: ${LIKEI_IM_BROADCAST_GROUP_LIKEI} + LOTFUN: "${LIKEI_IM_BROADCAST_GROUP_LOTFUN}" + LIKEI: "${LIKEI_IM_BROADCAST_GROUP_LIKEI}" tencet-trtc: secretId: ${LIKEI_TRTC_SECRET_ID} secretKey: ${LIKEI_TRTC_SECRET_KEY} diff --git a/nacos_config/rc-service-order/application.yml b/nacos_config/rc-service-order/application.yml index 9aa1ebf..9f0c678 100644 --- a/nacos_config/rc-service-order/application.yml +++ b/nacos_config/rc-service-order/application.yml @@ -52,6 +52,7 @@ red-circle: gateway-base-url: ${LIKEI_MIFA_PAY_GATEWAY_BASE_URL} rsa-private-key: ${LIKEI_MIFA_PAY_RSA_PRIVATE_KEY} platform-rsa-public-key: ${LIKEI_MIFA_PAY_PLATFORM_RSA_PUBLIC_KEY} + notify-url: ${LIKEI_MIFA_PAY_NOTIFY_URL:https://jvapi.haiyihy.com/play-server-notice/mifa_pay/receive_payment} airwallex: client-id: ${LIKEI_AIRWALLEX_CLIENT_ID} diff --git a/nacos_config/rc-service-other/application.yml b/nacos_config/rc-service-other/application.yml index 115cf44..eb5644d 100644 --- a/nacos_config/rc-service-other/application.yml +++ b/nacos_config/rc-service-other/application.yml @@ -114,7 +114,7 @@ activity: scheduler: - room-empty-clean: false + room-online-quantity-reconcile: true room-red-packet-expire: false game-king-reward: false noble-daily-reward: false diff --git a/rc-common-business/common-business-core/src/main/java/com/red/circle/common/business/core/util/CountryCodeAliasUtils.java b/rc-common-business/common-business-core/src/main/java/com/red/circle/common/business/core/util/CountryCodeAliasUtils.java new file mode 100644 index 0000000..f99f1e8 --- /dev/null +++ b/rc-common-business/common-business-core/src/main/java/com/red/circle/common/business/core/util/CountryCodeAliasUtils.java @@ -0,0 +1,119 @@ +package com.red.circle.common.business.core.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.red.circle.tool.core.json.JacksonUtils; +import com.red.circle.tool.core.text.StringUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * 国家码别名工具. + */ +public final class CountryCodeAliasUtils { + + private static final String SA = "SA"; + private static final String KSA = "KSA"; + + private CountryCodeAliasUtils() { + } + + /** + * 归一化国家码. + */ + public static String normalize(String code) { + return StringUtils.isBlank(code) ? null : code.trim().toUpperCase(); + } + + /** + * 沙特兼容归一化,保留非沙特国家原有写法. + */ + public static String normalizeSaudiCode(String code) { + String normalized = normalize(code); + return isSaudiCode(normalized) ? SA : normalized; + } + + /** + * 是否是沙特国家码. + */ + public static boolean isSaudiCode(String code) { + String normalized = normalize(code); + return SA.equals(normalized) || KSA.equals(normalized); + } + + /** + * 获取国家码别名集合. + */ + public static Set legacyAliases(String code) { + return buildMatchCodes(code, null, Collections.emptyList()); + } + + /** + * 解析存储中的别名数组. + */ + public static List parseAliasCodes(String aliasCodes) { + if (StringUtils.isBlank(aliasCodes)) { + return Collections.emptyList(); + } + + try { + List values = JacksonUtils.readValue(aliasCodes, new TypeReference>() {}); + return normalizeCodes(values); + } catch (Exception ignore) { + return normalizeCodes(List.of(aliasCodes.split(","))); + } + } + + /** + * 序列化别名数组到存储值. + */ + public static String toAliasCodesJson(Collection aliasCodes) { + List values = normalizeCodes(aliasCodes); + if (values.isEmpty()) { + return ""; + } + return JacksonUtils.toJson(values); + } + + /** + * 构造完整匹配国家码集合. + */ + public static Set buildMatchCodes(String alphaTwo, String alphaThree, + Collection aliasCodes) { + LinkedHashSet result = new LinkedHashSet<>(); + appendCode(result, alphaTwo); + appendCode(result, alphaThree); + if (aliasCodes != null) { + aliasCodes.forEach(code -> appendCode(result, code)); + } + + if (result.stream().anyMatch(CountryCodeAliasUtils::isSaudiCode)) { + result.add(SA); + result.add(KSA); + } + return result; + } + + /** + * 规范化国家码列表. + */ + public static List normalizeCodes(Collection codes) { + if (codes == null || codes.isEmpty()) { + return Collections.emptyList(); + } + LinkedHashSet result = new LinkedHashSet<>(); + codes.stream().map(CountryCodeAliasUtils::normalize).filter(StringUtils::isNotBlank) + .forEach(result::add); + return new ArrayList<>(result); + } + + private static void appendCode(Set result, String code) { + String normalized = normalize(code); + if (StringUtils.isNotBlank(normalized)) { + result.add(normalized); + } + } +} diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/sys/SysCountryCodeCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/sys/SysCountryCodeCmd.java index f048751..fddffd8 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/sys/SysCountryCodeCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/sys/SysCountryCodeCmd.java @@ -2,10 +2,11 @@ package com.red.circle.other.inner.model.cmd.sys; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import com.red.circle.framework.core.dto.CommonCommand; -import java.io.Serial; -import java.sql.Timestamp; -import lombok.Data; +import com.red.circle.framework.core.dto.CommonCommand; +import java.io.Serial; +import java.sql.Timestamp; +import java.util.List; +import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @@ -51,10 +52,15 @@ public class SysCountryCodeCmd extends CommonCommand { */ private String countryName; - /** - * 别名. - */ - private String aliasName; + /** + * 别名. + */ + private String aliasName; + + /** + * 国家码别名集合. + */ + private List countryCodeAliases; /** * 代码分配情况. diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/sys/SysCountryCodeDTO.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/sys/SysCountryCodeDTO.java index 7a3ee4b..8349061 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/sys/SysCountryCodeDTO.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/sys/SysCountryCodeDTO.java @@ -2,10 +2,11 @@ package com.red.circle.other.inner.model.dto.sys; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import com.red.circle.framework.dto.DTO; -import java.io.Serial; -import java.sql.Timestamp; -import lombok.Data; +import com.red.circle.framework.dto.DTO; +import java.io.Serial; +import java.sql.Timestamp; +import java.util.List; +import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @@ -51,10 +52,15 @@ public class SysCountryCodeDTO extends DTO { */ private String countryName; - /** - * 别名. - */ - private String aliasName; + /** + * 别名. + */ + private String aliasName; + + /** + * 国家码别名集合. + */ + private List countryCodeAliases; /** * 代码分配情况. diff --git a/rc-service/rc-service-console/console-start/src/main/resources/application.yml b/rc-service/rc-service-console/console-start/src/main/resources/application.yml index 926199d..00fd0a2 100644 --- a/rc-service/rc-service-console/console-start/src/main/resources/application.yml +++ b/rc-service/rc-service-console/console-start/src/main/resources/application.yml @@ -64,10 +64,12 @@ framework: red-circle: console: security: - ignore-urls: - - /console/account/login - - /console/actuator/** - - /console/datav/active/user-country-code - - /console/datav/online/user/count - - /console/datav/online/room/count - - /console/user/base/info/im/sig + ignore-urls: + - /console/account/login + - /console/actuator/** + - /console/internal/resident-activity/invite/grant-gold + - /console/internal/resident-activity/invite/grant-props + - /console/datav/active/user-country-code + - /console/datav/online/user/count + - /console/datav/online/room/count + - /console/user/base/info/im/sig diff --git a/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/WebPayRestController.java b/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/WebPayRestController.java index 991f627..3b94a96 100644 --- a/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/WebPayRestController.java +++ b/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/WebPayRestController.java @@ -2,14 +2,16 @@ package com.red.circle.order.adapter.app; import com.red.circle.common.business.dto.cmd.IdLongCmd; import com.red.circle.common.business.dto.cmd.IdStringCmd; -import com.red.circle.order.app.dto.clientobject.pay.ApplicationCommodityCardV2CO; -import com.red.circle.order.app.dto.clientobject.pay.PayApplicationCO; -import com.red.circle.order.app.dto.clientobject.pay.PayCountryCO; -import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; -import com.red.circle.order.app.dto.clientobject.pay.UserProfileAndCountryCO; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; -import com.red.circle.order.app.dto.cmd.PayWebApplicationCommodityCmd; +import com.red.circle.order.app.dto.clientobject.pay.ApplicationCommodityCardV2CO; +import com.red.circle.order.app.dto.clientobject.pay.PayApplicationCO; +import com.red.circle.order.app.dto.clientobject.pay.PayCountryCO; +import com.red.circle.order.app.dto.clientobject.pay.PayOrderStatusCO; +import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; +import com.red.circle.order.app.dto.clientobject.pay.UserProfileAndCountryCO; +import com.red.circle.order.app.dto.cmd.PayOrderStatusCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; +import com.red.circle.order.app.dto.cmd.PayWebApplicationCommodityCmd; import com.red.circle.order.app.dto.cmd.PayWebUserCmd; import com.red.circle.order.app.service.InAppPurchaseProductService; import com.red.circle.order.app.service.WebPayService; @@ -99,13 +101,26 @@ public class WebPayRestController { * @eo.method post * @eo.request-type json */ - @PostMapping("/recharge") - public PlaceAnOrderResponseCO placeAnOrder(@RequestBody @Validated PayPlaceAnOrderCmd cmd) { - return inAppPurchaseProductService.placeAnOrder(cmd); - } - - /** - * 支付收据 (后续版本计划移除). + @PostMapping("/recharge") + public PlaceAnOrderResponseCO placeAnOrder(@RequestBody @Validated PayPlaceAnOrderCmd cmd) { + return inAppPurchaseProductService.placeAnOrder(cmd); + } + + /** + * 查询支付单状态. + * + * @eo.name 查询支付单状态. + * @eo.url /order-status + * @eo.method get + * @eo.request-type formdata + */ + @GetMapping("/order-status") + public PayOrderStatusCO orderStatus(@Validated PayOrderStatusCmd cmd) { + return inAppPurchaseProductService.orderStatus(cmd); + } + + /** + * 支付收据 (后续版本计划移除). * * @eo.name 支付收据 (后续版本计划移除). * @eo.url /pay-receipt diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java index 023a0b4..0b7a5c0 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java @@ -1,19 +1,19 @@ package com.red.circle.order.app.command.pay.web; -import com.red.circle.component.pay.paymax.PayMaxUtils; -import com.red.circle.order.app.common.InAppPurchaseCommon; -import com.red.circle.order.domain.order.MiFaPayReceivePaymentNotice; -import com.red.circle.order.infra.config.MiFaPayProperties; -import com.red.circle.order.infra.database.mongo.order.InAppPurchaseCollectionReceiptService; -import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService; -import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; -import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice; -import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; -import com.red.circle.tool.core.date.TimestampUtils; -import com.red.circle.tool.core.text.StringUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; +import com.red.circle.component.pay.paymax.PayMaxUtils; +import com.red.circle.order.app.common.InAppPurchaseCommon; +import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport; +import com.red.circle.order.domain.order.MiFaPayReceivePaymentNotice; +import com.red.circle.order.infra.config.MiFaPayProperties; +import com.red.circle.order.infra.database.mongo.order.InAppPurchaseCollectionReceiptService; +import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayNoticeService; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService; +import com.red.circle.tool.core.text.StringUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -30,11 +30,12 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe { private static final String SUCCESS = "SUCCESS"; private static final String FAIL = "FAIL"; - - private final MiFaPayProperties miFaPayProperties; - private final InAppPurchaseCommon inAppPurchaseCommon; - private final InAppPurchaseDetailsService inAppPurchaseDetailsService; - private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService; + + private final MiFaPayProperties miFaPayProperties; + private final InAppPurchaseCommon inAppPurchaseCommon; + private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService; + private final OrderUserPurchasePayNoticeService orderUserPurchasePayNoticeService; + private final OrderUserPurchasePayService orderUserPurchasePayService; /** * 处理MiFaPay支付回调 @@ -76,70 +77,74 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe { return FAIL; } - MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData = notice.getNoticeData(); - - // 查询订单 - InAppPurchaseDetails order = inAppPurchaseDetailsService.getById(noticeData.getOrderId()); - if (Objects.isNull(order)) { - log.error("MiFaPay回调未找到订单: orderId={}", noticeData.getOrderId()); - return FAIL; - } - - // 检查订单状态,避免重复处理 - if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) { - log.warn("MiFaPay订单已处理: orderId={}, status={}", order.getId(), order.getStatus()); - return SUCCESS; - } - - // 记录回调通知 - inAppPurchaseDetailsService.addPayNotices(order.getId(), - new InAppPurchaseEventNotice() - .setEventId(notice.getSign()) - .setNoticeType("mifapay-" + noticeData.getOrderStatus()) - .setNoticeData(notice) - .setCreateTime(TimestampUtils.now()) - ); - - // 处理支付成功 - if (noticeData.isSuccess()) { - if (!Objects.equals(order.statusEqCreate(), Boolean.TRUE)) { - log.warn("MiFaPay订单状态不是CREATE: orderId={}", order.getId()); - return SUCCESS; - } - - // 收款单 - if (order.receiptTypeEqReceipt()) { - inAppPurchaseCommon.inAppPurchaseReceipt(order); - log.info("MiFaPay收款单处理成功: orderId={}", order.getId()); - return SUCCESS; - } - - // 付款单 - if (order.receiptTypeEqPayment()) { - inAppPurchaseCommon.inAppPurchasePayment(order); - log.info("MiFaPay付款单处理成功: orderId={}", order.getId()); - return SUCCESS; - } - - log.error("MiFaPay订单类型错误: orderId={}", order.getId()); - return FAIL; - } - - // 处理支付失败 - if (noticeData.isFailed()) { - if (order.receiptTypeEqReceipt()) { - inAppPurchaseCollectionReceiptService.updateStatusSuccessFail(order.getId()); - } - inAppPurchaseDetailsService.updateStatusReason( - order.getId(), - InAppPurchaseStatus.FAIL, - String.format("MiFaPay支付失败: %s", noticeData.getOrderStatus()) - ); - log.info("MiFaPay支付失败处理完成: orderId={}", order.getId()); - return SUCCESS; - } - - log.error("MiFaPay未知订单状态: orderId={}, status={}", order.getId(), noticeData.getOrderStatus()); - return SUCCESS; - } -} + MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData = notice.getNoticeData(); + + OrderUserPurchasePay mysqlOrder = getMysqlOrder(noticeData.getOrderId()); + if (Objects.isNull(mysqlOrder)) { + log.error("MiFaPay回调未找到MySQL订单: orderId={}", noticeData.getOrderId()); + return FAIL; + } + return handleMysqlOrder(notice, noticeData, mysqlOrder); + } + + private String handleMysqlOrder(MiFaPayReceivePaymentNotice notice, + MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData, + OrderUserPurchasePay mysqlOrder) { + orderUserPurchasePayNoticeService.addNotice( + mysqlOrder.getId(), + notice.getSign(), + "mifapay-" + noticeData.getOrderStatus(), + notice, + mysqlOrder.getUpdateUser() + ); + + if (!Objects.equals(mysqlOrder.getPayStatus(), MiFaPayMysqlOrderSupport.PAY_STATUS_PREPAYMENT)) { + log.warn("MiFaPay订单已处理(MySQL): orderId={}, status={}", + mysqlOrder.getId(), mysqlOrder.getPayStatus()); + return SUCCESS; + } + + InAppPurchaseDetails order = MiFaPayMysqlOrderSupport.toInAppPurchaseDetails(mysqlOrder); + + if (noticeData.isSuccess()) { + if (order.receiptTypeEqReceipt()) { + inAppPurchaseCommon.inAppPurchaseReceipt(order, + () -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId())); + log.info("MiFaPay收款单处理成功(MySQL): orderId={}", mysqlOrder.getId()); + return SUCCESS; + } + + if (order.receiptTypeEqPayment()) { + inAppPurchaseCommon.inAppPurchasePayment(order, + () -> orderUserPurchasePayService.updateSuccess(mysqlOrder.getId())); + log.info("MiFaPay付款单处理成功(MySQL): orderId={}", mysqlOrder.getId()); + return SUCCESS; + } + + log.error("MiFaPay订单类型错误(MySQL): orderId={}", mysqlOrder.getId()); + return FAIL; + } + + if (noticeData.isFailed()) { + if (order.receiptTypeEqReceipt() && StringUtils.isNotBlank(mysqlOrder.getReferenceId())) { + inAppPurchaseCollectionReceiptService.updateStatusSuccessFail( + mysqlOrder.getReferenceId()); + } + orderUserPurchasePayService.updateFail( + mysqlOrder.getId(), + String.format("MiFaPay支付失败: %s", noticeData.getOrderStatus()) + ); + log.info("MiFaPay支付失败处理完成(MySQL): orderId={}", mysqlOrder.getId()); + return SUCCESS; + } + + log.error("MiFaPay未知订单状态(MySQL): orderId={}, status={}", + mysqlOrder.getId(), noticeData.getOrderStatus()); + return SUCCESS; + } + + private OrderUserPurchasePay getMysqlOrder(String orderId) { + Long mysqlOrderId = MiFaPayMysqlOrderSupport.parseOrderId(orderId); + return mysqlOrderId == null ? null : orderUserPurchasePayService.getById(mysqlOrderId); + } +} diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebOrderStatusQueryExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebOrderStatusQueryExe.java new file mode 100644 index 0000000..49d44cb --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebOrderStatusQueryExe.java @@ -0,0 +1,51 @@ +package com.red.circle.order.app.command.pay.web; + +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport; +import com.red.circle.order.app.dto.clientobject.pay.PayOrderStatusCO; +import com.red.circle.order.app.dto.cmd.PayOrderStatusCmd; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService; +import com.red.circle.order.inner.asserts.OrderErrorCode; +import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 支付单状态查询. + * + * @author system + */ +@Component +@RequiredArgsConstructor +public class PayWebOrderStatusQueryExe { + + private final OrderUserPurchasePayService orderUserPurchasePayService; + + public PayOrderStatusCO execute(PayOrderStatusCmd cmd) { + OrderUserPurchasePay mysqlOrder = getMysqlOrder(cmd.getOrderId()); + ResponseAssert.notNull(OrderErrorCode.NO_PURCHASE_RECORD_FOUND, mysqlOrder); + ResponseAssert.isTrue(OrderErrorCode.NO_PURCHASE_RECORD_FOUND, + Objects.equals(mysqlOrder.getUserId(), cmd.getReqUserId())); + InAppPurchaseStatus status = MiFaPayMysqlOrderSupport + .toInAppPurchaseStatus(mysqlOrder.getPayStatus()); + return new PayOrderStatusCO() + .setOrderId(Objects.toString(mysqlOrder.getId(), null)) + .setTradeNo(mysqlOrder.getFactoryOrderId()) + .setStatus(Objects.toString(status, null)) + .setSuccess(MiFaPayMysqlOrderSupport.isSuccess(mysqlOrder.getPayStatus())) + .setFinished(MiFaPayMysqlOrderSupport.isFinished(mysqlOrder.getPayStatus())) + .setReason(mysqlOrder.getReason()) + .setFactoryCode(mysqlOrder.getFactoryCode()) + .setFactoryChannelCode(mysqlOrder.getPaymentChannel()) + .setCurrency(mysqlOrder.getPaymentUnit()) + .setAmount(mysqlOrder.getPaymentAmount()) + .setUpdateTime(mysqlOrder.getUpdateTime()); + } + + private OrderUserPurchasePay getMysqlOrder(String orderId) { + Long mysqlOrderId = MiFaPayMysqlOrderSupport.parseOrderId(orderId); + return mysqlOrderId == null ? null : orderUserPurchasePayService.getById(mysqlOrderId); + } +} diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java index 40ee917..fbaeeee 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java @@ -1,7 +1,8 @@ -package com.red.circle.order.app.command.pay.web.strategy; - -import com.red.circle.component.pay.paymax.service.PlaceAnOrderParam; -import com.red.circle.framework.core.asserts.ResponseAssert; +package com.red.circle.order.app.command.pay.web.strategy; + +import com.red.circle.component.pay.paymax.service.PlaceAnOrderParam; +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.response.ResponseErrorCode; import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam; import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO; @@ -101,13 +102,13 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra .amount(amountInCents) .currency(details.getCurrency()) .payWay(extractPayWay(details.getChannelCode())) - .payType(details.getFactoryChannel()) - .userIp(details.getRequestIp()) - .returnUrl(details.getSuccessRedirectUrl()) - .notifyUrl(MiFaPayService.NOTIFY_URL) - .language("en") - .email(memberInfo.getEmail()) - .countryCode(replaceCountryCode(details.getCountryCode())) + .payType(details.getFactoryChannel()) + .userIp(details.getRequestIp()) + .returnUrl(details.getSuccessRedirectUrl()) + .notifyUrl(miFaPayService.getNotifyUrl()) + .language("en") + .email(memberInfo.getEmail()) + .countryCode(replaceCountryCode(details.getCountryCode())) .firstName(memberInfo.getFirstName()) .lastName(memberInfo.getLastName()) .phone(memberInfo.getPhone()) @@ -125,13 +126,9 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra return underscoreIndex > 0 ? channelCode.substring(0, underscoreIndex) : channelCode; } - private static String replaceCountryCode(String countryCode) { - if ("KSA".equalsIgnoreCase(countryCode)) { - return countryCode.replaceAll("K", ""); - } - - return countryCode; - } + private static String replaceCountryCode(String countryCode) { + return CountryCodeAliasUtils.normalizeSaudiCode(countryCode); + } private String getPlaceAnOrderPaymentDetailParam(PayPlaceAnOrderDetailsV2 details) { diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/PayPlaceAnOrderService.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/PayPlaceAnOrderService.java index 95b41cd..3793534 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/PayPlaceAnOrderService.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/PayPlaceAnOrderService.java @@ -4,16 +4,19 @@ import com.google.api.client.util.Maps; import com.red.circle.component.pay.paymax.PayMaxUtils; import com.red.circle.framework.core.request.RequestClientEnum; import com.red.circle.framework.web.props.EnvProperties; -import com.red.circle.order.app.common.PayerMaxProperties; -import com.red.circle.order.app.common.QuoteQueryParam; -import com.red.circle.order.app.common.QuoteQueryResponse; -import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; -import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService; -import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; -import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice; -import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseFactory; -import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseStatusStep; -import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; +import com.red.circle.order.app.common.MiFaPayMysqlOrderSupport; +import com.red.circle.order.app.common.PayerMaxProperties; +import com.red.circle.order.app.common.QuoteQueryParam; +import com.red.circle.order.app.common.QuoteQueryResponse; +import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; +import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService; +import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseFactory; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseStatusStep; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayNoticeService; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService; +import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; import com.red.circle.tool.core.date.TimestampUtils; import com.red.circle.tool.core.http.RcHttpClient; import com.red.circle.tool.core.json.JacksonUtils; @@ -32,21 +35,36 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class PayPlaceAnOrderService { - private final EnvProperties envProperties; - private final InAppPurchaseDetailsService inAppPurchaseDetailsService; - private final FactoryPlaceAnOrderStrategyV2 factoryPlaceAnOrderStrategyV2; - private final PayerMaxProperties payerMaxProperties; - private final RcHttpClient rcHttpClient = RcHttpClient.builder().build(); + private final EnvProperties envProperties; + private final InAppPurchaseDetailsService inAppPurchaseDetailsService; + private final OrderUserPurchasePayNoticeService orderUserPurchasePayNoticeService; + private final OrderUserPurchasePayService orderUserPurchasePayService; + private final FactoryPlaceAnOrderStrategyV2 factoryPlaceAnOrderStrategyV2; + private final PayerMaxProperties payerMaxProperties; + private final RcHttpClient rcHttpClient = RcHttpClient.builder().build(); public PlaceAnOrderResponseCO doRequest(PayPlaceAnOrderDetailsV2 param) { // 第三方:请求下单 - PlaceAnOrderResponseCO response = factoryPlaceAnOrderStrategyV2.getStrategy( - param.getStrategyCode()) - .doOperation(param); - - // 创建站内:预支付订单. - inAppPurchaseDetailsService.create( - new InAppPurchaseDetails() + PlaceAnOrderResponseCO response = factoryPlaceAnOrderStrategyV2.getStrategy( + param.getStrategyCode()) + .doOperation(param); + + if (MiFaPayMysqlOrderSupport.isMiFaPay(param.getFactoryCode())) { + Long purchasePayId = MiFaPayMysqlOrderSupport.parseOrderId(param.getOrderId()); + orderUserPurchasePayService.save( + MiFaPayMysqlOrderSupport.buildPrepaymentOrder(param, response, env())); + orderUserPurchasePayNoticeService.addNotice( + purchasePayId, + "custom-request-response-param", + "API-INTERNAL-PARAMETERS", + response.extra(), + param.getCreateUserId()); + return response; + } + + // 创建站内:预支付订单. + inAppPurchaseDetailsService.create( + new InAppPurchaseDetails() .setId(param.getOrderId()) .setTrackId(param.getApplicationId()) .setTrackCommodityType(param.getAppCommodityType()) diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/InAppPurchaseCommon.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/InAppPurchaseCommon.java index 06a22f2..aacbead 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/InAppPurchaseCommon.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/InAppPurchaseCommon.java @@ -77,24 +77,31 @@ public class InAppPurchaseCommon { private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService; private final UserLevelClient userLevelClient; private final PropsActivityClient propsActivityClient; - private final UserOneTimeTaskClient userOneTimeTaskClient; - private final UserProfileClient userProfileClient; - - public void inAppPurchaseReceipt(InAppPurchaseDetails order) { - - if (!order.receiptTypeEqReceipt()) { - return; - } + private final UserOneTimeTaskClient userOneTimeTaskClient; + private final UserProfileClient userProfileClient; + + public void inAppPurchaseReceipt(InAppPurchaseDetails order) { + inAppPurchaseReceipt(order, + () -> inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.SUCCESS)); + } + + public void inAppPurchaseReceipt(InAppPurchaseDetails order, Runnable successHandler) { + Runnable finalSuccessHandler = successHandler != null ? successHandler : () -> { + }; + + if (!order.receiptTypeEqReceipt()) { + return; + } if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) { return; } - - if (CollectionUtils.isEmpty(order.getProducts())) { - inAppPurchaseCollectionReceiptService.updateStatusSuccess(order.getTrackId()); - inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.SUCCESS); - return; - } + + if (CollectionUtils.isEmpty(order.getProducts())) { + inAppPurchaseCollectionReceiptService.updateStatusSuccess(order.getTrackId()); + finalSuccessHandler.run(); + return; + } saveOrderPurchaseHistory(order); @@ -121,14 +128,21 @@ public class InAppPurchaseCommon { }).filter(Objects::nonNull).collect(Collectors.toList()) ) ); - - inAppPurchaseCollectionReceiptService.updateStatusSuccess(order.getTrackId()); - inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.SUCCESS); - } - - public void inAppPurchasePayment(InAppPurchaseDetails order) { - InAppPurchaseProduct product = order.firstProduct(); - ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, product); + + inAppPurchaseCollectionReceiptService.updateStatusSuccess(order.getTrackId()); + finalSuccessHandler.run(); + } + + public void inAppPurchasePayment(InAppPurchaseDetails order) { + inAppPurchasePayment(order, + () -> inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.SUCCESS)); + } + + public void inAppPurchasePayment(InAppPurchaseDetails order, Runnable successHandler) { + Runnable finalSuccessHandler = successHandler != null ? successHandler : () -> { + }; + InAppPurchaseProduct product = order.firstProduct(); + ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, product); saveOrderPurchaseHistory(order); @@ -136,13 +150,13 @@ public class InAppPurchaseCommon { inAppPurchase(order); } - if (PayApplicationCommodityType.FREIGHT_GOLD.eq(product.getCode())) { - inShipPurchase(order); - } - - // 修改订单状态 - inAppPurchaseDetailsService.updateStatus(order.getId(), InAppPurchaseStatus.SUCCESS); - } + if (PayApplicationCommodityType.FREIGHT_GOLD.eq(product.getCode())) { + inShipPurchase(order); + } + + // 修改订单状态 + finalSuccessHandler.run(); + } private void inAppPurchase(InAppPurchaseDetails order) { diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/MiFaPayMysqlOrderSupport.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/MiFaPayMysqlOrderSupport.java new file mode 100644 index 0000000..0f3c16b --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/MiFaPayMysqlOrderSupport.java @@ -0,0 +1,168 @@ +package com.red.circle.order.app.common; + +import com.red.circle.framework.core.request.RequestClientEnum; +import com.red.circle.order.app.command.pay.web.strategy.PayPlaceAnOrderDetailsV2; +import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; +import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseFactory; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseProduct; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; +import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseReceiptType; +import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.text.StringUtils; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * MiFaPay MySQL订单映射工具. + */ +public final class MiFaPayMysqlOrderSupport { + + public static final String FACTORY_CODE = "MIFA_PAY"; + public static final String PAY_STATUS_PREPAYMENT = "PREPAYMENT"; + public static final String PAY_STATUS_SUCCESSFUL = "SUCCESSFUL"; + public static final String PAY_STATUS_FAIL = "FAIL"; + public static final String REFUND_STATUS_NONE = "NONE"; + + private MiFaPayMysqlOrderSupport() { + } + + public static boolean isMiFaPay(String factoryCode) { + return Objects.equals(FACTORY_CODE, factoryCode); + } + + public static Long parseOrderId(String orderId) { + if (StringUtils.isBlank(orderId)) { + return null; + } + try { + return Long.valueOf(orderId.trim()); + } catch (NumberFormatException ignore) { + return null; + } + } + + public static OrderUserPurchasePay buildPrepaymentOrder(PayPlaceAnOrderDetailsV2 param, + PlaceAnOrderResponseCO response, String env) { + Timestamp now = TimestampUtils.now(); + InAppPurchaseProduct product = firstProduct(param.getProducts()); + Long referenceId = parseOrderId(param.getApplicationId()); + + return new OrderUserPurchasePay() + .setId(parseOrderId(param.getOrderId())) + .setEvn(env) + .setSysOrigin(param.getSysOrigin()) + .setUserId(param.getAcceptUserId()) + .setFactoryCode(param.getFactoryCode()) + .setFactoryOrderId(Objects.toString(response.getTradeNo(), "")) + .setReferenceId(Objects.toString(param.getApplicationId(), "")) + .setReceiptType(Objects.toString(param.getReceiptType(), + InAppPurchaseReceiptType.PAYMENT.name())) + .setPaymentChannel(Objects.toString(param.getChannelCode(), "")) + .setFactoryChannel(Objects.toString(param.getFactoryChannel(), "")) + .setProductCode(product != null ? Objects.toString(product.getCode(), "") : "") + .setProductContent(product != null ? Objects.toString(product.getContent(), "") : "") + .setProductDescriptor(Objects.toString(param.getProductDescriptor(), "")) + .setPaymentUnit(Objects.toString(param.getCurrency(), "")) + .setPaymentAmount(defaultDecimal(param.getAmount())) + .setComputeUsdAmount(defaultDecimal(param.getAmountUsd())) + .setComputeUsdRate(computeUsdRate(param.getAmount(), param.getAmountUsd())) + .setGiveAwayContent("") + .setCountryId(param.getCountry() != null ? param.getCountry().getId() : 0L) + .setCountryCode(Objects.toString(response.getCountryCode(), param.getCountryCode())) + .setPayCountryId(param.getPayCountry() != null ? param.getPayCountry().getId() : 0L) + .setPayStatus(PAY_STATUS_PREPAYMENT) + .setReason("") + .setRefundStatus(REFUND_STATUS_NONE) + .setReceiptDetailsId(referenceId != null ? referenceId : 0L) + .setCreateTime(now) + .setUpdateTime(now) + .setCreateUser(param.getCreateUserId()) + .setUpdateUser(param.getCreateUserId()); + } + + public static InAppPurchaseDetails toInAppPurchaseDetails(OrderUserPurchasePay order) { + List products = StringUtils.isBlank(order.getProductCode()) + ? Collections.emptyList() + : List.of(new InAppPurchaseProduct() + .setId(order.getReceiptDetailsId()) + .setName(order.getProductCode()) + .setCode(order.getProductCode()) + .setDescribe(order.getProductDescriptor()) + .setContent(order.getProductContent()) + .setAmountUsd(defaultDecimal(order.getComputeUsdAmount())) + .setQuantity(1)); + + return new InAppPurchaseDetails() + .setId(Objects.toString(order.getId(), null)) + .setReceiptType(parseReceiptType(order.getReceiptType())) + .setTrackId(order.getReferenceId()) + .setSysOrigin(order.getSysOrigin()) + .setAcceptUserId(order.getUserId()) + .setOrderId(order.getFactoryOrderId()) + .setEnv(order.getEvn()) + .setFactory(new InAppPurchaseFactory() + .setPlatform(RequestClientEnum.H5.getClientName()) + .setFactoryCode(order.getFactoryCode()) + .setFactoryChannelCode(order.getPaymentChannel())) + .setProducts(products) + .setProductDescriptor(order.getProductDescriptor()) + .setCountryCode(order.getCountryCode()) + .setCurrency(order.getPaymentUnit()) + .setAmount(defaultDecimal(order.getPaymentAmount())) + .setAmountUsd(defaultDecimal(order.getComputeUsdAmount())) + .setTrialPeriod(Boolean.FALSE) + .setStatus(toInAppPurchaseStatus(order.getPayStatus())) + .setReason(order.getReason()) + .setCreateTime(order.getCreateTime()) + .setUpdateTime(order.getUpdateTime()) + .setCreateUser(order.getCreateUser()); + } + + public static InAppPurchaseStatus toInAppPurchaseStatus(String payStatus) { + if (Objects.equals(PAY_STATUS_SUCCESSFUL, payStatus)) { + return InAppPurchaseStatus.SUCCESS; + } + if (Objects.equals(PAY_STATUS_FAIL, payStatus)) { + return InAppPurchaseStatus.FAIL; + } + return InAppPurchaseStatus.CREATE; + } + + public static boolean isSuccess(String payStatus) { + return Objects.equals(PAY_STATUS_SUCCESSFUL, payStatus); + } + + public static boolean isFinished(String payStatus) { + return isSuccess(payStatus) || Objects.equals(PAY_STATUS_FAIL, payStatus); + } + + private static String computeUsdRate(BigDecimal paymentAmount, BigDecimal usdAmount) { + if (paymentAmount == null || usdAmount == null || BigDecimal.ZERO.compareTo(usdAmount) == 0) { + return ""; + } + return paymentAmount.divide(usdAmount, 8, RoundingMode.HALF_UP) + .stripTrailingZeros() + .toPlainString(); + } + + private static InAppPurchaseProduct firstProduct(List products) { + return products == null || products.isEmpty() ? null : products.get(0); + } + + private static InAppPurchaseReceiptType parseReceiptType(String receiptType) { + if (Objects.equals(InAppPurchaseReceiptType.RECEIPT.name(), receiptType)) { + return InAppPurchaseReceiptType.RECEIPT; + } + return InAppPurchaseReceiptType.PAYMENT; + } + + private static BigDecimal defaultDecimal(BigDecimal value) { + return value != null ? value : BigDecimal.ZERO; + } +} diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java index 9271301..48661f5 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java @@ -20,23 +20,26 @@ import com.red.circle.mq.rocket.business.producer.TaskMqMessage; import com.red.circle.order.app.command.pay.app.execute.AppInAppPurchaseCmdExe; import com.red.circle.order.app.command.pay.app.execute.AppleAutoSubscriptionNoticeCmdExe; import com.red.circle.order.app.command.pay.app.execute.GoogleCheckOneTimeProductNoticeCmdExe; -import com.red.circle.order.app.command.pay.web.AirwallexServerNoticeCmdExe; -import com.red.circle.order.app.command.pay.web.ClipspayServerNoticeCmdExe; -import com.red.circle.order.app.command.pay.web.CollectionReceiptQueryExe; -import com.red.circle.order.app.command.pay.web.PayPalServerNoticeCmdExe; -import com.red.circle.order.app.command.pay.web.PayWebPlaceAnOrderCmdExe; -import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeReceivePaymentCmdExe; -import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeRefundNoticeCmdExe; +import com.red.circle.order.app.command.pay.web.AirwallexServerNoticeCmdExe; +import com.red.circle.order.app.command.pay.web.ClipspayServerNoticeCmdExe; +import com.red.circle.order.app.command.pay.web.CollectionReceiptQueryExe; +import com.red.circle.order.app.command.pay.web.PayPalServerNoticeCmdExe; +import com.red.circle.order.app.command.pay.web.PayWebOrderStatusQueryExe; +import com.red.circle.order.app.command.pay.web.PayWebPlaceAnOrderCmdExe; +import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeReceivePaymentCmdExe; +import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeRefundNoticeCmdExe; import com.red.circle.order.app.command.pay.web.PaynicornServerNoticeCmdExe; import com.red.circle.order.app.command.pay.web.MiFaPayServerNoticeReceivePaymentCmdExe; import com.red.circle.order.app.command.pay.web.ReceiptPayWebPlaceAnOrderReceiptCmdExe; -import com.red.circle.order.app.dto.clientobject.PayMaxResponseCO; -import com.red.circle.order.app.dto.clientobject.PayerMaxResponseV2CO; -import com.red.circle.order.app.dto.clientobject.PurchaseReceiptCO; -import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; -import com.red.circle.order.app.dto.cmd.AbstractPurchaseCmd; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; +import com.red.circle.order.app.dto.clientobject.PayMaxResponseCO; +import com.red.circle.order.app.dto.clientobject.PayerMaxResponseV2CO; +import com.red.circle.order.app.dto.clientobject.PurchaseReceiptCO; +import com.red.circle.order.app.dto.clientobject.pay.PayOrderStatusCO; +import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; +import com.red.circle.order.app.dto.cmd.AbstractPurchaseCmd; +import com.red.circle.order.app.dto.cmd.PayOrderStatusCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; import com.red.circle.tool.core.date.LocalDateTimeUtils; import com.red.circle.tool.core.json.JacksonUtils; @@ -58,10 +61,11 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class InAppPurchaseProductServiceImpl implements InAppPurchaseProductService { - private final AppInAppPurchaseCmdExe appInAppPurchaseCmdExe; - private final PayWebPlaceAnOrderCmdExe payWebPlaceAnOrderCmdExe; - private final PayPalServerNoticeCmdExe payPalServerNoticeCmdExe; - private final CollectionReceiptQueryExe collectionReceiptQueryExe; + private final AppInAppPurchaseCmdExe appInAppPurchaseCmdExe; + private final PayWebPlaceAnOrderCmdExe payWebPlaceAnOrderCmdExe; + private final PayWebOrderStatusQueryExe payWebOrderStatusQueryExe; + private final PayPalServerNoticeCmdExe payPalServerNoticeCmdExe; + private final CollectionReceiptQueryExe collectionReceiptQueryExe; private final ClipspayServerNoticeCmdExe clipspayServerNoticeCmdExe; private final AirwallexServerNoticeCmdExe airwallexServerNoticeCmdExe; private final PaynicornServerNoticeCmdExe paynicornServerNoticeCmdExe; @@ -145,12 +149,17 @@ public class InAppPurchaseProductServiceImpl implements InAppPurchaseProductServ } @Override - public PlaceAnOrderResponseCO placeAnOrder(PayPlaceAnOrderCmd cmd) { - return payWebPlaceAnOrderCmdExe.execute(cmd); - } - - @Override - public PlaceAnOrderResponseCO payReceipt(PayPlaceAnOrderReceiptCmd cmd) { + public PlaceAnOrderResponseCO placeAnOrder(PayPlaceAnOrderCmd cmd) { + return payWebPlaceAnOrderCmdExe.execute(cmd); + } + + @Override + public PayOrderStatusCO orderStatus(PayOrderStatusCmd cmd) { + return payWebOrderStatusQueryExe.execute(cmd); + } + + @Override + public PlaceAnOrderResponseCO payReceipt(PayPlaceAnOrderReceiptCmd cmd) { return receiptPayWebPlaceAnOrderReceiptCmdExe.execute(cmd); } diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java index 5988055..ffdb759 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java @@ -1,15 +1,16 @@ package com.red.circle.order.app.service; -import com.red.circle.component.pay.paymax.PayMaxUtils; -import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam; -import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO; -import com.red.circle.order.infra.config.MiFaPayProperties; -import com.red.circle.tool.core.http.RcHttpClient; -import com.red.circle.tool.core.json.JacksonUtils; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import com.red.circle.component.pay.paymax.PayMaxUtils; +import com.red.circle.order.app.common.MiFaPayPlaceAnOrderParam; +import com.red.circle.order.app.dto.clientobject.MiFaPayResponseCO; +import com.red.circle.order.infra.config.MiFaPayProperties; +import com.red.circle.tool.core.http.RcHttpClient; +import com.red.circle.tool.core.json.JacksonUtils; +import com.red.circle.tool.core.text.StringUtils; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; import java.util.Objects; @@ -20,13 +21,12 @@ import java.util.Objects; */ @Slf4j @Service -public class MiFaPayService { - - /** - * 下单接口路径 - */ - public static final String NOTIFY_URL = ""; - private static final String PLACE_ORDER_PATH = "/paygateway/mbpay/order/en_v2"; +public class MiFaPayService { + + /** + * 下单接口路径 + */ + private static final String PLACE_ORDER_PATH = "/paygateway/mbpay/order/en_v2"; private final String active; private final MiFaPayProperties miFaPayProperties; @@ -44,9 +44,11 @@ public class MiFaPayService { * @param param 下单参数 * @return 支付响应 */ - public MiFaPayResponseCO placeAnOrder(MiFaPayPlaceAnOrderParam param) { - // 设置商户编号 - param.setMerNo(miFaPayProperties.getMerNo()); + public MiFaPayResponseCO placeAnOrder(MiFaPayPlaceAnOrderParam param) { + validateRequiredConfig(); + + // 设置商户编号 + param.setMerNo(miFaPayProperties.getMerNo()); String url = miFaPayProperties.getGatewayBaseUrl() + PLACE_ORDER_PATH; @@ -78,28 +80,61 @@ public class MiFaPayService { .block(); // 非生产环境打印响应日志 - if (!Objects.equals(active, "prod")) { - log.warn("MiFaPay Response: {}", JacksonUtils.toJson(response)); - } - - // 验证响应签名 - if (response != null && response.isSuccess() && response.getData() != null) { -// boolean validSign = PayMaxUtils.validSignRsa( -// response.getData(), -// response.getSign(), -// miFaPayProperties.getPlatformRsaPublicKey() -// ); -// if (!validSign) { -// log.error("MiFaPay response sign verification failed!"); -// } - } - - return response; - } - - /** - * MiFaPay请求体结构 - */ + if (!Objects.equals(active, "prod")) { + log.warn("MiFaPay Response: {}", JacksonUtils.toJson(response)); + } + + verifySuccessfulResponse(response); + + return response; + } + + public String getNotifyUrl() { + validateRequiredValue("notifyUrl", miFaPayProperties.getNotifyUrl()); + return miFaPayProperties.getNotifyUrl(); + } + + private void verifySuccessfulResponse(MiFaPayResponseCO response) { + if (response == null || !response.isSuccess()) { + return; + } + + validateRequiredValue("response.data", response.getData()); + validateRequiredValue("response.sign", response.getSign()); + + if (StringUtils.isNotBlank(response.getMerAccount()) + && !Objects.equals(response.getMerAccount(), miFaPayProperties.getMerAccount())) { + throw new IllegalStateException("MiFaPay response merAccount mismatch"); + } + + boolean validSign = PayMaxUtils.validSignRsa( + response.getData(), + response.getSign(), + miFaPayProperties.getPlatformRsaPublicKey() + ); + if (!validSign) { + throw new IllegalStateException("MiFaPay response signature verification failed"); + } + } + + private void validateRequiredConfig() { + validateRequiredValue("merAccount", miFaPayProperties.getMerAccount()); + validateRequiredValue("merNo", miFaPayProperties.getMerNo()); + validateRequiredValue("gatewayBaseUrl", miFaPayProperties.getGatewayBaseUrl()); + validateRequiredValue("rsaPrivateKey", miFaPayProperties.getRsaPrivateKey()); + validateRequiredValue("platformRsaPublicKey", miFaPayProperties.getPlatformRsaPublicKey()); + validateRequiredValue("notifyUrl", miFaPayProperties.getNotifyUrl()); + } + + private void validateRequiredValue(String fieldName, String value) { + if (StringUtils.isBlank(value)) { + throw new IllegalStateException("MiFaPay config missing: " + fieldName); + } + } + + /** + * MiFaPay请求体结构 + */ @Data public static class MiFaPayRequest { /** diff --git a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/clientobject/pay/PayOrderStatusCO.java b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/clientobject/pay/PayOrderStatusCO.java new file mode 100644 index 0000000..7f927e6 --- /dev/null +++ b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/clientobject/pay/PayOrderStatusCO.java @@ -0,0 +1,76 @@ +package com.red.circle.order.app.dto.clientobject.pay; + +import com.red.circle.framework.dto.DTO; +import java.math.BigDecimal; +import java.sql.Timestamp; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 支付单状态. + * + * @author system + */ +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class PayOrderStatusCO extends DTO { + + private static final long serialVersionUID = 1L; + + /** + * 站内支付单号. + */ + private String orderId; + + /** + * 第三方平台订单号. + */ + private String tradeNo; + + /** + * 当前支付状态. + */ + private String status; + + /** + * 是否支付成功. + */ + private Boolean success; + + /** + * 是否已进入终态. + */ + private Boolean finished; + + /** + * 失败或挂起原因. + */ + private String reason; + + /** + * 厂商编码. + */ + private String factoryCode; + + /** + * 厂商渠道编码. + */ + private String factoryChannelCode; + + /** + * 币种. + */ + private String currency; + + /** + * 金额. + */ + private BigDecimal amount; + + /** + * 最后更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/cmd/PayOrderStatusCmd.java b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/cmd/PayOrderStatusCmd.java new file mode 100644 index 0000000..4602efe --- /dev/null +++ b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/dto/cmd/PayOrderStatusCmd.java @@ -0,0 +1,26 @@ +package com.red.circle.order.app.dto.cmd; + +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import jakarta.validation.constraints.NotBlank; +import java.io.Serial; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 支付单状态查询. + * + * @author system + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PayOrderStatusCmd extends AppExtCommand { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 站内支付单号. + */ + @NotBlank(message = "orderId required.") + private String orderId; +} diff --git a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java index e166151..dfcf4f9 100644 --- a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java +++ b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java @@ -2,14 +2,16 @@ package com.red.circle.order.app.service; import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; import com.red.circle.common.business.dto.cmd.IdStringCmd; -import com.red.circle.order.app.dto.clientobject.PayMaxResponseCO; -import com.red.circle.order.app.dto.clientobject.PayerMaxResponseV2CO; -import com.red.circle.order.app.dto.clientobject.PurchaseReceiptCO; -import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; -import com.red.circle.order.app.dto.cmd.AbstractPurchaseCmd; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; -import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; -import java.util.Map; +import com.red.circle.order.app.dto.clientobject.PayMaxResponseCO; +import com.red.circle.order.app.dto.clientobject.PayerMaxResponseV2CO; +import com.red.circle.order.app.dto.clientobject.PurchaseReceiptCO; +import com.red.circle.order.app.dto.clientobject.pay.PayOrderStatusCO; +import com.red.circle.order.app.dto.clientobject.pay.PlaceAnOrderResponseCO; +import com.red.circle.order.app.dto.cmd.AbstractPurchaseCmd; +import com.red.circle.order.app.dto.cmd.PayOrderStatusCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderCmd; +import com.red.circle.order.app.dto.cmd.PayPlaceAnOrderReceiptCmd; +import java.util.Map; /** * 内购产品服务. @@ -30,10 +32,15 @@ public interface InAppPurchaseProductService { PayMaxResponseCO payerMaxServerNoticeRefundNotice(byte[] body); - PlaceAnOrderResponseCO placeAnOrder(PayPlaceAnOrderCmd cmd); - - @Deprecated - PlaceAnOrderResponseCO payReceipt(PayPlaceAnOrderReceiptCmd cmd); + PlaceAnOrderResponseCO placeAnOrder(PayPlaceAnOrderCmd cmd); + + /** + * 查询支付单状态. + */ + PayOrderStatusCO orderStatus(PayOrderStatusCmd cmd); + + @Deprecated + PlaceAnOrderResponseCO payReceipt(PayPlaceAnOrderReceiptCmd cmd); @Deprecated Object collectionReceipt(IdStringCmd cmd); diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java index cc1dc91..e930ad1 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java @@ -36,8 +36,13 @@ public class MiFaPayProperties { */ private String rsaPrivateKey; - /** - * 平台公钥(用于验签) - */ - private String platformRsaPublicKey; -} + /** + * 平台公钥(用于验签) + */ + private String platformRsaPublicKey; + + /** + * 支付结果异步通知地址 + */ + private String notifyUrl; +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseCollectionReceiptServiceImpl.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseCollectionReceiptServiceImpl.java index 3c3fd67..3ecb75c 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseCollectionReceiptServiceImpl.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseCollectionReceiptServiceImpl.java @@ -22,22 +22,24 @@ import org.springframework.stereotype.Service; */ @Service @RequiredArgsConstructor -public class InAppPurchaseCollectionReceiptServiceImpl implements - InAppPurchaseCollectionReceiptService { - - private final MongoTemplate mongoTemplate; +public class InAppPurchaseCollectionReceiptServiceImpl implements + InAppPurchaseCollectionReceiptService { + + private static final String ID_FIELD = "_id"; + + private final MongoTemplate mongoTemplate; @Override public void create(InAppPurchaseCollectionReceipt receipt) { mongoTemplate.save(receipt); } - @Override - public void update(InAppPurchaseCollectionReceipt receipt) { - mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(receipt.getId())), - new Update() - .set("acceptUserId", receipt.getAcceptUserId()) - .set("amount", receipt.getAmount()) + @Override + public void update(InAppPurchaseCollectionReceipt receipt) { + mongoTemplate.updateFirst(Query.query(Criteria.where(ID_FIELD).is(receipt.getId())), + new Update() + .set("acceptUserId", receipt.getAcceptUserId()) + .set("amount", receipt.getAmount()) .set("amountUsd", receipt.getAmountUsd()) .set("usdExchangeRate", receipt.getUsdExchangeRate()) .set("currency", receipt.getCurrency()) @@ -50,32 +52,32 @@ public class InAppPurchaseCollectionReceiptServiceImpl implements InAppPurchaseCollectionReceipt.class); } - @Override - public void close(String id, Long operationUserId) { - mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)), - new Update() - .set("updateTime", TimestampUtils.now()) - .set("status", InAppPurchaseCollectionReceiptStatus.CLOSE) + @Override + public void close(String id, Long operationUserId) { + mongoTemplate.updateFirst(Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("updateTime", TimestampUtils.now()) + .set("status", InAppPurchaseCollectionReceiptStatus.CLOSE) .set("updateUser", operationUserId), InAppPurchaseCollectionReceipt.class); } - @Override - public void updateStatusSuccess(String id) { - mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)), - new Update() - .set("updateTime", TimestampUtils.now()) - .set("status", InAppPurchaseCollectionReceiptStatus.SUCCESS) + @Override + public void updateStatusSuccess(String id) { + mongoTemplate.updateFirst(Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("updateTime", TimestampUtils.now()) + .set("status", InAppPurchaseCollectionReceiptStatus.SUCCESS) .set("lockTime", TimestampUtils.now()), InAppPurchaseCollectionReceipt.class); } - @Override - public void updateStatusSuccessFail(String id) { - mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)), - new Update() - .set("updateTime", TimestampUtils.now()) - .set("status", InAppPurchaseCollectionReceiptStatus.FAIL) + @Override + public void updateStatusSuccessFail(String id) { + mongoTemplate.updateFirst(Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("updateTime", TimestampUtils.now()) + .set("status", InAppPurchaseCollectionReceiptStatus.FAIL) .set("lockTime", TimestampUtils.now()), InAppPurchaseCollectionReceipt.class); } @@ -84,14 +86,14 @@ public class InAppPurchaseCollectionReceiptServiceImpl implements public List list( InAppPurchaseCollectionReceiptQuery query) { - Criteria criteria = StringUtils.isBlank(query.getSysOrigin()) - ? new Criteria() - : Criteria.where("sysOrigin").is(query.getSysOrigin()); - - if (StringUtils.isNotBlank(query.getId())) { - query.setLastId(null); - criteria.and("id").is(query.getId()); - } + Criteria criteria = StringUtils.isBlank(query.getSysOrigin()) + ? new Criteria() + : Criteria.where("sysOrigin").is(query.getSysOrigin()); + + if (StringUtils.isNotBlank(query.getId())) { + query.setLastId(null); + criteria.and(ID_FIELD).is(query.getId()); + } if (StringUtils.isNotBlank(query.getStatus())) { criteria.and("status").is(query.getStatus()); @@ -112,32 +114,32 @@ public class InAppPurchaseCollectionReceiptServiceImpl implements ); } - if (Objects.nonNull(query.getLastId())) { - criteria.and("id").lt(query.getLastId()); - } + if (Objects.nonNull(query.getLastId())) { + criteria.and(ID_FIELD).lt(query.getLastId()); + } if (Objects.isNull(query.getLimit())) { query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE); } - return mongoTemplate.find(Query.query(criteria) - .with(Sort.by(Sort.Order.desc("id"))) - .limit(query.getLimit()), - InAppPurchaseCollectionReceipt.class); - } + return mongoTemplate.find(Query.query(criteria) + .with(Sort.by(Sort.Order.desc(ID_FIELD))) + .limit(query.getLimit()), + InAppPurchaseCollectionReceipt.class); + } - @Override - public InAppPurchaseCollectionReceipt getById(String id) { - return mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)), - InAppPurchaseCollectionReceipt.class); - } + @Override + public InAppPurchaseCollectionReceipt getById(String id) { + return mongoTemplate.findOne(Query.query(Criteria.where(ID_FIELD).is(id)), + InAppPurchaseCollectionReceipt.class); + } - @Override - public void lockTime(String id, long minute) { - mongoTemplate.updateFirst(Query.query(Criteria.where("id").is(id)), - new Update() - .set("updateTime", TimestampUtils.now()) - .set("lockTime", TimestampUtils.nowPlusMinutes(minute)), + @Override + public void lockTime(String id, long minute) { + mongoTemplate.updateFirst(Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("updateTime", TimestampUtils.now()) + .set("lockTime", TimestampUtils.nowPlusMinutes(minute)), InAppPurchaseCollectionReceipt.class); } diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java index fa47f35..7f23069 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java @@ -33,20 +33,22 @@ import org.springframework.stereotype.Service; */ @Service @RequiredArgsConstructor -public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsService { - - private final MongoTemplate mongoTemplate; +public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsService { + + private static final String ID_FIELD = "_id"; + + private final MongoTemplate mongoTemplate; @Override public void create(InAppPurchaseDetails inAppPurchaseDetails) { mongoTemplate.insert(inAppPurchaseDetails); } - @Override - public InAppPurchaseDetails getById(String id) { - return mongoTemplate.findOne(Query.query(Criteria.where("id").is(id)), - InAppPurchaseDetails.class); - } + @Override + public InAppPurchaseDetails getById(String id) { + return mongoTemplate.findOne(Query.query(Criteria.where(ID_FIELD).is(id)), + InAppPurchaseDetails.class); + } @Override public InAppPurchaseDetails getByOrderId(String orderId) { @@ -56,34 +58,34 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ } @Override - public boolean existsPayNotice(String id, String eventId, String noticeType) { - return mongoTemplate.exists(Query.query( - Criteria.where("id").is(id) - .elemMatch(Criteria.where("eventId").is(eventId).is("noticeType").is(noticeType)) - ), InAppPurchaseDetails.class); - } + public boolean existsPayNotice(String id, String eventId, String noticeType) { + return mongoTemplate.exists(Query.query( + Criteria.where(ID_FIELD).is(id) + .elemMatch(Criteria.where("eventId").is(eventId).is("noticeType").is(noticeType)) + ), InAppPurchaseDetails.class); + } @Override - public boolean existsPayNoticeByEventId(String id, String eventId) { - return mongoTemplate.exists(Query.query( - Criteria.where("id").is(id).elemMatch(Criteria.where("eventId").is(eventId)) - ), InAppPurchaseDetails.class); - } + public boolean existsPayNoticeByEventId(String id, String eventId) { + return mongoTemplate.exists(Query.query( + Criteria.where(ID_FIELD).is(id).elemMatch(Criteria.where("eventId").is(eventId)) + ), InAppPurchaseDetails.class); + } @Override - public boolean existsPayNoticeByNoticeType(String id, String noticeType) { - return mongoTemplate.exists(Query.query( - Criteria.where("id").is(id).elemMatch(Criteria.where("noticeType").is(noticeType)) - ), InAppPurchaseDetails.class); - } + public boolean existsPayNoticeByNoticeType(String id, String noticeType) { + return mongoTemplate.exists(Query.query( + Criteria.where(ID_FIELD).is(id).elemMatch(Criteria.where("noticeType").is(noticeType)) + ), InAppPurchaseDetails.class); + } @Override - public void addPayNotices(String id, InAppPurchaseEventNotice payNotice) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("id").is(id)), - new Update() - .push("payNotices", payNotice) - .set("updateTime", TimestampUtils.now()), + public void addPayNotices(String id, InAppPurchaseEventNotice payNotice) { + mongoTemplate.updateFirst( + Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .push("payNotices", payNotice) + .set("updateTime", TimestampUtils.now()), InAppPurchaseDetails.class ); } @@ -100,12 +102,12 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ } @Override - public void updateStatus(String id, InAppPurchaseStatus status) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("id").is(id)), - new Update() - .set("status", status) - .push("statusSteps", new InAppPurchaseStatusStep() + public void updateStatus(String id, InAppPurchaseStatus status) { + mongoTemplate.updateFirst( + Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("status", status) + .push("statusSteps", new InAppPurchaseStatusStep() .setStatus(status) .setCreateTime(TimestampUtils.now()) ) @@ -115,12 +117,12 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ } @Override - public void updateStatusReason(String id, InAppPurchaseStatus status, String reason) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("id").is(id)), - new Update() - .set("status", status) - .set("reason", reason) + public void updateStatusReason(String id, InAppPurchaseStatus status, String reason) { + mongoTemplate.updateFirst( + Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("status", status) + .set("reason", reason) .push("statusSteps", new InAppPurchaseStatusStep() .setStatus(status) .setCreateTime(TimestampUtils.now()) @@ -138,13 +140,13 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ } // 各大云厂商mongo版本差异和自定义特性在属性字段上可能存在支持上会出现偏差,不允许字段出现. or $特殊属性 - String newKey = key.replaceAll("\\.", "_").replaceAll("\\$", "_"); - details.getMetadata().put(newKey, val); - return mongoTemplate.updateFirst( - Query.query(Criteria.where("id").is(details.getId()) - .and("version").is(details.getVersion())), - new Update() - .set("metadata", details.getMetadata()) + String newKey = key.replaceAll("\\.", "_").replaceAll("\\$", "_"); + details.getMetadata().put(newKey, val); + return mongoTemplate.updateFirst( + Query.query(Criteria.where(ID_FIELD).is(details.getId()) + .and("version").is(details.getVersion())), + new Update() + .set("metadata", details.getMetadata()) .set("version", details.getVersion() + 1) .set("updateTime", TimestampUtils.now()), InAppPurchaseDetails.class @@ -154,12 +156,12 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ @Override public List list(InAppPurchaseDetailsQuery query) { - Criteria criteria = new Criteria(); - - if (StringUtils.isNotBlank(query.getId())) { - query.setLastId(null); - criteria.and("id").is(query.getId()); - } + Criteria criteria = new Criteria(); + + if (StringUtils.isNotBlank(query.getId())) { + query.setLastId(null); + criteria.and(ID_FIELD).is(query.getId()); + } if (Objects.nonNull(query.getReceiptType())) { criteria.and("receiptType").is(query.getReceiptType()); } @@ -203,27 +205,27 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ ); } - if (StringUtils.isNotBlank(query.getLastId())) { - criteria.and("id").lt(query.getLastId()); - } + if (StringUtils.isNotBlank(query.getLastId())) { + criteria.and(ID_FIELD).lt(query.getLastId()); + } if (Objects.isNull(query.getLimit()) || query.getLimit() <= 0) { query.setLimit(PageConstant.DEFAULT_LIMIT_SIZE); } - return mongoTemplate - .find(Query.query(criteria) - .with(Sort.by(Sort.Order.desc("id"))) - .limit(query.getLimit()), InAppPurchaseDetails.class); - } + return mongoTemplate + .find(Query.query(criteria) + .with(Sort.by(Sort.Order.desc(ID_FIELD))) + .limit(query.getLimit()), InAppPurchaseDetails.class); + } @Override public Map statistics(InAppPurchaseDetailsQuery query) { - Criteria criteria = new Criteria(); - - if (StringUtils.isNotBlank(query.getId())) { - criteria.and("id").is(query.getId()); - } + Criteria criteria = new Criteria(); + + if (StringUtils.isNotBlank(query.getId())) { + criteria.and(ID_FIELD).is(query.getId()); + } if (Objects.nonNull(query.getReceiptType())) { criteria.and("receiptType").is(query.getReceiptType()); } @@ -336,12 +338,12 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ } @Override - public void updateMetadata(String id, Map metadata) { - mongoTemplate.updateFirst( - Query.query(Criteria.where("id").is(id)), - new Update() - .set("metadata", metadata), - InAppPurchaseDetails.class + public void updateMetadata(String id, Map metadata) { + mongoTemplate.updateFirst( + Query.query(Criteria.where(ID_FIELD).is(id)), + new Update() + .set("metadata", metadata), + InAppPurchaseDetails.class ); } diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayDAO.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayDAO.java new file mode 100644 index 0000000..6775b8d --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayDAO.java @@ -0,0 +1,10 @@ +package com.red.circle.order.infra.database.rds.dao.order; + +import com.red.circle.framework.mybatis.dao.BaseDAO; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; + +/** + * Web支付预下单 Mapper. + */ +public interface OrderUserPurchasePayDAO extends BaseDAO { +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayNoticeDAO.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayNoticeDAO.java new file mode 100644 index 0000000..8c4e49e --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/dao/order/OrderUserPurchasePayNoticeDAO.java @@ -0,0 +1,10 @@ +package com.red.circle.order.infra.database.rds.dao.order; + +import com.red.circle.framework.mybatis.dao.BaseDAO; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePayNotice; + +/** + * Web支付通知明细 Mapper. + */ +public interface OrderUserPurchasePayNoticeDAO extends BaseDAO { +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePay.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePay.java new file mode 100644 index 0000000..11b16e7 --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePay.java @@ -0,0 +1,113 @@ +package com.red.circle.order.infra.database.rds.entity.order; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * Web支付预下单记录. + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("order_user_purchase_pay") +public class OrderUserPurchasePay implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + @TableField("evn") + private String evn; + + @TableField("sys_origin") + private String sysOrigin; + + @TableField("user_id") + private Long userId; + + @TableField("factory_code") + private String factoryCode; + + @TableField("factory_order_id") + private String factoryOrderId; + + @TableField("reference_id") + private String referenceId; + + @TableField("receipt_type") + private String receiptType; + + @TableField("payment_channel") + private String paymentChannel; + + @TableField("factory_channel") + private String factoryChannel; + + @TableField("product_code") + private String productCode; + + @TableField("product_content") + private String productContent; + + @TableField("product_descriptor") + private String productDescriptor; + + @TableField("payment_unit") + private String paymentUnit; + + @TableField("payment_amount") + private BigDecimal paymentAmount; + + @TableField("compute_usd_amount") + private BigDecimal computeUsdAmount; + + @TableField("compute_usd_rate") + private String computeUsdRate; + + @TableField("give_away_content") + private String giveAwayContent; + + @TableField("country_id") + private Long countryId; + + @TableField("country_code") + private String countryCode; + + @TableField("pay_country_id") + private Long payCountryId; + + @TableField("pay_status") + private String payStatus; + + @TableField("reason") + private String reason; + + @TableField("refund_status") + private String refundStatus; + + @TableField("receipt_details_id") + private Long receiptDetailsId; + + @TableField("create_time") + private Timestamp createTime; + + @TableField("update_time") + private Timestamp updateTime; + + @TableField("create_user") + private Long createUser; + + @TableField("update_user") + private Long updateUser; +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePayNotice.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePayNotice.java new file mode 100644 index 0000000..a6a7e3e --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/entity/order/OrderUserPurchasePayNotice.java @@ -0,0 +1,37 @@ +package com.red.circle.order.infra.database.rds.entity.order; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.red.circle.framework.mybatis.entity.TimestampBaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * Web支付通知明细. + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("order_user_purchase_pay_notice") +public class OrderUserPurchasePayNotice extends TimestampBaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + @TableField("purchase_pay_id") + private Long purchasePayId; + + @TableField("event_id") + private String eventId; + + @TableField("notice_type") + private String noticeType; + + @TableField("notice_data") + private String noticeData; +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayNoticeService.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayNoticeService.java new file mode 100644 index 0000000..983ad13 --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayNoticeService.java @@ -0,0 +1,16 @@ +package com.red.circle.order.infra.database.rds.service.order; + +import com.red.circle.framework.mybatis.service.BaseService; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePayNotice; + +/** + * Web支付通知明细服务. + */ +public interface OrderUserPurchasePayNoticeService extends BaseService { + + /** + * 保存通知. + */ + void addNotice(Long purchasePayId, String eventId, String noticeType, Object noticeData, + Long operationUserId); +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayService.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayService.java new file mode 100644 index 0000000..54a0edb --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/OrderUserPurchasePayService.java @@ -0,0 +1,20 @@ +package com.red.circle.order.infra.database.rds.service.order; + +import com.red.circle.framework.mybatis.service.BaseService; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; + +/** + * Web支付预下单服务. + */ +public interface OrderUserPurchasePayService extends BaseService { + + /** + * 标记支付成功. + */ + void updateSuccess(Long id); + + /** + * 标记支付失败. + */ + void updateFail(Long id, String reason); +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayNoticeServiceImpl.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayNoticeServiceImpl.java new file mode 100644 index 0000000..4b84cfb --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayNoticeServiceImpl.java @@ -0,0 +1,31 @@ +package com.red.circle.order.infra.database.rds.service.order.impl; + +import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; +import com.red.circle.order.infra.database.rds.dao.order.OrderUserPurchasePayNoticeDAO; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePayNotice; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayNoticeService; +import com.red.circle.tool.core.json.JacksonUtils; +import java.util.Objects; +import org.springframework.stereotype.Service; + +/** + * Web支付通知明细服务实现. + */ +@Service +public class OrderUserPurchasePayNoticeServiceImpl + extends BaseServiceImpl + implements OrderUserPurchasePayNoticeService { + + @Override + public void addNotice(Long purchasePayId, String eventId, String noticeType, Object noticeData, + Long operationUserId) { + OrderUserPurchasePayNotice notice = new OrderUserPurchasePayNotice() + .setPurchasePayId(purchasePayId) + .setEventId(Objects.toString(eventId, "")) + .setNoticeType(Objects.toString(noticeType, "")) + .setNoticeData(JacksonUtils.toJson(noticeData)); + notice.setCreateUser(operationUserId); + notice.setUpdateUser(operationUserId); + save(notice); + } +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayServiceImpl.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayServiceImpl.java new file mode 100644 index 0000000..1a328f6 --- /dev/null +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/rds/service/order/impl/OrderUserPurchasePayServiceImpl.java @@ -0,0 +1,40 @@ +package com.red.circle.order.infra.database.rds.service.order.impl; + +import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; +import com.red.circle.order.infra.database.rds.dao.order.OrderUserPurchasePayDAO; +import com.red.circle.order.infra.database.rds.entity.order.OrderUserPurchasePay; +import com.red.circle.order.infra.database.rds.service.order.OrderUserPurchasePayService; +import com.red.circle.tool.core.date.TimestampUtils; +import org.springframework.stereotype.Service; + +/** + * Web支付预下单服务实现. + */ +@Service +public class OrderUserPurchasePayServiceImpl + extends BaseServiceImpl + implements OrderUserPurchasePayService { + + private static final String PAY_STATUS_SUCCESSFUL = "SUCCESSFUL"; + private static final String PAY_STATUS_FAIL = "FAIL"; + + @Override + public void updateSuccess(Long id) { + update() + .set(OrderUserPurchasePay::getPayStatus, PAY_STATUS_SUCCESSFUL) + .set(OrderUserPurchasePay::getReason, "") + .set(OrderUserPurchasePay::getUpdateTime, TimestampUtils.now()) + .eq(OrderUserPurchasePay::getId, id) + .execute(); + } + + @Override + public void updateFail(Long id, String reason) { + update() + .set(OrderUserPurchasePay::getPayStatus, PAY_STATUS_FAIL) + .set(OrderUserPurchasePay::getReason, reason) + .set(OrderUserPurchasePay::getUpdateTime, TimestampUtils.now()) + .eq(OrderUserPurchasePay::getId, id) + .execute(); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/query/ActivityGiftListQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/query/ActivityGiftListQryExe.java index 9a6eca7..29cd816 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/query/ActivityGiftListQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/query/ActivityGiftListQryExe.java @@ -1,13 +1,14 @@ -package com.red.circle.other.app.command.gift.query; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.red.circle.common.business.dto.cmd.AppExtCommand; -import com.red.circle.other.app.dto.clientobject.gift.ActivityGiftCO; -import com.red.circle.other.domain.gateway.user.UserProfileGateway; -import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; -import com.red.circle.other.domain.model.user.UserProfile; -import com.red.circle.other.infra.database.cache.service.other.BannerCacheService; +package com.red.circle.other.app.command.gift.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import com.red.circle.other.app.dto.clientobject.gift.ActivityGiftCO; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.cache.service.other.BannerCacheService; import com.red.circle.other.infra.database.mongo.entity.sys.ActivityConfig; import com.red.circle.other.infra.database.mongo.service.sys.ActivityConfigService; import com.red.circle.other.infra.database.rds.entity.gift.GiftConfig; @@ -36,9 +37,10 @@ public class ActivityGiftListQryExe { private final ActivityConfigService activityConfigService; private final GiftConfigService giftConfigService; - private final BannerConfigService bannerConfigService; - private final UserProfileGateway userProfileGateway; - private final UserRegionGateway userRegionGateway; + private final BannerConfigService bannerConfigService; + private final UserProfileGateway userProfileGateway; + private final UserRegionGateway userRegionGateway; + private final CountryCodeAliasSupport countryCodeAliasSupport; public List execute(AppExtCommand cmd) { // 获取进行中的活动 @@ -78,12 +80,13 @@ public class ActivityGiftListQryExe { Map bannerUrlMap; if (!bannerIds.isEmpty()) { - LambdaQueryWrapper query = Wrappers.lambdaQuery() - .in(BannerConfig::getId, bannerIds); - bannerUrlMap = bannerConfigService.list(query).stream() - .filter(bannerCache -> StringUtils.isBlank(bannerCache.getRegions()) || bannerCache.getRegions().contains(regionId)) - .filter(bannerCache -> StringUtils.isBlank(bannerCache.getCountryCode()) || bannerCache.getCountryCode().contains(countryCode)) - .collect(Collectors.toMap(BannerConfig::getId, e -> e, (u1, u2) -> u1)); + LambdaQueryWrapper query = Wrappers.lambdaQuery() + .in(BannerConfig::getId, bannerIds); + bannerUrlMap = bannerConfigService.list(query).stream() + .filter(bannerCache -> StringUtils.isBlank(bannerCache.getRegions()) || bannerCache.getRegions().contains(regionId)) + .filter(bannerCache -> StringUtils.isBlank(bannerCache.getCountryCode()) + || countryCodeAliasSupport.containsCode(bannerCache.getCountryCode(), countryCode)) + .collect(Collectors.toMap(BannerConfig::getId, e -> e, (u1, u2) -> u1)); } else { bannerUrlMap = new HashMap<>(); } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomGiftListQueryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomGiftListQueryExe.java index 775869f..213507b 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomGiftListQueryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomGiftListQueryExe.java @@ -1,17 +1,18 @@ package com.red.circle.other.app.command.room.query; -import cn.hutool.core.util.StrUtil; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.google.common.collect.Lists; -import com.red.circle.common.business.dto.cmd.app.AppFlowCmd; -import com.red.circle.common.business.enums.SVIPLevelEnum; +import cn.hutool.core.util.StrUtil; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.google.common.collect.Lists; +import com.red.circle.common.business.dto.cmd.app.AppFlowCmd; +import com.red.circle.common.business.enums.SVIPLevelEnum; import com.red.circle.other.app.common.room.RoomVoiceProfileCommon; import com.red.circle.other.app.convertor.live.RoomProfileAppConvertor; -import com.red.circle.other.app.dto.clientobject.room.RoomBrowseRecordsV2CO; -import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; -import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; -import com.red.circle.other.domain.gateway.user.ability.UserSVipGateway; -import com.red.circle.other.infra.database.mongo.entity.activity.RankQueenCount; +import com.red.circle.other.app.dto.clientobject.room.RoomBrowseRecordsV2CO; +import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.gateway.user.ability.UserSVipGateway; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.mongo.entity.activity.RankQueenCount; import com.red.circle.other.infra.database.mongo.entity.activity.RankQueenType; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; import com.red.circle.other.infra.database.mongo.entity.live.RoomProfile; @@ -37,10 +38,11 @@ import java.util.stream.Stream; @AllArgsConstructor public class RoomGiftListQueryExe { - private final UserSVipGateway userSVipGateway; - private final RankCountService rankCountService; - private final UserRegionGateway userRegionGateway; - private final RoomProfileAppConvertor roomProfileAppConvertor; + private final UserSVipGateway userSVipGateway; + private final RankCountService rankCountService; + private final UserRegionGateway userRegionGateway; + private final CountryCodeAliasSupport countryCodeAliasSupport; + private final RoomProfileAppConvertor roomProfileAppConvertor; private final ActiveVoiceRoomService activeVoiceRoomService; private final RoomVoiceProfileCommon roomVoiceProfileCommon; private final RoomUserBlacklistService roomUserBlacklistService; @@ -183,7 +185,8 @@ public class RoomGiftListQueryExe { return assemblyRoomVoiceProfile(activeVoiceRooms, cmd); } - List excludeCountryCodes = List.of("SA", "AE", "EG", "MA", "US"); + List excludeCountryCodes = new ArrayList<>( + countryCodeAliasSupport.expandCodes(List.of("SA", "AE", "EG", "MA", "US"))); List roomVoiceProfiles = Lists.newArrayList(); if (CollectionUtils.isEmpty(activeVoiceRooms)) { List roomVoiceProfileCos = roomVoiceProfileCommon.listRoomVoiceProfilesV2( diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java index 19f65a3..706c731 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/room/query/RoomVoiceDiscoverQryExe.java @@ -1,21 +1,22 @@ package com.red.circle.other.app.command.room.query; -import com.alibaba.fastjson.JSON; -import com.google.common.collect.Sets; -import com.google.gson.Gson; -import com.red.circle.common.business.dto.cmd.AppExtCommand; -import com.red.circle.common.business.enums.SVIPLevelEnum; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.red.circle.common.business.dto.cmd.AppExtCommand; +import com.red.circle.common.business.enums.SVIPLevelEnum; import com.red.circle.live.inner.endpoint.LiveMicClient; import com.red.circle.other.app.common.room.RoomVoiceProfileCommon; import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; import com.red.circle.other.app.service.game.GameLudoService; import com.red.circle.other.app.service.room.RocketStatusCacheService; import com.red.circle.other.domain.gateway.user.UserProfileGateway; -import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; -import com.red.circle.other.domain.gateway.user.ability.UserSVipGateway; -import com.red.circle.other.domain.model.user.UserProfile; -import com.red.circle.other.domain.rocket.RocketStatus; -import com.red.circle.other.infra.database.cache.service.user.RegionRoomCacheService; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.gateway.user.ability.UserSVipGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.domain.rocket.RocketStatus; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.cache.service.user.RegionRoomCacheService; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager; import com.red.circle.other.infra.database.mongo.entity.live.RoomUserBlacklist; @@ -60,15 +61,16 @@ public class RoomVoiceDiscoverQryExe { private final RegionRoomCacheService regionRoomCacheService; private final LiveMicClient liveMicClient; private final RedisTemplate redisTemplate; - private final RocketStatusCacheService rocketStatusCacheService; - private final GameLudoService gameLudoService; - private final UserProfileGateway userProfileGateway; + private final RocketStatusCacheService rocketStatusCacheService; + private final GameLudoService gameLudoService; + private final UserProfileGateway userProfileGateway; + private final CountryCodeAliasSupport countryCodeAliasSupport; private static final String EMPTY_ROOM_CACHE_KEY = "empty_room_cache:*"; - // 区域隔离常量 - 与ActiveVoiceRoomServiceImpl保持一致 - private static final Set TR_REGIONS = Set.of("TR"); - private static final Set SA_REGIONS = Set.of("BD","SA", "IN", "PK"); + // 区域隔离常量 - 与ActiveVoiceRoomServiceImpl保持一致 + private static final Set TR_REGIONS = Set.of("TR"); + private static final Set SA_REGIONS = Set.of("BD","SA", "IN", "PK"); private static final Set AR_REGIONS = Set.of("AE", "AR", "BH", "DJ", "DZ", "EG", "EY", "ER", "IL", "IQ", "JO", "KM", "KW", "LB", "LY", "MA", "MR", "OM", "PS", "QA", "SD", "SO", "SS", "SY", "TD", "TN", "YE"); private static final Set OTHER_REGIONS = Set.of("OTHER", "PH", "ID", "GH", "IR", "AF", "NG", "DE", "MYS", "TM", "AZ", "NP"); @@ -261,7 +263,8 @@ public class RoomVoiceDiscoverQryExe { return emptyRooms.stream() .filter(room -> Objects.equals(sysOrigin, room.getSysOrigin()) && (isAllRegion || shouldShowRoom(region, room.getRegion())) && - (!SA_REGIONS.contains(region) || userCountryCode == null || userCountryCode.equals(room.getCountryCode()))) + (!SA_REGIONS.contains(region) || userCountryCode == null + || countryCodeAliasSupport.matches(userCountryCode, room.getCountryCode()))) .collect(Collectors.toList()); } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/BannerQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/BannerQryExe.java index e5c3217..46836ad 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/BannerQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/BannerQryExe.java @@ -1,16 +1,17 @@ -package com.red.circle.other.app.command.sys.query; - -import com.google.common.collect.Lists; -import com.red.circle.common.business.dto.cmd.AppExtCommand; +package com.red.circle.other.app.command.sys.query; + +import com.google.common.collect.Lists; +import com.red.circle.common.business.dto.cmd.AppExtCommand; import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.order.inner.endpoint.OrderPurchaseHistoryClient; import com.red.circle.other.app.convertor.sys.SysConfigAppConvertor; import com.red.circle.other.app.dto.clientobject.sys.BannerCO; -import com.red.circle.other.domain.gateway.user.UserProfileGateway; -import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; -import com.red.circle.other.domain.model.user.UserProfile; -import com.red.circle.other.infra.database.cache.entity.sys.BannerCache; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.cache.entity.sys.BannerCache; import com.red.circle.other.infra.database.cache.service.other.BannerCacheService; import com.red.circle.other.infra.database.rds.entity.sys.BannerConfig; import com.red.circle.other.infra.database.rds.service.sys.BannerConfigService; @@ -36,9 +37,10 @@ public class BannerQryExe { private final UserRegionGateway userRegionGateway; private final BannerCacheService bannerCacheService; private final BannerConfigService bannerConfigService; - private final SysConfigAppConvertor sysConfigAppConvertor; - private final OrderPurchaseHistoryClient orderPurchaseHistoryClient; - private final UserProfileGateway userProfileGateway; + private final SysConfigAppConvertor sysConfigAppConvertor; + private final OrderPurchaseHistoryClient orderPurchaseHistoryClient; + private final UserProfileGateway userProfileGateway; + private final CountryCodeAliasSupport countryCodeAliasSupport; public List execute(AppExtCommand cmd, List typeList) { UserProfile userProfile = userProfileGateway.getByUserId(cmd.requiredReqUserId()); @@ -55,10 +57,11 @@ public class BannerQryExe { return Lists.newArrayList(); } return sysConfigAppConvertor.toListBannerCache(configs); - }).stream() - .filter(bannerCache -> StringUtils.isBlank(bannerCache.getRegions()) || bannerCache.getRegions().contains(regionId)) - .filter(bannerCache -> StringUtils.isBlank(bannerCache.getCountryCode()) || bannerCache.getCountryCode().contains(countryCode)) - .toList(); + }).stream() + .filter(bannerCache -> StringUtils.isBlank(bannerCache.getRegions()) || bannerCache.getRegions().contains(regionId)) + .filter(bannerCache -> StringUtils.isBlank(bannerCache.getCountryCode()) + || countryCodeAliasSupport.containsCode(bannerCache.getCountryCode(), countryCode)) + .toList(); return sysConfigAppConvertor.toListBannerCO(list); } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/SysCountryOpenTopQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/SysCountryOpenTopQryExe.java index de066db..d00609f 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/SysCountryOpenTopQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/sys/query/SysCountryOpenTopQryExe.java @@ -1,8 +1,9 @@ package com.red.circle.other.app.command.sys.query; -import cn.hutool.core.collection.CollectionUtil; -import com.google.common.collect.Lists; -import com.red.circle.other.app.dto.clientobject.sys.CountryCodeCO; +import cn.hutool.core.collection.CollectionUtil; +import com.google.common.collect.Lists; +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.other.app.dto.clientobject.sys.CountryCodeCO; import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; import com.red.circle.other.infra.database.rds.service.sys.SysCountryCodeService; @@ -37,15 +38,19 @@ public class SysCountryOpenTopQryExe { if (Objects.isNull(sysCountryCode)) { return null; } - return new CountryCodeCO() - .setId(sysCountryCode.getId()) - .setAlphaTwo(sysCountryCode.getAlphaTwo()) - .setAlphaThree(sysCountryCode.getAlphaThree()) - .setCountryName(sysCountryCode.getCountryName()) - .setAliasName(sysCountryCode.getAliasName()) - .setNationalFlag(sysCountryCode.getNationalFlag()) - .setPhonePrefix(sysCountryCode.getPhonePrefix()) - .setOpen(sysCountryCode.getOpen()) + return new CountryCodeCO() + .setId(sysCountryCode.getId()) + .setAlphaTwo(sysCountryCode.getAlphaTwo()) + .setAlphaThree(sysCountryCode.getAlphaThree()) + .setCountryName(sysCountryCode.getCountryName()) + .setAliasName(sysCountryCode.getAliasName()) + .setCountryCodeAliases(CountryCodeAliasUtils.normalizeCodes( + CountryCodeAliasUtils.buildMatchCodes(sysCountryCode.getAlphaTwo(), + sysCountryCode.getAlphaThree(), + CountryCodeAliasUtils.parseAliasCodes(sysCountryCode.getAliasCodes())))) + .setNationalFlag(sysCountryCode.getNationalFlag()) + .setPhonePrefix(sysCountryCode.getPhonePrefix()) + .setOpen(sysCountryCode.getOpen()) .setTop(sysCountryCode.getTop()); } 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 5ff03dd..dc3ae11 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 @@ -371,10 +371,9 @@ public class GameLuckyGiftCommon { } - private static int getRewardMultiple(GameLuckyGiftParam param, Long rewardAmount) { - long payAmount = param.getQuantity() * param.getGift().getGiftCandy().longValue(); - return (int) (rewardAmount / payAmount); - } + private static int getRewardMultiple(GameLuckyGiftParam param, Long rewardAmount) { + return LuckyGiftMultipleCalculator.calculate(rewardAmount, param.getGift().getGiftCandy()); + } /** * 抽奖. @@ -458,10 +457,10 @@ public class GameLuckyGiftCommon { /** * 处理用户连续赠送幸运礼物奖励逻辑 */ - private void saveLuckGiftCount(GameLuckyGiftParam param, Long awardAmount) { - - long payAmount = param.getQuantity() * param.getGift().getGiftCandy().longValue(); - int multiple = (int) (awardAmount / payAmount); + private void saveLuckGiftCount(GameLuckyGiftParam param, Long awardAmount) { + + long payAmount = param.getQuantity() * param.getGift().getGiftCandy().longValue(); + int multiple = LuckyGiftMultipleCalculator.calculate(awardAmount, param.getGift().getGiftCandy()); gameLuckyGiftCountService.save(new GameLuckyGiftCount() .setId(param.getId()) diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftMultipleCalculator.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftMultipleCalculator.java new file mode 100644 index 0000000..b69f4fa --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftMultipleCalculator.java @@ -0,0 +1,22 @@ +package com.red.circle.other.app.common.gift; + +import java.math.BigDecimal; + +final class LuckyGiftMultipleCalculator { + + private LuckyGiftMultipleCalculator() { + } + + static int calculate(Long rewardAmount, BigDecimal giftUnitPrice) { + if (rewardAmount == null || rewardAmount <= 0 || giftUnitPrice == null) { + return 0; + } + + long unitPrice = giftUnitPrice.longValue(); + if (unitPrice <= 0) { + return 0; + } + + return (int) (rewardAmount / unitPrice); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessor.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessor.java index 54d175d..68759e7 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessor.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessor.java @@ -81,7 +81,8 @@ public class LuckyGiftResultProcessor { } UserPropsResourcesDTO avatarFrame = getUserAvatarFrameProps(sendUserProfile); - long payAmount = resolvePayAmount(event, giftConfig); + BigDecimal giftUnitPrice = resolveGiftUnitPrice(event, giftConfig); + long payAmount = resolvePayAmount(event, giftUnitPrice); BigDecimal balanceAfter = BigDecimal.valueOf(Objects.requireNonNullElse(response.getBalanceAfter(), 0L)); for (LuckyGiftGoClient.LuckyGiftGoDrawResult result : response.getResults()) { @@ -91,7 +92,7 @@ public class LuckyGiftResultProcessor { } long rewardNum = Objects.requireNonNullElse(result.getRewardNum(), 0L); - int multiple = payAmount > 0 && rewardNum > 0 ? (int) (rewardNum / payAmount) : 0; + int multiple = LuckyGiftMultipleCalculator.calculate(rewardNum, giftUnitPrice); gameLuckyGiftCountService.saveOrUpdate(new GameLuckyGiftCount() .setId(Long.valueOf(result.getId())) @@ -121,9 +122,15 @@ public class LuckyGiftResultProcessor { } } - private long resolvePayAmount(GameLuckyGiftBusinessEvent event, GiftConfigDTO giftConfig) { - BigDecimal giftPrice = Objects.nonNull(event.getGiftPrice()) ? event.getGiftPrice() : giftConfig.getGiftCandy(); - return giftPrice.multiply(BigDecimal.valueOf(Objects.requireNonNullElse(event.getQuantity(), 0))).longValue(); + private BigDecimal resolveGiftUnitPrice(GameLuckyGiftBusinessEvent event, GiftConfigDTO giftConfig) { + return Objects.nonNull(event.getGiftPrice()) ? event.getGiftPrice() : giftConfig.getGiftCandy(); + } + + private long resolvePayAmount(GameLuckyGiftBusinessEvent event, BigDecimal giftUnitPrice) { + if (giftUnitPrice == null) { + return 0L; + } + return giftUnitPrice.multiply(BigDecimal.valueOf(Objects.requireNonNullElse(event.getQuantity(), 0))).longValue(); } private void sendLuckyGiftMessage( diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommon.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommon.java index b044419..8c2d83b 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommon.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommon.java @@ -139,23 +139,24 @@ public class RoomVoiceProfileCommon { .collect(Collectors.toList()); } - private RoomVoiceProfileCO mergeActiveVoiceRoom(UserProfileDTO userProfile, - RoomProfileManager manager, - ActiveVoiceRoom activeVoiceRoom) { + private RoomVoiceProfileCO mergeActiveVoiceRoom(UserProfileDTO userProfile, + RoomProfileManager manager, + ActiveVoiceRoom activeVoiceRoom) { if (Objects.isNull(userProfile) || Objects.isNull(manager) || Objects.isNull(activeVoiceRoom)) { return null; } - RoomVoiceProfileCO roomVoiceProfile = mergeRoomVoiceProfile(userProfile, manager); - if (Objects.isNull(roomVoiceProfile)) { - return null; - } - roomVoiceProfile.setHotRoom(activeVoiceRoom.getHot()); - roomVoiceProfile.setRoomGameIcon(""); - roomVoiceProfile.setUserSVipLevel(SVIPLevelEnum.valueOf(activeVoiceRoom.getSuperVipLevel())); - return roomVoiceProfile; - } + RoomVoiceProfileCO roomVoiceProfile = mergeRoomVoiceProfile(userProfile, manager); + if (Objects.isNull(roomVoiceProfile)) { + return null; + } + roomVoiceProfile.putRoomMemberQuantity(toRoomMemberQuantity(activeVoiceRoom.getOnlineQuantity())); + roomVoiceProfile.setHotRoom(activeVoiceRoom.getHot()); + roomVoiceProfile.setRoomGameIcon(""); + roomVoiceProfile.setUserSVipLevel(SVIPLevelEnum.valueOf(activeVoiceRoom.getSuperVipLevel())); + return roomVoiceProfile; + } private RoomVoiceProfileCO mergeRoomVoiceProfile(UserProfileDTO userProfile, RoomProfileManager manager) { @@ -185,5 +186,12 @@ public class RoomVoiceProfileCommon { && !Objects.equals(manager.getEvent(), RoomEventEnum.CLOSE.name()); } + private Integer toRoomMemberQuantity(Long onlineQuantity) { + if (Objects.isNull(onlineQuantity) || onlineQuantity < 0) { + return 0; + } + return onlineQuantity > Integer.MAX_VALUE ? Integer.MAX_VALUE : onlineQuantity.intValue(); + } + } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/convertor/sys/SysCountryAppConvertor.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/convertor/sys/SysCountryAppConvertor.java index 6e2efaa..8ae855e 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/convertor/sys/SysCountryAppConvertor.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/convertor/sys/SysCountryAppConvertor.java @@ -1,18 +1,30 @@ -package com.red.circle.other.app.convertor.sys; - -import com.red.circle.framework.core.convertor.ConvertorModel; -import com.red.circle.other.app.dto.clientobject.sys.CountryCodeCO; -import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; -import java.util.List; -import org.mapstruct.Mapper; +package com.red.circle.other.app.convertor.sys; + +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.framework.core.convertor.ConvertorModel; +import com.red.circle.other.app.dto.clientobject.sys.CountryCodeCO; +import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; /** * @author pengliang on 2021/3/10 */ @Mapper(componentModel = ConvertorModel.SPRING) -public interface SysCountryAppConvertor { - - List toListCountryCodeCO(List countryCodes); - - CountryCodeCO toCountryCodeCO(SysCountryCode countryCode); -} +public interface SysCountryAppConvertor { + + List toListCountryCodeCO(List countryCodes); + + @Mapping(target = "countryCodeAliases", source = ".") + CountryCodeCO toCountryCodeCO(SysCountryCode countryCode); + + default List mapCountryCodeAliases(SysCountryCode countryCode) { + if (countryCode == null) { + return List.of(); + } + return CountryCodeAliasUtils.normalizeCodes( + CountryCodeAliasUtils.buildMatchCodes(countryCode.getAlphaTwo(), countryCode.getAlphaThree(), + CountryCodeAliasUtils.parseAliasCodes(countryCode.getAliasCodes()))); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/EmptyRoomCleanTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/EmptyRoomCleanTask.java deleted file mode 100644 index ad1394e..0000000 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/EmptyRoomCleanTask.java +++ /dev/null @@ -1,75 +0,0 @@ -// 路径: rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/EmptyRoomCleanTask.java -package com.red.circle.other.app.scheduler; - -import com.alibaba.nacos.client.naming.utils.CollectionUtils; -import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; -import com.red.circle.component.redis.annotation.TaskCacheLock; -import com.red.circle.live.inner.endpoint.LiveMicClient; -import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; -import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; - -/** - * 清理空房间定时任务 - * 每2分钟清理一次DB中没人的房间 - */ -@Slf4j -@Component -@RequiredArgsConstructor -@ConditionalOnProperty(name = "scheduler.room-empty-clean", havingValue = "true", matchIfMissing = true) -public class EmptyRoomCleanTask { - - private final ActiveVoiceRoomService activeVoiceRoomService; - private final LiveMicClient liveMicClient; - - /** - * 每2分钟清理一次空房间 - */ - @Scheduled(cron = "0 */2 * * * ?") - @TaskCacheLock(key = "EMPTY_ROOM_CLEAN_TASK", expireSecond = 59 * 2) - public void cleanEmptyRooms() { - try { - // 获取所有活跃房间 - List allRooms = activeVoiceRoomService.listDiscover(SysOriginPlatformEnum.LIKEI.name(),true,"",null,200); - if (CollectionUtils.isEmpty(allRooms)) { - return; - } - - List emptyRoomIds = new ArrayList<>(); - - // 检查每个房间的在线人数 - for (ActiveVoiceRoom room : allRooms) { - // 跳过固定权重的房间(这些是固定展示的房间) - if (room.getFixedWeights() != 0) { - continue; - } - - try { - Long roomUserCount = liveMicClient.getLiveRoomUserSize(room.getId()).getBody(); - if (roomUserCount != null && roomUserCount == 0) { - emptyRoomIds.add(room.getId()); - } - } catch (Exception e) { - - } - } - - // 批量删除空房间 - if (!CollectionUtils.isEmpty(emptyRoomIds)) { - activeVoiceRoomService.removeByIds(emptyRoomIds); - log.info("清理空房间完成,删除房间数量: {}, 房间ID: {}", emptyRoomIds.size(), emptyRoomIds); - } else { - log.warn("清理空房间失败, 无法获取房间在线人数"); - } - } catch (Exception e) { - log.error("清理空房间任务执行失败", e); - } - } -} \ No newline at end of file diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomOnlineQuantityReconcileTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomOnlineQuantityReconcileTask.java new file mode 100644 index 0000000..29948fc --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/RoomOnlineQuantityReconcileTask.java @@ -0,0 +1,92 @@ +package com.red.circle.other.app.scheduler; + +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.component.redis.annotation.TaskCacheLock; +import com.red.circle.live.inner.endpoint.LiveMicClient; +import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; +import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; +import com.red.circle.tool.core.collection.CollectionUtils; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 定时对账在线房间人数,修正 ActiveVoiceRoom.onlineQuantity 漂移。 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(name = "scheduler.room-online-quantity-reconcile", havingValue = "true", matchIfMissing = true) +public class RoomOnlineQuantityReconcileTask { + + private static final int PER_SYS_ORIGIN_LIMIT = 200; + + private final ActiveVoiceRoomService activeVoiceRoomService; + private final LiveMicClient liveMicClient; + + @Scheduled(cron = "0 */2 * * * ?") + @TaskCacheLock(key = "ROOM_ONLINE_QUANTITY_RECONCILE_TASK", expireSecond = 59 * 2) + public void reconcileOnlineQuantity() { + try { + List activeRooms = listActiveRooms(); + if (CollectionUtils.isEmpty(activeRooms)) { + return; + } + + int correctedCount = 0; + int failedCount = 0; + for (ActiveVoiceRoom room : activeRooms) { + if (Objects.isNull(room.getId()) || Objects.isNull(room.getRoomAccount())) { + continue; + } + + try { + Long actualQuantity = liveMicClient.getLiveRoomUserSize(room.getId()).getBody(); + actualQuantity = Objects.nonNull(actualQuantity) ? actualQuantity : 0L; + Long currentQuantity = Objects.nonNull(room.getOnlineQuantity()) ? room.getOnlineQuantity() : 0L; + if (Objects.equals(currentQuantity, actualQuantity)) { + continue; + } + activeVoiceRoomService.updateQuantityByRoomAccount(room.getRoomAccount(), actualQuantity); + correctedCount++; + log.info("reconcile room online quantity, roomId={}, roomAccount={}, currentQuantity={}, actualQuantity={}", + room.getId(), room.getRoomAccount(), currentQuantity, actualQuantity); + } catch (Exception e) { + failedCount++; + log.warn("reconcile room online quantity failed, roomId={}, roomAccount={}", + room.getId(), room.getRoomAccount(), e); + } + } + + log.info("room online quantity reconcile finished, scanCount={}, correctedCount={}, failedCount={}", + activeRooms.size(), correctedCount, failedCount); + } catch (Exception e) { + log.error("room online quantity reconcile task failed", e); + } + } + + private List listActiveRooms() { + Map rooms = new LinkedHashMap<>(); + for (SysOriginPlatformEnum sysOrigin : SysOriginPlatformEnum.getVoiceSystems()) { + List currentRooms = activeVoiceRoomService.listDiscover( + sysOrigin.name(), + true, + "", + null, + PER_SYS_ORIGIN_LIMIT + ); + if (CollectionUtils.isEmpty(currentRooms)) { + continue; + } + currentRooms.forEach(room -> rooms.putIfAbsent(room.getId(), room)); + } + return List.copyOf(rooms.values()); + } +} diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/party3rd/callback/trtc/EnterRoomCallbackStrategyTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/party3rd/callback/trtc/EnterRoomCallbackStrategyTest.java new file mode 100644 index 0000000..1c5e6c2 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/party3rd/callback/trtc/EnterRoomCallbackStrategyTest.java @@ -0,0 +1,43 @@ +package com.red.circle.other.app.command.party3rd.callback.trtc; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.red.circle.external.inner.model.callback.trtc.EventInfo; +import com.red.circle.external.inner.model.callback.trtc.TrtcCallbackEvent; +import com.red.circle.other.app.common.live.OnlineRoomCommon; +import com.red.circle.other.infra.database.cache.service.other.RoomManagerCacheService; +import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; +import org.junit.jupiter.api.Test; + +class EnterRoomCallbackStrategyTest { + + @Test + void doOperation_shouldUpdateOnlineQuantityWithIncrementedRoomUserSize() { + OnlineRoomCommon onlineRoomCommon = mock(OnlineRoomCommon.class); + ActiveVoiceRoomService activeVoiceRoomService = mock(ActiveVoiceRoomService.class); + RoomManagerCacheService roomManagerCacheService = mock(RoomManagerCacheService.class); + EnterRoomCallbackStrategy strategy = new EnterRoomCallbackStrategy( + onlineRoomCommon, + activeVoiceRoomService, + roomManagerCacheService + ); + + TrtcCallbackEvent event = new TrtcCallbackEvent(); + EventInfo eventInfo = new EventInfo(); + eventInfo.setRoomId("123456"); + eventInfo.setUserId("9527"); + eventInfo.setEventTs("1710000000"); + event.setEventInfo(eventInfo); + + when(activeVoiceRoomService.existsByRoomAccount("123456")).thenReturn(true); + when(roomManagerCacheService.incrementNumberPeople("123456")).thenReturn(6L); + + strategy.doOperation(event); + + verify(activeVoiceRoomService).updateQuantityByRoomAccount("123456", 6L); + verify(onlineRoomCommon, never()).addOnlineRoomByAccount("123456"); + } +} diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessorTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessorTest.java new file mode 100644 index 0000000..744498f --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/gift/LuckyGiftResultProcessorTest.java @@ -0,0 +1,126 @@ +package com.red.circle.other.app.common.gift; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.red.circle.component.redis.service.RedisService; +import com.red.circle.external.inner.endpoint.message.ImGroupClient; +import com.red.circle.external.inner.model.cmd.message.BroadcastGroupMsgBodyCmd; +import com.red.circle.mq.business.model.event.gift.GameLuckyGiftBusinessEvent; +import com.red.circle.mq.business.model.event.gift.GameLuckyGiftUser; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.other.app.dto.clientobject.game.GameLuckyGiftMsgCO; +import com.red.circle.other.app.service.task.RoomDailyTaskProgressService; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; +import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; +import com.red.circle.other.infra.database.rds.entity.game.GameLuckyGiftCount; +import com.red.circle.other.infra.database.rds.service.game.GameLuckyGiftCountService; +import com.red.circle.other.infra.database.rds.service.live.RoomMemberService; +import com.red.circle.other.inner.model.dto.material.GiftConfigDTO; +import com.red.circle.other.inner.model.dto.user.UserProfileDTO; +import java.math.BigDecimal; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +class LuckyGiftResultProcessorTest { + + @Test + void process_shouldUseGiftUnitPriceToCalculateMultiple() { + GameLuckyGiftCountService gameLuckyGiftCountService = mock(GameLuckyGiftCountService.class); + GiftCacheService giftCacheService = mock(GiftCacheService.class); + UserProfileGateway userProfileGateway = mock(UserProfileGateway.class); + UserProfileAppConvertor userProfileAppConvertor = mock(UserProfileAppConvertor.class); + ImGroupClient imGroupClient = mock(ImGroupClient.class); + RoomDailyTaskProgressService roomDailyTaskProgressService = mock(RoomDailyTaskProgressService.class); + RoomMemberService roomMemberService = mock(RoomMemberService.class); + RedisService redisService = mock(RedisService.class); + RoomProfileManagerService roomProfileManagerService = mock(RoomProfileManagerService.class); + GameLuckyGiftCommon gameLuckyGiftCommon = mock(GameLuckyGiftCommon.class); + LuckyGiftResultProcessor processor = new LuckyGiftResultProcessor( + gameLuckyGiftCountService, + giftCacheService, + userProfileGateway, + userProfileAppConvertor, + imGroupClient, + roomDailyTaskProgressService, + roomMemberService, + redisService, + roomProfileManagerService, + gameLuckyGiftCommon + ); + + GiftConfigDTO giftConfig = new GiftConfigDTO() + .setId(11L) + .setGiftCandy(BigDecimal.valueOf(173)) + .setGiftPhoto("gift.png"); + UserProfile senderProfile = new UserProfile(); + senderProfile.setId(1001L); + senderProfile.setAccount("sender001"); + senderProfile.setUserNickname("sender"); + + UserProfile acceptProfile = new UserProfile(); + acceptProfile.setId(2002L); + acceptProfile.setUserNickname("receiver"); + + UserProfileDTO senderProfileDTO = new UserProfileDTO(); + senderProfileDTO.setId(1001L); + senderProfileDTO.setAccount("sender001"); + senderProfileDTO.setUserNickname("sender"); + senderProfileDTO.setUserAvatar("sender.png"); + + when(giftCacheService.getById(11L)).thenReturn(giftConfig); + when(userProfileGateway.getByUserId(1001L)).thenReturn(senderProfile); + when(userProfileGateway.getByUserId(2002L)).thenReturn(acceptProfile); + when(userProfileAppConvertor.toUserProfileDTO(senderProfile)).thenReturn(senderProfileDTO); + when(gameLuckyGiftCommon.getRewardMultipleType(20)).thenReturn("SUPER"); + when(roomMemberService.getRoomMember(3003L, 1001L)).thenReturn(null); + + GameLuckyGiftBusinessEvent event = new GameLuckyGiftBusinessEvent() + .setBusinessId("biz-001") + .setUserId(1001L) + .setRoomId(3003L) + .setRoomAccount("room-account") + .setSysOrigin("LIKEI") + .setGiftId(11L) + .setGiftPrice(BigDecimal.valueOf(173)) + .setQuantity(4) + .setGiftCombos(0) + .setRegionCode("SA") + .setUsers(List.of( + new GameLuckyGiftUser() + .setId(90001L) + .setAcceptUserId(2002L) + )); + + LuckyGiftGoClient.LuckyGiftGoDrawResponse response = new LuckyGiftGoClient.LuckyGiftGoDrawResponse() + .setBusinessId("biz-001") + .setBalanceAfter(999999L) + .setResults(List.of( + new LuckyGiftGoClient.LuckyGiftGoDrawResult() + .setId("90001") + .setAcceptUserId(2002L) + .setIsWin(true) + .setRewardNum(3460L) + )); + + processor.process(event, response); + + ArgumentCaptor countCaptor = ArgumentCaptor.forClass(GameLuckyGiftCount.class); + verify(gameLuckyGiftCountService).saveOrUpdate(countCaptor.capture()); + GameLuckyGiftCount saved = countCaptor.getValue(); + assertEquals(Long.valueOf(692L), saved.getPayAmount()); + assertEquals(Integer.valueOf(20), saved.getMultiple()); + assertEquals(Long.valueOf(3460L), saved.getAwardAmount()); + + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(BroadcastGroupMsgBodyCmd.class); + verify(imGroupClient).sendMessageBroadcast(messageCaptor.capture()); + GameLuckyGiftMsgCO message = (GameLuckyGiftMsgCO) messageCaptor.getValue().getData(); + assertEquals(Integer.valueOf(20), message.getMultiple()); + assertEquals(Long.valueOf(3460L), message.getAwardAmount()); + } +} diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommonTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommonTest.java new file mode 100644 index 0000000..d6c72f8 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/common/room/RoomVoiceProfileCommonTest.java @@ -0,0 +1,105 @@ +package com.red.circle.other.app.common.room; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.red.circle.other.app.convertor.live.RoomProfileAppConvertor; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.other.app.dto.clientobject.family.RoomSettingCO; +import com.red.circle.other.app.dto.clientobject.room.RoomVoiceProfileCO; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; +import com.red.circle.other.infra.database.mongo.entity.live.RoomCounter; +import com.red.circle.other.infra.database.mongo.entity.live.RoomProfileManager; +import com.red.circle.other.infra.database.mongo.entity.live.RoomSetting; +import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; +import com.red.circle.other.inner.enums.room.RoomEventEnum; +import com.red.circle.other.inner.model.dto.user.UserProfileDTO; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class RoomVoiceProfileCommonTest { + + @Test + void toListRoomVoiceProfileCO_shouldUseActiveVoiceRoomOnlineQuantityAsMemberQuantity() { + UserProfileGateway userProfileGateway = mock(UserProfileGateway.class); + UserProfileAppConvertor userProfileAppConvertor = mock(UserProfileAppConvertor.class); + RoomProfileAppConvertor roomProfileAppConvertor = mock(RoomProfileAppConvertor.class); + RoomProfileManagerService roomProfileManagerService = mock(RoomProfileManagerService.class); + RoomVoiceProfileCommon roomVoiceProfileCommon = new RoomVoiceProfileCommon( + userProfileGateway, + userProfileAppConvertor, + roomProfileAppConvertor, + roomProfileManagerService + ); + + ActiveVoiceRoom activeVoiceRoom = new ActiveVoiceRoom() + .setId(10001L) + .setUserId(20001L) + .setOnlineQuantity(6L) + .setSuperVipLevel("NONE"); + + RoomProfileManager roomProfileManager = new RoomProfileManager(); + roomProfileManager.setId(10001L); + roomProfileManager.setUserId(20001L); + roomProfileManager.setDel(Boolean.FALSE); + roomProfileManager.setEvent(RoomEventEnum.AVAILABLE.name()); + roomProfileManager.setSetting(RoomSetting.createDefaultRoomSetting()); + roomProfileManager.setCounter(new RoomCounter().setMemberCount(1).setAdminCount(0)); + + UserProfileDTO userProfileDTO = new UserProfileDTO(); + userProfileDTO.setId(20001L); + + RoomVoiceProfileCO convertedProfile = new RoomVoiceProfileCO() + .setId(10001L) + .setUserId(20001L); + + when(roomProfileManagerService.mapByRoomIds(java.util.Set.of(10001L))) + .thenReturn(Map.of(10001L, roomProfileManager)); + when(userProfileGateway.mapByUserIds(java.util.Set.of(20001L))) + .thenReturn(Map.of()); + when(userProfileAppConvertor.toMapUserProfileDTO(anyMap())) + .thenReturn(Map.of(20001L, userProfileDTO)); + when(roomProfileAppConvertor.toRoomVoiceProfileCO(roomProfileManager)) + .thenReturn(convertedProfile); + when(roomProfileAppConvertor.toRoomSettingCO(roomProfileManager.getSetting())) + .thenReturn(new RoomSettingCO()); + + List roomProfiles = roomVoiceProfileCommon.toListRoomVoiceProfileCO( + List.of(activeVoiceRoom)); + + assertEquals(1, roomProfiles.size()); + assertEquals("6", getExtValue(roomProfiles.get(0), "memberQuantity")); + } + + private Object getExtValue(RoomVoiceProfileCO roomVoiceProfileCO, String key) { + Map extValues = findExtValues(roomVoiceProfileCO); + assertNotNull(extValues, "extValues should exist on RoomVoiceProfileCO"); + return extValues.get(key); + } + + private Map findExtValues(Object target) { + Class current = target.getClass(); + while (current != null) { + try { + Field field = current.getDeclaredField("extValues"); + field.setAccessible(true); + Object value = field.get(target); + if (value instanceof Map extValues) { + return extValues; + } + return null; + } catch (NoSuchFieldException ignored) { + current = current.getSuperclass(); + } catch (IllegalAccessException e) { + throw new IllegalStateException("read extValues failed", e); + } + } + return null; + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/sys/CountryCodeCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/sys/CountryCodeCO.java index 4bd5c01..990a2ee 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/sys/CountryCodeCO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/sys/CountryCodeCO.java @@ -1,10 +1,11 @@ package com.red.circle.other.app.dto.clientobject.sys; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import com.red.circle.framework.dto.ClientObject; -import lombok.Data; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.red.circle.framework.dto.ClientObject; +import java.util.List; +import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @@ -40,10 +41,15 @@ public class CountryCodeCO extends ClientObject { */ private String countryName; - /** - * 别名. - */ - private String aliasName; + /** + * 别名. + */ + private String aliasName; + + /** + * 国家码别名集合. + */ + private List countryCodeAliases; /** * 国旗. diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/common/sys/CountryCodeAliasSupport.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/common/sys/CountryCodeAliasSupport.java new file mode 100644 index 0000000..43f1261 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/common/sys/CountryCodeAliasSupport.java @@ -0,0 +1,82 @@ +package com.red.circle.other.infra.common.sys; + +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; +import com.red.circle.other.infra.database.rds.service.sys.SysCountryCodeService; +import com.red.circle.tool.core.text.StringUtils; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 国家码别名支持. + */ +@Component +@RequiredArgsConstructor +public class CountryCodeAliasSupport { + + private final SysCountryCodeService sysCountryCodeService; + + /** + * 获取某个国家码的全部可匹配代码. + */ + public Set getMatchCodes(String code) { + String normalized = CountryCodeAliasUtils.normalize(code); + if (StringUtils.isBlank(normalized)) { + return Collections.emptySet(); + } + + SysCountryCode sysCountryCode = sysCountryCodeService.getByCode(normalized); + if (sysCountryCode == null) { + return CountryCodeAliasUtils.legacyAliases(normalized); + } + + return CountryCodeAliasUtils.buildMatchCodes(sysCountryCode.getAlphaTwo(), + sysCountryCode.getAlphaThree(), + CountryCodeAliasUtils.parseAliasCodes(sysCountryCode.getAliasCodes())); + } + + /** + * 扩展多组国家码. + */ + public Set expandCodes(Collection codes) { + if (codes == null || codes.isEmpty()) { + return Collections.emptySet(); + } + + LinkedHashSet result = new LinkedHashSet<>(); + for (String code : codes) { + result.addAll(getMatchCodes(code)); + } + return result; + } + + /** + * 两个国家码是否匹配. + */ + public boolean matches(String leftCode, String rightCode) { + Set leftCodes = getMatchCodes(leftCode); + Set rightCodes = getMatchCodes(rightCode); + if (leftCodes.isEmpty() || rightCodes.isEmpty()) { + return false; + } + return leftCodes.stream().anyMatch(rightCodes::contains); + } + + /** + * 逗号分隔国家列表是否包含指定国家码. + */ + public boolean containsCode(String csvCodes, String countryCode) { + if (StringUtils.isBlank(csvCodes) || StringUtils.isBlank(countryCode)) { + return false; + } + return Arrays.stream(csvCodes.split(",")) + .map(CountryCodeAliasUtils::normalize) + .filter(StringUtils::isNotBlank) + .anyMatch(code -> matches(code, countryCode)); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/sys/CountryCodeInnerConvertor.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/sys/CountryCodeInnerConvertor.java index 5bb7ad9..26580fd 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/sys/CountryCodeInnerConvertor.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/convertor/sys/CountryCodeInnerConvertor.java @@ -1,26 +1,43 @@ -package com.red.circle.other.infra.convertor.sys; - -import com.red.circle.framework.core.convertor.ConvertorModel; -import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; -import com.red.circle.other.inner.model.cmd.sys.SysCountryCodeCmd; -import com.red.circle.other.inner.model.dto.sys.SysCountryCodeDTO; -import java.util.List; -import java.util.Map; -import org.mapstruct.Mapper; +package com.red.circle.other.infra.convertor.sys; + +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.framework.core.convertor.ConvertorModel; +import com.red.circle.other.infra.database.rds.entity.sys.SysCountryCode; +import com.red.circle.other.inner.model.cmd.sys.SysCountryCodeCmd; +import com.red.circle.other.inner.model.dto.sys.SysCountryCodeDTO; +import java.util.List; +import java.util.Map; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; /** * @author pengliang on 2023/5/24 */ @Mapper(componentModel = ConvertorModel.SPRING) -public interface CountryCodeInnerConvertor { - - SysCountryCodeDTO toSysCountryCodeDTO(SysCountryCode sysCountryCode); - - List toListSysCountryCodeDTO(List sysCountryCodes); - - Map toMapLongSysCountryCodeDTO( - Map sysCountryCodeMap); - - SysCountryCode toSysCountryCode(SysCountryCodeCmd cmd); - -} +public interface CountryCodeInnerConvertor { + + @Mapping(target = "countryCodeAliases", source = ".") + SysCountryCodeDTO toSysCountryCodeDTO(SysCountryCode sysCountryCode); + + List toListSysCountryCodeDTO(List sysCountryCodes); + + Map toMapLongSysCountryCodeDTO( + Map sysCountryCodeMap); + + @Mapping(target = "aliasCodes", source = "countryCodeAliases") + SysCountryCode toSysCountryCode(SysCountryCodeCmd cmd); + + default List mapCountryCodeAliases(SysCountryCode sysCountryCode) { + if (sysCountryCode == null) { + return List.of(); + } + return CountryCodeAliasUtils.normalizeCodes( + CountryCodeAliasUtils.buildMatchCodes(sysCountryCode.getAlphaTwo(), sysCountryCode.getAlphaThree(), + CountryCodeAliasUtils.parseAliasCodes(sysCountryCode.getAliasCodes()))); + } + + default String mapCountryCodeAliases(List countryCodeAliases) { + return CountryCodeAliasUtils.toAliasCodesJson(countryCodeAliases); + } + +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/ActiveVoiceRoomServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/ActiveVoiceRoomServiceImpl.java index 1df3187..880ee82 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/ActiveVoiceRoomServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/ActiveVoiceRoomServiceImpl.java @@ -1,9 +1,10 @@ -package com.red.circle.other.infra.database.mongo.service.live.impl; - -import com.alibaba.fastjson.JSON; -import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; -import com.red.circle.framework.mybatis.constant.PageConstant; -import com.red.circle.other.infra.database.mongo.dto.query.live.ActiveVoiceRoomQuery; +package com.red.circle.other.infra.database.mongo.service.live.impl; + +import com.alibaba.fastjson.JSON; +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.framework.mybatis.constant.PageConstant; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.mongo.dto.query.live.ActiveVoiceRoomQuery; import com.red.circle.other.infra.database.mongo.dto.query.live.FamilyOnlineRoomQuery; import com.red.circle.other.infra.database.mongo.entity.live.ActiveVoiceRoom; import com.red.circle.other.infra.database.mongo.service.live.ActiveVoiceRoomService; @@ -32,12 +33,13 @@ import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor -public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { - - private final MongoTemplate mongoTemplate; - - private static final Set TR_REGIONS = Set.of("TR"); - private static final Set SA_REGIONS = Set.of("BD","SA", "IN", "PK"); +public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { + + private final MongoTemplate mongoTemplate; + private final CountryCodeAliasSupport countryCodeAliasSupport; + + private static final Set TR_REGIONS = Set.of("TR"); + private static final Set SA_REGIONS = Set.of("BD","SA", "IN", "PK"); private static final Set AR_REGIONS = Set.of("AE", "AR", "BH", "DJ", "DZ", "EG", "EY", "ER", "IL", "IQ", "JO", "KM", "KW", "LB", "LY", "MA", "MR", "OM", "PS", "QA", "SD", "SO", "SS", "SY", "TD", "TN", "YE"); private static final Set OTHER_REGIONS = Set.of("OTHER", "PH", "ID", "GH", "IR", "AF", "NG", "DE", "MYS", "TM", "AZ", "NP"); private static final Set ALL_SPECIAL_REGIONS; @@ -116,9 +118,9 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { criteria.and("roomAccount").is(qryCmd.getRoomAccount()); } - if (StringUtils.isNotBlank(qryCmd.getCountryCode())) { - criteria.and("countryCode").is(qryCmd.getCountryCode()); - } + if (StringUtils.isNotBlank(qryCmd.getCountryCode())) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(qryCmd.getCountryCode())); + } Query query = Query.query(criteria); @@ -148,10 +150,10 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { if (userRegionGroup == null && !isAllRegion) { criteria.and("region").nin(excludeRegions); } - // SA_REGIONS 用户只能看到相同国家的房间 - if (SA_REGIONS.contains(region) && !isAllRegion && StringUtils.isNotBlank(countryCode)) { - criteria.and("countryCode").is(countryCode); - } + // SA_REGIONS 用户只能看到相同国家的房间 + if (SA_REGIONS.contains(region) && !isAllRegion && StringUtils.isNotBlank(countryCode)) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode)); + } Aggregation aggregation = Aggregation.newAggregation( // 1. 匹配国家(可选条件) Aggregation.match(criteria), @@ -237,9 +239,10 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { public List listSearchCountryRoom(String sysOrigin, String countryCode, Integer limit) { - Query query = Query.query( - Criteria.where("sysOrigin").is(sysOrigin).and("countryCode").is(countryCode) - ); + Query query = Query.query( + Criteria.where("sysOrigin").is(sysOrigin) + .and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode)) + ); return mongoTemplate.find(query.limit(limit), ActiveVoiceRoom.class); } @@ -258,12 +261,12 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { } @Override - public List listExcludeCountry(AppActiveVoiceRoomQryCmd query) { - Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin()); - if (StringUtils.isNotBlank(query.getCountryCode())) { - criteria.and("countryCode").nin(query.getCountryCode()); - criteria.and("region").nin("AR", "TR"); - } + public List listExcludeCountry(AppActiveVoiceRoomQryCmd query) { + Criteria criteria = Criteria.where("sysOrigin").is(query.getSysOrigin()); + if (StringUtils.isNotBlank(query.getCountryCode())) { + criteria.and("countryCode").nin(countryCodeAliasSupport.getMatchCodes(query.getCountryCode())); + criteria.and("region").nin("AR", "TR"); + } Query mQuery = Query.query(criteria); mQuery.with(Sort.by(Sort.Order.desc("onlineQuantity"))); return mongoTemplate.find(mQuery.limit(query.getLimit()), ActiveVoiceRoom.class); @@ -278,9 +281,9 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { criteria.and("id").is(query.getRoomId()); } - if (StringUtils.isNotBlank(query.getCountryCode())) { - criteria.and("countryCode").is(query.getCountryCode()); - } + if (StringUtils.isNotBlank(query.getCountryCode())) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(query.getCountryCode())); + } if (StringUtils.isNotBlank(query.getRoomTag())) { criteria.and("roomTag").is(query.getRoomTag()); @@ -318,7 +321,8 @@ public class ActiveVoiceRoomServiceImpl implements ActiveVoiceRoomService { if (Objects.nonNull(query.getRoomId())) { criteria.and("roomId").is(query.getRoomId()); } - criteria.and("countryCode").nin("SA", "AE", "EG", "MA", "US"); + criteria.and("countryCode").nin( + countryCodeAliasSupport.expandCodes(List.of("SA", "AE", "EG", "MA", "US"))); if (StringUtils.isNotBlank(query.getRoomTag())) { criteria.and("roomTag").is(query.getRoomTag()); diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/RoomProfileManagerServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/RoomProfileManagerServiceImpl.java index c9eae41..48df9fa 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/RoomProfileManagerServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/live/impl/RoomProfileManagerServiceImpl.java @@ -1,11 +1,12 @@ package com.red.circle.other.infra.database.mongo.service.live.impl; -import com.alibaba.fastjson.JSON; -import com.google.common.collect.Lists; -import com.mongodb.BasicDBObject; -import com.red.circle.common.business.core.ReplaceString; -import com.red.circle.common.business.core.SensitiveWordFilter; +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Lists; +import com.mongodb.BasicDBObject; +import com.red.circle.common.business.core.ReplaceString; +import com.red.circle.common.business.core.SensitiveWordFilter; import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; import com.red.circle.other.infra.database.cache.service.other.RoomManagerCacheService; import com.red.circle.other.infra.database.mongo.entity.live.RoomCounter; import com.red.circle.other.infra.database.mongo.entity.live.RoomProfile; @@ -57,6 +58,7 @@ public class RoomProfileManagerServiceImpl implements RoomProfileManagerService private final RedisTemplate redisTemplate; private final ActiveVoiceRoomService activeVoiceRoomService; private final RoomManagerCacheService roomManagerCacheService; + private final CountryCodeAliasSupport countryCodeAliasSupport; /** * Redis缓存Key前缀 @@ -262,11 +264,11 @@ public class RoomProfileManagerServiceImpl implements RoomProfileManagerService public List listSelectiveLimit100(String sysOrigin, String countryCode, List excludeIds) { - Criteria criteria = Criteria.where("sysOrigin").in(sysOrigin) - .and("del").is(Boolean.FALSE); - if (StringUtils.isNotBlank(countryCode)) { - criteria.and("countryCode").is(countryCode); - } + Criteria criteria = Criteria.where("sysOrigin").in(sysOrigin) + .and("del").is(Boolean.FALSE); + if (StringUtils.isNotBlank(countryCode)) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode)); + } if (CollectionUtils.isNotEmpty(excludeIds)) { criteria.and("id").nin(excludeIds); @@ -282,10 +284,10 @@ public class RoomProfileManagerServiceImpl implements RoomProfileManagerService public List listSelectiveLimit100(String sysOrigin, List countryCodes, List excludeIds) { - Criteria criteria = Criteria.where("sysOrigin").in(sysOrigin); - if (CollectionUtils.isNotEmpty(countryCodes)) { - criteria.and("countryCode").nin(countryCodes); - } + Criteria criteria = Criteria.where("sysOrigin").in(sysOrigin); + if (CollectionUtils.isNotEmpty(countryCodes)) { + criteria.and("countryCode").nin(countryCodeAliasSupport.expandCodes(countryCodes)); + } if (CollectionUtils.isNotEmpty(excludeIds)) { criteria.and("id").nin(excludeIds); diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamPolicyManagerServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamPolicyManagerServiceImpl.java index c6aec31..289d6b3 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamPolicyManagerServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamPolicyManagerServiceImpl.java @@ -1,7 +1,8 @@ -package com.red.circle.other.infra.database.mongo.service.team.team.impl; - -import com.google.common.collect.Maps; -import com.red.circle.other.infra.database.mongo.dto.team.TeamPolicy; +package com.red.circle.other.infra.database.mongo.service.team.team.impl; + +import com.google.common.collect.Maps; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.mongo.dto.team.TeamPolicy; import com.red.circle.other.infra.database.mongo.entity.team.team.TeamPolicyManager; import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService; import com.red.circle.other.inner.enums.team.TeamPolicyTypeEnum; @@ -25,9 +26,10 @@ import java.util.stream.Collectors; */ @Service @RequiredArgsConstructor -public class TeamPolicyManagerServiceImpl implements TeamPolicyManagerService { - - private final MongoTemplate mongoTemplate; +public class TeamPolicyManagerServiceImpl implements TeamPolicyManagerService { + + private final MongoTemplate mongoTemplate; + private final CountryCodeAliasSupport countryCodeAliasSupport; @Override public List listSysOriginRegion(String sysOrigin, String region) { @@ -57,9 +59,9 @@ public class TeamPolicyManagerServiceImpl implements TeamPolicyManagerService { }else { criteria.and("policyType").ne(TeamPolicyTypeEnum.SALARY_DIAMOND.name()); } - if (StringUtils.isNotBlank(countryCode)) { - criteria.and("countryCode").is(countryCode); - } + if (StringUtils.isNotBlank(countryCode)) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode)); + } return mongoTemplate.find( Query.query(criteria).with(Sort.by(Sort.Order.desc("createTime"))), TeamPolicyManager.class); } @@ -96,9 +98,9 @@ public class TeamPolicyManagerServiceImpl implements TeamPolicyManagerService { }else { criteria.and("policyType").ne(TeamPolicyTypeEnum.SALARY_DIAMOND.name()); } - if (StringUtils.isNotBlank(countryCode)) { - criteria.and("countryCode").is(countryCode); - } + if (StringUtils.isNotBlank(countryCode)) { + criteria.and("countryCode").in(countryCodeAliasSupport.getMatchCodes(countryCode)); + } return mongoTemplate.findOne(Query.query(criteria), TeamPolicyManager.class); } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/sys/SysCountryCode.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/sys/SysCountryCode.java index f302742..06641b6 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/sys/SysCountryCode.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/sys/SysCountryCode.java @@ -1,9 +1,9 @@ -package com.red.circle.other.infra.database.rds.entity.sys; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; +package com.red.circle.other.infra.database.rds.entity.sys; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; import com.red.circle.framework.mybatis.entity.TimestampBaseEntity; import java.io.Serial; @@ -61,8 +61,14 @@ public class SysCountryCode extends TimestampBaseEntity { /** * 别名. */ - @TableField("alias_name") - private String aliasName; + @TableField("alias_name") + private String aliasName; + + /** + * 国家码别名集合(JSON数组). + */ + @TableField("alias_codes") + private String aliasCodes; /** * 代码分配情况. diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/BannerConfigServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/BannerConfigServiceImpl.java index 7e513f7..f66807e 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/BannerConfigServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/BannerConfigServiceImpl.java @@ -1,20 +1,24 @@ -package com.red.circle.other.infra.database.rds.service.sys.impl; - -import com.red.circle.framework.dto.PageResult; -import com.red.circle.framework.mybatis.constant.PageConstant; -import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; -import com.red.circle.other.infra.database.rds.dao.sys.BannerConfigDAO; -import com.red.circle.other.infra.database.rds.entity.sys.BannerConfig; -import com.red.circle.other.infra.database.rds.service.sys.BannerConfigService; +package com.red.circle.other.infra.database.rds.service.sys.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.red.circle.framework.dto.PageResult; +import com.red.circle.framework.mybatis.constant.PageConstant; +import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; +import com.red.circle.other.infra.database.rds.dao.sys.BannerConfigDAO; +import com.red.circle.other.infra.database.rds.entity.sys.BannerConfig; +import com.red.circle.other.infra.database.rds.service.sys.BannerConfigService; import com.red.circle.other.inner.model.cmd.sys.SysBannerConfigQryCmd; -import com.red.circle.tool.core.collection.CollectionUtils; -import com.red.circle.tool.core.date.TimestampUtils; -import com.red.circle.tool.core.text.StringUtils; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import org.springframework.stereotype.Service; +import com.red.circle.tool.core.collection.CollectionUtils; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.text.StringUtils; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; /** *

@@ -24,9 +28,12 @@ import org.springframework.stereotype.Service; * @author pengliang * @since 2021-01-26 */ -@Service -public class BannerConfigServiceImpl extends - BaseServiceImpl implements BannerConfigService { +@Service +@RequiredArgsConstructor +public class BannerConfigServiceImpl extends + BaseServiceImpl implements BannerConfigService { + + private final CountryCodeAliasSupport countryCodeAliasSupport; @Override public List listEffective(String sysOrigin, String platform, String countryCode, List types) { @@ -37,7 +44,7 @@ public class BannerConfigServiceImpl extends wrapper.in(BannerConfig::getPlatform, "", platform) ) .and(StringUtils.isNotBlank(countryCode), wrapper -> - wrapper.apply("(country_code = '' OR FIND_IN_SET({0}, country_code))", countryCode) + applyCountryCodeFilter(wrapper, countryCode) ) .gt(BannerConfig::getExpiredTime, LocalDateTime.now()) .eq(BannerConfig::getShowcase, Boolean.TRUE) @@ -52,9 +59,9 @@ public class BannerConfigServiceImpl extends .and(StringUtils.isNotBlank(query.getPlatform()), wrapper -> wrapper.in(BannerConfig::getPlatform, "", query.getPlatform()) ) - .and(StringUtils.isNotBlank(query.getCountryCode()), wrapper -> - wrapper.apply("(country_code = '' OR FIND_IN_SET({0}, country_code))", query.getCountryCode()) - ) + .and(StringUtils.isNotBlank(query.getCountryCode()), wrapper -> + applyCountryCodeFilter(wrapper, query.getCountryCode()) + ) .eq(Objects.equals(query.getShowcase(), 1), BannerConfig::getShowcase, Boolean.TRUE) .gt(Objects.equals(query.getShowcase(), 1), BannerConfig::getExpiredTime, TimestampUtils.now()) .eq(Objects.equals(query.getShowcase(), 2), BannerConfig::getShowcase, Boolean.FALSE) @@ -66,7 +73,7 @@ public class BannerConfigServiceImpl extends } @Override - public Boolean updateInfo(BannerConfig bannerConfig) { + public Boolean updateInfo(BannerConfig bannerConfig) { return update() .set(BannerConfig::getCover, bannerConfig.getCover()) .set(BannerConfig::getSmallCover, bannerConfig.getSmallCover()) @@ -82,9 +89,22 @@ public class BannerConfigServiceImpl extends .set(BannerConfig::getStartTime, bannerConfig.getStartTime()) .set(BannerConfig::getSort, bannerConfig.getSort()) .set(BannerConfig::getRegions, bannerConfig.getRegions()) - .eq(BannerConfig::getId, bannerConfig.getId()) - .last(PageConstant.LIMIT_ONE) - .execute(); - } - -} + .eq(BannerConfig::getId, bannerConfig.getId()) + .last(PageConstant.LIMIT_ONE) + .execute(); + } + + private void applyCountryCodeFilter(LambdaQueryWrapper wrapper, String countryCode) { + Set countryCodes = countryCodeAliasSupport.getMatchCodes(countryCode); + List aliasList = new ArrayList<>(countryCodes); + StringBuilder sql = new StringBuilder("(country_code = ''"); + Object[] params = new Object[aliasList.size()]; + for (int i = 0; i < aliasList.size(); i++) { + sql.append(" OR FIND_IN_SET({").append(i).append("}, country_code)"); + params[i] = aliasList.get(i); + } + sql.append(")"); + wrapper.apply(sql.toString(), params); + } + +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/SysCountryCodeServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/SysCountryCodeServiceImpl.java index eaf974b..c2acc7f 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/SysCountryCodeServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/sys/impl/SysCountryCodeServiceImpl.java @@ -1,7 +1,8 @@ -package com.red.circle.other.infra.database.rds.service.sys.impl; - -import com.red.circle.framework.core.asserts.ResponseAssert; -import com.red.circle.framework.core.response.CommonErrorCode; +package com.red.circle.other.infra.database.rds.service.sys.impl; + +import com.red.circle.common.business.core.util.CountryCodeAliasUtils; +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.dto.PageResult; import com.red.circle.framework.mybatis.constant.PageConstant; import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl; @@ -35,20 +36,39 @@ public class SysCountryCodeServiceImpl extends BaseServiceImpl implements SysCountryCodeService { @Override - public SysCountryCode getByCode(String code) { - if (StringUtils.isBlank(code)) { - return null; - } - - int len = code.length(); - boolean isTwo = Objects.equals(len, NumConstant.TWO); - String upperCaseCode = code.toUpperCase(); - return query() - .eq(isTwo, SysCountryCode::getAlphaTwo, upperCaseCode) - .eq(!isTwo, SysCountryCode::getAlphaThree, upperCaseCode) - .last(PageConstant.LIMIT_ONE) - .getOne(); - } + public SysCountryCode getByCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + + String normalizedCode = CountryCodeAliasUtils.normalize(code); + SysCountryCode countryCode = getByExactCode(normalizedCode); + if (Objects.nonNull(countryCode)) { + return countryCode; + } + + return query().list().stream() + .filter(item -> CountryCodeAliasUtils + .buildMatchCodes(item.getAlphaTwo(), item.getAlphaThree(), + CountryCodeAliasUtils.parseAliasCodes(item.getAliasCodes())) + .contains(normalizedCode)) + .findFirst() + .orElse(null); + } + + private SysCountryCode getByExactCode(String code) { + if (StringUtils.isBlank(code)) { + return null; + } + + int len = code.length(); + boolean isTwo = Objects.equals(len, NumConstant.TWO); + return query() + .eq(isTwo, SysCountryCode::getAlphaTwo, code) + .eq(!isTwo, SysCountryCode::getAlphaThree, code) + .last(PageConstant.LIMIT_ONE) + .getOne(); + } @Override public List listOpenCountry() { diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/user/UserRegionGatewayImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/user/UserRegionGatewayImpl.java index 04478e5..c47ee15 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/user/UserRegionGatewayImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/user/UserRegionGatewayImpl.java @@ -1,12 +1,13 @@ -package com.red.circle.other.infra.gateway.user; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.red.circle.other.domain.gateway.user.UserProfileGateway; -import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; -import com.red.circle.other.domain.model.user.UserProfile; -import com.red.circle.other.domain.model.user.ability.RegionConfig; +package com.red.circle.other.infra.gateway.user; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.red.circle.other.domain.gateway.user.UserProfileGateway; +import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; +import com.red.circle.other.domain.model.user.UserProfile; +import com.red.circle.other.domain.model.user.ability.RegionConfig; +import com.red.circle.other.infra.common.sys.CountryCodeAliasSupport; import com.red.circle.other.infra.convertor.user.UserRegionInfraConvertor; import com.red.circle.other.infra.database.cache.service.user.SysRegionCacheService; import com.red.circle.other.infra.database.cache.service.user.UserRegionCacheService; @@ -51,9 +52,10 @@ public class UserRegionGatewayImpl implements UserRegionGateway { private final UserProfileGateway userProfileGateway; private final SysRegionCacheService sysRegionCacheService; private final SysRegionConfigService sysRegionConfigService; - private final UserRegionInfraConvertor userRegionInfraConvertor; - private final SysRegionAssistConfigService sysRegionAssistConfigService; - private final UserRegionCacheService userRegionCacheService; + private final UserRegionInfraConvertor userRegionInfraConvertor; + private final SysRegionAssistConfigService sysRegionAssistConfigService; + private final UserRegionCacheService userRegionCacheService; + private final CountryCodeAliasSupport countryCodeAliasSupport; /** * key=用户id, value=区域code 获取一组区域code. @@ -182,15 +184,15 @@ public class UserRegionGatewayImpl implements UserRegionGateway { continue; } - // 2.1 国家匹配 - if (StringUtils.isNotBlank(userProfile.getCountryCode())) { - SysRegionConfig regionConfig = configs.stream() - .filter(region -> StringUtils.containsIgnoreCase(region.getCountryCodes(), - userProfile.getCountryCode()) - ).findFirst().orElse(null); - UserRegionDTO userRegion = createUserRegionDTO(userProfile, regionConfig); - if (Objects.nonNull(userRegion)) { - userRegions.add(userRegion); + // 2.1 国家匹配 + if (StringUtils.isNotBlank(userProfile.getCountryCode())) { + SysRegionConfig regionConfig = configs.stream() + .filter(region -> countryCodeAliasSupport.containsCode(region.getCountryCodes(), + userProfile.getCountryCode())) + .findFirst().orElse(null); + UserRegionDTO userRegion = createUserRegionDTO(userProfile, regionConfig); + if (Objects.nonNull(userRegion)) { + userRegions.add(userRegion); continue; } } @@ -904,12 +906,12 @@ public class UserRegionGatewayImpl implements UserRegionGateway { if (StringUtils.isNotBlank(userProfile.getCountryCode())) { - SysRegionConfig sysRegionConfig = configs.stream() - .filter(region -> StringUtils.isNotBlank(region.getCountryCodes()) - && StringUtils.isNotBlank(userProfile.getCountryCode()) - && region.getCountryCodes().toLowerCase() - .contains(userProfile.getCountryCode().toLowerCase())).findFirst() - .orElse(null); + SysRegionConfig sysRegionConfig = configs.stream() + .filter(region -> StringUtils.isNotBlank(region.getCountryCodes()) + && StringUtils.isNotBlank(userProfile.getCountryCode()) + && countryCodeAliasSupport.containsCode(region.getCountryCodes(), + userProfile.getCountryCode())).findFirst() + .orElse(null); if (Objects.nonNull(sysRegionConfig)) { resultMap.put(userProfile.getId(), sysRegionConfig); diff --git a/sql/20260420_mifapay_order_mysql.sql b/sql/20260420_mifapay_order_mysql.sql new file mode 100644 index 0000000..facf4d1 --- /dev/null +++ b/sql/20260420_mifapay_order_mysql.sql @@ -0,0 +1,64 @@ +SET @table_schema = DATABASE(); + +SET @ddl = IF( + EXISTS( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @table_schema + AND TABLE_NAME = 'order_user_purchase_pay' + AND COLUMN_NAME = 'receipt_type' + ), + 'SELECT 1', + "ALTER TABLE `order_user_purchase_pay` ADD COLUMN `receipt_type` varchar(32) NOT NULL DEFAULT 'PAYMENT' COMMENT '单据类型 PAYMENT/RECEIPT' AFTER `reference_id`" +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @table_schema + AND TABLE_NAME = 'order_user_purchase_pay' + AND COLUMN_NAME = 'product_descriptor' + ), + 'SELECT 1', + "ALTER TABLE `order_user_purchase_pay` ADD COLUMN `product_descriptor` varchar(255) NOT NULL DEFAULT '' COMMENT '商品描述' AFTER `product_content`" +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS( + SELECT 1 + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @table_schema + AND TABLE_NAME = 'order_user_purchase_pay' + AND COLUMN_NAME = 'reason' + ), + 'SELECT 1', + "ALTER TABLE `order_user_purchase_pay` ADD COLUMN `reason` varchar(255) NOT NULL DEFAULT '' COMMENT '失败或挂起原因' AFTER `pay_status`" +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +UPDATE `order_user_purchase_pay` +SET `receipt_type` = 'PAYMENT' +WHERE `receipt_type` = ''; + +CREATE TABLE IF NOT EXISTS `order_user_purchase_pay_notice` ( + `id` bigint NOT NULL COMMENT '主键ID', + `purchase_pay_id` bigint NOT NULL COMMENT '支付预订单ID', + `event_id` varchar(128) NOT NULL DEFAULT '' COMMENT '事件标识', + `notice_type` varchar(64) NOT NULL DEFAULT '' COMMENT '通知类型', + `notice_data` longtext COMMENT '通知内容(JSON)', + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_user` bigint DEFAULT NULL COMMENT '创建人', + `update_user` bigint DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + KEY `idx_purchase_pay_notice_order` (`purchase_pay_id`, `id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Web支付通知明细'; diff --git a/sql/20260420_sys_country_code_alias_codes.sql b/sql/20260420_sys_country_code_alias_codes.sql new file mode 100644 index 0000000..d014d0d --- /dev/null +++ b/sql/20260420_sys_country_code_alias_codes.sql @@ -0,0 +1,6 @@ +ALTER TABLE `sys_country_code` + ADD COLUMN `alias_codes` varchar(255) NOT NULL DEFAULT '' COMMENT '国家code别名集合(JSON数组)' AFTER `alias_name`; + +UPDATE `sys_country_code` +SET `alias_codes` = '["SA","KSA"]' +WHERE `alpha_two` = 'SA' OR `alpha_three` = 'KSA';