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

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

本篇目标:从业务正确性出发设计实时链路,理解为什么"消息是提醒,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 鉴权、用户房间与幂等邀请

相关推荐
一鸣AI编程1 小时前
AI UI 测试平台从 0 到 1:8 次跑通一条 19 步用例的实战复盘
前端
Java内核笔记1 小时前
Spring Boot 4 空安全源码剖析:JSpecify 是怎么让全生态 API null-safe 的
spring boot·后端
用户852495071841 小时前
一条点赞,六张表:SQL 数据库设计实战
后端
PedroQue991 小时前
Vite 1.2.0发布:自动生成pages.json
前端·vite
ly76891 小时前
React 19 从入门到工程实践:核心原理、新特性与项目实战
前端·react.js·前端框架
MetaLite1 小时前
SpringBoot项目Maven-BOM统一版本就不会冲突吗
spring boot·后端·maven
tedcloud1231 小时前
diagram-design 怎么安装?用 AI 自动生成更专业的架构图、流程图
linux·运维·前端·人工智能·开源·流程图
何智超1 小时前
React 18 并发更新:高优先级先渲染,为什么最终状态不会算错?
前端
前端小万1 小时前
两个半月的时间如何赚到 3000 块钱。
前端