yumi-java/rc-service/rc-service-console/docs/country-dashboard-game-metrics-design.md
2026-05-12 20:12:16 +08:00

13 KiB
Raw Blame History

Admin 数据大屏游戏数据表技术方案

目标

在新 admin 的 统计管理 / 数据大屏 中增加独立的“游戏数据”表,用于按日期、国家、游戏维度查看消耗和收益数据。

页面查询不能直接扫描 likei_wallet.wallet_gold_asset_record_1..12、第三方回调日志或游戏明细表。所有 admin 查询默认读取游戏维度预聚合表;只有后台刷新任务和手动回填任务允许按时间窗口扫描原始数据。

现状

当前国家数据大屏入口:

  • 前端路由:chatapp3-admin/apps/src/router/routes/modules/statistics.ts
  • 前端页面:chatapp3-admin/apps/src/views/statistics/datav.vue
  • 前端接口:chatapp3-admin/apps/src/api/legacy/datav.ts
  • 后端 ControllerDatavRestController
  • 后端 ServiceCountryDashboardServiceImpl
  • 后端 MapperCountryDashboardDAO.xml
  • 现有查询文档:chatapp3-java/rc-service/rc-service-console/docs/country-dashboard-data-query.md

现有大屏已经聚合了国家维度的 game_total_flowgame_payoutgame_user,但没有保留游戏维度。新增功能不改动现有总览口径,新增一套游戏维度查询和预聚合表。

页面设计

datav.vue 的表格区域增加第三个 tab

  • 指标总览
  • 充值相关数据
  • 游戏数据

“游戏数据”表复用页面已有筛选项:

  • 系统:sysOrigin
  • 统计时区:statTimezone
  • 周期:periodType
  • 日期范围:startDate/endDate
  • 国家多选:countryCodes

新增游戏筛选项:

  • gameProvider:游戏厂商,支持 INTERNALBAISHUNGAME_OPENLINGXIAN,可为空。
  • gameKeyword:游戏 ID 或游戏名称模糊搜索。

表格字段:

字段 说明
日期/周期 periodKeyDAY 显示日期WEEK/MONTH/ALL 显示周期
国家 countryCode / countryName
游戏厂商 gameProvider
游戏 ID gameId
游戏名称 gameName
消耗 用户玩游戏支出,现有钱包口径为 type = 1
返奖 游戏返还给用户,现有钱包口径为 type = 0
收益 consumeAmount - payoutAmount
返奖率 payoutAmount / consumeAmount
收益率 profitAmount / consumeAmount
游戏用户数 周期内去重用户数
流水笔数 周期内消耗和返奖记录数
更新时间 预聚合刷新时间

表格必须服务端分页,默认 limit=50,最大 limit=200

后端接口

新增接口:

GET /console/datav/country-dashboard/game-metrics

请求参数:

参数 必填 说明
sysOrigin 系统来源
periodType DAY / WEEK / MONTH / ALL
startDate DAY/WEEK/MONTH 必填 查询开始日期
endDate DAY/WEEK/MONTH 必填 查询结束日期
statTimezone 默认 Asia/Riyadh
countryCodes 多国家码,逗号分隔
countryKeyword 国家码或国家名搜索
gameProvider 游戏厂商
gameKeyword 游戏 ID 或名称搜索
cursor 页码,默认 1
limit 页大小,默认 50最大 200
sortField 允许 consumeAmount/payoutAmount/profitAmount/userCount/orderCount
sortOrder ascend/descend

响应结构:

{
  "current": 1,
  "size": 50,
  "total": 120,
  "records": [
    {
      "periodKey": "2026-05-12",
      "periodName": "2026-05-12",
      "statTimezone": "Asia/Riyadh",
      "countryCode": "SA",
      "countryName": "Saudi Arabia",
      "gameProvider": "BAISHUN",
      "gameId": "1067",
      "gameName": "example game",
      "consumeAmount": "1000.00",
      "payoutAmount": "720.00",
      "profitAmount": "280.00",
      "payoutRate": "72.00%",
      "profitRate": "28.00%",
      "userCount": 86,
      "orderCount": 320,
      "refreshedAt": "2026-05-12 10:20:00"
    }
  ]
}

后端新增对象建议:

  • CountryDashboardGameMetricQryCmd
  • CountryDashboardGameMetricCO
  • CountryDashboardGameMetricDTO
  • CountryDashboardGameMetricService
  • CountryDashboardGameMetricDAO
  • CountryDashboardGameMetricDAO.xml

预聚合表

country_dashboard_game_period_metric

页面直接查询这张表。

