实时团队邀请的架构与数据流

第二篇:实时团队邀请的架构与数据流

本篇目标:从业务正确性出发设计实时链路,理解为什么"消息是提醒,REST 才是账本"。

上一篇 · 返回目录 · 下一篇

1. 原始问题不是"缺一个 WebSocket"

Alice 提交 Bob 的邮箱,API 写入成员关系并向 Alice 返回成功,但 Bob 没有发请求,因此页面仍是旧列表。需求实际包含三件事:数据库可靠记录成员关系;Bob 在线时尽快知道;Bob 的页面最终与数据库一致。三件事不能全交给一条实时消息。

2. 三层职责

  • PostgreSQL 保存业务事实,当前邀请使用单条 INSERT 的原子性,并由外键和唯一约束兜底;
  • Socket.IO 发送 team.membership.created 变化提醒,优化感知速度;
  • REST 读取权威快照,Bob 收到事件后重新 GET /api/teams。
flowchart LR Alice[Alice 浏览器] -->|POST 邀请| API[NestJS API] API -->|保存成员关系| DB[(PostgreSQL)] API -->|成功后发布| RT[Socket.IO] RT -->|用户私有房间| Bob[Bob 浏览器] Bob -->|GET /api/teams| API API -->|权威快照| Bob

3. 完整时序

sequenceDiagram participant Alice as Alice 浏览器 participant API as NestJS API participant DB as PostgreSQL participant Socket as Socket.IO participant Bob as Bob 浏览器 Bob->>Socket: 登录后连接并加入 user:bobId Alice->>API: POST /api/teams/:teamId/members API->>DB: 检查权限、用户、已有成员 API->>DB: 保存新成员关系 DB-->>API: 保存成功 API->>Socket: 发布 team.membership.created Socket-->>Bob: 推送事件 API-->>Alice: 返回成员摘要 Bob->>Bob: 显示通知 Bob->>API: GET /api/teams API-->>Bob: 最新团队快照

必须先提交数据库再发事件。若顺序相反,Bob 回源可能查不到成员;保存失败时还会收到错误的成功通知。

这里的"先提交"是指 repository.save 对单条成员记录的写入成功;当前 addTeamMember 没有把前置查询、INSERT 和通知包在一个显式业务事务中。数据库唯一约束负责处理检查与写入之间的竞争窗口。

4. 先画出数据模型,避免把连接当成员关系

系统里至少有四种不同对象:

  • User:注册账号;
  • Team:团队;
  • TeamMember:用户与团队之间的持久关系,包含 role;
  • Socket:某个浏览器当前建立的临时连接。

一个 User 可以有多条 TeamMember,也可以同时有多个 Socket。关闭浏览器只会删除连接,不会删除成员关系;退出团队只会改变数据库关系,不代表必须注销账号。把这四个对象分清,很多设计错误会自然消失。

erDiagram USER ||--o{ TEAM_MEMBER : joins TEAM ||--o{ TEAM_MEMBER : contains USER ||--o{ SOCKET_CONNECTION : opens

5. 为什么事件名使用过去式

team.membership.created 表示"成员关系已经创建",而不是一个待执行命令。只有新记录保存成功才发布。目标用户原本就是成员时,接口可以幂等返回已有记录,但不能再次发布 created。

合理的 payload 包含 eventId、occurredAt、team 摘要和 membership 摘要。eventId 用于去重,其他字段足以生成可读通知;完整团队与项目树仍由 REST 返回。

6. 在线、离线和重连

  • Bob 在线:事件到达,立即展示通知并刷新列表;
  • Bob 离线:当前 best-effort 方案不保存离线 toast,但下次登录的 GET /api/teams 会恢复业务状态;
  • Bob 重连:主动同步团队快照,补偿断线期间可能漏掉的事件。

7. 失败发生在不同位置时怎样处理

失败点 数据库是否已有成员 是否应该发布 用户最终怎样恢复
权限或参数校验失败 否 否 Alice 看到明确 HTTP 错误
INSERT 失败 否 否 不显示成功,修复数据库问题
INSERT 成功、emit 同步异常 是 尝试过但未成功 Bob 下次 GET 恢复,不能回滚成员
Bob 离线、空房间 emit 是 服务端已 best-effort 调用 Bob 下次登录 GET 恢复
Bob 收到事件、GET 失败 是 已发送 页面提示同步失败,可重试或刷新
事件重复到达 是 已发送 eventId 去重,只需一次回源

这张表说明错误处理也必须服从"数据库事实优先"。实时层失败不应把成功写入变回失败,前端回源失败也不应伪装成用户没有加入团队。

8. 为什么不直接把 payload push 进数组

Socket.IO 会保持当前连接内已送达 packet 的顺序,但连接中断时事件可能缺失,payload 也不包含页面所需的所有字段。直接把 payload 当完整状态,无法修复漏失,也可能因业务层重复发送而重复插入。当前 eventId 去重属于防御性保护;若未来引入重试、Outbox 或多个消息来源,它会更重要。页面可以立即展示 toast,团队卡片则在 REST 返回后更新,这是有意的职责分离。

9. 当前一致性边界

当前方案保证数据库成功后才尝试发布、重复邀请不重复写入、已有成员不重复通知、漏掉消息仍能 REST 恢复。它不保证 emit 后浏览器一定收到、不保存离线通知,也不处理跨 API 实例房间共享和数据库与消息的原子提交。

若要求每条通知必达且可追踪,需要 Outbox、消息队列、消费确认和持久化通知中心;作品 Demo 没有盲目引入这些复杂度。

10. 常见错误设计

  1. 让邀请者浏览器直接向 Bob 发消息:浏览器不应决定他人权限;
  2. 写库失败仍发送成功事件:事件必须位于成功分支;
  3. Bob 离线就不能邀请:在线状态不应阻止业务事实写入;
  4. 只在首次 connect 拉数据:重连后必须补一次快照。

11. 本篇小结

实时邀请不是用 Socket.IO 替代 REST,而是在 REST 业务链路外增加低延迟提醒:先写数据库,后发事件;事件让界面回源,REST 返回最终状态。

下一篇:NestJS + Socket.IO 鉴权、用户房间与幂等邀请

相关推荐
liangshanbo121512 分钟前
前端高级面试题:WebSocket 双向流式通信怎么设计?
前端·websocket·网络协议
可乐鸡翅yeah_19 分钟前
新手梳理:HLS 线上问题,哪些该提给 CDN,哪些该找后端切片服务
开发语言·前端·javascript·vue.js·网络协议·http·m3u8在线
zwd200520 分钟前
Manim arrange 和 arrange_in_grid 用法详解:buff、aligned_edge、rows/cols(0.21.0 实测)
前端·python·edge
小小龙学IT31 分钟前
Go 语言 reflect 反射包深度解析:从 Type/Value 到三大定律与工业实践
前端·golang
小溪学编程40 分钟前
C语言篇:枚举类型
java·c语言·前端
xingpanvip43 分钟前
星盘接口开发文档:推算星座接口指南
前端·php
Da Da 泓1 小时前
HTML浅浅入门
前端·html
এ慕ོ冬℘゜1 小时前
es6基础
前端·javascript·es6
熊猫钓鱼>_>1 小时前
从2D平铺到3D沉浸:我用HarmonyOS 7端侧AI做了一个全程数据不出设备的空间化私密相册
前端·人工智能·3d·华为·华为云·harmonyos·鸿蒙
大圣编蚕1 小时前
Spring AOP 失效排查:从原理到实战的完整指南
java·后端·spring