存储 WhatsApp 语音通话和视频通话的通话记录,需要关注几个关键方面:通话元数据、参与者信息、通话状态、持续时间、时间戳以及潜在的费用(如果涉及)。与消息内容不同,通话的实际音频/视频流通常不会被 WhatsApp 服务器存储(因为它们通常是端到端加密的点对点连接,或者通过媒体中继服务器传输但不会持久化),数据库只记录通话的元数据。
1. 核心通话记录表 (CallRecords Table)
最核心的存储实体是一个专门的通话记录表。
表结构示例:
SQL
CREATE TABLE CallRecords (
call_id BIGINT PRIMARY KEY, -- 通话的唯一ID (UUID 或 分布式ID生成器)
caller_user_id BIGINT NOT NULL, -- 主叫方用户ID (外键 Users.user_id)
call_type VARCHAR(20) NOT NULL, -- 通话类型:'VOICE_CALL', 'VIDEO_CALL'
start_timestamp BIGINT NOT NULL, -- 通话开始的Unix毫秒时间戳
end_timestamp BIGINT, -- 通话结束 喀麦隆 whatsapp 数据库 的Unix毫秒时间戳 (如果通话完成)
duration_seconds INT, -- 通话持续时间 (秒)
call_status VARCHAR(50) NOT NULL, -- 通话状态:'INITIATED', 'ONGOING', 'COMPLETED', 'MISSED', 'CANCELED', 'FAILED'
call_outcome VARCHAR(50), -- 通话结果细分:'ANSWERED', 'NO_ANSWER', 'BUSY', 'DECLINED', 'NETWORK_ERROR'
is_group_call BOOLEAN NOT NULL DEFAULT FALSE, -- 是否为群组通话
group_id BIGINT, -- 如果是群组通话,关联 Group.group_id
last_updated_at BIGINT NOT NULL -- 最后更新时间戳
-- 其他可选字段,用于更高级的诊断或计费
-- network_type_caller VARCHAR(20), -- 主叫方网络类型 (Wi-Fi, Mobile Data)
-- network_type_callee VARCHAR(20), -- 被叫方网络类型
-- data_transfered_caller BIGINT, -- 主叫方数据传输量 (字节)
-- data_transfered_callee BIGINT, -- 被叫方数据传输量 (字节)
-- error_code VARCHAR(50), -- 通话失败时的错误码
-- FOREIGN KEY (caller_user_id) REFERENCES Users(user_id),
-- FOREIGN KEY (group_id) REFERENCES Groups(group_id)
);
字段说明:
call_id: 通话的唯一标识符。在分布式系统中,这通常由一个分布式 ID 生成器(如 Snowflake ID)生成。
caller_user_id: 发起通话的用户 ID。
call_type: 指明是语音通话还是视频通话。
start_timestamp / end_timestamp / duration_seconds: 记录通话的时长和时间点。这些对于用户查看通话记录和统计分析至关重要。
call_status / call_outcome: 详细描述通话的最终结果,对于用户了解通话是否成功或失败非常重要。例如,“未接来电”就是 MISSED 状态。
is_group_call / group_id: 区分一对一通话和群组通话。
2. 通话参与者表 (CallParticipants Table)
对于群组通话,以及记录一对一通话中的被叫方信息,需要一个独立的表来存储通话的参与者。
表结构示例:
SQL
CREATE TABLE CallParticipants (
call_id BIGINT NOT NULL, -- 外键,关联 CallRecords.call_id
participant_user_id BIGINT NOT NULL, -- 参与者用户ID (外键 Users.user_id)
is_caller BOOLEAN NOT NULL, -- 是否是主叫方
joined_timestamp BIGINT, -- 参与者加入通话的时间戳
left_timestamp BIGINT, -- 参与者离开通话的时间戳
participant_status VARCHAR(50) NOT NULL, -- 参与者在通话中的状态:'RINGING', 'JOINED', 'LEFT', 'DECLINED', 'NO_ANSWER'
PRIMARY KEY (call_id, participant_user_id) -- 复合主键
-- FOREIGN KEY (call_id) REFERENCES CallRecords(call_id),
-- FOREIGN KEY (participant_user_id) REFERENCES Users(user_id)
);
作用:
一对一通话: 在此表中会有一条记录表示主叫方,一条记录表示被叫方。
群组通话: 会有 N 条记录,其中 N 是参与通话的成员数量,每条记录对应一个群组成员。这允许详细追踪每个成员在群组通话中的行为(何时加入、何时离开)。
3. 工作流程与状态更新
通话记录的存储是一个动态过程,涉及客户端和服务器之间的多次状态更新。
通话发起:
主叫方客户端向 WhatsApp 服务器发送通话发起请求。
服务器在 CallRecords 表中插入一条新记录,call_status 为 'INITIATED',记录 caller_user_id 和 start_timestamp。
同时,在 CallParticipants 表中插入主叫方记录。
服务器向被叫方(或群组其他成员)推送来电通知。
通话响铃/接听/拒绝:
被叫方收到通知后,其客户端发送状态回执给服务器(响铃、拒绝、接听)。
服务器更新 CallParticipants 表中对应 participant_user_id 的 participant_status。
如果接听,服务器更新 CallRecords 表中的 call_status 为 'ONGOING'。
通话结束:
任何一方挂断电话或连接中断。
客户端发送结束信号。
服务器更新 CallRecords 表的 end_timestamp、duration_seconds 和最终 call_status ('COMPLETED', 'MISSED', 'CANCELED')。
更新 CallParticipants 表中对应参与者的 left_timestamp 和最终 participant_status。
实时通知: 通话状态的变化会通过实时推送机制(如 XMPP/WebSockets)通知给所有相关参与者,以便他们的客户端更新 UI。
4. 可扩展性与性能
分片(Sharding): CallRecords 和 CallParticipants 表都将非常庞大。
通常会根据 caller_user_id 或 call_id 进行分片,将一个用户的通话记录或一个特定通话的所有参与者记录存储在同一个分片上,以优化查询。
索引:
CallRecords: (caller_user_id, start_timestamp) 复合索引用于快速查找某个用户的所有通话记录。
CallParticipants: (participant_user_id, call_id) 复合索引用于查找某个用户作为参与者(无论主叫被叫)的所有通话。
内存数据库/缓存: 对于正在进行的通话状态,可能会在内存数据库中进行短期缓存,以支持快速状态更新和查询,减少主数据库的压力。
数据生命周期管理: 随着时间的推移,旧的通话记录可能会被归档或清除,以管理存储容量。
5. 客户端本地存储
与消息类似,为了提供离线访问和快速加载,WhatsApp 客户端也会将用户的通话记录同步并缓存到设备本地的数据库中(如 SQLite),供用户随时查看。
通过这种细致的元数据存储和实时更新机制,WhatsApp 能够准确地记录和展示用户的语音和视频通话历史。