diff --git a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java index f3529427..ac6c1f2c 100644 --- a/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java +++ b/rc-service/rc-service-console/console-adapter/src/main/java/com/red/circle/console/adapter/app/user/DatavRestController.java @@ -7,6 +7,7 @@ import com.red.circle.console.app.dto.cmd.datav.CountryDashboardGameMetricQryCmd import com.red.circle.console.app.dto.cmd.datav.CountryDashboardQryCmd; import com.red.circle.console.app.dto.cmd.datav.CountryDashboardRechargeDetailQryCmd; import com.red.circle.console.app.service.app.datav.CountryDashboardDailyMetricService; +import com.red.circle.console.app.service.app.datav.CountryDashboardBackfillLockService; import com.red.circle.console.app.service.app.datav.CountryDashboardGameMetricService; import com.red.circle.console.app.service.app.datav.CountryDashboardService; import com.red.circle.console.app.service.app.user.DatavService; @@ -41,6 +42,7 @@ public class DatavRestController extends BaseController { private final DatavService userExpandService; private final CountryDashboardService countryDashboardService; private final CountryDashboardDailyMetricService countryDashboardDailyMetricService; + private final CountryDashboardBackfillLockService countryDashboardBackfillLockService; private final CountryDashboardGameMetricService countryDashboardGameMetricService; @Value("${red-circle.country-dashboard.query-enabled:true}") @@ -116,14 +118,21 @@ public class DatavRestController extends BaseController { } LocalDate parsedStartDate = LocalDate.parse(startDate); LocalDate parsedEndDate = LocalDate.parse(endDate); - List refreshedTimezones = countryDashboardDailyMetricService.refreshRange( - parsedStartDate, parsedEndDate, sysOrigin, statTimezones); + Map refreshed = countryDashboardBackfillLockService.call(() -> { + List countryTimezones = countryDashboardDailyMetricService.refreshRange( + parsedStartDate, parsedEndDate, sysOrigin, statTimezones); + List gameTimezones = countryDashboardGameMetricService.refreshRange( + parsedStartDate, parsedEndDate, sysOrigin, statTimezones); + return Map.of( + "countryStatTimezones", countryTimezones, + "gameStatTimezones", gameTimezones); + }); return Map.of( "status", "success", "startDate", parsedStartDate.toString(), "endDate", parsedEndDate.toString(), "sysOrigin", sysOrigin, - "statTimezones", refreshedTimezones); + "statTimezones", refreshed); } @PostMapping("/country-dashboard/game-metrics/backfill") @@ -137,8 +146,9 @@ public class DatavRestController extends BaseController { } LocalDate parsedStartDate = LocalDate.parse(startDate); LocalDate parsedEndDate = LocalDate.parse(endDate); - List refreshedTimezones = countryDashboardGameMetricService.refreshRange( - parsedStartDate, parsedEndDate, sysOrigin, statTimezones); + List refreshedTimezones = countryDashboardBackfillLockService.call( + () -> countryDashboardGameMetricService.refreshRange( + parsedStartDate, parsedEndDate, sysOrigin, statTimezones)); return Map.of( "status", "success", "startDate", parsedStartDate.toString(), diff --git a/rc-service/rc-service-console/console-application/pom.xml b/rc-service/rc-service-console/console-application/pom.xml index 9ae78c06..872b29ca 100644 --- a/rc-service/rc-service-console/console-application/pom.xml +++ b/rc-service/rc-service-console/console-application/pom.xml @@ -23,13 +23,19 @@ business-rocketmq - - easyexcel - com.alibaba - ${easyexcel.version} - - - + + easyexcel + com.alibaba + ${easyexcel.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + rc-service-console diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/scheduler/CountryDashboardDailyMetricTask.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/scheduler/CountryDashboardDailyMetricTask.java index 1a3f88f3..4b0f28af 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/scheduler/CountryDashboardDailyMetricTask.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/scheduler/CountryDashboardDailyMetricTask.java @@ -6,6 +6,7 @@ import com.red.circle.console.app.service.app.datav.CountryDashboardGameMetricSe import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Component; */ @Slf4j @Component +@ConditionalOnProperty(name = "red-circle.country-dashboard.refresh-enabled", havingValue = "true") @RequiredArgsConstructor public class CountryDashboardDailyMetricTask { diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImpl.java index 7e9c6cd0..33af9773 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImpl.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImpl.java @@ -37,7 +37,8 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class ResidentCpActivityServiceImpl implements ResidentCpActivityService { - private static final int CP_LEVEL_COUNT = 5; + private static final int CP_LEVEL_COUNT = 6; + private static final int CP_MIN_LEVEL = 0; private static final int QUERY_LIMIT = 50; private static final String CONFIG_GROUP_NAME = "CP"; private static final String CONFIG_DATA_TYPE = "int"; @@ -53,8 +54,8 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService String normalizedSysOrigin = normalizeSysOrigin(sysOrigin); List configs = listCpCabinConfigs(normalizedSysOrigin); List levels = new ArrayList<>(CP_LEVEL_COUNT); - for (int level = 1; level <= CP_LEVEL_COUNT; level++) { - SysCpCabinConfigDTO config = level <= configs.size() ? configs.get(level - 1) : null; + for (int level = CP_MIN_LEVEL; level < CP_LEVEL_COUNT; level++) { + SysCpCabinConfigDTO config = level < configs.size() ? configs.get(level) : null; levels.add(toLevelCO(level, config)); } return new ResidentCpConfigCO() @@ -76,8 +77,8 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService for (ResidentCpLevelConfigSaveCmd levelCmd : sortedLevels(cmd.getLevels())) { SysCpCabinConfigDTO config = Optional.ofNullable(levelCmd.getCabinConfigId()) .map(configById::get) - .orElseGet(() -> levelCmd.getLevel() <= configs.size() - ? configs.get(levelCmd.getLevel() - 1) + .orElseGet(() -> levelCmd.getLevel() < configs.size() + ? configs.get(levelCmd.getLevel()) : null); boolean exists = Objects.nonNull(config) && Objects.nonNull(config.getId()); if (!exists) { @@ -133,14 +134,14 @@ public class ResidentCpActivityServiceImpl implements ResidentCpActivityService ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin()); List levels = sortedLevels(cmd.getLevels()); ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, - "CP levels must contain exactly 5 items.", levels.size() == CP_LEVEL_COUNT); + "CP levels must contain exactly 6 items.", levels.size() == CP_LEVEL_COUNT); Set levelSet = new HashSet<>(); long previousRequiredValue = -1L; for (ResidentCpLevelConfigSaveCmd level : levels) { ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, - "CP level must be between 1 and 5.", - Objects.nonNull(level.getLevel()) && level.getLevel() >= 1 - && level.getLevel() <= CP_LEVEL_COUNT && levelSet.add(level.getLevel())); + "CP level must be between 0 and 5.", + Objects.nonNull(level.getLevel()) && level.getLevel() >= CP_MIN_LEVEL + && level.getLevel() < CP_LEVEL_COUNT && levelSet.add(level.getLevel())); long requiredValue = defaultRequiredValue(level.getRequiredIntimacyValue()); ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, "requiredIntimacyValue must be greater than or equal to zero.", requiredValue >= 0); diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardBackfillLockService.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardBackfillLockService.java new file mode 100644 index 00000000..4ba235b7 --- /dev/null +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardBackfillLockService.java @@ -0,0 +1,42 @@ +package com.red.circle.console.app.service.app.datav; + +import com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO; +import java.util.Objects; +import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * Coordinates dashboard backfill and CDC worker writes through a MySQL named lock. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CountryDashboardBackfillLockService { + + private static final String LOCK_NAME = "country_dashboard_backfill_lock"; + + private final CountryDashboardDAO countryDashboardDAO; + + @Value("${red-circle.country-dashboard.backfill-lock-timeout-seconds:10}") + private Integer lockTimeoutSeconds; + + public T call(Supplier supplier) { + Integer acquired = countryDashboardDAO.getDashboardBackfillLock( + LOCK_NAME, Math.max(Objects.requireNonNullElse(lockTimeoutSeconds, 10), 0)); + if (!Objects.equals(acquired, 1)) { + throw new IllegalStateException("country dashboard backfill lock is held by CDC worker"); + } + try { + return supplier.get(); + } finally { + try { + countryDashboardDAO.releaseDashboardBackfillLock(LOCK_NAME); + } catch (Exception ex) { + log.warn("release country dashboard backfill lock failed", ex); + } + } + } +} diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardDailyMetricService.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardDailyMetricService.java index 672b0fc7..e9fb4a90 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardDailyMetricService.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardDailyMetricService.java @@ -56,7 +56,7 @@ public class CountryDashboardDailyMetricService { @Value("${red-circle.country-dashboard.refresh-sys-origins:LIKEI}") private String refreshSysOrigins; - @Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh,UTC,Asia/Shanghai}") + @Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh}") private String refreshTimezones; private volatile boolean metricSchemaEnsured; @@ -486,7 +486,7 @@ public class CountryDashboardDailyMetricService { config = trimToNull(refreshTimezones); } if (config == null) { - config = "Asia/Riyadh,UTC,Asia/Shanghai"; + config = "Asia/Riyadh"; } Map zones = new LinkedHashMap<>(); diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardGameMetricServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardGameMetricServiceImpl.java index ef048476..f7eb4944 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardGameMetricServiceImpl.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardGameMetricServiceImpl.java @@ -56,7 +56,7 @@ public class CountryDashboardGameMetricServiceImpl implements CountryDashboardGa @Value("${red-circle.country-dashboard.refresh-sys-origins:LIKEI}") private String refreshSysOrigins; - @Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh,UTC,Asia/Shanghai}") + @Value("${red-circle.country-dashboard.refresh-timezones:Asia/Riyadh}") private String refreshTimezones; private volatile boolean metricSchemaEnsured; @@ -478,7 +478,7 @@ public class CountryDashboardGameMetricServiceImpl implements CountryDashboardGa config = trimToNull(refreshTimezones); } if (config == null) { - config = "Asia/Riyadh,UTC,Asia/Shanghai"; + config = "Asia/Riyadh"; } Map zones = new LinkedHashMap<>(); diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java index 3430d822..f0a227aa 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/datav/CountryDashboardServiceImpl.java @@ -72,6 +72,7 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { private final CountryDashboardDAO countryDashboardDAO; private final MongoTemplate mongoTemplate; + private volatile boolean rechargeDetailSchemaEnsured; @Override public CountryDashboardCO query(CountryDashboardQryCmd cmd) { @@ -82,15 +83,6 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { condition.periodType, condition.startTime, condition.endTime, condition.countryKeyword, condition.statTimezone, condition.sysOrigin)); - mergeMongo(rows, condition, listGiftConsume(condition), - (row, amount) -> row.setGiftConsume(add(row.getGiftConsume(), amount))); - mergeMongo(rows, condition, listLuckyGiftAnchorShare(condition), - (row, amount) -> row.setLuckyGiftAnchorShare(add(row.getLuckyGiftAnchorShare(), amount))); - mergeMongo(rows, condition, listBankSalaryTransfer(condition), - (row, amount) -> row.setSalaryTransfer(add(row.getSalaryTransfer(), amount))); - mergeDailyActive(rows, condition, listDailyActive(condition)); - mergeRetention(rows, condition); - List records = rows.values().stream() .peek(this::calculateFormulaFields) .sorted(Comparator.comparing(CountryDashboardMetricCO::getPeriodKey, @@ -294,18 +286,45 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { @Override public PageResult pageRechargeDetails( CountryDashboardRechargeDetailQryCmd cmd) { + ensureRechargeDetailSchema(); int cursor = Math.max(1, cmd == null || cmd.getCursor() == null ? 1 : cmd.getCursor()); int limit = Math.max(1, cmd == null || cmd.getLimit() == null ? 100 : cmd.getLimit()); limit = Math.min(100, limit); + QueryCondition condition = QueryCondition.of(cmd); + String countryCodes = trimToNull(cmd == null ? null : cmd.getCountryCodes()); + long total = safeLong(countryDashboardDAO.countRechargeDetails( + condition.startTime, condition.endTime, condition.countryKeyword, countryCodes, + condition.sysOrigin, condition.storageTimezoneOffset, condition.statTimezoneOffset)); + List records = total <= 0 ? List.of() + : countryDashboardDAO.listRechargeDetails( + condition.startTime, condition.endTime, condition.countryKeyword, countryCodes, + condition.sysOrigin, condition.storageTimezoneOffset, condition.statTimezoneOffset, + (cursor - 1) * limit, limit) + .stream() + .map(this::toRechargeDetailCO) + .toList(); PageResult result = new PageResult<>(); result.setCurrent(cursor); result.setSize(limit); - result.setTotal(0L); - result.setRecords(List.of()); + result.setTotal(total); + result.setRecords(records); return result; } + private void ensureRechargeDetailSchema() { + if (rechargeDetailSchemaEnsured) { + return; + } + synchronized (this) { + if (rechargeDetailSchemaEnsured) { + return; + } + countryDashboardDAO.createRechargeDetailTable(); + rechargeDetailSchemaEnsured = true; + } + } + private CountryDashboardRechargeDetailCO toRechargeDetailCO( CountryDashboardRechargeDetailDTO dto) { return new CountryDashboardRechargeDetailCO() diff --git a/rc-service/rc-service-console/console-application/src/test/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImplTest.java b/rc-service/rc-service-console/console-application/src/test/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImplTest.java new file mode 100644 index 00000000..b47bb751 --- /dev/null +++ b/rc-service/rc-service-console/console-application/src/test/java/com/red/circle/console/app/service/app/activity/ResidentCpActivityServiceImplTest.java @@ -0,0 +1,51 @@ +package com.red.circle.console.app.service.app.activity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpConfigCO; +import com.red.circle.console.app.dto.clienobject.app.activity.cp.ResidentCpLevelConfigCO; +import com.red.circle.console.app.service.app.sys.CpCabinConfigService; +import com.red.circle.framework.dto.PageResult; +import com.red.circle.other.inner.model.cmd.sys.SysCpCabinConfigQryCmd; +import com.red.circle.other.inner.model.dto.sys.SysCpCabinConfigDTO; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ResidentCpActivityServiceImplTest { + + @Test + void getConfigShouldReturnCpLevelsFromZeroToFive() { + CpCabinConfigService cpCabinConfigService = mock(CpCabinConfigService.class); + ResidentCpActivityServiceImpl service = new ResidentCpActivityServiceImpl( + cpCabinConfigService); + PageResult page = new PageResult<>(); + page.setRecords(List.of( + config(100L, 1L, 100L), + config(101L, 2L, 200L), + config(102L, 3L, 300L), + config(103L, 4L, 400L), + config(104L, 5L, 500L) + )); + when(cpCabinConfigService.getCpCabin(any(SysCpCabinConfigQryCmd.class))).thenReturn(page); + + ResidentCpConfigCO result = service.getConfig("likei"); + + assertEquals("LIKEI", result.getSysOrigin()); + assertEquals(List.of(0, 1, 2, 3, 4, 5), + result.getLevels().stream().map(ResidentCpLevelConfigCO::getLevel).toList()); + assertEquals(100L, result.getLevels().get(0).getRequiredIntimacyValue()); + assertEquals(100L, result.getLevels().get(0).getCabinConfigId()); + assertEquals(0L, result.getLevels().get(5).getRequiredIntimacyValue()); + } + + private static SysCpCabinConfigDTO config(Long id, Long sort, Long unlockValue) { + return new SysCpCabinConfigDTO() + .setId(id) + .setName("CP " + sort + "级") + .setCabinUnlockValue(unlockValue) + .setSort(sort); + } +} diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java index 7b2b5e3e..94129341 100644 --- a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dao/datav/CountryDashboardDAO.java @@ -30,8 +30,16 @@ public interface CountryDashboardDAO extends BaseDAO { int createGamePeriodUserMetricTable(); + int createRechargeDetailTable(); + int ensurePeriodMetricTimezoneSchema(); + Integer getDashboardBackfillLock( + @Param("lockName") String lockName, + @Param("timeoutSeconds") Integer timeoutSeconds); + + Integer releaseDashboardBackfillLock(@Param("lockName") String lockName); + List listNewUsers( @Param("periodType") String periodType, @Param("startTime") LocalDateTime startTime, diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml index 6f2f9c77..d993fe5a 100644 --- a/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml +++ b/rc-service/rc-service-console/console-infrastructure/src/main/resources/dao/datav/CountryDashboardDAO.xml @@ -3,6 +3,14 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + + CREATE TABLE IF NOT EXISTS `country_dashboard_daily_metric` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', @@ -181,6 +189,28 @@ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏游戏维度周期用户去重明细' + + CREATE TABLE IF NOT EXISTS `country_dashboard_recharge_detail` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `source_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '充值来源', + `record_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '来源记录ID', + `source_channel` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '来源渠道', + `sys_origin` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '系统来源', + `user_id` bigint NOT NULL COMMENT '用户ID', + `country_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家码', + `country_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家名称', + `amount` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT 'USD金额', + `coin_quantity` decimal(20,2) DEFAULT NULL COMMENT '金币数量', + `remark` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '备注', + `create_time` datetime NOT NULL COMMENT '业务创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_recharge_source_record` (`source_type`, `record_id`), + KEY `idx_recharge_query` (`sys_origin`, `create_time`, `country_code`), + KEY `idx_recharge_user` (`user_id`, `create_time`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏充值明细预聚合' + + SET @country_dashboard_schema_lock = GET_LOCK('country_dashboard_period_timezone_schema', 60); SET @current_schema = DATABASE(); @@ -1423,41 +1453,68 @@ diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExe.java index 0ab5ea73..170a295f 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExe.java @@ -29,7 +29,7 @@ import org.springframework.stereotype.Component; public class UserCpRightsConfigQueryExe { private static final String RELATIONSHIP_STATUS_NONE = "NONE"; - private static final int CP_RIGHTS_LEVEL_COUNT = 5; + private static final int CP_RIGHTS_LEVEL_COUNT = 6; private final CpCabinConfigService cpCabinConfigService; private final CpRelationshipService cpRelationshipService; @@ -106,7 +106,7 @@ public class UserCpRightsConfigQueryExe { : configs; return IntStream.range(0, CP_RIGHTS_LEVEL_COUNT) .mapToObj(index -> toLevel(relationType, getLevelConfig(resolvedConfigs, index), - index + 1, hasRelationship, + index, hasRelationship, intimacyValue)) .toList(); } diff --git a/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExeTest.java b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExeTest.java new file mode 100644 index 00000000..e01ceea9 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/test/java/com/red/circle/other/app/command/user/query/UserCpRightsConfigQueryExeTest.java @@ -0,0 +1,92 @@ +package com.red.circle.other.app.command.user.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.red.circle.framework.core.dto.ReqSysOrigin; +import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpRightsConfigCO; +import com.red.circle.other.app.dto.cmd.user.relation.cp.CpRightsConfigQueryCmd; +import com.red.circle.other.infra.database.rds.entity.user.user.CpCabinConfig; +import com.red.circle.other.infra.database.rds.entity.user.user.CpRelationship; +import com.red.circle.other.infra.database.rds.entity.user.user.CpValue; +import com.red.circle.other.infra.database.rds.service.user.user.CpCabinConfigService; +import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService; +import com.red.circle.other.infra.database.rds.service.user.user.CpValueService; +import com.red.circle.other.infra.enums.user.user.CpRelationshipStatus; +import java.math.BigDecimal; +import java.util.List; +import org.junit.jupiter.api.Test; + +class UserCpRightsConfigQueryExeTest { + + @Test + void formedRelationRightsLevelShouldStartFromZero() { + Fixture fixture = new Fixture(); + + for (String relationType : List.of("CP", "BROTHER", "SISTERS")) { + CpRightsConfigCO result = fixture.exe.execute(cmd(relationType)); + + assertEquals(relationType, result.getRelationType()); + assertEquals("NORMAL", result.getRelationshipStatus()); + assertEquals(0L, result.getIntimacyValue()); + assertEquals(6, result.getLevels().size()); + assertEquals(List.of(0, 1, 2, 3, 4, 5), + result.getLevels().stream().map(CpRightsConfigCO.LevelCO::getLevel).toList()); + assertEquals(relationType + "_CABIN_LEVEL_0", + result.getLevels().get(0).getRights().get(0).getRightKey()); + assertTrue(result.getLevels().get(0).getUnlocked()); + assertTrue(result.getLevels().get(0).getRights().get(0).getUnlocked()); + } + } + + private static CpRightsConfigQueryCmd cmd(String relationType) { + CpRightsConfigQueryCmd cmd = new CpRightsConfigQueryCmd(); + cmd.setReqUserId(1001L); + cmd.setReqSysOrigin(ReqSysOrigin.of("LIKEI")); + cmd.setRelationType(relationType); + return cmd; + } + + private static class Fixture { + + private final CpCabinConfigService cpCabinConfigService = mock(CpCabinConfigService.class); + private final CpRelationshipService cpRelationshipService = mock(CpRelationshipService.class); + private final CpValueService cpValueService = mock(CpValueService.class); + private final UserCpRightsConfigQueryExe exe = new UserCpRightsConfigQueryExe( + cpCabinConfigService, cpRelationshipService, cpValueService); + + private Fixture() { + for (String relationType : List.of("CP", "BROTHER", "SISTERS")) { + when(cpRelationshipService.getByUserIdOrCpUserId(1001L, relationType)) + .thenReturn(relationship(relationType)); + } + when(cpValueService.getById(3001L)).thenReturn(new CpValue().setCpVal(BigDecimal.ZERO)); + when(cpCabinConfigService.getCpRightsLevelConfig("LIKEI")).thenReturn(List.of( + levelConfig("Level Zero", 0L), + levelConfig("Level One", 100L), + levelConfig("Level Two", 200L), + levelConfig("Level Three", 300L), + levelConfig("Level Four", 400L), + levelConfig("Level Five", 500L) + )); + } + + private static CpRelationship relationship(String relationType) { + return new CpRelationship() + .setUserId(1001L) + .setCpUserId(2001L) + .setCpValId(3001L) + .setRelationType(relationType) + .setStatus(CpRelationshipStatus.NORMAL.name()); + } + + private static CpCabinConfig levelConfig(String name, Long cabinUnlockValue) { + return new CpCabinConfig() + .setName(name) + .setCabinUnlockValue(cabinUnlockValue) + .setCover("https://example.com/" + name + ".png"); + } + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/user/relation/cp/CpRightsConfigCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/user/relation/cp/CpRightsConfigCO.java index 2113e6d7..fa668dcb 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/user/relation/cp/CpRightsConfigCO.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/user/relation/cp/CpRightsConfigCO.java @@ -62,7 +62,7 @@ public class CpRightsConfigCO extends ClientObject { public static class LevelCO { /** - * Level number. + * Level number, starts from 0. */ private Integer level; diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpCabinConfigServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpCabinConfigServiceImpl.java index fe160f52..248cdb14 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpCabinConfigServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/user/user/impl/CpCabinConfigServiceImpl.java @@ -24,7 +24,7 @@ public class CpCabinConfigServiceImpl extends BaseServiceImpl implements CpCabinConfigService { - private static final int CP_RIGHTS_LEVEL_COUNT = 5; + private static final int CP_RIGHTS_LEVEL_COUNT = 6; @Override public List getCpCabinConfig(String sysOriginName) { diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/props/ActivitySourceGroupGatewayImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/props/ActivitySourceGroupGatewayImpl.java index f4fe27a7..7661e137 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/props/ActivitySourceGroupGatewayImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/props/ActivitySourceGroupGatewayImpl.java @@ -414,8 +414,8 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa private Long firstVipResourceId(VipLevelConfig vipConfig) { return Stream.of( - vipConfig.getBadgeResourceId(), vipConfig.getShortBadgeResourceId(), + vipConfig.getBadgeResourceId(), vipConfig.getAvatarFrameResourceId(), vipConfig.getEntryEffectResourceId(), vipConfig.getChatBubbleResourceId(), @@ -431,12 +431,17 @@ public class ActivitySourceGroupGatewayImpl implements ActivitySourceGroupGatewa private String resolveVipCover(VipLevelConfig vipConfig, Map vipCoverMap) { PropsSourceRecord sourceRecord = vipCoverMap.get(firstVipResourceId(vipConfig)); - if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getCover())) { + if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getCover()) + && !sourceRecord.getCover().isBlank()) { return sourceRecord.getCover(); } + if (Objects.nonNull(sourceRecord) && Objects.nonNull(sourceRecord.getSourceUrl()) + && !sourceRecord.getSourceUrl().isBlank()) { + return sourceRecord.getSourceUrl(); + } return Stream.of( - vipConfig.getBadgeUrl(), vipConfig.getShortBadgeUrl(), + vipConfig.getBadgeUrl(), vipConfig.getAvatarFrameUrl(), vipConfig.getEntryEffectUrl(), vipConfig.getChatBubbleUrl(), diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/material/props/impl/PropsActivityRewardGroupClientServiceImpl.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/material/props/impl/PropsActivityRewardGroupClientServiceImpl.java index ca61214f..3bd230ef 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/material/props/impl/PropsActivityRewardGroupClientServiceImpl.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/material/props/impl/PropsActivityRewardGroupClientServiceImpl.java @@ -392,8 +392,8 @@ public class PropsActivityRewardGroupClientServiceImpl implements private Long firstVipResourceId(VipLevelConfig vipConfig) { return Stream.of( - vipConfig.getBadgeResourceId(), vipConfig.getShortBadgeResourceId(), + vipConfig.getBadgeResourceId(), vipConfig.getAvatarFrameResourceId(), vipConfig.getEntryEffectResourceId(), vipConfig.getChatBubbleResourceId(), @@ -422,8 +422,8 @@ public class PropsActivityRewardGroupClientServiceImpl implements return sourceRecord.getSourceUrl(); } return Stream.of( - vipConfig.getBadgeUrl(), vipConfig.getShortBadgeUrl(), + vipConfig.getBadgeUrl(), vipConfig.getAvatarFrameUrl(), vipConfig.getEntryEffectUrl(), vipConfig.getChatBubbleUrl(),