From 835b6d5367225bbd6120e88a5d0dd9d17080a5a8 Mon Sep 17 00:00:00 2001 From: hy001 Date: Fri, 8 May 2026 20:53:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=AF=8F=E6=97=A5=E4=BB=BB=E5=8A=A1=E9=92=B1?= =?UTF-8?q?=E5=8C=85=E5=85=85=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/gift/BlessingsGiftGiveCmdExe.java | 59 ++++--- .../command/gift/GiftGiveAwayBatchCmdExe.java | 59 ++++--- .../app/command/gift/LuckyGiftGiveCmdExe.java | 54 +++++-- .../app/common/task/TaskCenterGoClient.java | 49 ++++++ .../freight/SendFreightShipCmdExe.java | 13 +- .../app/common/task/TaskCenterGoClient.java | 145 ++++++++++++++++++ .../rds/service/WalletGoldBalanceService.java | 24 +-- .../impl/WalletGoldBalanceServiceImpl.java | 57 +++---- .../infra/tool/WalletAssetSyncTool.java | 84 ++++++---- .../infra/tool/WalletAssetSyncToolTest.java | 70 +++++++++ 10 files changed, 495 insertions(+), 119 deletions(-) create mode 100644 rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/common/task/TaskCenterGoClient.java create mode 100644 rc-service/rc-service-wallet/wallet-start/src/test/java/com/red/circle/wallet/infra/tool/WalletAssetSyncToolTest.java diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/BlessingsGiftGiveCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/BlessingsGiftGiveCmdExe.java index b1143f40..4e17f044 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/BlessingsGiftGiveCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/BlessingsGiftGiveCmdExe.java @@ -7,11 +7,12 @@ import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.mq.business.model.event.gift.GiveAwayGiftBatchEvent; import com.red.circle.mq.business.model.event.gift.GiveAwayGiftRoomAccepts; import com.red.circle.mq.business.model.event.gift.GiveGiftConfig; -import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; -import com.red.circle.mq.rocket.business.producer.GiftMqMessage; -import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; -import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; +import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; +import com.red.circle.mq.rocket.business.producer.GiftMqMessage; +import com.red.circle.mq.rocket.business.producer.TaskMqMessage; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; +import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService; import com.red.circle.other.inner.enums.material.GiftCurrencyType; import com.red.circle.other.inner.enums.material.GiftTabEnum; @@ -45,10 +46,11 @@ public class BlessingsGiftGiveCmdExe { private final GiftCacheService giftCacheService; private final GiftMqMessage giftMqMessage; - private final TaskMqMessage taskMqMessage; - private final WalletGoldClient walletGoldClient; - private final CpRelationshipService cpRelationshipService; - public BigDecimal execute(GiveAwayGiftBatchCmd cmd) { + private final TaskMqMessage taskMqMessage; + private final WalletGoldClient walletGoldClient; + private final CpRelationshipService cpRelationshipService; + private final TaskCenterGoClient taskCenterGoClient; + public BigDecimal execute(GiveAwayGiftBatchCmd cmd) { // 过滤赠送人非法值 ResponseAssert.notEmpty(BaseErrorEnum.REQUEST_PARAMETER_ERROR, cmd.getAccepts()); @@ -80,8 +82,9 @@ public class BlessingsGiftGiveCmdExe { GiveAwayGiftBatchEvent giftBatchEvent = toGiveAwayGiftBatchEvent(cmd, giftConfig, receipt.getAssetRecordId()); // log.warn("cptesttest execute:{}", JacksonUtils.toJson(giftBatchEvent)); - giftBatchEvent.setCheckBlessingsGift(Boolean.TRUE); - giftMqMessage.sendGift(receipt.getAssetRecordId().toString(), giftBatchEvent); + giftBatchEvent.setCheckBlessingsGift(Boolean.TRUE); + giftMqMessage.sendGift(receipt.getAssetRecordId().toString(), giftBatchEvent); + reportTaskCenterGiftEvent(cmd, giftConfig, receipt); @@ -91,8 +94,8 @@ public class BlessingsGiftGiveCmdExe { .day(LocalDateTimeUtils.nowFormat("yyyy-MM-dd")) .build());*/ - return receipt.getBalance().getDollarAmount(); - } + return receipt.getBalance().getDollarAmount(); + } /** * 付钱. @@ -113,11 +116,31 @@ public class BlessingsGiftGiveCmdExe { // 1006 类型不在范围 ResponseAssert.failure(CommonErrorEnum.TYPE_IS_NOT_IN_SCOPE); - throw new IllegalArgumentException("param error."); - - } - - private GiveAwayGiftBatchEvent toGiveAwayGiftBatchEvent(GiveAwayGiftBatchCmd cmd, + throw new IllegalArgumentException("param error."); + + } + + private void reportTaskCenterGiftEvent(GiveAwayGiftBatchCmd cmd, GiftConfigDTO giftConfig, + WalletReceiptResDTO receipt) { + if (Objects.isNull(receipt)) { + return; + } + taskCenterGoClient.reportGiftConsumeGold(cmd.requireReqSysOrigin(), + cmd.requiredReqUserId(), + receipt.getAssetRecordId(), + giftConfig.getId(), + giftConfig.getGiftName(), + giftConfig.getGiftTab(), + giftConfig.getGiftCandy(), + cmd.getQuantity(), + cmd.filterAcceptUserId().size(), + receipt.getOpAmount().getDollarAmount(), + cmd.getRoomId(), + cmd.getDynamicContentId(), + "BLESSINGS_GIFT"); + } + + private GiveAwayGiftBatchEvent toGiveAwayGiftBatchEvent(GiveAwayGiftBatchCmd cmd, GiftConfigDTO giftConfig, Long trackId) { GiveAwayGiftBatchEvent giveAwayGiftBatchEvent = new GiveAwayGiftBatchEvent(); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/GiftGiveAwayBatchCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/GiftGiveAwayBatchCmdExe.java index 0b909810..6f7ddeb6 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/GiftGiveAwayBatchCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/GiftGiveAwayBatchCmdExe.java @@ -5,12 +5,13 @@ import com.red.circle.common.business.enums.DiamondOrigin; import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.core.response.ResponseErrorCode; -import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; -import com.red.circle.mq.rocket.business.producer.GiftMqMessage; -import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.other.app.convertor.material.GiftAppConvertor; -import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; -import com.red.circle.other.app.service.task.TaskService; +import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; +import com.red.circle.mq.rocket.business.producer.GiftMqMessage; +import com.red.circle.mq.rocket.business.producer.TaskMqMessage; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.material.GiftAppConvertor; +import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; +import com.red.circle.other.app.service.task.TaskService; import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; @@ -52,9 +53,10 @@ public class GiftGiveAwayBatchCmdExe { private final GiftCacheService giftCacheService; private final TaskMqMessage taskMqMessage; private final UserProfileGateway userProfileGateway; - private final WalletDiamondClient walletDiamondClient; - private final TaskService taskService; - private final UserRegionGateway userRegionGateway; + private final WalletDiamondClient walletDiamondClient; + private final TaskService taskService; + private final UserRegionGateway userRegionGateway; + private final TaskCenterGoClient taskCenterGoClient; public BigDecimal execute(GiveAwayGiftBatchCmd cmd) { @@ -83,10 +85,11 @@ public class GiftGiveAwayBatchCmdExe { // 付钱 WalletReceiptDTO receipt = consumeCandy(cmd, giftConfig); log.warn("GiftGiveAwayBatchCmdExe{}", receipt); - - giftMqMessage.sendGift(receipt.getConsumeId().toString(), - giftAppConvertor.toGiveAwayGiftBatchEvent(cmd, giftConfig, receipt.getConsumeId())); - userProfileGateway.removeCache(cmd.requiredReqUserId()); + + giftMqMessage.sendGift(receipt.getConsumeId().toString(), + giftAppConvertor.toGiveAwayGiftBatchEvent(cmd, giftConfig, receipt.getConsumeId())); + reportTaskCenterGiftEvent(cmd, giftConfig, receipt); + userProfileGateway.removeCache(cmd.requiredReqUserId()); // 检查是否未完成任务 Boolean taskStatus = taskService.checkTaskStatus(cmd.getReqUserId(), 2L, LocalDateTimeUtils.nowFormat("yyyy-MM-dd")); @@ -159,11 +162,31 @@ public class GiftGiveAwayBatchCmdExe { } // 类型不在范围 - ResponseAssert.failure(CommonErrorCode.TYPE_IS_NOT_IN_SCOPE); - throw new IllegalArgumentException("param error."); - } - - private boolean isGiftTypeEqDiamond(GiftConfigDTO giftConfigInfo) { + ResponseAssert.failure(CommonErrorCode.TYPE_IS_NOT_IN_SCOPE); + throw new IllegalArgumentException("param error."); + } + + private void reportTaskCenterGiftEvent(GiveAwayGiftBatchCmd cmd, GiftConfigDTO giftConfig, + WalletReceiptDTO receipt) { + if (!isGiftTypeEqGold(giftConfig) || Objects.isNull(receipt)) { + return; + } + taskCenterGoClient.reportGiftConsumeGold(cmd.requireReqSysOrigin(), + cmd.requiredReqUserId(), + receipt.getConsumeId(), + giftConfig.getId(), + giftConfig.getGiftName(), + giftConfig.getGiftTab(), + giftConfig.getGiftCandy(), + cmd.getQuantity(), + cmd.filterAcceptUserId().size(), + receipt.getProcessAmount(), + cmd.getRoomId(), + cmd.getDynamicContentId(), + "NORMAL_GIFT"); + } + + private boolean isGiftTypeEqDiamond(GiftConfigDTO giftConfigInfo) { return Objects.equals(giftConfigInfo.getType(), GiftCurrencyType.DIAMOND.name()); } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/LuckyGiftGiveCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/LuckyGiftGiveCmdExe.java index ba703239..f9818b0e 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/LuckyGiftGiveCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/gift/LuckyGiftGiveCmdExe.java @@ -4,12 +4,13 @@ import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.response.CommonErrorCode; import com.red.circle.framework.core.response.ResponseErrorCode; import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; -import com.red.circle.mq.rocket.business.producer.GiftMqMessage; -import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.other.app.common.gift.GameLuckyGiftCommon; -import com.red.circle.other.app.convertor.material.GiftAppConvertor; -import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; -import com.red.circle.other.app.service.task.TaskService; +import com.red.circle.mq.rocket.business.producer.GiftMqMessage; +import com.red.circle.mq.rocket.business.producer.TaskMqMessage; +import com.red.circle.other.app.common.gift.GameLuckyGiftCommon; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.material.GiftAppConvertor; +import com.red.circle.other.app.dto.cmd.gift.GiveAwayGiftBatchCmd; +import com.red.circle.other.app.service.task.TaskService; import com.red.circle.other.domain.gateway.user.UserProfileGateway; import com.red.circle.other.infra.database.cache.service.other.GiftCacheService; import com.red.circle.other.inner.asserts.GiftErrorCode; @@ -46,9 +47,10 @@ public class LuckyGiftGiveCmdExe { private final WalletGoldClient walletGoldClient; private final GiftCacheService giftCacheService; private final UserProfileGateway userProfileGateway; - private final GameLuckyGiftCommon gameLuckyGiftCommon; - private final TaskMqMessage taskMqMessage; - private final TaskService taskService; + private final GameLuckyGiftCommon gameLuckyGiftCommon; + private final TaskMqMessage taskMqMessage; + private final TaskService taskService; + private final TaskCenterGoClient taskCenterGoClient; public BigDecimal execute(GiveAwayGiftBatchCmd cmd) { @@ -98,6 +100,7 @@ public class LuckyGiftGiveCmdExe { giftAppConvertor.toGiveAwayGiftBatchEvent(cmd, giftConfig, trackId) ); } + reportTaskCenterGiftEvent(cmd, giftConfig, receipt); // 抽奖mq if (luckyGift) { @@ -149,8 +152,31 @@ public class LuckyGiftGiveCmdExe { // 类型不在范围 ResponseAssert.failure(CommonErrorCode.TYPE_IS_NOT_IN_SCOPE); - throw new IllegalArgumentException("param error."); - - } - -} + throw new IllegalArgumentException("param error."); + + } + + private void reportTaskCenterGiftEvent(GiveAwayGiftBatchCmd cmd, GiftConfigDTO giftConfig, + WalletReceiptResDTO receipt) { + if (Objects.isNull(receipt)) { + return; + } + String source = Objects.equals(giftConfig.getGiftTab(), GiftTabEnum.MAGIC.name()) + ? "MAGIC_GIFT" + : "LUCKY_GIFT"; + taskCenterGoClient.reportGiftConsumeGold(cmd.requireReqSysOrigin(), + cmd.requiredReqUserId(), + receipt.getAssetRecordId(), + giftConfig.getId(), + giftConfig.getGiftName(), + giftConfig.getGiftTab(), + giftConfig.getGiftCandy(), + cmd.getQuantity(), + cmd.filterAcceptUserId().size(), + receipt.getOpAmount().getDollarAmount(), + cmd.getRoomId(), + cmd.getDynamicContentId(), + source); + } + +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/task/TaskCenterGoClient.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/task/TaskCenterGoClient.java index b57cd097..7d5f66f2 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/task/TaskCenterGoClient.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/task/TaskCenterGoClient.java @@ -3,7 +3,9 @@ package com.red.circle.other.app.common.task; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; +import java.math.BigDecimal; import java.time.Instant; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import lombok.Data; @@ -17,6 +19,7 @@ import org.springframework.stereotype.Component; public class TaskCenterGoClient { private static final String EVENT_TYPE_GAME_CONSUME_GOLD = "GAME_CONSUME_GOLD"; + private static final String EVENT_TYPE_GIFT_CONSUME_GOLD = "GIFT_CONSUME_GOLD"; @Value("${task.center.go.enabled:true}") private boolean enabled; @@ -51,6 +54,52 @@ public class TaskCenterGoClient { .setPayload(payload)); } + public void reportGiftConsumeGold(String sysOrigin, + Long userId, + Long trackId, + Long giftId, + String giftName, + String giftTab, + BigDecimal giftCandy, + Integer quantity, + Integer acceptUserSize, + BigDecimal consumeGold, + Long roomId, + Long dynamicContentId, + String source) { + if (Objects.isNull(userId) || Objects.isNull(trackId) || Objects.isNull(consumeGold) + || consumeGold.compareTo(BigDecimal.ZERO) <= 0) { + return; + } + + long deltaGold = consumeGold.longValue(); + if (deltaGold <= 0) { + return; + } + + Map payload = new HashMap<>(); + payload.put("trackId", trackId); + payload.put("giftId", giftId); + payload.put("giftName", giftName); + payload.put("giftTab", giftTab); + payload.put("giftCandy", giftCandy); + payload.put("quantity", quantity); + payload.put("acceptUserSize", acceptUserSize); + payload.put("actualAmount", consumeGold); + payload.put("roomId", roomId); + payload.put("dynamicContentId", dynamicContentId); + payload.put("source", source); + + reportEvent(new TaskCenterEventRequest() + .setSysOrigin(sysOrigin) + .setEventId("GIFT_CONSUME:" + trackId) + .setEventType(EVENT_TYPE_GIFT_CONSUME_GOLD) + .setUserId(userId) + .setDeltaValue(deltaGold) + .setOccurredAt(Instant.now().toString()) + .setPayload(payload)); + } + private void reportEvent(TaskCenterEventRequest request) { if (!enabled) { return; diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/freight/SendFreightShipCmdExe.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/freight/SendFreightShipCmdExe.java index ccd8576d..b6f76ef1 100644 --- a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/freight/SendFreightShipCmdExe.java +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/command/freight/SendFreightShipCmdExe.java @@ -30,12 +30,13 @@ import com.red.circle.other.inner.model.dto.user.UserOneTimeTaskDTO; import com.red.circle.other.inner.model.dto.user.UserProfileDTO; import com.red.circle.tool.core.date.LocalDateTimeUtils; import com.red.circle.tool.core.json.JacksonUtils; -import com.red.circle.tool.core.num.ArithmeticUtils; +import com.red.circle.tool.core.num.ArithmeticUtils; import com.red.circle.tool.core.tuple.PennyAmount; import com.red.circle.other.inner.asserts.user.UserErrorCode; import com.red.circle.wallet.app.common.invite.InviteCampaignGoClient; +import com.red.circle.wallet.app.common.task.TaskCenterGoClient; import com.red.circle.wallet.app.dto.cmd.FreightShipSendCmd; -import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; +import com.red.circle.wallet.app.dto.cmd.SalaryWithdrawCmd; import com.red.circle.wallet.domain.gateway.FreightShipGateway; import com.red.circle.wallet.domain.gateway.WalletGoldGateway; import com.red.circle.wallet.domain.wallet.WalletReceipt; @@ -96,6 +97,7 @@ public class SendFreightShipCmdExe { private final InviteCampaignGoClient inviteCampaignGoClient; private final UserRegionClient userRegionClient; private final OfficialNoticeClient officialNoticeClient; + private final TaskCenterGoClient taskCenterGoClient; public BigDecimal execute(FreightShipSendCmd cmd) { @@ -190,6 +192,13 @@ public class SendFreightShipCmdExe { // 累计充值 incrRechargeQuantity(cmd); + taskCenterGoClient.reportShippingAgentRechargeGold(cmd.requireReqSysOrigin(), + cmd.getAcceptUserId(), + runningWater.getId(), + cmd.getQuantity(), + getGoldToSUD(cmd), + cmd.requiredReqUserId(), + cmd.getReqTraceId()); notifyInviteCampaignRecharge(cmd, runningWater); //累计今日充值,每日累计充值抽奖活动 diff --git a/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/common/task/TaskCenterGoClient.java b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/common/task/TaskCenterGoClient.java new file mode 100644 index 00000000..fd79afae --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-application/src/main/java/com/red/circle/wallet/app/common/task/TaskCenterGoClient.java @@ -0,0 +1,145 @@ +package com.red.circle.wallet.app.common.task; + +import com.alibaba.fastjson.JSON; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TaskCenterGoClient { + + private static final String EVENT_TYPE_RECHARGE_GOLD = "RECHARGE_GOLD"; + + @Value("${task.center.go.enabled:true}") + private boolean enabled; + + @Value("${task.center.go.base-url:}") + private String baseUrl; + + @Value("${task.center.go.internal-token:}") + private String internalToken; + + @Value("${task.center.go.timeout-millis:3000}") + private Integer timeoutMillis; + + public void reportShippingAgentRechargeGold(String sysOrigin, + Long userId, + Long runningWaterId, + BigDecimal goldQuantity, + BigDecimal usdQuantity, + Long freightUserId, + String traceId) { + if (Objects.isNull(userId) || Objects.isNull(runningWaterId) || Objects.isNull(goldQuantity)) { + return; + } + long deltaValue = goldQuantity.setScale(0, RoundingMode.DOWN).longValue(); + if (deltaValue <= 0) { + return; + } + + Map payload = new HashMap<>(); + payload.put("channel", "SHIPPING_AGENT"); + payload.put("runningWaterId", runningWaterId); + payload.put("goldQuantity", goldQuantity); + payload.put("usdQuantity", usdQuantity); + payload.put("freightUserId", freightUserId); + payload.put("traceId", traceId); + + reportEvent(new TaskCenterEventRequest() + .setSysOrigin(sysOrigin) + .setEventId("RECHARGE_GOLD:SHIPPING_AGENT:" + runningWaterId) + .setEventType(EVENT_TYPE_RECHARGE_GOLD) + .setUserId(userId) + .setDeltaValue(deltaValue) + .setOccurredAt(Instant.now().toString()) + .setPayload(payload)); + } + + private void reportEvent(TaskCenterEventRequest request) { + if (!enabled) { + return; + } + String normalizedBaseUrl = trimRightSlash(Objects.toString(baseUrl, "").trim()); + if (normalizedBaseUrl.isEmpty()) { + log.warn("Task center go baseUrl is blank, skip event report. eventId={}", request.getEventId()); + return; + } + + HttpURLConnection connection = null; + try { + URL url = new URL(normalizedBaseUrl + "/internal/task-center/event"); + connection = (HttpURLConnection) url.openConnection(); + int timeout = Objects.requireNonNullElse(timeoutMillis, 3000); + connection.setConnectTimeout(timeout); + connection.setReadTimeout(timeout); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("X-Internal-Token", Objects.toString(internalToken, "")); + + byte[] body = JSON.toJSONString(request).getBytes(StandardCharsets.UTF_8); + try (OutputStream outputStream = connection.getOutputStream()) { + outputStream.write(body); + } + + int status = connection.getResponseCode(); + if (status < 200 || status >= 300) { + log.warn("Task center go report failed. status={}, body={}, eventId={}", + status, readResponse(connection), request.getEventId()); + } + } catch (Exception e) { + log.warn("Task center go report error. eventId={}", request.getEventId(), e); + } finally { + if (Objects.nonNull(connection)) { + connection.disconnect(); + } + } + } + + private String readResponse(HttpURLConnection connection) { + try (InputStream inputStream = connection.getErrorStream() != null + ? connection.getErrorStream() + : connection.getInputStream()) { + if (Objects.isNull(inputStream)) { + return ""; + } + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (Exception e) { + return ""; + } + } + + private String trimRightSlash(String value) { + while (value.endsWith("/")) { + value = value.substring(0, value.length() - 1); + } + return value; + } + + @Data + @Accessors(chain = true) + private static class TaskCenterEventRequest { + private String sysOrigin; + private String eventId; + private String eventType; + private Long userId; + private Long deltaValue; + private String occurredAt; + private Map payload; + } +} diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/WalletGoldBalanceService.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/WalletGoldBalanceService.java index 19bc31de..51ae5a8a 100644 --- a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/WalletGoldBalanceService.java +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/WalletGoldBalanceService.java @@ -15,10 +15,10 @@ public interface WalletGoldBalanceService extends BaseService */ boolean exists(Long userId); - /** - * 初始化钱包,不存在创建. - */ - void init(String sysOrigin, Long userId, Long income, Long expenditure); + /** + * 初始化钱包,不存在创建. + */ + boolean init(String sysOrigin, Long userId, Long income, Long expenditure); /** * 获取用户余额(注意可能出现负数). @@ -27,15 +27,15 @@ public interface WalletGoldBalanceService extends BaseService Long getBalanceTotal(Long userId); - /** - * 增加收入. - */ - void incrIncome(Long userId, Long amount); + /** + * 增加收入. + */ + boolean incrIncome(Long userId, Long amount); - /** - * 增加支出. - */ - void incrExpenditure(Long userId, Long amount); + /** + * 增加支出. + */ + boolean incrExpenditure(Long userId, Long amount); /** * 增加支出. diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/impl/WalletGoldBalanceServiceImpl.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/impl/WalletGoldBalanceServiceImpl.java index ea7db25c..8e59bf45 100644 --- a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/impl/WalletGoldBalanceServiceImpl.java +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/database/rds/service/impl/WalletGoldBalanceServiceImpl.java @@ -31,19 +31,20 @@ public class WalletGoldBalanceServiceImpl extends .orElse(Boolean.FALSE); } - @Override - public void init(String sysOrigin, Long userId, Long income, Long expenditure) { - try { - save(new WalletGoldBalance() - .setId(userId) - .setSysOrigin(sysOrigin) - .setIncome(income) - .setExpenditure(expenditure) - ); - } catch (DuplicateKeyException ex) { - // ignore - } - } + @Override + public boolean init(String sysOrigin, Long userId, Long income, Long expenditure) { + try { + return save(new WalletGoldBalance() + .setId(userId) + .setSysOrigin(sysOrigin) + .setIncome(income) + .setExpenditure(expenditure) + ); + } catch (DuplicateKeyException ex) { + // ignore + return false; + } + } public Long getBalance(Long userId) { Long balance = Optional.ofNullable(getById(userId)) @@ -60,21 +61,21 @@ public class WalletGoldBalanceServiceImpl extends } - @Override - public void incrIncome(Long userId, Long amount) { - update().set(WalletGoldBalance::getUpdateTime, DateUtils.now()) - .setSql(String.format("income=income+%s", amount)) - .eq(WalletGoldBalance::getId, userId) - .last(PageConstant.LIMIT_ONE) - .execute(); - } - - @Override - public void incrExpenditure(Long userId, Long amount) { - update().set(WalletGoldBalance::getUpdateTime, DateUtils.now()) - .setSql(String.format("expenditure=expenditure+%s", amount)) - .eq(WalletGoldBalance::getId, userId) - .last(PageConstant.LIMIT_ONE) + @Override + public boolean incrIncome(Long userId, Long amount) { + return update().set(WalletGoldBalance::getUpdateTime, DateUtils.now()) + .setSql(String.format("income=income+%s", amount)) + .eq(WalletGoldBalance::getId, userId) + .last(PageConstant.LIMIT_ONE) + .execute(); + } + + @Override + public boolean incrExpenditure(Long userId, Long amount) { + return update().set(WalletGoldBalance::getUpdateTime, DateUtils.now()) + .setSql(String.format("expenditure=expenditure+%s", amount)) + .eq(WalletGoldBalance::getId, userId) + .last(PageConstant.LIMIT_ONE) .execute(); } diff --git a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/tool/WalletAssetSyncTool.java b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/tool/WalletAssetSyncTool.java index 2709105e..229978e4 100644 --- a/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/tool/WalletAssetSyncTool.java +++ b/rc-service/rc-service-wallet/wallet-infrastructure/src/main/java/com/red/circle/wallet/infra/tool/WalletAssetSyncTool.java @@ -4,11 +4,11 @@ import com.alibaba.nacos.shaded.com.google.common.base.Throwables; import com.red.circle.component.tencent.cls.service.ClsService; import com.red.circle.framework.web.props.EnvProperties; import com.red.circle.mq.business.model.event.wallet.WalletReceiptSyncEvent; -import com.red.circle.tool.core.date.DateUtils; -import com.red.circle.tool.core.sequence.IdWorkerUtils; -import com.red.circle.tool.core.text.StringUtils; -import com.red.circle.other.inner.endpoint.user.user.UserProfileClient; -import com.red.circle.other.inner.model.dto.user.UserProfileDTO; +import com.red.circle.tool.core.date.DateUtils; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.tool.core.text.StringUtils; +import com.red.circle.other.inner.endpoint.user.user.UserProfileClient; +import com.red.circle.other.inner.model.dto.user.UserProfileDTO; import com.red.circle.wallet.domain.gateway.WalletExceptionLogGateway; import com.red.circle.wallet.domain.wallet.WalletBizType; import com.red.circle.wallet.domain.wallet.WalletErrorLogReason; @@ -145,28 +145,58 @@ public class WalletAssetSyncTool { } - /** - * 刷新金币同步. - */ - public void syncGold(WalletReceiptSyncEvent receipt) { - try { - if (receipt.checkTypeIncome()) { - walletGoldBalanceService.incrIncome(receipt.getUserId(), - receipt.getAmount().getPennyAmount()); - } - - if (receipt.checkExpenditure()) { - walletGoldBalanceService.incrExpenditure(receipt.getUserId(), - receipt.getAmount().getPennyAmount()); - } - } catch (Exception ex) { - saveErrorLog(receipt, WalletErrorLogReason.REST_BALANCE); - ex.printStackTrace(); - } - } - - - private WalletRemarks toWalletRemarks(WalletReceiptSyncEvent receipt) { + /** + * 刷新金币同步. + */ + public void syncGold(WalletReceiptSyncEvent receipt) { + try { + if (!syncGoldBalanceChange(receipt)) { + initMissingGoldBalance(receipt); + } + } catch (Exception ex) { + saveErrorLog(receipt, WalletErrorLogReason.REST_BALANCE); + throw new IllegalStateException("sync gold balance failed", ex); + } + } + + private boolean syncGoldBalanceChange(WalletReceiptSyncEvent receipt) { + if (receipt.checkTypeIncome()) { + return walletGoldBalanceService.incrIncome(receipt.getUserId(), + receipt.getAmount().getPennyAmount()); + } + + if (receipt.checkExpenditure()) { + return walletGoldBalanceService.incrExpenditure(receipt.getUserId(), + receipt.getAmount().getPennyAmount()); + } + return true; + } + + private void initMissingGoldBalance(WalletReceiptSyncEvent receipt) { + long amount = receipt.getAmount().getPennyAmount(); + long balance = receipt.getBalance().getPennyAmount(); + long income = Math.max(balance, 0L); + long expenditure = 0L; + + if (receipt.checkExpenditure()) { + income = Math.max(balance + amount, 0L); + expenditure = Math.max(amount, 0L); + } + + if (walletGoldBalanceService.init(receipt.getSysOrigin(), receipt.getUserId(), income, + expenditure)) { + log.warn("初始化缺失金币余额记录: userId={}, eventType={}, eventId={}", + receipt.getUserId(), receipt.getEventType(), receipt.getEventId()); + return; + } + if (!syncGoldBalanceChange(receipt)) { + throw new IllegalStateException( + "sync gold balance no rows after init: userId=" + receipt.getUserId()); + } + } + + + private WalletRemarks toWalletRemarks(WalletReceiptSyncEvent receipt) { WalletRemarks remarks = new WalletRemarks() .setId(IdWorkerUtils.getId()) .setBizId(Objects.toString(receipt.getAssetRecordId())) diff --git a/rc-service/rc-service-wallet/wallet-start/src/test/java/com/red/circle/wallet/infra/tool/WalletAssetSyncToolTest.java b/rc-service/rc-service-wallet/wallet-start/src/test/java/com/red/circle/wallet/infra/tool/WalletAssetSyncToolTest.java new file mode 100644 index 00000000..407364a1 --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-start/src/test/java/com/red/circle/wallet/infra/tool/WalletAssetSyncToolTest.java @@ -0,0 +1,70 @@ +package com.red.circle.wallet.infra.tool; + +import static org.mockito.Mockito.inOrder; +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.common.business.core.enums.ReceiptType; +import com.red.circle.mq.business.model.event.wallet.WalletReceiptSyncEvent; +import com.red.circle.tool.core.tuple.PennyAmount; +import com.red.circle.wallet.infra.database.rds.service.WalletGoldBalanceService; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +class WalletAssetSyncToolTest { + + @Test + void syncGoldInitializesMissingBalanceWhenExpenditureUpdateMissesRow() { + WalletGoldBalanceService balanceService = mock(WalletGoldBalanceService.class); + when(balanceService.incrExpenditure(1001L, 1_500_000L)).thenReturn(false); + when(balanceService.init("LIKEI", 1001L, 10_000_000L, 1_500_000L)).thenReturn(true); + WalletAssetSyncTool tool = newTool(balanceService); + + WalletReceiptSyncEvent event = new WalletReceiptSyncEvent(); + event.setReceiptType(ReceiptType.EXPENDITURE); + event.setSysOrigin("LIKEI"); + event.setUserId(1001L); + event.setEventType("VOICE_ROOM_RED_PACKET_SEND"); + event.setEventId("VRPS:123"); + event.setAmount(PennyAmount.ofPenny(1_500_000L)); + event.setBalance(PennyAmount.ofPenny(8_500_000L)); + + tool.syncGold(event); + + InOrder ordered = inOrder(balanceService); + ordered.verify(balanceService).incrExpenditure(1001L, 1_500_000L); + ordered.verify(balanceService).init("LIKEI", 1001L, 10_000_000L, 1_500_000L); + verify(balanceService, never()).incrIncome(1001L, 1_500_000L); + } + + @Test + void syncGoldInitializesMissingBalanceWhenIncomeUpdateMissesRow() { + WalletGoldBalanceService balanceService = mock(WalletGoldBalanceService.class); + when(balanceService.incrIncome(1002L, 1_500_000L)).thenReturn(false); + when(balanceService.init("LIKEI", 1002L, 11_500_000L, 0L)).thenReturn(true); + WalletAssetSyncTool tool = newTool(balanceService); + + WalletReceiptSyncEvent event = new WalletReceiptSyncEvent(); + event.setReceiptType(ReceiptType.INCOME); + event.setSysOrigin("LIKEI"); + event.setUserId(1002L); + event.setEventType("VOICE_ROOM_RED_PACKET_CLAIM"); + event.setEventId("VRPC:123"); + event.setAmount(PennyAmount.ofPenny(1_500_000L)); + event.setBalance(PennyAmount.ofPenny(11_500_000L)); + + tool.syncGold(event); + + InOrder ordered = inOrder(balanceService); + ordered.verify(balanceService).incrIncome(1002L, 1_500_000L); + ordered.verify(balanceService).init("LIKEI", 1002L, 11_500_000L, 0L); + verify(balanceService, never()).incrExpenditure(1002L, 1_500_000L); + } + + private WalletAssetSyncTool newTool(WalletGoldBalanceService balanceService) { + return new WalletAssetSyncTool(null, null, null, null, null, balanceService, null, null, null, + null); + } +}