CREATE TABLE IF NOT EXISTS `country_dashboard_game_period_metric` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `period_type` varchar(16) NOT NULL COMMENT '周期类型:DAY/WEEK/MONTH/ALL',
  `period_key` varchar(32) NOT NULL COMMENT '周期键',
  `stat_timezone` varchar(64) NOT NULL DEFAULT 'Asia/Riyadh' COMMENT '统计时区',
  `period_name` varchar(64) NOT NULL DEFAULT '' COMMENT '周期名称',
  `period_start_date` date DEFAULT NULL COMMENT '周期开始日期',
  `period_end_date` date DEFAULT NULL COMMENT '周期结束日期,开区间',
  `sys_origin` varchar(32) NOT NULL DEFAULT '' COMMENT '系统来源',
  `country_code` varchar(64) NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家码',
  `country_name` varchar(128) NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家名称',
  `game_provider` varchar(32) NOT NULL DEFAULT 'UNKNOWN' COMMENT '游戏厂商',
  `game_id` varchar(64) NOT NULL DEFAULT 'UNKNOWN' COMMENT '游戏ID',
  `game_name` varchar(128) NOT NULL DEFAULT 'UNKNOWN' COMMENT '游戏名称',
  `consume_amount` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '用户消耗',
  `payout_amount` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '用户返奖',
  `profit_amount` decimal(20,2) NOT NULL DEFAULT 0.00 COMMENT '平台收益',
  `user_count` bigint NOT NULL DEFAULT 0 COMMENT '去重游戏用户数',
  `order_count` bigint NOT NULL DEFAULT 0 COMMENT '流水笔数',
  `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 '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_game_period` (`period_type`, `period_key`, `stat_timezone`, `sys_origin`, `country_code`, `game_provider`, `game_id`),
  KEY `idx_game_period_query` (`sys_origin`, `stat_timezone`, `period_type`, `period_key`, `country_code`),
  KEY `idx_game_lookup` (`sys_origin`, `stat_timezone`, `game_provider`, `game_id`, `period_type`, `period_key`),
  KEY `idx_game_period_sort` (`sys_origin`, `stat_timezone`, `period_type`, `period_key`, `consume_amount`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏游戏维度周期预聚合';

country_dashboard_game_period_user_metric

用于 WEEK/MONTH/ALL 去重用户数,避免直接把日用户数相加导致重复。

CREATE TABLE IF NOT EXISTS `country_dashboard_game_period_user_metric` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `period_type` varchar(16) NOT NULL COMMENT '周期类型:DAY/WEEK/MONTH/ALL',
  `period_key` varchar(32) NOT NULL COMMENT '周期键',
  `stat_timezone` varchar(64) NOT NULL DEFAULT 'Asia/Riyadh' COMMENT '统计时区',
  `period_name` varchar(64) NOT NULL DEFAULT '' COMMENT '周期名称',
  `period_start_date` date DEFAULT NULL COMMENT '周期开始日期',
  `period_end_date` date DEFAULT NULL COMMENT '周期结束日期,开区间',
  `sys_origin` varchar(32) NOT NULL DEFAULT '' COMMENT '系统来源',
  `country_code` varchar(64) NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家码',
  `country_name` varchar(128) NOT NULL DEFAULT 'UNKNOWN' COMMENT '国家名称',
  `game_provider` varchar(32) NOT NULL DEFAULT 'UNKNOWN' COMMENT '游戏厂商',
  `game_id` varchar(64) NOT NULL DEFAULT 'UNKNOWN' COMMENT '游戏ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_game_period_user` (`period_type`, `period_key`, `stat_timezone`, `sys_origin`, `country_code`, `game_provider`, `game_id`, `user_id`),
  KEY `idx_game_user_count` (`period_type`, `period_key`, `stat_timezone`, `sys_origin`, `game_provider`, `game_id`, `country_code`),
  KEY `idx_game_user_lookup` (`sys_origin`, `stat_timezone`, `user_id`, `game_provider`, `game_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家数据大屏游戏维度周期用户去重明细';

数据来源和游戏识别

以钱包流水作为财务主口径:

  • wallet_gold_asset_record_1..12.type = 1:消耗
  • wallet_gold_asset_record_1..12.type = 0:返奖
  • 金额沿用现有大屏口径:penny_amount / 100.00
  • LUCKY_GIFTLUCKY_GIFT_GOLD_REWARD 继续归属幸运礼物模块,不并入游戏数据表。

游戏维度来源:

来源 识别方式 名称补全
百顺 钱包 event_id = baishun_order_idempotency.wallet_event_id,取 vendor_game_id;兜底解析 event_type = BAISHUN_GAME_{gameId} baishun_game_catalog.name
统一三方游戏 钱包 event_id = CONCAT('GAME_OPEN:', game_open_callback_log.order_id),取 game_open_callback_log.game_id;兜底解析 event_type = GAME_OPEN_{gameId} lingxian_game_catalog.namesys_game_list_config.name
Lucky Box game_lucky_box_count 固定为 gameProvider=INTERNAL, gameId=LUCKY_BOX 固定名称 Lucky Box
其他站内游戏 event_type 映射稳定 gameId,例如 FRUIT_GAME/GAME_FRUIT_* -> FRUIT_GAMELUDO_* -> LUDO_GAME 代码内映射或后续配置表

如果某条流水无法识别具体游戏,落到:

  • gameProvider = UNKNOWN
  • gameId = event_type
  • gameName = event_type

这样可以保证金额不丢失,同时便于后续补映射。

刷新和回填

新增刷新服务建议为 CountryDashboardGameMetricService

默认任务:

  • 每 10 分钟刷新最近 1 天。
  • 每天 00:30 刷新最近 8 天。
  • 支持手动回填,单次最大 366 天,和现有国家大屏保持一致。

刷新逻辑:

  1. sysOriginstatTimezone 计算周期窗口,转成存储时区时间窗口。
  2. 扫描窗口内原始游戏流水,按 period + country + gameProvider + gameId 聚合金额和流水笔数。
  3. 删除对应周期的 country_dashboard_game_period_user_metric,写入去重用户明细。
  4. 从用户明细聚合 user_count
  5. 删除并写入对应周期的 country_dashboard_game_period_metric
  6. ALL 周期的金额从 DAY 周期表汇总,用户数从日级用户明细去重汇总。

刷新任务失败只影响游戏数据表,不影响现有国家大屏总览。

查询性能要求

页面查询只能访问 country_dashboard_game_period_metric,禁止在请求链路访问以下原始表:

  • likei_wallet.wallet_gold_asset_record_1..12
  • game_open_callback_log
  • baishun_order_idempotency
  • game_lucky_box_count
  • 第三方游戏 catalog 表

分页查询必须满足:

  • sysOrigin 必传。
  • periodTypestatTimezone 必须落到联合索引前缀。
  • DAY/WEEK/MONTH 查询必须带日期范围,并转成 period_keyperiod_start_date 范围。
  • ALL 只查 period_type='ALL' AND period_key='ALL',不回退全历史实时扫描。
  • countryKeywordgameKeyword 只允许在已经命中周期范围之后过滤;大范围模糊搜索需要限制日期范围。
  • 只允许白名单排序字段,避免动态 SQL 注入和无法使用索引的任意排序。
  • limit 上限 200。

刷新任务扫描原始钱包分表时必须带:

  • sys_origin
  • create_time 毫秒闭开区间
  • 游戏 event_type 白名单或前缀条件

现有 wallet 分表有 idx_sorigin_etype_ctime(sys_origin,event_type,create_time)idx_event(event_id),刷新 SQL 应优先利用这两个索引。不能对 create_time 列包函数做过滤,只能在 SELECT 或 GROUP BY 阶段转换时间。

实施步骤

  1. 新增 SQL 迁移:创建 country_dashboard_game_period_metriccountry_dashboard_game_period_user_metric
  2. 新增后端 DTO/Cmd/CO/DAO/Service并在 DatavRestController 暴露 /country-dashboard/game-metrics
  3. 新增刷新任务和手动回填入口,复用现有 red-circle.country-dashboard.refresh-sys-originsrefresh-timezones 配置。
  4. 新 admin 新增接口方法 countryDashboardGameMetrics
  5. datav.vue 增加“游戏数据”tab、游戏筛选项和分页表格。
  6. 首次上线后先回填最近 8 天,再回填历史需要展示的范围。

验证方案

SQL 验证:

EXPLAIN
SELECT *
FROM country_dashboard_game_period_metric
WHERE sys_origin = 'LIKEI'
  AND stat_timezone = 'Asia/Riyadh'
  AND period_type = 'DAY'
  AND period_key BETWEEN '2026-05-01' AND '2026-05-12'
ORDER BY consume_amount DESC
LIMIT 50;

预期使用 idx_game_period_queryidx_game_period_sort,不能全表扫描。

数据口径验证:

SELECT SUM(consume_amount), SUM(payout_amount)
FROM country_dashboard_game_period_metric
WHERE sys_origin = 'LIKEI'
  AND stat_timezone = 'Asia/Riyadh'
  AND period_type = 'DAY'
  AND period_key = '2026-05-12';

对比现有 country_dashboard_period_metric 同日同国家汇总的 game_total_flow/game_payout,差异应只来自“无法识别但仍落 UNKNOWN”的映射归类不应出现金额丢失。

功能验证:

  • 查询 DAY 日期范围,表格按日期、国家、游戏拆分。
  • 查询 WEEK/MONTH用户数为周期内去重不是日用户数累加。
  • 查询 ALL不触发实时原始流水扫描。
  • 国家筛选、游戏厂商筛选、游戏关键字筛选、分页、排序都能返回稳定结果。
  • 刷新任务失败时,现有数据大屏总览接口不受影响。