尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Rivet Actors 实时聊天室实战:Actor 状态管理、事件广播与多房间隔离完整实现

Rivet Actors 实时聊天室实战:Actor 状态管理、事件广播与多房间隔离完整实现 Rivet Actors 实时聊天室实战Actor 状态管理、事件广播与多房间隔离完整实现【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors实时聊天室是验证有状态工作负载能力的典型场景需要瞬时把消息推送给所有在线客户端需要让历史记录在进程重启后依然完整还需要让不同房间的数据互不干扰。仓库中的 examples/chat-room 示例项目正是用 Rivet Actors 把这些诉求落地的完整参考实现。本文以此项目为主体结合仓库内 RivetKit SDK 的源码与测试逐层拆解 Actor 定义、SQLite 持久化、事件广播、客户端接入与测试验证的完整链路帮助你把同样的模式迁移到自己的协作类应用中。项目全景一个聊天室由哪些部分组成先看 examples/chat-room 的目录结构理解一个 Actor 示例项目的完整形态examples/chat-room/ ├── src/ │ └── index.ts # 服务端Actor 定义 registry 注册 服务启动 ├── frontend/ │ ├── App.tsx # React 前端useActor useEvent 实时订阅 │ └── main.tsx # React 入口 ├── tests/ │ └── chat-room.test.ts # vitest 集成测试 ├── package.json # dev / test / build 脚本与依赖 ├── vite.config.ts # 开发代理/actors、/metadata、/health ├── vitest.config.ts ├── tsconfig.json ├── turbo.json # 依赖 rivetkit/react、rivetkit 的构建顺序 ├── Dockerfile # 多阶段镜像构建server static 资源 ├── index.html └── README.md整个项目只有一份服务端入口 src/index.ts其余是前端与工程化配置。它的数据流模型非常简洁客户端通过 React 的createRivetKit建立与 Actor 服务器的连接Action是客户端可远程调用的函数负责写入状态如sendMessageEvent是服务端向所有已连接客户端主动推送的广播如newMessageSQLite DB是 Actor 的持久化存储保证历史消息在重启后不丢失。这一「Action 写入 Event 广播 DB 持久化」的三段式模型正是 Rivet Actors 处理有状态实时交互的核心范式也贯穿本文后续所有小节。快速启动五分钟跑起聊天室根据 examples/chat-room/README.md 的说明本项目使用concurrently同时启动服务端与前端git clone https://github.com/rivet-dev/rivet.git cd rivet/examples/chat-room npm install npm run dev对应到仓库中 examples/chat-room/package.json 的脚本定义{ name: chat-room, version: 2.0.21, type: module, scripts: { dev: concurrently -n server,vite \tsx --watch src/index.ts\ \vite\, dev:server: tsx --watch src/index.ts, check-types: tsc --noEmit, test: vitest run, build: vite build, start: tsx src/index.ts }, dependencies: { rivetkit/react: workspace:*, react: ^18.2.0, react-dom: ^18.2.0, rivetkit: workspace:* } }脚本含义如下npm run dev用tsx --watch热重载服务端同时启动 Vite 开发服务器日志以server/vite两个前缀区分npm run dev:server只启动 Actor 服务端适合调试后端逻辑npm run test运行 vitest 集成测试见后文「测试验证」小节npm run build构建前端静态资源npm run start以生产方式运行服务端。启动后Actor 服务器监听在http://localhost:6420端口由 src/index.ts 中registry.start()与 frontend/App.tsx 中createRivetKit(http://localhost:6420)共同约定。开发期的跨域与 WebSocket 代理由 vite.config.ts 处理server: { clearScreen: false, proxy: { /actors: { target: http://localhost:6420, ws: true }, /metadata: { target: http://localhost:6420 }, /health: { target: http://localhost:6420 }, }, },其中/actors走 WebSocketws: true——这正是实时事件推送的通道/metadata与/health是 HTTP 端点分别用于获取 Actor 元信息与健康检查。若在仓库中直接使用注意rivetkit与rivetkit/react采用workspace:*工作区依赖需在仓库根目录执行pnpm install建立 workspace 链接后再运行示例。服务端实现用 actor() 定义有状态消息实体聊天室的服务端全部逻辑集中在 src/index.ts它展示了 Rivet Actors 三个最核心的抽象db持久化、events广播、actions远程调用。消息类型与 SQLite 持久化db首先定义一个消息的数据结构并给 Actor 挂上一个 SQLite 数据库用它来持久化聊天记录import { actor, event, setup } from rivetkit; import { db } from rivetkit/db; export type Message { sender: string; text: string; timestamp: number }; export const chatRoom actor({ // Persist chat history in the actors SQLite database db: db({ onMigrate: async (db) { await db.execute( CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender TEXT NOT NULL, text TEXT NOT NULL, timestamp INTEGER NOT NULL ) ); }, }), // ... });要点拆解db({ onMigrate })为 Actor 声明一个内置 SQLite 数据库onMigrate在 Actor 首次初始化时执行负责建表等迁移逻辑。这里创建了messages表id自增主键sender/text为消息内容timestamp为毫秒级时间戳该表保存在 Actor 自己的持久化存储中因此即使 Actor 实例休眠或重启聊天历史也能恢复——这是「Persistent chat history」特性的底层来源在 rivetkit-typescript/packages/rivetkit/src/actor/definition.ts 中可以看到actor()定义的完整形态BaseActorDefinition携带config内部通过flattenActionHandlers、flattenActionInputSchemas等工具把actions/events/db等配置归一化最终由setup()注册为可寻址的 Actor 类型。事件广播events服务端主动推送给客户端事件是「客户端被实时通知」的关键机制声明方式与使用方式如下events: { newMessage: eventMessage(), },在sendMessage中通过c.broadcast(newMessage, message)把新消息推送给所有已连接的客户端。每个事件声明为强类型eventMessage()让广播内容与前端订阅回调的类型完全对齐编译期即可校验负载结构避免手写字符串协议带来的类型漂移。动作actions客户端可远程调用的函数actions是客户端可以直接跨网络调用的函数聊天室定义了两个核心动作actions: { sendMessage: async (c, sender: string, text: string) { const message: Message { sender, text, timestamp: Date.now() }; await c.db.execute( INSERT INTO messages (sender, text, timestamp) VALUES (?, ?, ?), sender, text, message.timestamp, ); // Send events to all connected clients c.broadcast(newMessage, message); return message; }, getHistory: async (c) { const rows await c.db.execute( SELECT sender, text, timestamp FROM messages ORDER BY id ASC, ); return rows as Message[]; }, },sendMessage(sender, text)先写入 SQLite再通过c.broadcast(newMessage, message)广播最后把消息对象返回给调用方。注意写入与广播的顺序——先落库、后广播保证任何重放或重连后客户端拉取到的历史与实时流是一致的getHistory()按id ASC读出全部消息作为新客户端加入时的「初始历史」。前端在建立连接后首先调用它渲染历史再用useEvent增量追加新消息二者配合实现无缝的聊天记录加载。注册与启动setup start最后把 Actor 注册进 registry 并启动服务export const registry setup({ use: { chatRoom }, }); // Start the server on port 6420 registry.start();setup({ use: { chatRoom } })声明该服务器对外暴露的 Actor 类型集合export它以便前端createRivetKittypeof registry与测试setupTest(ctx, registry)复用同一份类型与运行时registry.start()启动 HTTP WebSocket 服务默认端口 6420。前端实现useActor 订阅实时消息流前端是纯 React入口在 frontend/main.tsx业务逻辑在 frontend/App.tsx。建立类型安全的连接import { createRivetKit } from rivetkit/react; import type { Message, registry } from ../src/index.ts; const { useActor } createRivetKittypeof registry(http://localhost:6420);createRivetKittypeof registry直接把服务端registry的类型带到前端此后useActor({ name: chatRoom, ... })返回的connection上会自动推导出sendMessage、getHistory的签名与newMessage事件的负载类型。前后端共享同一份类型定义是这种架构在大型协作项目中最有价值的收益。接入 Actor 与实时事件export function App() { const [roomId, setRoomId] useState(general); const [username, setUsername] useState(User); const [input, setInput] useState(); const [messages, setMessages] useStateMessage[]([]); const chatRoom useActor({ name: chatRoom, key: [roomId], }); useEffect(() { if (chatRoom.connection) { chatRoom.connection.getHistory().then(setMessages); } }, [chatRoom.connection]); chatRoom.useEvent(newMessage, (message: Message) { setMessages((prev) [...prev, message]); }); // ... }两个关键 Hook 的行为useActor({ name: chatRoom, key: [roomId] })按key定位/创建 Actor 实例并建立连接key变化如切换房间名会重建连接。返回的connection是已连通的 RPC 句柄未连通时为nulluseEvent(newMessage, handler)订阅服务端广播收到新消息后以函数式setMessages增量追加天然避免闭包捕获过期状态的问题。发送消息与界面渲染const sendMessage async () { if (chatRoom.connection input.trim()) { await chatRoom.connection.sendMessage(username, input); setInput(); } };界面层把消息按sender/text/timestamp渲染时间戳通过new Date(msg.timestamp).toLocaleTimeString()本地化展示输入框与发送按钮在connection尚未建立时处于disabled状态防止连接未就绪时的无效调用。这套「连接状态驱动 UI 可用性」的处理方式同样适用于生产级协作应用。多房间隔离key 驱动的 Actor 实例模型README 明确指出「Multiple chat rooms: Each room is a separate actor instance with isolated state」。这个特性的实现机制在前端调用中可见一斑const chatRoom useActor({ name: chatRoom, key: [roomId], });name是 Actor 类型key是同一类型下的实例标识name key共同决定一个唯一的 Actor 实例不同roomId如general、design会解析到不同的 Actor 实例每个实例拥有自己独立的 SQLite 数据库与事件连接集合——这就是「隔离状态」的来源房间 A 的消息不会出现在房间 B 的历史里房间 A 的广播也只到达房间 A 的客户端同一roomId的多个客户端连接到同一个实例因此能共享同一份messages表并同时收到newMessage广播。测试中也印证了这一点client.chatRoom.getOrCreate([test-room])与getOrCreate([test-timestamps])使用不同的 key得到的是相互独立的实例详见 tests/chat-room.test.ts。这种「一个逻辑实体 一个 Actor 实例」的映射是 Rivet Actors 相对传统无状态服务的关键差异状态归属清晰、天然支持按 key 的水平切分。测试验证用 vitest 覆盖核心行为examples/chat-room/tests/chat-room.test.ts 通过rivetkit/test的setupTest启动 Actor 运行时进行集成测试覆盖了聊天室的核心契约import { setupTest } from rivetkit/test; import { expect, test } from vitest; import { registry } from ../src/index.ts; test(Chat room can handle message sending and history, async (ctx) { const { client } await setupTest(ctx, registry); const room client.chatRoom.getOrCreate([test-room]); // Test initial state const initialHistory await room.getHistory(); expect(initialHistory).toEqual([]); // Send a message const message1 await room.sendMessage(Alice, Hello everyone!); expect(message1).toMatchObject({ sender: Alice, text: Hello everyone!, timestamp: expect.any(Number), }); // Send another message, verify order const message2 await room.sendMessage(Bob, Hi Alice!); const history await room.getHistory(); expect(history).toHaveLength(2); expect(history[0]).toEqual(message1); expect(history[1]).toEqual(message2); });这套测试还覆盖了另外三个行为维度时间戳单调递增连续发送三条消息断言message2.timestamp message1.timestamp并遍历历史保证整体有序多用户支持Alice / Bob / Charlie 交替发言后历史长度为 4且sender与text顺序与发送顺序完全一致空消息边界发送空字符串也能正常落库并返回text为空、timestamp 0说明动作层对参数没有隐式的非空校验边界行为明确。这些断言直接验证了 README 中「Real-time messaging」「Persistent chat history」「Multiple chat rooms」三个核心特性的可测试性也为你在自己的 Actor 上添加测试提供了模板setupTest(ctx, registry)返回一个类型安全的clientclient.actorName.getOrCreate([key])即得到一个可调用的 Actor 句柄。容器化部署多阶段构建与静态托管examples/chat-room/Dockerfile 提供了把「Actor 服务器 前端静态资源」打包进一个镜像的参考做法# Build stage FROM node:22-alpine AS builder WORKDIR /app RUN corepack enable corepack prepare pnpmlatest --activate COPY package.json ./ RUN pnpm install --frozen-lockfilefalse COPY . . RUN pnpm run build # Runtime stage FROM node:22-alpine AS runtime WORKDIR /app RUN corepack enable corepack prepare pnpmlatest --activate COPY package.json ./ RUN pnpm install --prod --frozen-lockfilefalse COPY --frombuilder /app/dist ./dist COPY --frombuilder /app/public ./dist/public EXPOSE 8080 ENV PORT8080 ENV NODE_ENVproduction CMD [node_modules/.bin/srvx, dist/server.js]关键点多阶段构建builder 阶段安装完整依赖含 devDependencies并执行pnpm run buildruntime 阶段只安装生产依赖并复制构建产物dist与前端静态资源public镜像体积更小启动方式用srvx同时托管dist/server.jsActor 服务器与dist/public前端静态文件对外暴露 8080 端口生产环境只需一个容器工作目录约定注释指出srvx的--static路径相对 CWD/app解析因此public/映射到/app/public/构建时须保证静态资源输出到该位置。需要说明的是本示例以npm脚本作为开发入口Docker 采用pnpm安装依赖两者在 package.json 与 Dockerfile 中并存是示例仓库的既有配置直接照搬时请按你的包管理器选择其一。总结把聊天室模式迁移到你的有状态应用从 examples/chat-room 可以提炼出一套可复用的 Actor 应用骨架需求Rivet Actors 对应能力本示例中的体现实时推送eventsc.broadcastnewMessage事件广播历史持久化db SQLite 迁移messages表与onMigrate多租户/多房间隔离actor({ name, key })实例模型房间名作为 key客户端远程调用actionssendMessage/getHistory类型安全全链路typeof registry共享类型createRivetKittypeof registry自动化验证setupTest vitest顺序、时间戳、多用户、空消息用例把「房间」换成「会话」「租户」「文档」「游戏对局」中的任意一个把messages表换成对应的业务表就能得到一套具备实时协作、持久化与隔离能力的有状态服务。仓库中的 docs/actors 文档体系actions / state / events / sqlite 等主题可作为继续深入每一层能力的阅读入口而 rivetkit-typescript/packages/rivetkit 下的 SDK 源码则是验证行为细节的第一手资料。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表