From 7299b1c0881a16316c2f828be5ab04e7f4abfd65 Mon Sep 17 00:00:00 2001 From: hy001 Date: Sat, 23 May 2026 13:27:45 +0800 Subject: [PATCH] fix: precompute country dashboard active metrics --- .../CountryDashboardDailyMetricService.java | 183 ++++++++++++++++++ .../datav/CountryDashboardServiceImpl.java | 23 +++ .../rds/dao/datav/CountryDashboardDAO.java | 2 + .../dto/datav/CountryDashboardMetricDTO.java | 14 ++ .../CountryDashboardPeriodMetricDTO.java | 14 ++ .../dao/datav/CountryDashboardDAO.xml | 159 ++++++++++++++- 6 files changed, 393 insertions(+), 2 deletions(-) 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 e9fb4a90..4ed4d81e 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 @@ -5,6 +5,7 @@ import com.red.circle.console.infra.database.rds.dao.datav.CountryDashboardDAO; import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardDailyMetricDTO; import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardMetricDTO; import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardPeriodMetricDTO; +import com.red.circle.console.infra.database.rds.dto.datav.CountryDashboardRetentionUserDTO; import java.math.BigDecimal; import java.time.DayOfWeek; import java.time.Instant; @@ -17,6 +18,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.WeekFields; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedHashMap; import java.util.List; @@ -26,7 +28,13 @@ import java.util.Objects; import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionTemplate; @@ -49,9 +57,12 @@ public class CountryDashboardDailyMetricService { private static final ZoneId STORAGE_ZONE_ID = ZoneId.of("Asia/Riyadh"); private static final String STORAGE_TIMEZONE = "Asia/Riyadh"; private static final String STORAGE_TIMEZONE_OFFSET = "+03:00"; + private static final String COLLECTION_USER_DAILY_ACTIVE_LOG = "user_daily_active_log"; + private static final int USER_BATCH_SIZE = 1000; private final CountryDashboardDAO countryDashboardDAO; private final TransactionTemplate transactionTemplate; + private final MongoTemplate mongoTemplate; @Value("${red-circle.country-dashboard.refresh-sys-origins:LIKEI}") private String refreshSysOrigins; @@ -159,6 +170,7 @@ public class CountryDashboardDailyMetricService { countryDashboardDAO.createPeriodMetricTable(); countryDashboardDAO.createPeriodUserMetricTable(); countryDashboardDAO.ensurePeriodMetricTimezoneSchema(); + countryDashboardDAO.ensurePeriodMetricUserColumnsSchema(); metricSchemaEnsured = true; log.info("country dashboard metric tables ensured"); } @@ -284,6 +296,9 @@ public class CountryDashboardDailyMetricService { mergePeriodAmounts(rows, countryDashboardDAO.listLuckyBoxGame( window.periodType(), startTime, endTime, null, sysOrigin, STORAGE_TIMEZONE_OFFSET, statTimezone.offset()), window, sysOrigin, statTimezone); + mergePeriodDailyActive(rows, listDailyActive(window, sysOrigin), window, sysOrigin, + statTimezone); + mergePeriodRetention(rows, window, sysOrigin, statTimezone, startTime, endTime); RefreshWriteResult writeResult = transactionTemplate.execute(status -> { countryDashboardDAO.deletePeriodUserMetrics(window.periodType(), window.periodKey(), @@ -404,6 +419,159 @@ public class CountryDashboardDailyMetricService { } } + private void mergePeriodDailyActive(Map rows, + List metrics, PeriodWindow window, String sysOrigin, + StatTimezone statTimezone) { + if (metrics == null || metrics.isEmpty()) { + return; + } + for (CountryDashboardMetricDTO metric : metrics) { + CountryDashboardPeriodMetricDTO row = getPeriodRow(rows, metric, window, sysOrigin, + statTimezone); + row.setDailyActiveUser(safeLong(row.getDailyActiveUser()) + safeLong(metric.getDailyActiveUser())); + } + } + + private void mergePeriodRetention(Map rows, + PeriodWindow window, String sysOrigin, StatTimezone statTimezone, LocalDateTime startTime, + LocalDateTime endTime) { + List cohorts = countryDashboardDAO.listNewUserCohorts( + window.periodType(), startTime, endTime, null, sysOrigin, + STORAGE_TIMEZONE_OFFSET, statTimezone.offset()); + if (cohorts == null || cohorts.isEmpty()) { + return; + } + + LocalDate today = LocalDate.now(statTimezone.zoneId()); + Set userIds = new HashSet<>(); + Set activeDates = new HashSet<>(); + for (CountryDashboardRetentionUserDTO cohort : cohorts) { + if (cohort.getUserId() == null || !hasText(cohort.getRegisterDate())) { + continue; + } + userIds.add(cohort.getUserId()); + collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 1, today); + collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 7, today); + collectRetentionActiveDate(activeDates, cohort.getRegisterDate(), 30, today); + } + + Set activeUserDates = listActiveUserDateSet(userIds, activeDates, sysOrigin); + for (CountryDashboardRetentionUserDTO cohort : cohorts) { + if (cohort.getUserId() == null || !hasText(cohort.getRegisterDate())) { + continue; + } + CountryDashboardMetricDTO metric = new CountryDashboardMetricDTO() + .setCountryCode(cohort.getCountryCode()) + .setCountryName(cohort.getCountryName()); + CountryDashboardPeriodMetricDTO row = getPeriodRow(rows, metric, window, sysOrigin, + statTimezone); + mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 1, today, + activeUserDates); + mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 7, today, + activeUserDates); + mergeRetentionDay(row, cohort.getUserId(), cohort.getRegisterDate(), 30, today, + activeUserDates); + } + } + + private List listDailyActive(PeriodWindow window, String sysOrigin) { + if (window.periodStartDate() == null || window.periodEndDate() == null) { + return List.of(); + } + List criteria = new ArrayList<>(); + criteria.add(Criteria.where("activeDate") + .gte(window.periodStartDate().format(DATE_FORMATTER)) + .lt(window.periodEndDate().format(DATE_FORMATTER))); + if (hasText(sysOrigin)) { + criteria.add(Criteria.where("sysOrigin").is(sysOrigin)); + } + + List operations = new ArrayList<>(); + operations.add(Aggregation.match(new Criteria().andOperator(criteria.toArray(new Criteria[0])))); + operations.add(Aggregation.group("registerCountryCode", "userId")); + operations.add(Aggregation.group("_id.registerCountryCode").count().as("dailyActiveUser")); + + return mongoTemplate.aggregate(Aggregation.newAggregation(operations), + COLLECTION_USER_DAILY_ACTIVE_LOG, Document.class) + .getMappedResults() + .stream() + .map(this::toDailyActiveMetric) + .filter(Objects::nonNull) + .toList(); + } + + private CountryDashboardMetricDTO toDailyActiveMetric(Document document) { + String countryCode = normalizeCountryCode(document.get("_id")); + Long dailyActiveUser = toLong(document.get("dailyActiveUser")); + if (dailyActiveUser == null || dailyActiveUser <= 0) { + return null; + } + return new CountryDashboardMetricDTO() + .setCountryCode(countryCode) + .setCountryName(countryCode) + .setDailyActiveUser(dailyActiveUser); + } + + private void collectRetentionActiveDate(Set activeDates, String registerDate, + int retentionDays, LocalDate today) { + LocalDate targetDate = LocalDate.parse(registerDate, DATE_FORMATTER).plusDays(retentionDays); + if (!targetDate.isAfter(today)) { + activeDates.add(targetDate.format(DATE_FORMATTER)); + } + } + + private void mergeRetentionDay(CountryDashboardPeriodMetricDTO row, Long userId, + String registerDate, int retentionDays, LocalDate today, Set activeUserDates) { + LocalDate targetDate = LocalDate.parse(registerDate, DATE_FORMATTER).plusDays(retentionDays); + if (targetDate.isAfter(today)) { + return; + } + String activeKey = userId + "|" + targetDate.format(DATE_FORMATTER); + boolean retained = activeUserDates.contains(activeKey); + if (retentionDays == 1) { + row.setD1RetentionBaseUser(safeLong(row.getD1RetentionBaseUser()) + 1); + row.setD1RetentionUser(safeLong(row.getD1RetentionUser()) + (retained ? 1 : 0)); + return; + } + if (retentionDays == 7) { + row.setD7RetentionBaseUser(safeLong(row.getD7RetentionBaseUser()) + 1); + row.setD7RetentionUser(safeLong(row.getD7RetentionUser()) + (retained ? 1 : 0)); + return; + } + row.setD30RetentionBaseUser(safeLong(row.getD30RetentionBaseUser()) + 1); + row.setD30RetentionUser(safeLong(row.getD30RetentionUser()) + (retained ? 1 : 0)); + } + + private Set listActiveUserDateSet(Set userIds, Set activeDates, + String sysOrigin) { + Set result = new HashSet<>(); + if (userIds == null || userIds.isEmpty() || activeDates == null || activeDates.isEmpty()) { + return result; + } + List ids = new ArrayList<>(userIds); + for (int start = 0; start < ids.size(); start += USER_BATCH_SIZE) { + int end = Math.min(start + USER_BATCH_SIZE, ids.size()); + List criteria = new ArrayList<>(); + criteria.add(Criteria.where("userId").in(ids.subList(start, end))); + criteria.add(Criteria.where("activeDate").in(activeDates)); + if (hasText(sysOrigin)) { + criteria.add(Criteria.where("sysOrigin").is(sysOrigin)); + } + Query query = Query.query(new Criteria().andOperator(criteria.toArray(new Criteria[0]))); + query.fields().include("userId").include("activeDate"); + List documents = mongoTemplate.find(query, Document.class, + COLLECTION_USER_DAILY_ACTIVE_LOG); + for (Document document : documents) { + Long userId = toLong(document.get("userId")); + String activeDate = Objects.toString(document.get("activeDate"), null); + if (userId != null && hasText(activeDate)) { + result.add(userId + "|" + activeDate); + } + } + } + return result; + } + private CountryDashboardDailyMetricDTO getRow(Map rows, CountryDashboardMetricDTO metric, LocalDate statDate, String sysOrigin) { String countryCode = hasText(metric.getCountryCode()) ? metric.getCountryCode() : "UNKNOWN"; @@ -559,6 +727,21 @@ public class CountryDashboardDailyMetricService { return value == null ? 0L : value; } + private static Long toLong(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value == null) { + return null; + } + return Long.valueOf(Objects.toString(value)); + } + + private static String normalizeCountryCode(Object value) { + String countryCode = Objects.toString(value, "").trim(); + return countryCode.isEmpty() ? "UNKNOWN" : countryCode; + } + private static boolean hasText(String value) { return value != null && !value.trim().isEmpty(); } 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 fb134cf3..0202d9c4 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 @@ -73,9 +73,11 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { private final CountryDashboardDAO countryDashboardDAO; private final MongoTemplate mongoTemplate; private volatile boolean rechargeDetailSchemaEnsured; + private volatile boolean periodMetricSchemaEnsured; @Override public CountryDashboardCO query(CountryDashboardQryCmd cmd) { + ensurePeriodMetricSchema(); QueryCondition condition = QueryCondition.of(cmd); Map rows = new LinkedHashMap<>(); @@ -325,6 +327,20 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { } } + private void ensurePeriodMetricSchema() { + if (periodMetricSchemaEnsured) { + return; + } + synchronized (this) { + if (periodMetricSchemaEnsured) { + return; + } + countryDashboardDAO.createPeriodMetricTable(); + countryDashboardDAO.ensurePeriodMetricUserColumnsSchema(); + periodMetricSchemaEnsured = true; + } + } + private CountryDashboardRechargeDetailCO toRechargeDetailCO( CountryDashboardRechargeDetailDTO dto) { return new CountryDashboardRechargeDetailCO() @@ -351,6 +367,7 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { CountryDashboardMetricCO row = getRow(rows, metric.getCountryCode(), metric.getCountryName(), metric.getPeriodKey(), metric.getPeriodName()); row.setCountryNewUser(row.getCountryNewUser() + safeLong(metric.getCountryNewUser())); + row.setDailyActiveUser(row.getDailyActiveUser() + safeLong(metric.getDailyActiveUser())); row.setNewUserRecharge(add(row.getNewUserRecharge(), metric.getNewUserRecharge())); row.setOfficialRecharge(add(row.getOfficialRecharge(), metric.getOfficialRecharge())); row.setMifapayRecharge(add(row.getMifapayRecharge(), metric.getMifapayRecharge())); @@ -363,6 +380,12 @@ public class CountryDashboardServiceImpl implements CountryDashboardService { row.setGameTotalFlow(add(row.getGameTotalFlow(), metric.getGameTotalFlow())); row.setGameUser(row.getGameUser() + safeLong(metric.getGameUser())); row.setGamePayout(add(row.getGamePayout(), metric.getGamePayout())); + row.setD1RetentionUser(row.getD1RetentionUser() + safeLong(metric.getD1RetentionUser())); + row.setD1RetentionBaseUser(row.getD1RetentionBaseUser() + safeLong(metric.getD1RetentionBaseUser())); + row.setD7RetentionUser(row.getD7RetentionUser() + safeLong(metric.getD7RetentionUser())); + row.setD7RetentionBaseUser(row.getD7RetentionBaseUser() + safeLong(metric.getD7RetentionBaseUser())); + row.setD30RetentionUser(row.getD30RetentionUser() + safeLong(metric.getD30RetentionUser())); + row.setD30RetentionBaseUser(row.getD30RetentionBaseUser() + safeLong(metric.getD30RetentionBaseUser())); } } 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 94129341..1e04fac0 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 @@ -34,6 +34,8 @@ public interface CountryDashboardDAO extends BaseDAO { int ensurePeriodMetricTimezoneSchema(); + int ensurePeriodMetricUserColumnsSchema(); + Integer getDashboardBackfillLock( @Param("lockName") String lockName, @Param("timeoutSeconds") Integer timeoutSeconds); diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java index 739112cc..65652d6e 100644 --- a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardMetricDTO.java @@ -21,6 +21,8 @@ public class CountryDashboardMetricDTO { private Long countryNewUser; + private Long dailyActiveUser; + private BigDecimal newUserRecharge; private BigDecimal officialRecharge; @@ -44,4 +46,16 @@ public class CountryDashboardMetricDTO { private Long gameUser; private BigDecimal gamePayout; + + private Long d1RetentionUser; + + private Long d1RetentionBaseUser; + + private Long d7RetentionUser; + + private Long d7RetentionBaseUser; + + private Long d30RetentionUser; + + private Long d30RetentionBaseUser; } diff --git a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardPeriodMetricDTO.java b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardPeriodMetricDTO.java index 823ab80f..7f272971 100644 --- a/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardPeriodMetricDTO.java +++ b/rc-service/rc-service-console/console-infrastructure/src/main/java/com/red/circle/console/infra/database/rds/dto/datav/CountryDashboardPeriodMetricDTO.java @@ -32,6 +32,8 @@ public class CountryDashboardPeriodMetricDTO { private Long countryNewUser; + private Long dailyActiveUser; + private BigDecimal newUserRecharge; private BigDecimal officialRecharge; @@ -55,4 +57,16 @@ public class CountryDashboardPeriodMetricDTO { private Long gameUser; private BigDecimal gamePayout; + + private Long d1RetentionUser; + + private Long d1RetentionBaseUser; + + private Long d7RetentionUser; + + private Long d7RetentionBaseUser; + + private Long d30RetentionUser; + + private Long d30RetentionBaseUser; } 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 d993fe5a..2f6355b4 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 @@ -55,6 +55,7 @@ `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 '国家名称', `country_new_user` bigint NOT NULL DEFAULT 0 COMMENT '新增用户数', + `daily_active_user` bigint NOT NULL DEFAULT 0 COMMENT '当日活跃用户数', `new_user_recharge` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '新增用户充值', `official_recharge` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '官方充值', `mifapay_recharge` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT 'MifaPay充值', @@ -67,6 +68,12 @@ `game_total_flow` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '游戏流水', `game_user` bigint NOT NULL DEFAULT 0 COMMENT '游戏用户数', `game_payout` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '游戏返奖', + `d1_retention_user` bigint NOT NULL DEFAULT 0 COMMENT '次日留存用户数', + `d1_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT '次日留存基数', + `d7_retention_user` bigint NOT NULL DEFAULT 0 COMMENT '7日留存用户数', + `d7_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT '7日留存基数', + `d30_retention_user` bigint NOT NULL DEFAULT 0 COMMENT '30日留存用户数', + `d30_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT '30日留存基数', `refreshed_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最近刷新时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', @@ -454,6 +461,118 @@ DO IF(@country_dashboard_schema_lock = 1, RELEASE_LOCK('country_dashboard_period_timezone_schema'), 0); + + SET @country_dashboard_user_columns_schema_lock = GET_LOCK('country_dashboard_period_user_columns_schema', 60); + SET @current_schema = DATABASE(); + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'daily_active_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `daily_active_user` bigint NOT NULL DEFAULT 0 COMMENT ''当日活跃用户数'' AFTER `country_new_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd1_retention_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d1_retention_user` bigint NOT NULL DEFAULT 0 COMMENT ''次日留存用户数'' AFTER `game_payout`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd1_retention_base_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d1_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT ''次日留存基数'' AFTER `d1_retention_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd7_retention_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d7_retention_user` bigint NOT NULL DEFAULT 0 COMMENT ''7日留存用户数'' AFTER `d1_retention_base_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd7_retention_base_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d7_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT ''7日留存基数'' AFTER `d7_retention_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd30_retention_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d30_retention_user` bigint NOT NULL DEFAULT 0 COMMENT ''30日留存用户数'' AFTER `d7_retention_base_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + SET @ddl = IF( + @country_dashboard_user_columns_schema_lock = 1 + AND NOT EXISTS ( + SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'country_dashboard_period_metric' + AND COLUMN_NAME = 'd30_retention_base_user' + ), + 'ALTER TABLE `country_dashboard_period_metric` ADD COLUMN `d30_retention_base_user` bigint NOT NULL DEFAULT 0 COMMENT ''30日留存基数'' AFTER `d30_retention_user`', + 'SELECT 1' + ); + PREPARE stmt FROM @ddl; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + + DO IF(@country_dashboard_user_columns_schema_lock = 1, RELEASE_LOCK('country_dashboard_period_user_columns_schema'), 0); + + NULLIF(u.country_code, '') AS countryCode, NULLIF(u.country_name, '') AS countryName @@ -917,6 +1036,7 @@ t.period_key AS periodKey, MAX(t.period_name) AS periodName, IFNULL(SUM(t.country_new_user), 0) AS countryNewUser, + IFNULL(SUM(t.daily_active_user), 0) AS dailyActiveUser, IFNULL(SUM(t.new_user_recharge), 0) AS newUserRecharge, IFNULL(SUM(t.official_recharge), 0) AS officialRecharge, IFNULL(SUM(t.mifapay_recharge), 0) AS mifapayRecharge, @@ -928,7 +1048,13 @@ IFNULL(SUM(t.lucky_gift_payout), 0) AS luckyGiftPayout, IFNULL(SUM(t.game_total_flow), 0) AS gameTotalFlow, IFNULL(SUM(t.game_user), 0) AS gameUser, - IFNULL(SUM(t.game_payout), 0) AS gamePayout + IFNULL(SUM(t.game_payout), 0) AS gamePayout, + IFNULL(SUM(t.d1_retention_user), 0) AS d1RetentionUser, + IFNULL(SUM(t.d1_retention_base_user), 0) AS d1RetentionBaseUser, + IFNULL(SUM(t.d7_retention_user), 0) AS d7RetentionUser, + IFNULL(SUM(t.d7_retention_base_user), 0) AS d7RetentionBaseUser, + IFNULL(SUM(t.d30_retention_user), 0) AS d30RetentionUser, + IFNULL(SUM(t.d30_retention_base_user), 0) AS d30RetentionBaseUser FROM country_dashboard_period_metric t WHERE t.period_type = #{periodType} AND t.stat_timezone = #{statTimezone} @@ -1071,6 +1197,7 @@ country_code, country_name, country_new_user, + daily_active_user, new_user_recharge, official_recharge, mifapay_recharge, @@ -1083,6 +1210,12 @@ game_total_flow, game_user, game_payout, + d1_retention_user, + d1_retention_base_user, + d7_retention_user, + d7_retention_base_user, + d30_retention_user, + d30_retention_base_user, refreshed_at ) VALUES @@ -1098,6 +1231,7 @@ #{item.countryCode}, #{item.countryName}, IFNULL(#{item.countryNewUser}, 0), + IFNULL(#{item.dailyActiveUser}, 0), IFNULL(#{item.newUserRecharge}, 0), IFNULL(#{item.officialRecharge}, 0), IFNULL(#{item.mifapayRecharge}, 0), @@ -1110,6 +1244,12 @@ IFNULL(#{item.gameTotalFlow}, 0), IFNULL(#{item.gameUser}, 0), IFNULL(#{item.gamePayout}, 0), + IFNULL(#{item.d1RetentionUser}, 0), + IFNULL(#{item.d1RetentionBaseUser}, 0), + IFNULL(#{item.d7RetentionUser}, 0), + IFNULL(#{item.d7RetentionBaseUser}, 0), + IFNULL(#{item.d30RetentionUser}, 0), + IFNULL(#{item.d30RetentionBaseUser}, 0), NOW() ) @@ -1119,6 +1259,7 @@ period_end_date = VALUES(period_end_date), country_name = VALUES(country_name), country_new_user = VALUES(country_new_user), + daily_active_user = VALUES(daily_active_user), new_user_recharge = VALUES(new_user_recharge), official_recharge = VALUES(official_recharge), mifapay_recharge = VALUES(mifapay_recharge), @@ -1131,6 +1272,12 @@ game_total_flow = VALUES(game_total_flow), game_user = VALUES(game_user), game_payout = VALUES(game_payout), + d1_retention_user = VALUES(d1_retention_user), + d1_retention_base_user = VALUES(d1_retention_base_user), + d7_retention_user = VALUES(d7_retention_user), + d7_retention_base_user = VALUES(d7_retention_base_user), + d30_retention_user = VALUES(d30_retention_user), + d30_retention_base_user = VALUES(d30_retention_base_user), refreshed_at = NOW() @@ -1324,6 +1471,8 @@ MAX(t.country_name) AS countryName, 'ALL' AS periodKey, '全部' AS periodName, + IFNULL(SUM(t.country_new_user), 0) AS countryNewUser, + IFNULL(SUM(t.daily_active_user), 0) AS dailyActiveUser, IFNULL(SUM(t.new_user_recharge), 0) AS newUserRecharge, IFNULL(SUM(t.official_recharge), 0) AS officialRecharge, IFNULL(SUM(t.mifapay_recharge), 0) AS mifapayRecharge, @@ -1333,7 +1482,13 @@ IFNULL(SUM(t.lucky_gift_total_flow), 0) AS luckyGiftTotalFlow, IFNULL(SUM(t.lucky_gift_payout), 0) AS luckyGiftPayout, IFNULL(SUM(t.game_total_flow), 0) AS gameTotalFlow, - IFNULL(SUM(t.game_payout), 0) AS gamePayout + IFNULL(SUM(t.game_payout), 0) AS gamePayout, + IFNULL(SUM(t.d1_retention_user), 0) AS d1RetentionUser, + IFNULL(SUM(t.d1_retention_base_user), 0) AS d1RetentionBaseUser, + IFNULL(SUM(t.d7_retention_user), 0) AS d7RetentionUser, + IFNULL(SUM(t.d7_retention_base_user), 0) AS d7RetentionBaseUser, + IFNULL(SUM(t.d30_retention_user), 0) AS d30RetentionUser, + IFNULL(SUM(t.d30_retention_base_user), 0) AS d30RetentionBaseUser FROM country_dashboard_period_metric t WHERE t.period_type = 'DAY' AND t.stat_timezone = #{statTimezone}