diff --git a/nacos_config/rc-service-live/application.yml b/nacos_config/rc-service-live/application.yml index 0b15bbed..91026362 100644 --- a/nacos_config/rc-service-live/application.yml +++ b/nacos_config/rc-service-live/application.yml @@ -1,2 +1,10 @@ spring: - \ No newline at end of file + +task: + center: + go: + enabled: ${TASK_CENTER_GO_ENABLED:true} + base-url: ${TASK_CENTER_GO_URL:${LIKEI_GATEWAY_ROUTE_GO_URI:http://golang:2900}} + internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:} + timeout-millis: ${TASK_CENTER_GO_TIMEOUT_MILLIS:3000} + diff --git a/nacos_config/rc-service-order/application.yml b/nacos_config/rc-service-order/application.yml index 7106debc..70485f79 100644 --- a/nacos_config/rc-service-order/application.yml +++ b/nacos_config/rc-service-order/application.yml @@ -71,3 +71,11 @@ red-circle: telegram: bot-token: ${LIKEI_ORDER_TELEGRAM_BOT_TOKEN} webhook-url: ${LIKEI_ORDER_TELEGRAM_WEBHOOK_URL} + +task: + center: + go: + enabled: ${TASK_CENTER_GO_ENABLED:true} + base-url: ${TASK_CENTER_GO_URL:${LIKEI_GATEWAY_ROUTE_GO_URI:http://golang:2900}} + internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:} + timeout-millis: ${TASK_CENTER_GO_TIMEOUT_MILLIS:3000} diff --git a/nacos_config/rc-service-other/application.yml b/nacos_config/rc-service-other/application.yml index d7f31a00..763ee2c4 100644 --- a/nacos_config/rc-service-other/application.yml +++ b/nacos_config/rc-service-other/application.yml @@ -115,6 +115,22 @@ invite: internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:} timeout-millis: ${INVITE_CAMPAIGN_GO_TIMEOUT_MILLIS:10000} +task: + center: + go: + enabled: ${TASK_CENTER_GO_ENABLED:true} + base-url: ${TASK_CENTER_GO_URL:${LIKEI_GATEWAY_ROUTE_GO_URI:http://golang:2900}} + internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:} + timeout-millis: ${TASK_CENTER_GO_TIMEOUT_MILLIS:3000} + +voice-room: + region-broadcast: + go: + enabled: ${VOICE_ROOM_REGION_BROADCAST_GO_ENABLED:true} + base-url: ${VOICE_ROOM_REGION_BROADCAST_GO_URL:${TASK_CENTER_GO_URL:${LIKEI_GATEWAY_ROUTE_GO_URI:http://golang:2900}}} + internal-token: ${GAME_INTERNAL_CALLBACK_SECRET:} + timeout-millis: ${VOICE_ROOM_REGION_BROADCAST_GO_TIMEOUT_MILLIS:3000} + activity: ranking: reward: diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/material/GiftSpecialEnum.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/material/GiftSpecialEnum.java index ac195068..9772009c 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/material/GiftSpecialEnum.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/material/GiftSpecialEnum.java @@ -17,13 +17,18 @@ public enum GiftSpecialEnum { */ MUSIC, - /** - * 全局通报. - */ - NOTICE, - - /** - * 周星. + /** + * 全局通报. + */ + NOTICE, + + /** + * 全局广播礼物. + */ + GLOBAL_GIFT, + + /** + * 周星. */ STAR, diff --git a/rc-service/rc-service-live/live-application/src/main/java/com/red/circle/live/app/listener/UserHeartbeatListener.java b/rc-service/rc-service-live/live-application/src/main/java/com/red/circle/live/app/listener/UserHeartbeatListener.java index 27979aef..266e455f 100644 --- a/rc-service/rc-service-live/live-application/src/main/java/com/red/circle/live/app/listener/UserHeartbeatListener.java +++ b/rc-service/rc-service-live/live-application/src/main/java/com/red/circle/live/app/listener/UserHeartbeatListener.java @@ -17,9 +17,10 @@ import com.red.circle.live.domain.gateway.LiveMicrophoneGateway; import com.red.circle.live.domain.live.LiveMicrophone; import com.red.circle.live.domain.online.LiveRoomUser; import com.red.circle.live.infra.database.cache.service.LiveMicCacheService; -import com.red.circle.live.infra.database.cache.service.LiveMicUserCacheService; -import com.red.circle.live.infra.database.cache.service.LiveRoomCacheService; -import com.red.circle.live.infra.util.DateTimeAsiaRiyadhUtils; +import com.red.circle.live.infra.database.cache.service.LiveMicUserCacheService; +import com.red.circle.live.infra.database.cache.service.LiveRoomCacheService; +import com.red.circle.live.infra.client.TaskCenterGoClient; +import com.red.circle.live.infra.util.DateTimeAsiaRiyadhUtils; import com.red.circle.mq.business.model.event.user.UserHeartbeatEvent; import com.red.circle.mq.rocket.business.producer.UserMqMessageService; import com.red.circle.mq.rocket.business.streams.UserHeartbeatSink; @@ -80,6 +81,7 @@ public class UserHeartbeatListener implements MessageListener { private final AgoraRoomStateService agoraRoomStateService; private final AgoraMicMissingConfirmService agoraMicMissingConfirmService; private final AgoraMicStateCleanupService agoraMicStateCleanupService; + private final TaskCenterGoClient taskCenterGoClient; private static final int HEARTBEAT_INTERVAL_SECONDS = 60; @@ -336,8 +338,18 @@ public class UserHeartbeatListener implements MessageListener { return; } + Long previousRefreshTime = liveMicrophone.getRefreshTime(); liveMicrophone.updateLastActivityTime(); + Long currentRefreshTime = liveMicrophone.getRefreshTime(); liveMicCacheService.goUp(roomProfile.getId(), liveMicrophone.getMicIndex(), liveMicrophone); + taskCenterGoClient.reportMicDuration( + Objects.nonNull(event.getSysOrigin()) ? event.getSysOrigin().name() : "", + roomProfile.getId(), + liveMicrophone.getMicIndex(), + event.getUserId(), + previousRefreshTime, + currentRefreshTime, + "HEARTBEAT"); liveMicrophoneGateway.refreshNotActiveUser(roomProfile); } diff --git a/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/client/TaskCenterGoClient.java b/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/client/TaskCenterGoClient.java new file mode 100644 index 00000000..909f0d8f --- /dev/null +++ b/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/client/TaskCenterGoClient.java @@ -0,0 +1,156 @@ +package com.red.circle.live.infra.client; + +import com.alibaba.fastjson.JSON; +import com.red.circle.live.domain.constant.ActiveSecondConstant; +import java.io.InputStream; +import java.io.OutputStream; +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_MIC_DURATION_SECONDS = "MIC_DURATION_SECONDS"; + + @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 reportMicDuration(String sysOrigin, + Long roomId, + Integer micIndex, + Long userId, + Long previousRefreshTime, + Long currentTime, + String source) { + if (Objects.isNull(userId) || Objects.isNull(previousRefreshTime) || Objects.isNull(currentTime)) { + return; + } + long deltaSeconds = calculateMicDurationSeconds(previousRefreshTime, currentTime); + if (deltaSeconds <= 0) { + return; + } + + Map payload = new HashMap<>(); + payload.put("source", source); + payload.put("roomId", roomId); + payload.put("micIndex", micIndex); + payload.put("previousRefreshTime", previousRefreshTime); + payload.put("currentTime", currentTime); + + reportEvent(new TaskCenterEventRequest() + .setSysOrigin(sysOrigin) + .setEventId("MIC_DURATION:" + Objects.toString(source, "UNKNOWN") + + ":" + Objects.toString(roomId, "") + + ":" + Objects.toString(micIndex, "") + + ":" + userId + + ":" + previousRefreshTime + + ":" + currentTime) + .setEventType(EVENT_TYPE_MIC_DURATION_SECONDS) + .setUserId(userId) + .setDeltaValue(deltaSeconds) + .setOccurredAt(Instant.ofEpochMilli(currentTime).toString()) + .setPayload(payload)); + } + + private long calculateMicDurationSeconds(long previousRefreshTime, long currentTime) { + long deltaSeconds = (currentTime - previousRefreshTime) / 1000; + if (deltaSeconds <= 0) { + return 0; + } + return Math.min(deltaSeconds, ActiveSecondConstant.MIC_EXPIRATION_SECOND); + } + + 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-live/live-infrastructure/src/main/java/com/red/circle/live/infra/repository/LiveMicrophoneGatewayImpl.java b/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/repository/LiveMicrophoneGatewayImpl.java index 69638178..026d2b02 100644 --- a/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/repository/LiveMicrophoneGatewayImpl.java +++ b/rc-service/rc-service-live/live-infrastructure/src/main/java/com/red/circle/live/infra/repository/LiveMicrophoneGatewayImpl.java @@ -15,9 +15,10 @@ import com.red.circle.live.domain.live.LiveMicrophone; import com.red.circle.live.domain.live.LiveMusicStatus; import com.red.circle.live.domain.live.dto.LiveMicrophoneDTO; import com.red.circle.live.domain.live.dto.LiveMicrophoneNoticeDTO; -import com.red.circle.live.domain.online.LiveRoomUser; -import com.red.circle.live.domain.online.LiveRoomUserRefresh; -import com.red.circle.live.infra.database.cache.service.LiveRoomCacheService; +import com.red.circle.live.domain.online.LiveRoomUser; +import com.red.circle.live.domain.online.LiveRoomUserRefresh; +import com.red.circle.live.infra.client.TaskCenterGoClient; +import com.red.circle.live.infra.database.cache.service.LiveRoomCacheService; import com.red.circle.live.infra.repository.dto.RefreshOnlineUserNoticeDTO; import com.red.circle.live.infra.repository.dto.RefreshOnlineUserNoticeDTO.RefreshOnlineUserType; import com.red.circle.live.infra.convertor.LiveMicInfraConvertor; @@ -90,10 +91,11 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { private final LiveMusicHeartbeatService liveMusicHeartbeatService; private final LiveMicrophoneInfraConvertor liveMicrophoneInfraConvertor; private final UserLevelClient userLevelClient; - private final UserOnlineClient userOnlineClient; - private final UserCpClient userCpClient; - private final LiveRoomCacheService liveRoomCacheService; -// private final RedissonClient redissonClient; + private final UserOnlineClient userOnlineClient; + private final UserCpClient userCpClient; + private final LiveRoomCacheService liveRoomCacheService; + private final TaskCenterGoClient taskCenterGoClient; +// private final RedissonClient redissonClient; @Value("${rtc.authKey}") private String authKey; @@ -194,12 +196,15 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { // 下掉已上麦克风 List lives = liveMicCacheService.listLiveMicrophone(roomId, userId); if (CollectionUtils.isNotEmpty(lives)) { - List goDownMicIndex = lives.stream() + List goDownMicrophones = lives.stream() + .filter(mic -> !Objects.equals(mic.getMicIndex(), micIndex)) + .toList(); + List goDownMicIndex = goDownMicrophones.stream() .map(LiveMicrophone::getMicIndex) - .filter(index -> !Objects.equals(index, micIndex)) .toList(); if (CollectionUtils.isNotEmpty(goDownMicIndex)) { liveMicCacheService.goDown(roomId, goDownMicIndex); + reportMicDuration(roomProfile, roomId, goDownMicrophones, "SWITCH_MIC"); } } @@ -240,18 +245,20 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { // 房间是否真实存在 ResponseAssert.notNull(RoomErrorCode.ROOM_NOT_EXISTS, roomProfile); // 验证自己是否在当前麦克风 - ResponseAssert.isTrue(LiveMicErrorCode.MIC_PERMISSIONS, - liveMicCacheService.checkEqSelf( - roomId, - micIndex, - userId - ) - ); - // 下麦克风 - goDownMic(roomId, micIndex, roomProfile); - }catch (Exception e){ - ResponseAssert.isTrue(RoomErrorCode.ROOM_MEMBER_IS_MAX,false); - } finally { + ResponseAssert.isTrue(LiveMicErrorCode.MIC_PERMISSIONS, + liveMicCacheService.checkEqSelf( + roomId, + micIndex, + userId + ) + ); + LiveMicrophone liveMicrophone = liveMicCacheService.getLiveMicrophone(roomId, micIndex); + // 下麦克风 + goDownMic(roomId, micIndex, roomProfile); + reportMicDuration(roomProfile, roomId, liveMicrophone, "GO_DOWN"); + }catch (Exception e){ + ResponseAssert.isTrue(RoomErrorCode.ROOM_MEMBER_IS_MAX,false); + } finally { // lock.unlock(); micOpsUnlock(roomId, micIndex); } @@ -285,27 +292,58 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { micOpsLock(roomId, micIndex); try { // 检查权限 - checkKillMicRoleOpsPermissions(reqUserRole, - RoomUserRolesEnum.valueOf(liveMicrophone.getUser().getRoles())); - - goDownMic(roomId, micIndex, roomProfile); - - } finally { + checkKillMicRoleOpsPermissions(reqUserRole, + RoomUserRolesEnum.valueOf(liveMicrophone.getUser().getRoles())); + + goDownMic(roomId, micIndex, roomProfile); + reportMicDuration(roomProfile, roomId, liveMicrophone, "KILL_MIC"); + + } finally { micOpsUnlock(roomId, micIndex); } } - private void goDownMic(Long roomId, Integer micIndex, RoomProfileDTO roomProfile) { - // 下麦克风 - liveMicCacheService.goDown(roomId, micIndex); - // 清理活跃用户 - liveMicCacheService.refreshNotActiveUser(roomId); + private void goDownMic(Long roomId, Integer micIndex, RoomProfileDTO roomProfile) { + // 下麦克风 + liveMicCacheService.goDown(roomId, micIndex); + // 清理活跃用户 + liveMicCacheService.refreshNotActiveUser(roomId); // 发送通知 - micUserChangeNotice(roomProfile); - } - - private void checkKillMicRoleOpsPermissions(RoomUserRolesEnum reqUserRole, - RoomUserRolesEnum micUserRole) { + micUserChangeNotice(roomProfile); + } + + private void reportMicDuration(RoomProfileDTO roomProfile, + Long roomId, + List microphones, + String source) { + if (Objects.isNull(roomProfile) || CollectionUtils.isEmpty(microphones)) { + return; + } + microphones.forEach(microphone -> reportMicDuration(roomProfile, roomId, microphone, source)); + } + + private void reportMicDuration(RoomProfileDTO roomProfile, + Long roomId, + LiveMicrophone liveMicrophone, + String source) { + if (Objects.isNull(roomProfile) + || Objects.isNull(liveMicrophone) + || Objects.isNull(liveMicrophone.getUser()) + || Objects.isNull(liveMicrophone.getUser().getId())) { + return; + } + taskCenterGoClient.reportMicDuration( + roomProfile.getSysOrigin(), + roomId, + liveMicrophone.getMicIndex(), + liveMicrophone.getUser().getId(), + liveMicrophone.getRefreshTime(), + System.currentTimeMillis(), + source); + } + + private void checkKillMicRoleOpsPermissions(RoomUserRolesEnum reqUserRole, + RoomUserRolesEnum micUserRole) { ResponseAssert.isFalse(LiveMicErrorCode.MIC_PERMISSIONS, Objects.equals(micUserRole, RoomUserRolesEnum.HOMEOWNER)); ResponseAssert.isFalse(LiveMicErrorCode.MIC_PERMISSIONS, @@ -332,14 +370,16 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { micOpsLock(roomId, mickIndex); try { - LiveMicSettingSeat settingSeat = liveMicSettingService.getMicSeat(roomId, mickIndex); - ResponseAssert.notNull(LiveMicErrorCode.MIC_INDEX_NOT_FOUND, settingSeat); - settingSeat.setMicLock(Objects.equals(lock, Boolean.TRUE)); - liveMicSettingService.updateMicSeat(roomId, settingSeat); - // 下麦克风 - liveMicCacheService.goDown(roomId, mickIndex); - // 清理活跃用户 - liveMicCacheService.refreshNotActiveUser(roomId); + LiveMicSettingSeat settingSeat = liveMicSettingService.getMicSeat(roomId, mickIndex); + ResponseAssert.notNull(LiveMicErrorCode.MIC_INDEX_NOT_FOUND, settingSeat); + settingSeat.setMicLock(Objects.equals(lock, Boolean.TRUE)); + liveMicSettingService.updateMicSeat(roomId, settingSeat); + LiveMicrophone liveMicrophone = liveMicCacheService.getLiveMicrophone(roomId, mickIndex); + // 下麦克风 + liveMicCacheService.goDown(roomId, mickIndex); + reportMicDuration(roomProfile, roomId, liveMicrophone, "MIC_LOCK"); + // 清理活跃用户 + liveMicCacheService.refreshNotActiveUser(roomId); // 发送通知 micUserChangeNotice(roomProfile, settingSeat); } finally { @@ -364,34 +404,42 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway { } } - @Override - public void updateMickSize(Long roomId, Integer micSize) { - LiveMicSetting liveMicSetting = liveMicSettingService.updateMicSize(roomId, micSize); - - // T下所有麦克风用户 - if (CollectionUtils.isEmpty(liveMicSetting.getMicSeats())) { - liveMicCacheService.goDownAll(roomId); - } - - List microphones = liveMicCacheService.listLiveMicrophone(roomId); - - if (CollectionUtils.isNotEmpty(microphones)) { - - List existsMicIndex = microphones.stream().map(LiveMicrophone::getMicIndex) - .toList(); + @Override + public void updateMickSize(Long roomId, Integer micSize) { + List microphones = liveMicCacheService.listLiveMicrophone(roomId); + LiveMicSetting liveMicSetting = liveMicSettingService.updateMicSize(roomId, micSize); + + // T下所有麦克风用户 + if (CollectionUtils.isEmpty(liveMicSetting.getMicSeats())) { + liveMicCacheService.goDownAll(roomId); + reportMicDuration(roomManagerClient.getById(roomId).getBody(), roomId, microphones, "MIC_SIZE_UPDATE"); + micUserChangeNotice(liveMicSetting); + return; + } + + if (CollectionUtils.isNotEmpty(microphones)) { + + List existsMicIndex = microphones.stream().map(LiveMicrophone::getMicIndex) + .toList(); List thisNewSeats = liveMicSetting.getMicSeats().stream() .map(LiveMicSettingSeat::getMicIndex).toList(); List removeMicIndex = existsMicIndex.stream() - .filter(micIndex -> !thisNewSeats.contains(micIndex)) - .toList(); - - liveMicCacheService.goDown(roomId, removeMicIndex); - } - - micUserChangeNotice(liveMicSetting); - } + .filter(micIndex -> !thisNewSeats.contains(micIndex)) + .toList(); + + if (CollectionUtils.isNotEmpty(removeMicIndex)) { + List removedMicrophones = microphones.stream() + .filter(mic -> removeMicIndex.contains(mic.getMicIndex())) + .toList(); + liveMicCacheService.goDown(roomId, removeMicIndex); + reportMicDuration(roomManagerClient.getById(roomId).getBody(), roomId, removedMicrophones, "MIC_SIZE_UPDATE"); + } + } + + micUserChangeNotice(liveMicSetting); + } private RoomProfileDTO checkMicMutePermissions(Long roomId, Long opsUserId) { RoomProfileDTO roomProfile = roomManagerClient.getById(roomId).getBody(); diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/task/TaskCenterGoClient.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/task/TaskCenterGoClient.java new file mode 100644 index 00000000..92650740 --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/common/task/TaskCenterGoClient.java @@ -0,0 +1,143 @@ +package com.red.circle.order.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 reportRechargeGold(String sysOrigin, + Long userId, + Long purchaseHistoryId, + BigDecimal candyQuantity, + String platform, + String payPlatform, + Instant occurredAt) { + if (Objects.isNull(userId) || Objects.isNull(purchaseHistoryId) || Objects.isNull(candyQuantity)) { + return; + } + long deltaValue = candyQuantity.setScale(0, RoundingMode.DOWN).longValue(); + if (deltaValue <= 0) { + return; + } + + Map payload = new HashMap<>(); + payload.put("purchaseHistoryId", purchaseHistoryId); + payload.put("candyQuantity", candyQuantity); + payload.put("platform", platform); + payload.put("payPlatform", payPlatform); + + reportEvent(new TaskCenterEventRequest() + .setSysOrigin(sysOrigin) + .setEventId("RECHARGE_GOLD:" + purchaseHistoryId) + .setEventType(EVENT_TYPE_RECHARGE_GOLD) + .setUserId(userId) + .setDeltaValue(deltaValue) + .setOccurredAt(Objects.requireNonNullElse(occurredAt, 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-order/order-application/src/main/java/com/red/circle/order/app/listener/BuySuccessListener.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/listener/BuySuccessListener.java index c8f764af..9dce5258 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/listener/BuySuccessListener.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/listener/BuySuccessListener.java @@ -14,10 +14,11 @@ import com.red.circle.component.mq.service.ConsumerMessage; import com.red.circle.component.mq.service.MessageListener; import com.red.circle.component.redis.service.RedisService; import com.red.circle.framework.dto.ResultResponse; -import com.red.circle.mq.business.model.event.pay.BuySuccessEvent; -import com.red.circle.mq.business.model.event.pay.PurchaseProductEvent; -import com.red.circle.mq.rocket.business.streams.BuySuccessSink; -import com.red.circle.other.inner.endpoint.activity.CumulativeRechargeClient; +import com.red.circle.mq.business.model.event.pay.BuySuccessEvent; +import com.red.circle.mq.business.model.event.pay.PurchaseProductEvent; +import com.red.circle.mq.rocket.business.streams.BuySuccessSink; +import com.red.circle.order.app.common.task.TaskCenterGoClient; +import com.red.circle.other.inner.endpoint.activity.CumulativeRechargeClient; import com.red.circle.other.inner.endpoint.activity.PropsActivityClient; import com.red.circle.other.inner.endpoint.sys.EnumConfigClient; import com.red.circle.other.inner.enums.activity.PropsActivityTypeEnum; @@ -67,8 +68,9 @@ public class BuySuccessListener implements MessageListener { private final WalletDiamondClient walletDiamondClient; private final MessageEventProcess messageEventProcess; private final UserOneTimeTaskClient userOneTimeTaskClient; - private final CumulativeRechargeClient cumulativeRechargeClient; - private final RedisService redisService; + private final CumulativeRechargeClient cumulativeRechargeClient; + private final RedisService redisService; + private final TaskCenterGoClient taskCenterGoClient; @Override public Action consume(ConsumerMessage message) { @@ -121,10 +123,18 @@ public class BuySuccessListener implements MessageListener { if (!ArithmeticUtils.gt(eventBody.getCandyQuantity(), BigDecimal.ZERO) || Objects.equals(eventBody.getTrialPeriod(), Boolean.TRUE)) { - return; - } - - userExpandClient.updatePurchasingToTrue(eventBody.getUserId()); + return; + } + + taskCenterGoClient.reportRechargeGold(eventBody.getSysOrigin(), + eventBody.getUserId(), + eventBody.getPurchaseHistoryId(), + eventBody.getCandyQuantity(), + eventBody.getPlatform(), + eventBody.getPayPlatform(), + Objects.nonNull(eventBody.getBuyDateTime()) ? eventBody.getBuyDateTime().toInstant() : null); + + userExpandClient.updatePurchasingToTrue(eventBody.getUserId()); //累计今日充值,每日累计充值抽奖活动 cumulativeRechargeClient.incrTodayRechargeScore( diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameHkysRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameHkysRestController.java index 58f32546..d8e65be3 100644 --- a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameHkysRestController.java +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/game/party3rd/GameHkysRestController.java @@ -19,8 +19,9 @@ import com.red.circle.framework.core.security.UserCredential; import com.red.circle.framework.web.annotation.IgnoreResultResponse; import com.red.circle.mq.rocket.business.producer.TaskMqMessage; import com.red.circle.order.inner.model.enums.MonthlyRechargeType; -import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; -import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.game.GameLxwlUpdateBalanceCmd; import com.red.circle.other.app.dto.cmd.game.GameLxwlUserInfoCmd; import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskProgressUpdateCmd; @@ -49,8 +50,9 @@ import com.red.circle.tool.core.parse.DataTypeUtils; import com.red.circle.tool.core.text.StringUtils; import com.red.circle.tool.crypto.SecurityUtils; -import java.math.BigDecimal; -import java.util.Map; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -87,8 +89,9 @@ public class GameHkysRestController { private final RankingActivityService rankingActivityService; private final GameActivityService gameActivityService; private final RedisService redisService; - private final ActivityRechargeTicketService activityRechargeTicketService; - private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final ActivityRechargeTicketService activityRechargeTicketService; + private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final TaskCenterGoClient taskCenterGoClient; @IgnoreResultResponse @PostMapping("/getUserInfo") @@ -193,10 +196,13 @@ public class GameHkysRestController { // 累加用户获得的金币并发送抽奖券 // activityRechargeTicketService.accumulatedRechargeAndAddTicket(userId, Math.abs(cmd.getCoin()), MonthlyRechargeType.UNDEFINED); - } else { - - updateRoomDailyTask(cmd.getCoin(), userId); - } + } else { + + updateRoomDailyTask(cmd.getCoin(), userId); + reportTaskCenterGameEvents(sysOrigin, userId, "LINGXIAN", cmd.getOrderId(), + Math.abs(cmd.getCoin()), gameTaskPayload("LINGXIAN", cmd.getOrderId(), cmd.getGameId(), + cmd.getRoundId(), cmd.getRoomId())); + } return new HkysResponse() @@ -204,7 +210,7 @@ public class GameHkysRestController { .setData(jsonObject); } - private void updateRoomDailyTask(Long coins, Long userId) { + private void updateRoomDailyTask(Long coins, Long userId) { RoomDailyTaskCode taskType = RoomDailyTaskCode.PERSONAL_GAME_CONSUME; String redisKey = "room:daily:task:progress:" + taskType.name() + ":" + userId + ":" + ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); Long total = redisService.increment(redisKey, Math.abs(coins.intValue())); @@ -215,9 +221,28 @@ public class GameHkysRestController { .setTaskCode(taskType.name()) .setProgressValue(total.intValue()) ); - } - - private void incGameRankingRecord(GameLxwlUpdateBalanceCmd request, GameListConfig gameListConfig) { + } + + private void reportTaskCenterGameEvents(String sysOrigin, + Long userId, + String provider, + String orderId, + Long consumeGold, + Map payload) { + taskCenterGoClient.reportGameConsumeGold(sysOrigin, userId, provider, orderId, consumeGold, payload); + } + + private Map gameTaskPayload(String provider, String orderId, String gameId, String roundId, String roomId) { + Map payload = new HashMap<>(); + payload.put("provider", provider); + payload.put("orderId", orderId); + payload.put("gameId", gameId); + payload.put("roundId", roundId); + payload.put("roomId", roomId); + return payload; + } + + private void incGameRankingRecord(GameLxwlUpdateBalanceCmd request, GameListConfig gameListConfig) { if (request.getType() != 2 || gameListConfig == null || request.getCoin() <= 0) { return; } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/dynamic/add/DynamicContentAddCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/dynamic/add/DynamicContentAddCmdExe.java index b7fed6d4..7a86ab7e 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/dynamic/add/DynamicContentAddCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/dynamic/add/DynamicContentAddCmdExe.java @@ -15,10 +15,10 @@ import com.red.circle.framework.core.response.ResponseErrorCode; import com.red.circle.mq.business.model.event.approval.CensorContent; import com.red.circle.mq.business.model.event.approval.CensorProfileEvent; import com.red.circle.mq.business.model.event.dynamic.DynamicEvent; -import com.red.circle.mq.rocket.business.producer.CensorMqMessage; -import com.red.circle.mq.rocket.business.producer.DynamicMqMessage; -import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.other.app.dto.clientobject.dynamic.DynamicListCO; +import com.red.circle.mq.rocket.business.producer.CensorMqMessage; +import com.red.circle.mq.rocket.business.producer.DynamicMqMessage; +import com.red.circle.mq.rocket.business.producer.TaskMqMessage; +import com.red.circle.other.app.dto.clientobject.dynamic.DynamicListCO; import com.red.circle.other.app.dto.cmd.dynamic.DynamicContentAddCmd; import com.red.circle.other.app.enums.violation.ViolationTypeEnum; import com.red.circle.other.domain.gateway.user.UserProfileGateway; @@ -84,12 +84,12 @@ public class DynamicContentAddCmdExe { private final DynamicTimelineService dynamicTimelineService; private final DynamicBlacklistService dynamicBlacklistService; private final ApprovalDynamicContentService approvalDynamicContentService; - private final TaskMqMessage taskMqMessage; - private final UserAccountCommon userAccountCommon; - private final AdministratorService administratorService; - private final AdministratorAuthService administratorAuthService; - - public DynamicListCO execute(DynamicContentAddCmd cmd) { + private final TaskMqMessage taskMqMessage; + private final UserAccountCommon userAccountCommon; + private final AdministratorService administratorService; + private final AdministratorAuthService administratorAuthService; + + public DynamicListCO execute(DynamicContentAddCmd cmd) { //校验参数 checkParameters(cmd); @@ -97,12 +97,12 @@ public class DynamicContentAddCmdExe { Long contentId = IdWorkerUtils.getId(); //保存动态 - DynamicListCO save = save(cmd, contentId); - - dynamicCacheService.incrUserTodayDynamicCount(cmd.getReqUserId()); - - if (CollectionUtils.isEmpty(cmd.getPictures())) { - return save; + DynamicListCO save = save(cmd, contentId); + + dynamicCacheService.incrUserTodayDynamicCount(cmd.getReqUserId()); + + if (CollectionUtils.isEmpty(cmd.getPictures())) { + return save; } sendViolationContentMq(cmd); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameJoinCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameJoinCmdExe.java index 21ae72de..f45aa102 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameJoinCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameJoinCmdExe.java @@ -1,8 +1,9 @@ package com.red.circle.other.app.command.game.ludo; -import com.red.circle.framework.core.asserts.ResponseAssert; -import com.red.circle.other.app.common.game.GameTurntableCommon; +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.common.game.GameTurntableCommon; import com.red.circle.other.app.dto.cmd.game.GameTurntableGameIdCmd; import com.red.circle.other.infra.database.cache.key.GameListKeys; import com.red.circle.other.infra.database.cache.service.other.MarkCacheService; @@ -13,10 +14,12 @@ import com.red.circle.other.inner.asserts.RoomErrorCode; import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient; import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd; import com.red.circle.wallet.inner.model.dto.WalletReceiptDTO; -import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO; -import com.red.circle.wallet.inner.model.enums.GoldOrigin; -import java.util.Objects; -import java.util.concurrent.TimeUnit; +import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO; +import com.red.circle.wallet.inner.model.enums.GoldOrigin; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -30,9 +33,10 @@ import org.springframework.stereotype.Service; public class GameJoinCmdExe { private final MarkCacheService markCacheService; - private final WalletGoldClient walletGoldClient; - private final GameTurntableCommon gameTurntableCommon; - private final RoomTurntableGameMongoService roomTurntableGameMongoService; + private final WalletGoldClient walletGoldClient; + private final GameTurntableCommon gameTurntableCommon; + private final RoomTurntableGameMongoService roomTurntableGameMongoService; + private final TaskCenterGoClient taskCenterGoClient; /** * 加入游戏. @@ -69,8 +73,9 @@ public class GameJoinCmdExe { // 9104 游戏人数已满 ResponseAssert.isTrue(RoomErrorCode.GAME_THE_NUMBER_FULL, getJoinQuantity(cmd.getGameId()) <= 12); - gameTurntableCommon.join(cmd.getReqUserId(), roomTurntableGame); - WalletReceiptDTO walletReceiptDTO = new WalletReceiptDTO(); + gameTurntableCommon.join(cmd.getReqUserId(), roomTurntableGame); + reportTaskCenterGameEvents(cmd, roomTurntableGame); + WalletReceiptDTO walletReceiptDTO = new WalletReceiptDTO(); walletReceiptDTO.setBalance(walletReceipt.getBalance().getDollarAmount()); walletReceiptDTO.setProcessAmount(walletReceipt.getOpAmount().getDollarAmount()); return walletReceiptDTO; @@ -84,8 +89,19 @@ public class GameJoinCmdExe { return GameListKeys.getKey("TGAME_USIZE", String.valueOf(gameId)); } - private String getJoinedKey(Long gameId, Long userId) { - return GameListKeys.getKey("TGAME_JOINED:" + gameId, String.valueOf(userId)); - } - -} + private String getJoinedKey(Long gameId, Long userId) { + return GameListKeys.getKey("TGAME_JOINED:" + gameId, String.valueOf(userId)); + } + + private void reportTaskCenterGameEvents(GameTurntableGameIdCmd cmd, RoomTurntableGame roomTurntableGame) { + Long consumeGold = roomTurntableGame.getGameGolds().longValue(); + String orderId = roomTurntableGame.getGameId() + ":" + cmd.getReqUserId(); + Map payload = new HashMap<>(); + payload.put("provider", "TURNTABLE"); + payload.put("gameId", roomTurntableGame.getGameId()); + payload.put("roomId", roomTurntableGame.getRoomId()); + taskCenterGoClient.reportGameConsumeGold(cmd.requireReqSysOrigin(), cmd.getReqUserId(), + "TURNTABLE", orderId, consumeGold, payload); + } + +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameLudoJoinCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameLudoJoinCmdExe.java index 3c43121b..3431c42a 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameLudoJoinCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/game/ludo/GameLudoJoinCmdExe.java @@ -1,7 +1,8 @@ package com.red.circle.other.app.command.game.ludo; -import com.red.circle.framework.core.asserts.ResponseAssert; -import com.red.circle.other.app.convertor.game.GameLudoAppConvertor; +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.game.GameLudoAppConvertor; import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.dto.cmd.game.GameLudoJoinCmd; import com.red.circle.other.domain.gateway.user.UserProfileGateway; @@ -18,9 +19,11 @@ import com.red.circle.tool.core.date.TimestampUtils; import com.red.circle.tool.core.num.ArithmeticUtils; import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient; import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd; -import com.red.circle.wallet.inner.model.enums.GoldOrigin; -import java.math.BigDecimal; -import java.util.Objects; +import com.red.circle.wallet.inner.model.enums.GoldOrigin; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -36,9 +39,10 @@ public class GameLudoJoinCmdExe { private final GameCacheService gameCacheService; private final WalletGoldClient walletGoldClient; private final UserProfileGateway userProfileGateway; - private final GameLudoRunService gameLudoRunService; - private final GameLudoAppConvertor gameLudoAppConvertor; - private final UserProfileAppConvertor userProfileAppConvertor; + private final GameLudoRunService gameLudoRunService; + private final GameLudoAppConvertor gameLudoAppConvertor; + private final UserProfileAppConvertor userProfileAppConvertor; + private final TaskCenterGoClient taskCenterGoClient; public void execute(GameLudoJoinCmd cmd) { UserProfileDTO userProfile = userProfileAppConvertor.toUserProfileDTO( @@ -70,11 +74,12 @@ public class GameLudoJoinCmdExe { .setPlayerProfile(gameLudoAppConvertor.userProfileToPlayerProfile(userProfile)) .setCreateTime(TimestampUtils.now()) ); - gameLudoRunService.resetPlayers(gameLudoRun.getId(), gameLudoRun.getPlayers()); - gameCacheService.setJoinGameLudo(gameLudoRun.getId(), cmd.getPlayerUserId()); - } + gameLudoRunService.resetPlayers(gameLudoRun.getId(), gameLudoRun.getPlayers()); + gameCacheService.setJoinGameLudo(gameLudoRun.getId(), cmd.getPlayerUserId()); + reportTaskCenterGameEvents(cmd, gameLudoRun, consumeAmount); + } - private BigDecimal executeConsumeAmount(GameLudoJoinCmd cmd, BigDecimal amount) { + private BigDecimal executeConsumeAmount(GameLudoJoinCmd cmd, BigDecimal amount) { if (Objects.isNull(amount) || ArithmeticUtils.lteZero(amount)) { return BigDecimal.ZERO; } @@ -86,6 +91,16 @@ public class GameLudoJoinCmdExe { .origin(GoldOrigin.LUDO_GAME) .amount(amount) .build()) - ).getBalance().getDollarAmount(); - } -} + ).getBalance().getDollarAmount(); + } + + private void reportTaskCenterGameEvents(GameLudoJoinCmd cmd, GameLudoRun gameLudoRun, BigDecimal consumeAmount) { + String orderId = gameLudoRun.getId() + ":" + cmd.getPlayerUserId(); + Map payload = new HashMap<>(); + payload.put("provider", "LUDO"); + payload.put("gameId", gameLudoRun.getId()); + payload.put("roomId", gameLudoRun.getRoomId()); + taskCenterGoClient.reportGameConsumeGold(cmd.requireReqSysOrigin(), cmd.getPlayerUserId(), + "LUDO", orderId, consumeAmount.longValue(), payload); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/VoiceRoomRegionBroadcastGoClient.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/VoiceRoomRegionBroadcastGoClient.java new file mode 100644 index 00000000..49635a99 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/gift/VoiceRoomRegionBroadcastGoClient.java @@ -0,0 +1,89 @@ +package com.red.circle.other.app.common.gift; + +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +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 VoiceRoomRegionBroadcastGoClient { + + @Value("${voice-room.region-broadcast.go.enabled:true}") + private boolean enabled; + + @Value("${voice-room.region-broadcast.go.base-url:}") + private String baseUrl; + + @Value("${voice-room.region-broadcast.go.internal-token:}") + private String internalToken; + + @Value("${voice-room.region-broadcast.go.timeout-millis:3000}") + private Integer timeoutMillis; + + public boolean send(RegionBroadcastRequest request) { + if (!enabled) { + return false; + } + + String normalizedBaseUrl = trimRightSlash(Objects.toString(baseUrl, "").trim()); + if (normalizedBaseUrl.isEmpty()) { + log.warn("Voice room region broadcast go baseUrl is blank, skip broadcast. type={}, senderUserId={}", + request.getType(), request.getSenderUserId()); + return false; + } + + try (HttpResponse response = HttpUtil.createPost(normalizedBaseUrl + + "/internal/voice-room/region-broadcast") + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("X-Internal-Token", Objects.toString(internalToken, "")) + .timeout(Objects.requireNonNullElse(timeoutMillis, 3000)) + .body(JSON.toJSONString(request)) + .execute()) { + String responseBody = response.body(); + if (response.getStatus() < 200 || response.getStatus() >= 300) { + log.warn("Voice room region broadcast go http failed. status={}, body={}, type={}, senderUserId={}", + response.getStatus(), responseBody, request.getType(), request.getSenderUserId()); + return false; + } + if (responseBody == null || responseBody.trim().isEmpty()) { + return true; + } + JSONObject root = JSON.parseObject(responseBody); + if (root != null && root.containsKey("status") && !root.getBooleanValue("status")) { + log.warn("Voice room region broadcast go business failed. body={}, type={}, senderUserId={}", + responseBody, request.getType(), request.getSenderUserId()); + return false; + } + return true; + } catch (Exception e) { + log.warn("Voice room region broadcast go error. type={}, senderUserId={}", + request.getType(), request.getSenderUserId(), e); + return false; + } + } + + private String trimRightSlash(String value) { + while (value.endsWith("/")) { + value = value.substring(0, value.length() - 1); + } + return value; + } + + @Data + @Accessors(chain = true) + public static class RegionBroadcastRequest { + private String sysOrigin; + private String regionCode; + private Long senderUserId; + private String type; + private Object data; + } +} 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 new file mode 100644 index 00000000..b57cd097 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/common/task/TaskCenterGoClient.java @@ -0,0 +1,99 @@ +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.time.Instant; +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_GAME_CONSUME_GOLD = "GAME_CONSUME_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 reportGameConsumeGold(String sysOrigin, + Long userId, + String provider, + String orderId, + Long deltaGold, + Map payload) { + if (Objects.isNull(userId) || Objects.isNull(deltaGold) || deltaGold <= 0) { + return; + } + String eventId = "GAME_CONSUME:" + Objects.toString(provider, "UNKNOWN") + ":" + + Objects.toString(orderId, ""); + reportEvent(new TaskCenterEventRequest() + .setSysOrigin(sysOrigin) + .setEventId(eventId) + .setEventType(EVENT_TYPE_GAME_CONSUME_GOLD) + .setUserId(userId) + .setDeltaValue(deltaGold) + .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; + } + + try (HttpResponse response = HttpUtil.createPost(normalizedBaseUrl + "/internal/task-center/event") + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("X-Internal-Token", Objects.toString(internalToken, "")) + .timeout(Objects.requireNonNullElse(timeoutMillis, 3000)) + .body(JSON.toJSONString(request)) + .execute()) { + if (response.getStatus() < 200 || response.getStatus() >= 300) { + log.warn("Task center go report failed. status={}, body={}, eventId={}", + response.getStatus(), response.body(), request.getEventId()); + } + } catch (Exception e) { + log.warn("Task center go report error. eventId={}", request.getEventId(), e); + } + } + + 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-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/GiveGiftsListener.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/GiveGiftsListener.java index c26c7593..580b2af8 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/GiveGiftsListener.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/listener/gift/GiveGiftsListener.java @@ -30,11 +30,12 @@ import com.red.circle.mq.rocket.business.producer.GiftMqMessage; import com.red.circle.mq.rocket.business.streams.GiveGiftSink; import com.red.circle.other.app.command.game.barrage.BarrageGameBaishunExe; import com.red.circle.other.app.command.game.burstcrystal.GameBurstCrystalScheduleTwoFunQueryExe; -import com.red.circle.other.app.command.game.roompk.GameRoomPkUserCmdExe; -import com.red.circle.other.app.command.game.teampk.GameTeamPkBaseInfoCmdExe; -import com.red.circle.other.app.common.gift.GameLuckyGiftCommon; -import com.red.circle.other.app.dto.clientobject.game.*; -import com.red.circle.other.app.dto.clientobject.gift.SendGiftNotifyCO; +import com.red.circle.other.app.command.game.roompk.GameRoomPkUserCmdExe; +import com.red.circle.other.app.command.game.teampk.GameTeamPkBaseInfoCmdExe; +import com.red.circle.other.app.common.gift.GameLuckyGiftCommon; +import com.red.circle.other.app.common.gift.VoiceRoomRegionBroadcastGoClient; +import com.red.circle.other.app.dto.clientobject.game.*; +import com.red.circle.other.app.dto.clientobject.gift.SendGiftNotifyCO; import com.red.circle.other.app.dto.cmd.game.barrage.GameUserEffectsCmd; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.domain.live.LiveHeartbeatCache; @@ -91,9 +92,10 @@ public class GiveGiftsListener implements MessageListener { private final GiftMqMessage giftMqMessage; private final TeamMemberService teamMemberService; private final UserRegionGateway userRegionGateway; - private final MessageEventProcess messageEventProcess; - private final GameLuckyGiftCommon gameLuckyGiftCommon; - private final TeamBillCycleService teamBillCycleService; + private final MessageEventProcess messageEventProcess; + private final GameLuckyGiftCommon gameLuckyGiftCommon; + private final VoiceRoomRegionBroadcastGoClient voiceRoomRegionBroadcastGoClient; + private final TeamBillCycleService teamBillCycleService; private final TeamPolicyManagerService teamPolicyManagerService; private final EnumConfigCacheService enumConfigCacheService; private final LiveHeartbeatCacheService liveHeartbeatCacheService; @@ -418,7 +420,12 @@ public class GiveGiftsListener implements MessageListener { return; } String special = event.getGiftConfig().getSpecial(); - if (StringUtils.isBlank(special) || !special.contains(GiftSpecialEnum.NOTICE.name())) { + if (StringUtils.isBlank(special)) { + return; + } + boolean noticeGift = special.contains(GiftSpecialEnum.NOTICE.name()); + boolean globalGift = special.contains(GiftSpecialEnum.GLOBAL_GIFT.name()); + if (!noticeGift && !globalGift) { return; } @@ -442,15 +449,30 @@ public class GiveGiftsListener implements MessageListener { .setActualAmount(runningWater.getGiftValue().getActualAmount()) .setGiftType(runningWater.getGiftValue().getGiftType()); - imGroupClient.sendMessageBroadcast( - BroadcastGroupMsgBodyCmd.builder() - .toPlatform(event.getSysOrigin()) - .type(GroupMessageTypeEnum.SEND_GIFT) - .data(notify) - .build() - ); - log.warn("NOTICE礼物全服通报成功, trackId={}, giftId={}, sendUserId={}", - event.getTrackId(), event.getGiftConfig().getId(), event.getSendUserId()); + if (globalGift) { + boolean success = voiceRoomRegionBroadcastGoClient.send( + new VoiceRoomRegionBroadcastGoClient.RegionBroadcastRequest() + .setSysOrigin(event.getSysOrigin().name()) + .setSenderUserId(event.getSendUserId()) + .setType(GroupMessageTypeEnum.SEND_GIFT.name()) + .setData(notify) + ); + log.warn("GLOBAL_GIFT礼物区域广播{}, trackId={}, giftId={}, sendUserId={}", + success ? "成功" : "失败", event.getTrackId(), event.getGiftConfig().getId(), + event.getSendUserId()); + } + + if (noticeGift) { + imGroupClient.sendMessageBroadcast( + BroadcastGroupMsgBodyCmd.builder() + .toPlatform(event.getSysOrigin()) + .type(GroupMessageTypeEnum.SEND_GIFT) + .data(notify) + .build() + ); + log.warn("NOTICE礼物全服通报成功, trackId={}, giftId={}, sendUserId={}", + event.getTrackId(), event.getGiftConfig().getId(), event.getSendUserId()); + } } private Pair saveRunningWater(GiveAwayGiftBatchEvent event) { diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunServiceImpl.java index cb6f612e..6a06c0a4 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/GameBaishunServiceImpl.java @@ -22,8 +22,9 @@ import com.red.circle.framework.core.exception.ResponseException; import com.red.circle.framework.core.security.UserCredential; import com.red.circle.mq.business.model.event.task.TaskApprovalEvent; import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.order.inner.model.enums.MonthlyRechargeType; -import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.order.inner.model.enums.MonthlyRechargeType; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskProgressUpdateCmd; import com.red.circle.other.app.enums.violation.GameOriginEnum; @@ -56,9 +57,10 @@ import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd; import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd.GoldReceiptCmdBuilder; import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO; import com.red.circle.wallet.inner.model.enums.GoldOrigin; -import java.math.BigDecimal; -import java.util.Map; -import java.util.Objects; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -89,9 +91,10 @@ public class GameBaishunServiceImpl implements GameBaishunService { private final GameRankingGateway gameRankingGateway; private final GameListCacheService gameListCacheService; private final TaskService taskService; - private final GameActivityService gameActivityService; - private final ActivityRechargeTicketService activityRechargeTicketService; - private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final GameActivityService gameActivityService; + private final ActivityRechargeTicketService activityRechargeTicketService; + private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final TaskCenterGoClient taskCenterGoClient; @Value("${red-circle.game-rank.gameid}") private String gameId; @@ -227,10 +230,13 @@ public class GameBaishunServiceImpl implements GameBaishunService { // 累加用户获得的金币并发送抽奖券 // activityRechargeTicketService.accumulatedRechargeAndAddTicket(userId, request.getCurrencyDiff(), MonthlyRechargeType.UNDEFINED); - } else { - updateRoomDailyTask(request.getCurrencyDiff(), userId); - } - } + } else { + updateRoomDailyTask(request.getCurrencyDiff(), userId); + reportTaskCenterGameEvents(sysOrigin, userId, "BAISHUN_LEGACY", request.getOrderId(), + Math.abs(request.getCurrencyDiff()), gameTaskPayload("BAISHUN_LEGACY", + request.getOrderId(), request.getGameId(), request.getGameRoundId(), null)); + } + } log.warn("{} 发送游戏获奖通知--金币数量{}", GoldOrigin.BAISHUN_GAME.name(), request.getCurrencyDiff()); return BaishunResponse.success(new BaishunChangeCurrencyResponse() @@ -254,7 +260,7 @@ public class GameBaishunServiceImpl implements GameBaishunService { return baishunRuntimeConfigResolver.resolveByApp(appId, null); } - private void updateRoomDailyTask(Long coins, Long userId) { + private void updateRoomDailyTask(Long coins, Long userId) { RoomDailyTaskCode taskType = RoomDailyTaskCode.PERSONAL_GAME_CONSUME; String redisKey = "room:daily:task:progress:" + taskType.name() + ":" + userId + ":" + ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); Long total = redisService.increment(redisKey, Math.abs(coins.intValue())); @@ -265,9 +271,28 @@ public class GameBaishunServiceImpl implements GameBaishunService { .setTaskCode(taskType.name()) .setProgressValue(total.intValue()) ); - } - - private void sendBroadcast(BaishunChangeCurrencyRequest request, GameListConfig gameListConfig) { + } + + private void reportTaskCenterGameEvents(String sysOrigin, + Long userId, + String provider, + String orderId, + Long consumeGold, + Map payload) { + taskCenterGoClient.reportGameConsumeGold(sysOrigin, userId, provider, orderId, consumeGold, payload); + } + + private Map gameTaskPayload(String provider, String orderId, String gameId, String roundId, String roomId) { + Map payload = new HashMap<>(); + payload.put("provider", provider); + payload.put("orderId", orderId); + payload.put("gameId", gameId); + payload.put("roundId", roundId); + payload.put("roomId", roomId); + return payload; + } + + private void sendBroadcast(BaishunChangeCurrencyRequest request, GameListConfig gameListConfig) { if (gameListConfig == null) { return; } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java index 3f5b9248..dbbc015f 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/HotGameServiceImpl.java @@ -15,9 +15,10 @@ import com.red.circle.framework.core.asserts.ResponseAssert; import com.red.circle.framework.core.dto.ReqSysOrigin; import com.red.circle.framework.core.exception.ResponseException; import com.red.circle.framework.core.security.UserCredential; -import com.red.circle.mq.rocket.business.producer.TaskMqMessage; -import com.red.circle.order.inner.model.enums.MonthlyRechargeType; -import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.mq.rocket.business.producer.TaskMqMessage; +import com.red.circle.order.inner.model.enums.MonthlyRechargeType; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskProgressUpdateCmd; import com.red.circle.other.app.enums.violation.GameOriginEnum; @@ -93,9 +94,10 @@ public class HotGameServiceImpl implements HotGameService { private final RankingActivityService rankingActivityService; private final GameActivityService gameActivityService; private final UserActivityRechargeService userActivityRechargeService; - private final GameVipLevelWeeklyService gameVipLevelWeeklyService; - private final ActivityRechargeTicketService activityRechargeTicketService; - private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final GameVipLevelWeeklyService gameVipLevelWeeklyService; + private final ActivityRechargeTicketService activityRechargeTicketService; + private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final TaskCenterGoClient taskCenterGoClient; @Value("${red-circle.game-rank.gameid}") private String gameId; @@ -339,9 +341,12 @@ public class HotGameServiceImpl implements HotGameService { } // 处理游戏消费人任务 - else { - updateRoomDailyTask(param.getCoin(), userId); - } + else { + updateRoomDailyTask(param.getCoin(), userId); + reportTaskCenterGameEvents(sysOrigin, userId, "HOTGAME", param.getOrderId(), + Math.abs(param.getCoin()), gameTaskPayload("HOTGAME", param.getOrderId(), + param.getGameId())); + } return new HotGameResponse() .setData(new HotGameCoin() @@ -363,7 +368,7 @@ public class HotGameServiceImpl implements HotGameService { .setErrorCode(HotGameErrorEnum.SERVER_ERROR.getErrorCode()); } - private void updateRoomDailyTask(Long coins, Long userId) { + private void updateRoomDailyTask(Long coins, Long userId) { RoomDailyTaskCode taskType = RoomDailyTaskCode.PERSONAL_GAME_CONSUME; String redisKey = "room:daily:task:progress:" + taskType.name() + ":" + userId + ":" + ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); Long total = redisService.increment(redisKey, Math.abs(coins.intValue())); @@ -374,9 +379,26 @@ public class HotGameServiceImpl implements HotGameService { .setTaskCode(taskType.name()) .setProgressValue(total.intValue()) ); - } - - private void incGameRankingTmpActivity(HotGameUserCoinUpdate request) { + } + + private void reportTaskCenterGameEvents(String sysOrigin, + Long userId, + String provider, + String orderId, + Long consumeGold, + Map payload) { + taskCenterGoClient.reportGameConsumeGold(sysOrigin, userId, provider, orderId, consumeGold, payload); + } + + private Map gameTaskPayload(String provider, String orderId, String gameId) { + Map payload = new HashMap<>(); + payload.put("provider", provider); + payload.put("orderId", orderId); + payload.put("gameId", gameId); + return payload; + } + + private void incGameRankingTmpActivity(HotGameUserCoinUpdate request) { if (ZonedDateTimeAsiaRiyadhUtils.now().isAfter(getFinishTime())) { return; } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/YomiGameServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/YomiGameServiceImpl.java index 839e215a..29cdfb98 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/YomiGameServiceImpl.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/game/YomiGameServiceImpl.java @@ -8,9 +8,10 @@ import com.red.circle.external.inner.model.cmd.message.BroadcastGroupMsgBodyCmd; import com.red.circle.external.inner.model.enums.message.GroupMessageTypeEnum; import com.red.circle.framework.core.dto.ReqSysOrigin; import com.red.circle.framework.core.security.UserCredential; -import com.red.circle.framework.dto.ResultResponse; -import com.red.circle.order.inner.model.enums.MonthlyRechargeType; -import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; +import com.red.circle.framework.dto.ResultResponse; +import com.red.circle.order.inner.model.enums.MonthlyRechargeType; +import com.red.circle.other.app.common.task.TaskCenterGoClient; +import com.red.circle.other.app.convertor.user.UserProfileAppConvertor; import com.red.circle.other.app.dto.cmd.activity.RankingDataUpdateCmd; import com.red.circle.other.app.dto.cmd.party3rd.*; import com.red.circle.other.app.dto.cmd.task.RoomDailyTaskProgressUpdateCmd; @@ -73,9 +74,10 @@ public class YomiGameServiceImpl implements YomiGameService { private final RankingActivityService rankingActivityService; private final GameActivityService gameActivityService; private final GameRankingGateway gameRankingGateway; - private final ActivityRechargeTicketService activityRechargeTicketService; - private final RoomDailyTaskProgressService roomDailyTaskProgressService; - private final RedisService redisService; + private final ActivityRechargeTicketService activityRechargeTicketService; + private final RoomDailyTaskProgressService roomDailyTaskProgressService; + private final RedisService redisService; + private final TaskCenterGoClient taskCenterGoClient; @Override public YomiTokenCO getToken(YomiGetTokenCmd cmd) { @@ -186,9 +188,12 @@ public class YomiGameServiceImpl implements YomiGameService { // 累加用户获得的金币并发送抽奖券 // activityRechargeTicketService.accumulatedRechargeAndAddTicket(userId, Math.abs(changeValue.longValue()), MonthlyRechargeType.UNDEFINED); - } else { - updateRoomDailyTask(changeValue.longValue(), userId); - } + } else { + updateRoomDailyTask(changeValue.longValue(), userId); + reportTaskCenterGameEvents(sysOrigin, userId, "YOMI", cmd.getRecordId(), + Math.abs(changeValue.longValue()), gameTaskPayload("YOMI", cmd.getRecordId(), + cmd.getGameId(), cmd.getRoundId(), cmd.getRoomId())); + } return new YomiBalanceCO(currentBalance); @@ -199,7 +204,7 @@ public class YomiGameServiceImpl implements YomiGameService { } } - private void updateRoomDailyTask(Long coins, Long userId) { + private void updateRoomDailyTask(Long coins, Long userId) { RoomDailyTaskCode taskType = RoomDailyTaskCode.PERSONAL_GAME_CONSUME; String redisKey = "room:daily:task:progress:" + taskType.name() + ":" + userId + ":" + ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate(); Long total = redisService.increment(redisKey, Math.abs(coins.intValue())); @@ -210,9 +215,28 @@ public class YomiGameServiceImpl implements YomiGameService { .setTaskCode(taskType.name()) .setProgressValue(total.intValue()) ); - } - - private static boolean isConsume(YomiChangeBalanceCmd cmd) { + } + + private void reportTaskCenterGameEvents(String sysOrigin, + Long userId, + String provider, + String orderId, + Long consumeGold, + Map payload) { + taskCenterGoClient.reportGameConsumeGold(sysOrigin, userId, provider, orderId, consumeGold, payload); + } + + private Map gameTaskPayload(String provider, String orderId, String gameId, String roundId, String roomId) { + Map payload = new HashMap<>(); + payload.put("provider", provider); + payload.put("orderId", orderId); + payload.put("gameId", gameId); + payload.put("roundId", roundId); + payload.put("roomId", roomId); + return payload; + } + + private static boolean isConsume(YomiChangeBalanceCmd cmd) { return "bet".equals(cmd.getChangeCause()); } @@ -339,4 +363,4 @@ public class YomiGameServiceImpl implements YomiGameService { //游戏王日榜 gameActivityService.incKingGameDaily(userId, rewardAmount.longValue(), gameId); } -} \ No newline at end of file +} diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/gift/GiveGiftsListenerTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/gift/GiveGiftsListenerTest.java index 31384b11..a5a43e9c 100644 --- a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/gift/GiveGiftsListenerTest.java +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/listener/gift/GiveGiftsListenerTest.java @@ -23,6 +23,7 @@ import com.red.circle.other.app.command.game.burstcrystal.GameBurstCrystalSchedu import com.red.circle.other.app.command.game.roompk.GameRoomPkUserCmdExe; import com.red.circle.other.app.command.game.teampk.GameTeamPkBaseInfoCmdExe; import com.red.circle.other.app.common.gift.GameLuckyGiftCommon; +import com.red.circle.other.app.common.gift.VoiceRoomRegionBroadcastGoClient; import com.red.circle.other.app.dto.clientobject.gift.SendGiftNotifyCO; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService; @@ -45,6 +46,8 @@ import org.mockito.ArgumentCaptor; class GiveGiftsListenerTest { private final ImGroupClient imGroupClient = mock(ImGroupClient.class); + private final VoiceRoomRegionBroadcastGoClient voiceRoomRegionBroadcastGoClient = mock( + VoiceRoomRegionBroadcastGoClient.class); private final RoomProfileManagerService roomProfileManagerService = mock( RoomProfileManagerService.class); private final GiveGiftsListener listener = new GiveGiftsListener( @@ -54,6 +57,7 @@ class GiveGiftsListenerTest { mock(UserRegionGateway.class), mock(MessageEventProcess.class), mock(GameLuckyGiftCommon.class), + voiceRoomRegionBroadcastGoClient, mock(TeamBillCycleService.class), mock(TeamPolicyManagerService.class), mock(EnumConfigCacheService.class), @@ -101,11 +105,35 @@ class GiveGiftsListenerTest { assertEquals("NORMAL", data.getGiftType()); } + @Test + void sendNoticeGiftBroadcastShouldSendGlobalGiftToRegionBroadcastGo() { + when(voiceRoomRegionBroadcastGoClient.send(any())).thenReturn(true); + + listener.sendNoticeGiftBroadcast( + event(GiftSpecialEnum.GLOBAL_GIFT.name(), "GOLD"), + runningWater() + ); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(VoiceRoomRegionBroadcastGoClient.RegionBroadcastRequest.class); + verify(voiceRoomRegionBroadcastGoClient).send(captor.capture()); + verify(imGroupClient, never()).sendMessageBroadcast(any()); + + VoiceRoomRegionBroadcastGoClient.RegionBroadcastRequest request = captor.getValue(); + assertEquals(SysOriginPlatformEnum.LIKEI.name(), request.getSysOrigin()); + assertEquals(3001L, request.getSenderUserId()); + assertEquals(GroupMessageTypeEnum.SEND_GIFT.name(), request.getType()); + SendGiftNotifyCO data = (SendGiftNotifyCO) request.getData(); + assertTrue(data.getGlobalNews()); + assertEquals(5001L, data.getGiftId()); + } + @Test void sendNoticeGiftBroadcastShouldIgnoreNonNoticeGift() { listener.sendNoticeGiftBroadcast(event("ANIMATION", "GOLD"), runningWater()); verify(imGroupClient, never()).sendMessageBroadcast(any()); + verify(voiceRoomRegionBroadcastGoClient, never()).send(any()); } @Test @@ -114,6 +142,7 @@ class GiveGiftsListenerTest { runningWater()); verify(imGroupClient, never()).sendMessageBroadcast(any()); + verify(voiceRoomRegionBroadcastGoClient, never()).send(any()); } private GiveAwayGiftBatchEvent event(String special, String giftTab) { diff --git a/sql/20260506_user_login_logger_ip_region.sql b/sql/20260506_user_login_logger_ip_region.sql index 3ba39996..63584708 100644 --- a/sql/20260506_user_login_logger_ip_region.sql +++ b/sql/20260506_user_login_logger_ip_region.sql @@ -1,9 +1,93 @@ SET NAMES utf8mb4; -ALTER TABLE `user_login_logger` - ADD COLUMN `ip_country_code` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录IP国家码' AFTER `ip`, - ADD COLUMN `ip_country_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录IP国家/地区名称' AFTER `ip_country_code`, - ADD COLUMN `ip_region` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录IP地区/省' AFTER `ip_country_name`, - ADD COLUMN `req_zone_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求时区' AFTER `ip_region`, - ADD COLUMN `login_blocked` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否被登录访问校验屏蔽' AFTER `req_zone_id`, - ADD COLUMN `block_reason` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '登录访问校验屏蔽原因' AFTER `login_blocked`; +SET @current_schema = DATABASE(); + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'ip_country_code' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `ip_country_code` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '''' COMMENT ''登录IP国家码'' AFTER `ip`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'ip_country_name' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `ip_country_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '''' COMMENT ''登录IP国家/地区名称'' AFTER `ip_country_code`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'ip_region' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `ip_region` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '''' COMMENT ''登录IP地区/省'' AFTER `ip_country_name`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'req_zone_id' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `req_zone_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '''' COMMENT ''请求时区'' AFTER `ip_region`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'login_blocked' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `login_blocked` tinyint(1) NOT NULL DEFAULT 0 COMMENT ''是否被登录访问校验屏蔽'' AFTER `req_zone_id`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl = IF( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'user_login_logger' + AND COLUMN_NAME = 'block_reason' + ), + 'SELECT 1', + 'ALTER TABLE `user_login_logger` ADD COLUMN `block_reason` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '''' COMMENT ''登录访问校验屏蔽原因'' AFTER `login_blocked`' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/sql/20260508_payment_manager_menu.sql b/sql/20260508_payment_manager_menu.sql new file mode 100644 index 00000000..de89851f --- /dev/null +++ b/sql/20260508_payment_manager_menu.sql @@ -0,0 +1,117 @@ +SET NAMES utf8mb4; + +SET @payment_manager_menu_id := ( + SELECT `id` FROM `sys_menu` WHERE `alias` = 'PaymentManager' LIMIT 1 +); +SET @payment_manager_menu_id := IFNULL(@payment_manager_menu_id, 1700000600); + +INSERT INTO `sys_menu` ( + `id`, `parent_id`, `menu_name`, `path`, `menu_type`, `icon`, `create_user`, `update_user`, + `sort`, `status`, `router`, `alias` +) +SELECT + @payment_manager_menu_id, 0, '支付管理', '/payment/manager', 1, 'lucide:credit-card', + '0', '0', 193, 0, '/payment/manager', 'PaymentManager' +WHERE NOT EXISTS ( + SELECT 1 FROM `sys_menu` WHERE `alias` = 'PaymentManager' +); + +UPDATE `sys_menu` +SET + `parent_id` = 0, + `menu_name` = '支付管理', + `path` = '/payment/manager', + `menu_type` = 1, + `icon` = 'lucide:credit-card', + `create_user` = '0', + `update_user` = '0', + `sort` = 193, + `status` = 0, + `router` = '/payment/manager' +WHERE `alias` = 'PaymentManager'; + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `menu_name` = '订单列表', + `path` = 'operate/order-details', + `menu_type` = 2, + `icon` = 'form', + `sort` = 1, + `status` = 0, + `router` = 'payment/manager/order/list' +WHERE `alias` = 'OrderDetails'; + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `path` = 'operate/pay-country', + `menu_type` = 2, + `icon` = 'form', + `sort` = CASE `alias` WHEN 'PayCountry' THEN 2 ELSE 7 END, + `status` = 0, + `router` = 'payment/manager/country' +WHERE `alias` IN ('PayCountry', 'openPayCountry'); + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `path` = 'operate/region-relation', + `menu_type` = 2, + `icon` = 'form', + `sort` = 3, + `status` = 0, + `router` = 'payment/manager/region/relation' +WHERE `alias` = 'RegionRelation'; + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `path` = 'operate/pay-application', + `menu_type` = 2, + `icon` = 'form', + `sort` = 4, + `status` = 0, + `router` = 'payment/manager/application' +WHERE `alias` = 'PayApplication'; + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `path` = 'operate/pay-factory', + `menu_type` = 2, + `icon` = 'form', + `sort` = 5, + `status` = 0, + `router` = 'payment/manager/factory' +WHERE `alias` = 'PayFactory'; + +UPDATE `sys_menu` +SET + `parent_id` = @payment_manager_menu_id, + `path` = 'operate/pay-channel', + `menu_type` = 2, + `icon` = 'form', + `sort` = 6, + `status` = 0, + `router` = 'payment/manager/channel' +WHERE `alias` = 'PayChannel'; + +INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) +SELECT DISTINCT source.`role_id`, @payment_manager_menu_id +FROM `sys_role_menu` source +JOIN `sys_menu` child_menu ON child_menu.`id` = source.`menu_id` +WHERE child_menu.`alias` IN ( + 'OrderDetails', + 'PayCountry', + 'openPayCountry', + 'RegionRelation', + 'PayApplication', + 'PayFactory', + 'PayChannel' +) + AND NOT EXISTS ( + SELECT 1 FROM `sys_role_menu` target + WHERE target.`role_id` = source.`role_id` + AND target.`menu_id` = @payment_manager_menu_id + );