数据模型与一致性
聊天服务以 SQLite 保存身份映射、联系人关系、聊天内容、媒体、新事、事件和通话状态。原生记忆数据另有客户端本地模型。本页描述源码中的逻辑实体与约束,帮助调用方理解 ID、可见性、重试和删除语义;不提供任何用户数据库或生产记录。
标识符、时间和对外对象
聊天用户、会话、群、媒体、好友申请和通话主要使用 UUID 字符串。普通消息、新事、评论、通知和事件使用各自的自增整数;企业消息的对外 id 被转换成字符串。MLS 使用自增 sequence,设备 ID 允许 1 至 80 个字母、数字、下划线或短横线。多数接口路径的 UUID 正则只校验字符集合与长度,客户端仍应产生合法 UUID。
时间字段如 createdAt、updatedAt、expiresAt 是 Unix 毫秒值。API 通常使用 camelCase,数据库使用 snake_case。null 表示暂无值,例如尚未接通的 startedAt、没有最新消息的 lastMessage、无权查看的手机号。调用方不得把 null 当空字符串,也不能跨不同实体比较 ID 大小判断全局时间顺序。
身份与资料
| 实体 | 核心关系与约束 |
|---|---|
users |
本地 id;唯一 Account issuer_sub;显示名;唯一好友 ID chat_code;手机号查找摘要及有效期 |
user_profiles |
每用户一条;简介、加密联系方式、手机号可见范围、更新时间 |
user_profile_phone_viewers |
指定可见者的 owner/viewer 复合键;不能指定自己 |
oauth_flows |
state 摘要、服务端 verifier、到期时间、产品标识、原生挑战 |
login_tickets |
ticket 摘要、用户、到期时间、原生挑战及 state 摘要 |
device_sessions |
token 摘要、用户、到期时间 |
公开 User 只含 {id,displayName,friendId},不会返回 Account subject、token 摘要或查找摘要。手机号查找使用带服务端密钥的 HMAC-SHA256,并在登录时刷新为 24 小时有效;个人资料中的可展示联系电话是另一字段,使用 AES-256-GCM 保存,由资料可见性控制。两者用途和存储形式不同。
好友、单聊与个人视图
friend_requests 保存申请双方及时间,禁止自己申请自己,同方向双方组合唯一。friend_codes 每用户只保留一个当前临时码摘要;friend_lookup_attempts 按用户与查找方法记录 10 分钟窗口内次数。
conversations 保存排序后的 user_a 与 user_b,要求前者小于后者并且双方组合唯一;is_friend 表示好友关系是否仍有效。删除好友不会仅靠删一条消息实现。user_blocks 是有方向的屏蔽关系,但发送、通话和部分资料检查使用任一方向屏蔽的联合条件。
conversation_preferences 为每个用户保存 is_hidden 和 visible_after_message_id。删除聊天会把个人可见边界推进到当时最后一条消息并隐藏列表项;恢复只取消隐藏,不回退边界。hidden_messages 为用户、范围与消息建立单条隐藏记录。因此“删除消息”是当前用户视图删除,不能解释为对方撤回、数据库物理擦除或备份销毁。
消息、群与已读
messages 与 group_messages 分别保存所属范围、发送者、client_id、正文、可选媒体引用和时间。二者各自要求 (sender_id,client_id) 唯一;同一发送者不能在该表内把同一 clientId 改用于其他范围或其他内容。服务端发现完全相同重试返回既有消息,内容变化返回 CLIENT_ID_REUSED。
read_positions 保存单聊用户最后已读消息,群的已读位置位于 group_members。更新只前进,且目标消息必须属于当前范围。未读数还受个人可见边界、单条隐藏和发送者过滤影响,不能只用最新 ID 减已读 ID 计算。
groups 保存名称、群主与创建时间;group_members 复合键保存加入时间、最后已读、历史可见边界、隐藏和静音设置。个人群最多 50 人。新成员的历史边界、退出或移除成员后的范围权限,均由服务端当前成员记录决定。群主可邀请、改名、移除成员或解散;群主退出时的归属处理由服务端事务完成,不能由客户端只删本地列表代替。
媒体、新事与位置
聊天 media 保存所有者、sticker/file、文件名、MIME、字节数、BLOB 和创建时间;城市内置贴纸还有用于按账号去重的 builtin key。消息通过 media_id 引用。下载权限来自所有者或当前用户可见消息,不能只凭 UUID 下载。
新事数据单独保存于 moments_posts、moments_audience、moments_media、moments_likes、moments_comments、moments_notifications、moments_hidden 和 moments_reports。可见性为 friends、selected、public、private;selected 另存指定好友列表,public 仍需登录访问 API。点赞是用户与帖子的唯一组合;评论可引用同帖评论。未绑定帖子的媒体超过 24 小时会被清理。新事不是 MLS 加密聊天数据。
live_locations 以范围类型、范围 ID、用户为复合键,保存经纬度、精度、开始和更新时间。POST 显式开启共享,PUT 只更新既有共享,DELETE 停止。服务端没有在本表中设置自动过期字段,调用方应展示更新时间并明确停止共享。
MLS、通话与变更通知
MLS 表包括设备签名公钥、一次性密钥包、范围模式、加密事件和按设备收件确认。e2ee_scopes 的 preparing/active/blocked 与没有记录时的 legacy 共同构成状态机。密钥包以内容 SHA-256 作为 ID;事件唯一键为发送用户、设备和 clientId,payload 摘要用于检查重试一致性。
calls 保存通话范围、发起者、audio/video、状态、时间和结束原因;内部房间名不会出现在普通公开 call 对象中。call_invites 描述每位邀请者的响应,call_participants 描述实际加入和离开。voip_devices 将设备、推送 token 与登录 session 摘要关联。
events 是用户收件范围内的变更索引,含可空的 conversationId/groupId/messageId。MLS sequence 可作为 e2ee 通知的 messageId,但不因此变成普通消息 ID。长轮询默认每批最多 100 条。详见实时同步。
存储与产品边界
数据库启用外键及 WAL,写操作通过事务维护去重、消息和事件一致性。备份与恢复必须同时考虑数据库、媒体 BLOB、加密配置、MLS 客户端状态和 Account 身份映射,不能只复制某一个消息表。
enterprise_* 表属于共享后台的独立星企领域,组织、部门、成员角色、企业频道、企业待办、公告与服务均以企业 ID 隔离,不属于星线 App 个人聊天数据模型的前台功能承诺。更多接口字段见API 参考,数据保护边界见安全和隐私。