v4.25-玩家活跃.md 5.9 KB

v4.25 - 玩家活跃记录

一、需求概述

24小时监控玩家在线状态,每小时记录一次活跃数据。每个账户、每个游戏、每个角色每小时最多生成一条记录。

玩家A从1点在线到3点,实际产生3条数据(1点、2点、3点各一条)。

二、方案选型

方案对比

对比项 客户端每小时上报 心跳 + 服务端聚合
iOS 可靠性 ⚠️ 后台定时器不可靠 ⚠️ 同样依赖前台定时器
H5 可靠性 ⚠️ 切后台暂停JS ⚠️ 同样依赖页面可见
安卓兼容性 ⚠️ 部分厂商杀后台 ⚠️ 同样存在此问题
实现复杂度 中(多一层Redis + Cron)
接口调用量 低(1次/小时) 中(12次/小时)
扩展性 仅统计活跃 可扩展实时在线、掉线检测

结论

两种方案都依赖客户端定时器,切后台/被杀后定时器都会暂停,回到前台都会重新启动。核心区别在于漏报窗口:心跳5分钟 vs 每小时上报60分钟。

对于"每小时记录一次活跃"的需求,客户端每小时上报简单直接,够用。

采用方案:客户端每小时上报。

三、接口设计

3.1 活跃上报接口

  • 路径POST /api/v2/User/playerActive
  • 调用方:客户端定时任务,每小时调用一次
  • 请求参数
参数 类型 必填 说明
member_id int 玩家账号ID
game_id int 游戏ID
channel_id int 渠道ID(推广员)
server_id int 区服ID
server_name string 区服名
role_id int 角色ID
role_name string 角色名
  • 响应示例
{
    "code": 200,
    "msg": "上报成功",
    "data": []
}

3.2 服务端处理逻辑

  1. 校验参数完整性
  2. 查询玩家信息(cy_members)获取 member_create_time
  3. 查询角色信息(cy_role_info)获取 role_create_time
  4. 通过 channel_id(推广员,level=3)查询渠道层级关系,获取子会长、公会账号、商务账号
  5. 通过 game_id 查询游戏名
  6. 检查去重:同一 member_id + game_id + role_id + 小时 是否已存在记录
  7. 不存在则写入,存在则跳过

四、表结构

4.1 活跃记录表 cy_player_active_log

CREATE TABLE `cy_player_active_log` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `member_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '玩家账号ID',
    `subaccount_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '子账户ID',
    `game_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '游戏ID',
    `game_name` varchar(100) NOT NULL DEFAULT '' COMMENT '游戏名',
    `channel_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '渠道ID(推广员)',
    `server_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '区服ID',
    `server_name` varchar(100) NOT NULL DEFAULT '' COMMENT '区服名',
    `role_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '角色ID',
    `role_name` varchar(100) NOT NULL DEFAULT '' COMMENT '角色名',
    `role_create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '角色创建时间(时间戳)',
    `member_create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '账户注册时间(时间戳)',
    `promoter_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '推广员ID',
    `sub_president_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '子会长ID',
    `guild_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '公会账号ID(会长)',
    `business_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '商务账号ID',
    `create_time` datetime NOT NULL COMMENT '活跃时间(例如:2026-06-30 01:00:00)',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_active` (`member_id`, `game_id`, `role_id`, `create_time`),
    KEY `idx_game` (`game_id`, `create_time`),
    KEY `idx_channel` (`channel_id`, `create_time`),
    KEY `idx_guild` (`guild_id`, `create_time`),
    KEY `idx_business` (`business_id`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='玩家活跃记录';

4.2 字段说明

字段 说明
member_id 来自请求参数,关联 cy_members
subaccount_id 来自请求参数或玩家信息
game_id 来自请求参数
game_name 通过 game_id 查询 cy_game 表获取
channel_id 来自请求参数,即推广员ID(level=3)
promoter_id 等于 channel_id
sub_president_id 通过 channel.parent_id 向上查 level=2 的渠道
guild_id 通过 channel.parent_id 向上查 level=1 的渠道
business_id 通过 channel.parent_id 向上查 level=0 的渠道
create_time 服务端当前时间,取整到小时(如 2026-06-30 01:00:00)

4.3 渠道层级关系查询

渠道表 nw_channel 通过 id_pathparent_id 维护四级树形结构:

商务(level=0) → 公会/会长(level=1) → 子会长(level=2) → 推广员(level=3)

id_path 示例:,10,20,300,500,,一次查询即可拿到整条链路。

五、数据流向

客户端(每小时定时器)
    │
    ▼
POST /api/v2/User/playerActive
    │
    ├─ 查询 cy_members → member_create_time
    ├─ 查询 cy_role_info → role_create_time
    ├─ 查询 cy_game → game_name
    ├─ 查询 nw_channel(向上查层级) → 子会长、公会、商务
    ├─ 去重检查(uk_active唯一索引)
    │
    ▼
写入 cy_player_active_log

六、注意事项

  1. 去重uk_active 唯一索引保证同一玩家+游戏+角色+小时只有一条记录,重复上报用 INSERT IGNOREON DUPLICATE KEY
  2. 服务端时间create_time 使用服务端时间,不依赖客户端时间
  3. 渠道层级查询:建议缓存渠道层级关系,避免每次请求都查 nw_channel
  4. 批量查询优化:如后续需要查询某公会下的活跃数据,通过 guild_id 索引直接查,无需再关联渠道表