ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpacetimeDB 实时聊天应用实战:用 TypeScript 构建带编辑历史、已读回执与定时消息的 Discord 风格聊天室

SpacetimeDB 实时聊天应用实战:用 TypeScript 构建带编辑历史、已读回执与定时消息的 Discord 风格聊天室 SpacetimeDB 实时聊天应用实战用 TypeScript 构建带编辑历史、已读回执与定时消息的 Discord 风格聊天室【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB本文基于当前仓库中的完整示例应用 chat-app-20260107-120000/README.md系统讲解如何用 SpacetimeDB 的 TypeScript 后端spacetimedbSDK与 React 前端构建一个功能完整的实时聊天应用。示例覆盖消息编辑历史、Emoji 表情反应、输入中指示器、已读回执、未读计数、定时消息与阅后即焚Ephemeral消息等真实社交产品核心功能。读完本文你将掌握 SpacetimeDB 的表定义table/t类型系统、Reducer 业务逻辑、定时任务调度scheduleAt以及前端useTable订阅式数据驱动的完整开发链路。一、示例应用概览与功能清单这是一个运行在 SpacetimeDB 之上的实时聊天应用后端逻辑全部运行在数据库内Reducer前端通过 WebSocket 订阅数据表无需自建状态管理即可获得多端实时同步。核心聊天功能实时消息收发基于 WebSocket 连接所有客户端即时同步用户显示名与在线状态通过user表与userStatus表维护公开房间创建与加入支持create_room、join_room、leave_room消息限流每个用户每个房间每分钟最多发送 5 条消息消息编辑与历史示例核心亮点仅允许在发送后 5 分钟内编辑自己的消息完整编辑历史记录每次编辑的时间戳与前后内容被编辑过的消息显示 (edited) 指示标记提供全量审计轨迹谁、何时、从什么改成什么进阶特性Emoji 表情反应实时更新、按表情分组计数输入中指示器User is typing...已读回执Seen by X, Y, Z未读消息计数与角标定时消息未来时间自动发送支持取消阅后即焚消息指定时长后自动删除以上功能并非 README 中的宣传语而是可以在 后端业务逻辑 中逐一找到对应 Reducer 实现edit_message、toggle_reaction、start_typing、mark_message_read、schedule_message、send_ephemeral_message等。二、项目结构解析示例工程分为backend与client两大部分结构如下与 README 一致并补充了关键文件说明chat-app-20260107-120000/ ├── backend/spacetimedb/ │ ├── src/ │ │ ├── schema.ts # 数据库表与关系定义 │ │ └── index.ts # Reducer 与业务逻辑 │ ├── package.json # 依赖 spacetimedb ^1.11.0 │ └── tsconfig.json └── client/ ├── src/ │ ├── components/ # React 组件 │ │ ├── App.tsx │ │ ├── Sidebar.tsx │ │ ├── ChatArea.tsx │ │ ├── MessageItem.tsx # 消息编辑 UI 与历史展示 │ │ ├── MessageInput.tsx │ │ └── UserSetup.tsx │ ├── module_bindings/ # spacetime generate 生成的类型绑定 │ ├── config.ts │ ├── main.tsx │ └── index.css ├── package.json ├── tsconfig.json └── vite.config.ts从 客户端 package.json 可以看到前端基于 React 18 Vite 4 TypeScript 5前后端共享同一spacetimedbSDK^1.11.0后端 package.json 则仅声明spacetimedb一个依赖业务全部收敛在schema.ts与index.ts两个文件中。三、环境准备与运行步骤前置条件Node.js 18SpacetimeDB CLIspacetime命令1. 启动 SpacetimeDB 服务器spacetime start该命令启动本地数据库实例。示例默认连接地址为ws://localhost:3000与 客户端 config.ts 中的默认值一致。2. 发布后端模块cd backend/spacetimedb spacetime publish chat-app --module-path .chat-app是模块名客户端MODULE_NAME需与其一致发布过程会将schema.ts中的表结构、index.ts中的全部 Reducer 编译部署到 SpacetimeDB 实例。3. 生成客户端类型绑定spacetime generate --lang typescript --out-dir ../client/src/module_bindings --module-path .该命令根据已发布的模块生成 TypeScript 类型安全的绑定代码对应client/src/module_bindings/目录前端useTable(tables.user)等 API 依赖这些生成文件。4. 启动前端cd ../client npm run dev应用默认运行在http://localhost:3000客户端脚本dev即vite若使用 Vite 默认端口则为 5173具体以 config.ts 中的CLIENT_PORT与启动输出为准。注意npm run dev前需先执行npm install安装依赖后端目录同理否则会出现下文故障排查中提到的 Could not resolve spacetimedb/server 错误。四、消息编辑功能操作指南README 中给出了完整的用户操作路径在任意聊天房间发送消息将鼠标悬停在自己的消息上出现 Edit 按钮点击 Edit进入编辑模式修改消息内容按 Enter 或点击 Save 保存点击 (edited) 旁的箭头展开编辑历史查看全部变更记录包括时间戳与每次编辑前的旧内容对应的 UI 实现在 MessageItem.tsx编辑模式用useState管理isEditing与editContent支持 Enter 提交、Escape 取消L122-L157编辑历史按editedAt倒序排列每条记录展示编辑者显示名、编辑时间与previousContentL174-L206只有authorId等于当前身份的 我的消息 才渲染 Edit 按钮L247-L255五、客户端配置说明服务器地址在 client/src/config.ts 中配置// Client configuration export const CONFIG { SPACETIMEDB_URI: (import.meta as any).env?.VITE_SPACETIMEDB_URI || ws://localhost:3000, MODULE_NAME: chat-app, CLIENT_PORT: 5173, };与 README 中的示例相比实际代码做了更友好的处理支持通过VITE_SPACETIMEDB_URI环境变量覆盖服务器地址未设置时回退到本地ws://localhost:3000。部署到远程服务器时只需VITE_SPACETIMEDB_URIws://your-server:3000 npm run devMODULE_NAME必须与spacetime publish时使用的模块名一致。六、数据库 Schema 设计完整的表定义位于 backend/spacetimedb/src/schema.tsREADME 重点列出与消息编辑相关的核心表表名作用message主消息表含编辑追踪字段editedAt、isEditedmessage_edit所有消息编辑的历史记录user用户信息与显示名room聊天房间room_member房间成员关系与已读位置lastReadMessageId此外 Schema 还定义了支撑进阶特性的表scheduled_message定时消息、ephemeral_message阅后即焚、typing_indicator输入中指示、read_receipt已读回执、message_reaction表情反应、user_status在线状态。表定义要点以消息表为例schema.ts L57-L80export const Message table( { name: message, public: true, indexes: [ { name: message_room_id, algorithm: btree, columns: [roomId] }, { name: message_author_id, algorithm: btree, columns: [authorId] }, { name: message_created_at, algorithm: btree, columns: [createdAt] }, ], }, { id: t.u64().primaryKey().autoInc(), roomId: t.u64(), authorId: t.identity(), content: t.string(), createdAt: t.timestamp(), editedAt: t.timestamp().optional(), isEdited: t.bool(), } );几个值得注意的 Schema 设计细节主键自动递增id: t.u64().primaryKey().autoInc()写入时传id: 0n占位由数据库分配身份类型authorId: t.identity()直接关联调用者的认证身份ctx.sender天然防止伪造他人身份索引声明在表的元数据中声明btree索引如message_room_id、message_created_atReducer 中通过ctx.db.message.message_room_id.filter(roomId)按索引查询定时调度scheduled_message与ephemeral_message使用scheduled: send_scheduled_message语法将表行绑定到自动触发的 Reducer详见第八节七、编辑历史追踪的完整数据流README 描述了消息被编辑时的四步流程源码 index.ts 的edit_messageReducerL192-L234精确实现了该流程校验输入内容非空、不超过 2000 字符权限校验message.authorId.toHexString() ! ctx.sender.toHexString()时抛出SenderError(You can only edit your own messages)确保只能编辑自己的消息时间窗校验fiveMinutesAgo ctx.timestamp.microsSinceUnixEpoch - 300_000_000n超过 5 分钟5 × 60 × 1_000_000 微秒则拒绝编辑原内容落库到message_edit插入previousContent编辑前内容、newContent新内容、editedAt、editedBy更新主消息将content替换为新内容、editedAt置为当前时间、isEdited置为true// 存储编辑历史 ctx.db.messageEdit.insert({ id: 0n, messageId, previousContent: message.content, newContent: newContent.trim(), editedAt: ctx.timestamp, editedBy: ctx.sender, }); // 更新消息主体 ctx.db.message.id.update({ ...message, content: newContent.trim(), editedAt: ctx.timestamp, isEdited: true, });由于 SpacetimeDB 的所有数据库操作都是事务性的ACID 语义上述写历史 更新正文两步要么同时成功要么同时回滚保证审计轨迹与正文始终一致。前端通过订阅message_edit表实时获得每次编辑记录并渲染为可展开的历史面板。八、进阶特性背后的源码机制8.1 限流Rate Limitingsend_messageReducerL144-L158实现了每用户每房间每分钟 5 条的限制用oneMinuteAgo ctx.timestamp.microsSinceUnixEpoch - 60_000_000n计算时间窗口统计该房间内该作者在一分钟内创建的message记录数达到 5 条即抛SenderError。由于 Reducer 运行在数据库事务内该统计天然并发安全。8.2 定时消息与阅后即焚ScheduleAt 机制schedule_messageReducerL237-L268通过ScheduleAt.time(scheduledTime)把消息挂到未来时间点const scheduledTime ctx.timestamp.microsSinceUnixEpoch delayMinutes * 60_000_000n; ctx.db.scheduledMessage.insert({ scheduledId: 0n, scheduledAt: ScheduleAt.time(scheduledTime), roomId, authorId: ctx.sender, content: content.trim(), createdAt: ctx.timestamp, });到达预定时刻后SpacetimeDB 会自动调用 Schema 中绑定的 Reducersend_scheduled_messageL530-L555它把调度行中的内容插入message表、写入作者自己的已读回执调度行本身在 Reducer 完成后被自动删除。延迟时间被限制在 11440 分钟24 小时之间cancel_scheduled_message允许作者按scheduledId取消尚未发送的定时消息。阅后即焚的原理类似send_ephemeral_message先正常插入一条message再插入绑定delete_ephemeral_messageReducer 的ephemeral_message调度行durationMinutes限制在 160 分钟到期后delete_ephemeral_messageL557-L565直接ctx.db.message.id.delete(arg.messageId)删除正文。8.3 在线状态与连接生命周期Schema 通过user表维护用户信息通过userStatus表维护在线状态。clientConnected生命周期钩子L474-L508在客户端连接时创建用户若不存在并置为在线clientDisconnectedL510-L527在断开时置为离线并清理该用户在所有房间的输入中指示器。8.4 订阅驱动的前端渲染在 App.tsx 中前端通过useTable(tables.user)/useTable(tables.userStatus)订阅数据表const [users] useTable(tables.user); const [userStatuses] useTable(tables.userStatus);任何客户端调用 Reducer 改变表内容所有订阅该表的客户端都会收到增量更新——这正是 README Architecture Notes 中无外部状态管理、实时默认同步、类型安全三点的直接体现。当前用户身份通过全局的window.__my_identity获取由 SDK 连接时写入前端据此判定currentUser并决定是否渲染编辑按钮。九、故障排查与开发建议常见问题Could not resolve spacetimedb/server后端目录缺少依赖在backend/spacetimedb下执行npm install或npm i根据锁文件版本安装即可。WebSocket 连接失败确认spacetime start正在运行检查 config.ts 中的SPACETIMEDB_URI是否与服务器地址匹配本地为ws://localhost:3000。绑定未生成 / 类型不存在spacetime generate --lang typescript --out-dir ../client/src/module_bindings --module-path .必须在后端目录执行且先完成模块发布再生成否则拿不到最新 Schema。React StrictMode 破坏 WebSocket 连接README 明确提示示例应用已自动移除React.StrictModeStrictMode 在开发模式下会双调用 effect 导致重复连接若自行改造项目请留意这一点。开发与调试技巧使用浏览器开发者工具Network 面板检查 WebSocket 帧观察订阅与增量同步过程用spacetime logs chat-app查看模块运行日志与 Reducer 错误堆栈牢记 README 中的架构结论所有数据库操作均事务性且确定性执行同一输入必然产生同一数据库状态这是 SpacetimeDB 可审计、可复现的基础十、架构要点小结无外部状态管理SpacetimeDB 订阅机制驱动所有 UI 更新前端无需 Redux/Zustand实时默认所有变更通过 WebSocket 瞬时同步到所有订阅客户端类型安全spacetime generate生成的绑定代码让前后端共享编译期类型检查事务性所有 Reducer 内的多表操作遵循 ACID 语义编辑历史写入与正文更新原子生效代码即后端整个示例的后端仅两个 TypeScript 文件schema.ts 约 250 行、index.ts 约 565 行却完整覆盖了从限流、审计到定时调度的社交聊天全场景——这正是 SpacetimeDB应用逻辑内嵌数据库开发范式的直接演示。读者可直接以本示例为蓝本将编辑历史、已读回执、定时消息等模式复用到自己的实时应用IM、协作白板、实时评论等中。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表