兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
这是一个非常完整和设计精良的 Cloudflare Worker 项目。我将为您逐一解读这两个核心文件,并详细介绍其中使用的关键技术和设计模式。 ### 后端 我们将分两部分进行: 1. **`chatroom_do.js`**: 聊天室的大脑,一个有状态的持久对象 (Durable Object)。 2. **`worker.js`**: 项目的入口和总调度员,一个无状态的 Worker。 --- ### 第一部分: `chatroom_do.js` - 聊天室核心逻辑 这个文件定义了一个名为 `HibernatingChatRoom` 的类,它继承自 `DurableObject`。这意味着每个聊天室(如 "test" 房间、"future" 房间)都会有自己独立的一个 `HibernatingChatRoom` 实例,拥有独立的内存、存储和 WebSocket 连接。 #### 关键技术:Durable Objects (DO) * **是什么**:Durable Objects 是 Cloudflare 提供的一种**有状态**的 Worker。与普通 Worker 在每次请求后都可能被销毁不同,DO 实例会为同一个 ID(比如房间名)持续存在,并拥有自己的持久化存储。 * **为什么用在这里**:聊天室是典型的有状态应用。你需要记录: * 当前有哪些用户在线 (`sessions`)。 * 历史聊天记录 (`messages`)。 * 房间的访问权限 (`allowedUsers`)。 * 使用 DO 是实现这些功能的完美选择,它天然地将每个房间的状态隔离开来。 #### 函数级解读 ##### 1. `constructor(ctx, env)` - 构造与初始化 * **作用**:当一个特定房间的 DO 实例首次被创建时调用。 * **代码解读**: * `super(ctx, env)`: 调用父类构造函数,这是必须的。 * `this.ctx`: **DO 上下文对象**。它提供了访问 DO 核心功能的 API,如 `this.ctx.storage` (持久化存储) 和 `this.ctx.acceptWebSocket` (接受 WebSocket 连接)。 * `this.env`: 环境变量和绑定,与主 Worker 中的 `env` 相同。 * `this.messages = null`: **懒加载模式 (Lazy Loading)**。这里没有立即从存储中加载消息历史,而是设为 `null`。只有当第一个用户成功连接后,才会通过 `loadMessages()` 加载,这可以节省大量不必要的存储读取,尤其是在房间空闲时。 * `this.sessions = new Map()`: 用一个 Map 来存储当前所有活跃的 WebSocket 会话。Key 是会话ID,Value 是包含用户名、WebSocket 对象等信息的 `session` 对象。 * `this.allowedUsers = undefined`: **精巧的状态设计**。这里用 `undefined` 表示一个特殊状态:“这个房间的白名单功能从未被管理员配置过”。这与一个空的白名单(`new Set()`) 是有区别的,使得系统可以区分“不允许任何人进入”和“房间未激活”。 * `this.startHeartbeat()`: 启动心跳机制。 ##### 2. `debugLog(...)` & `broadcastDebugLog(...)` - 调试系统 * **作用**:一个内置的、实时的日志系统。 * **技术/模式**: * 它将日志存储在内存中的一个有界数组 (`this.debugLogs`) 中,防止内存无限增长。 * 通过 WebSocket (`MSG_TYPE_DEBUG_LOG`) 将日志实时推送给客户端,这对于远程调试非常有用。 ##### 3. 状态管理 (`initialize`, `saveState`, `loadMessages`, `saveMessages`) * **作用**:负责从持久化存储中读取和写入房间的状态。 * **技术/模式**: * `this.ctx.storage.get(...)` / `put(...)`: 使用 DO 的内置键值对存储。这个存储是与每个 DO 实例绑定的,保证了房间之间的数据隔离。 * `this.ctx.waitUntil(savePromise)`: **至关重要的技术**。当一个请求(如发送消息)即将结束时,DO 实例可能会进入休眠状态。`waitUntil` 告诉 Cloudflare 运行时:“请不要在我这个异步任务(`savePromise`)完成之前休眠我”,从而确保数据能被成功写入存储,避免数据丢失。 ##### 4. `handleWebSocketUpgrade`, `handleSessionInitialization`, `handleWebSocketSession` - WebSocket 连接处理 * **作用**:处理一个新用户的 WebSocket 连接请求。 * **技术/模式**: * **WebSocket 握手流程**: 1. `fetch` 收到 `Upgrade: websocket` 头的请求。 2. `handleWebSocketUpgrade` 创建一个 `WebSocketPair`,它包含一个客户端 `client` 和一个服务器端 `server`。 3. 返回 `client` 给用户浏览器,状态码为 `101`,完成升级。 4. `this.ctx.acceptWebSocket(server)`: DO 接管服务器端的 `server`,从此可以与客户端通信。 * **延迟关闭 (Delayed Close)**:在 `handleSessionInitialization` 中,如果用户授权失败,代码会先发送一条错误信息,然后使用 `setTimeout` 在 `waitUntil` 中延迟几秒再关闭连接。这是一个非常棒的**用户体验优化**,确保用户能看到错误提示,而不是连接被瞬间切断。 * **关注点分离**:将连接升级、权限验证、会话初始化这三个步骤分到不同的函数中,代码结构清晰。 ##### 5. `webSocketMessage`, `webSocketClose`, `webSocketError` - WebSocket 事件监听器 * **作用**:这些是 DO 内置的函数,当 DO 接管了一个 WebSocket 后,会自动调用它们来响应相应的事件。 * **`webSocketMessage`**: * 收到客户端消息时触发。 * 代码在这里实现了一个**消息分发器 (Dispatcher)**,通过 `JSON.parse` 解析消息,并根据 `data.type` 执行不同的逻辑(聊天、删除消息、WebRTC信令等)。 * **`webSocketClose` / `webSocketError`**: * 连接关闭或出错时触发。 * 它们都调用了统一的 `cleanupSession` 函数,来确保从 `this.sessions` 中移除会话并通知其他用户有人离开。 ##### 6. `handleApiRequest` & `handle...` API 处理器 * **作用**:处理所有发往 `/api/...` 的 HTTP 请求。 * **技术/模式**: * **内部路由**:使用 `Map` 对象创建了一个简单的路由表,将 API 路径映射到对应的处理函数,比一长串 `if/else` 更清晰、更易于扩展。 * **管理员权限**:通过检查 URL 查询参数 `secret` 是否与环境变量 `env.ADMIN_SECRET` 匹配来保护管理类 API,这是一种简单有效的认证方式。 * **白名单激活**:在 `handleAddUser` 中,当管理员首次为某个房间添加用户时,`this.allowedUsers` 从 `undefined` 变为一个 `Set` 对象,从而“激活”了这个房间。 ##### 7. `forwardRtcSignal` - WebRTC 信令转发 * **作用**:实现视频/语音通话的信令服务器功能。 * **技术/模式**: * **WebRTC Signaling**: WebRTC 需要一个中介(信令服务器)来帮助两个客户端交换连接信息(如 `offer`, `answer`, `candidate`)。这个 DO 就扮演了这个角色。 * 它本身**不处理任何音视频数据**,只是一个消息中继,将一个用户的信令消息准确地转发给目标用户。 ##### 8. `broadcast(...)` & `broadcastUserListUpdate()` - 广播机制 * **作用**:向房间内所有(或除某人外的所有)在线用户发送消息。 * **技术/模式**: * 遍历 `this.sessions` 并调用每个 `session.ws.send()`。 * **健壮性设计**:在 `try...catch` 块中发送消息,并能处理发送失败的情况(比如某个客户端网络突然断开)。它会将发送失败的会话收集起来并进行清理,保证了会话列表的健康。 --- ### 第二部分: `worker.js` - 入口与总调度员 这个文件是整个应用的入口点。它是一个**无状态**的 Worker,核心职责是**路由和转发**。 #### 关键技术:Cloudflare Workers * **是什么**:在 Cloudflare 的全球边缘网络上运行的轻量级 JavaScript 执行环境。它们非常快,因为代码部署在全球各地,离用户很近。 * **为什么用在这里**:作为整个应用的前端门户,它负责: * 处理与具体房间无关的全局任务(如文件上传、AI 调用)。 * 根据 URL 判断请求应该由谁处理,然后将请求“派发”给正确的 DO 实例。 * 响应定时任务。 #### 函数级解读 ##### 1. `globalThis.global = globalThis;` - Polyfill * **作用**:这是一个兼容性补丁。Cloudflare Worker 的运行时环境更像浏览器,没有 Node.js 中的 `global` 对象。但某些从 npm 安装的库(如代码注释中提到的 ECharts)可能会错误地依赖 `global`。这行代码创建了 `global` 并让它指向全局对象 `self` (`globalThis`),解决了这类库的兼容性问题。 ##### 2. `fetch(request, env, ctx)` - 主入口函数 * **作用**:所有进入你 Worker 域名的 HTTP 请求都会先经过这里。 * **技术/模式**:**路由器 (Router)**。这是这个函数的核心角色。它的逻辑可以分解为以下几步: 1. **CORS 预检**:处理 `OPTIONS` 请求,这是实现跨域 API 的标准步骤。 2. **管理页面路由 (`/management`)**:这是一个巧妙的技巧。它读取静态的 `management.html` 文件,然后用环境变量 `MANAGEMENT_ROOMS_LIST` 的内容替换掉 HTML 中的一个占位符,从而动态生成一个定制化的管理页面。 3. **全局 API 路由 (`/upload`, `/ai-explain`, etc.)**:这些 API 不属于任何特定房间,因此由主 Worker 直接处理。 * `/upload`: 接收文件,生成一个唯一的文件名(包含时间戳和随机串以防冲突),然后使用 `env.R2_BUCKET.put()` 将文件流式上传到 R2 存储桶,最后返回一个公开可访问的 URL。 4. **DO 转发路由 (`/api/...`, `/{roomName}`):** 这是连接主 Worker 和 DO 的桥梁。 * **获取 DO Stub**: * `env.CHAT_ROOM_DO.idFromName(roomName)`: 这是获取 DO 的关键。它使用房间名生成一个**确定性的、唯一的 ID**。这意味着所有对 "test" 房间的请求都会得到同一个 ID,从而访问到同一个 DO 实例。 * `env.CHAT_ROOM_DO.get(doId)`: 通过 ID 获取一个 **"Stub"** 对象。这个 Stub 是一个代理,你可以像调用本地对象的方法一样调用它,但实际上它会通过网络向真正的 DO 实例发送请求。 * **转发请求**: `stub.fetch(request)` 将原始的 HTTP 请求原封不动地转发给 DO 实例的 `fetch` 方法去处理。 5. **静态文件服务**:如果请求是访问某个房间(如 `https://.../test`),它会先向 DO 发送请求。DO 内部判断这是一个页面访问请求后,会返回一个带特殊头 `X-DO-Request-HTML` 的响应。主 Worker 看到这个头,就知道应该返回 `index.html` 的内容。 ##### 3. `scheduled(event, env, ctx)` - 定时任务处理器 * **作用**:当 `wrangler.toml` 中定义的 Cron 表达式到达指定时间时,Cloudflare 会自动调用这个函数。 * **技术/模式**: * **Cron Triggers**: Cloudflare 提供的定时任务服务。 * **任务映射 (`taskMap`)**: 代码没有使用 `if/else` 来判断是哪个 cron 触发了,而是用一个 `Map` (`taskMap`) 将 cron 字符串映射到具体的处理函数。这是一种非常优雅和可扩展的设计。 * **RPC (Remote Procedure Call)**: 在 `sendAutoPost` 和 `scheduled` 函数中,调用 DO 的方式是 `stub.cronPost(...)` 和 `stub.broadcastSystemMessage(...)`。这**不是** `fetch` 调用,而是**直接方法调用**(RPC)。RPC 比模拟一个完整的 HTTP 请求更轻量、更高效,是 Worker 与其拥有的 DO 之间进行内部通信的最佳方式。 * `ctx.waitUntil`: 同样,在这里使用 `waitUntil` 确保对 DO 的 RPC 调用有足够的时间在后台完成,即使 `scheduled` 函数本身已经执行完毕。 ### 总结:一个优雅的分布式应用架构 这两个文件共同构成了一个设计模式清晰、功能强大的实时应用: * **关注点分离 (Separation of Concerns)**: * `worker.js` (无状态) 负责**路由、全局API和任务调度**。 * `chatroom_do.js` (有状态) 负责**单个聊天室的所有业务逻辑和状态管理**。 * **高效通信**: * 客户端与服务器之间使用 **WebSockets** 进行实时双向通信。 * 主 Worker 与 DO 之间使用高效的 **RPC** 进行内部通信。 * **健壮性和可扩展性**: * 使用**心跳机制**和**健壮的广播逻辑**来维护健康的连接。 * 通过 **DO**,系统可以轻松扩展到成千上万个独立的聊天室,而无需担心状态冲突。 * 使用**懒加载**和 `waitUntil` 等技术优化性能和数据一致性。 ### 前端 当然,我们继续用同样的方式来详细解读您的前端应用。您提供了两个 HTML 文件,它们分别代表了两个独立的前端应用:一个用于管理的后台 (`management.html`) 和一个给最终用户使用的聊天室客户端 (`index.html`)。 我们将逐一进行分析。 --- ### 第一部分: `management.html` - 聊天室白名单管理后台 这是一个纯粹的管理工具,使用原生 JavaScript (Vanilla JS) 构建,界面简洁、功能专一。它的核心任务是让管理员能够方便地管理不同聊天室的用户白名单。 #### 整体概览与关键技术 * **用途**: 授权或移除用户访问特定聊天室的权限。 * **技术栈**: * **原生 JavaScript**: 没有使用任何前端框架(如 React, Vue),代码轻量且直接。 * **Fetch API**: 用于与后端进行所有的数据交换。 * **动态 HTML 注入**: 一个非常巧妙的技术。`worker.js` 在返回这个 HTML 文件之前,会用一个真实的房间列表替换掉 `/* MANAGEMENT_ROOMS_LIST_PLACEHOLDER */` 这个占位符。这使得前端页面能“知道”应该检查哪些房间的状态,而无需硬编码。 * **URL 参数驱动**: 通过 URL 的查询参数(`?secret=...` & `?room=...`)来传递管理员密钥和初始加载的房间名,方便分享和直接访问。 * **响应式 CSS**: 使用 `@media` 查询,确保在桌面和移动设备上都有良好的可用性。 #### 与后端的交互方式 这个管理页面**只通过 HTTP REST API** 与后端通信,它**不使用 WebSocket**。所有请求都指向了您在 `worker.js` 和 `chatroom_do.js` 中定义的 `/api/...` 路由。 * **读取操作 (GET)**: * `GET /api/room/status?roomName={room}`: 用于检查一个房间是否已“激活”(即是否已有白名单)。 * `GET /api/users/list?roomName={room}`: 获取指定房间的完整用户白名单列表。 * **写入操作 (POST)**: * `POST /api/users/add?roomName={room}&secret={secret}`: 向指定房间的白名单中添加一个新用户。 * `POST /api/users/remove?roomName={room}&secret={secret}`: 从指定房间的白名单中移除一个用户。 * **认证方式**: 所有写入操作都必须在 URL 中提供正确的 `secret` 参数,后端会用它与环境变量 `ADMIN_SECRET` 进行比对,实现简单的 API 密钥认证。 #### 逐函数解读 (JavaScript 部分) 1. **全局变量和 DOM 元素获取** * 代码首先通过 `URLSearchParams` 从当前页面的 URL 中解析出 `secret` 和 `room`。 * 然后获取所有需要操作的 HTML 元素的引用(输入框、按钮、列表等)。 2. `showStatus(msg, type)` * **作用**: 一个简单的 UI 反馈函数。 * **逻辑**: 在页面的状态栏 (`#status-message`) 显示信息,并根据 `type`('info', 'success', 'error')应用不同的 CSS 类,改变颜色以提示用户。 3. `fetchActivatedRooms()` * **作用**: 找出所有已经激活了白名单功能的房间。 * **逻辑**: * 它遍历由后端注入的 `potentialRoomsToCheck` 数组。 * 对每个房间名,它都发起一个 `fetch('/api/room/status?...')` 请求。 * **`Promise.all(...)`**: 这是一个关键技术,它允许**并行**发起所有这些网络请求,而不是一个接一个地等待,极大地提高了加载速度。 * 请求完成后,它过滤出那些响应中 `active: true` 的房间,并调用 `renderActivatedRooms` 来更新 UI。 4. `renderActivatedRooms(rooms)` * **作用**: 将已激活的房间列表渲染到页面上。 * **逻辑**: 清空旧列表,然后为每个房间名创建一个可点击的 `<li>` 元素。点击这个元素会自动填充房间名输入框并加载该房间的用户列表。 5. `fetchUsers()` * **作用**: 获取并显示指定房间的白名单用户。 * **逻辑**: * 读取房间名输入框的值。 * 调用 `fetch('/api/users/list?...')`。 * 成功后,调用 `renderUsers` 来更新用户列表 UI。 6. `renderUsers(users, isActive)` * **作用**: 将用户列表渲染到页面上。 * **逻辑**: * 清空旧列表。 * 根据 `isActive` 和 `users` 数组的长度,显示不同的提示信息(如“房间未激活”、“白名单为空”)。 * 为每个用户创建一个 `<li>` 元素,包含用户名和一个“移除”按钮。移除按钮绑定了 `removeUser` 函数。 7. `addUser()` / `removeUser(u)` * **作用**: 添加或移除用户的核心操作函数。 * **逻辑**: * **权限检查**: 首先检查 `adminSecret` 是否存在。 * **用户反馈**: 在请求期间禁用按钮并显示“添加中…”或“移除中…”的状态,提升用户体验。 * **API 调用**: 使用 `fetch` 发起 `POST` 请求,将用户名放在 JSON `body` 中。 * **状态更新**: 操作成功后,**重新调用 `fetchUsers()` 和 `fetchActivatedRooms()`** 来刷新整个页面的状态,确保数据的一致性。 8. `updateApiLinks()` * **作用**: 一个非常贴心的辅助功能。 * **逻辑**: 当用户在房间输入框中输入时,它会动态更新页面下方“管理工具”区域所有链接的 `href` 属性,将 `{roomName}` 占位符替换为当前输入的房间名,方便管理员快速访问各种调试 API。 9. **`DOMContentLoaded` 事件监听器** * **作用**: 整个应用的入口点。 * **逻辑**: 当页面 HTML 完全加载并解析完毕后执行。它会检查 URL 中是否有 `initialRoom` 参数,如果有就自动加载该房间。然后,它会获取所有已激活的房间列表,并为所有按钮绑定相应的点击事件处理函数。 --- ### 第二部分: `index.html` - 实时聊天室客户端 这是功能非常丰富的主应用,一个单页面应用 (SPA),集成了实时聊天、文件上传、音视频通话、AI 助手等多种功能。 #### 整体概览与关键技术 * **用途**: 用户加入聊天室,进行实时交流。 * **技术栈**: * **WebSockets**: 核心技术,用于与后端的 `Durable Object` 建立持久化连接,实现消息的实时收发。 * **原生 JavaScript**: 同样未使用框架,但代码结构和模式非常成熟。 * **marked.js**: 用于将用户输入的 Markdown 格式文本解析成 HTML,支持富文本消息。 * **WebRTC (Real-Time Communication)**: 用于实现用户间的音视频通话。前端负责获取音视频流、建立对等连接,后端 DO 仅作为**信令服务器**转发连接信息。 * **Web Audio API & MediaRecorder API**: 用于录制和处理音频消息。 * **File API, FileReader, Canvas API**: 用于实现客户端图片预览、压缩和上传。 * **性能优化**: * **`requestAnimationFrame`** 和 **消息队列**: 将收到的多条消息先放入队列,然后在一个动画帧内批量更新 DOM,避免了频繁的页面重绘,极大地提升了流畅度。 * **`requestIdleCallback`**: 在处理大量历史消息时,利用浏览器的空闲时间分块处理,避免阻塞主线程,防止页面卡顿。 * **事件节流 (`throttle`)**: 防止某些高频事件(如滚动)过于频繁地触发处理函数。 * **响应式设计与移动端优化**: * 通过 CSS `@media` 查询适配不同屏幕尺寸。 * 实现了移动端常见的侧滑菜单 (`sidebar`)。 * 使用了 `env(safe-area-inset-bottom)` 来适配 iPhone 等带有“刘海”或“下巴”的设备,防止输入框被遮挡。 #### 与后端的交互方式 这个客户端同时使用 **WebSockets** 和 **HTTP API** 与后端交互,分工明确。 * **WebSocket 通信 (与 `chatroom_do.js` 交互)**: * **连接**: `new WebSocket('wss://.../{roomName}?username={username}')`,将房间名和用户名通过 URL 传递给后端。 * **发送的消息类型**: * `'chat'`: 发送文本、图片、音频消息。 * `'delete'`: 请求删除自己发送的某条消息。 * `'offer'`, `'answer'`, `'candidate'`, `'call_end'`: WebRTC 信令,用于建立和结束音视频通话。 * **接收的消息类型**: * `'welcome'`: 连接成功后,服务器发来的欢迎消息,包含历史记录。 * `'chat'`: 其他用户发送的新消息。 * `'delete'`: 某条消息被删除的通知。 * `'user_join'` / `'user_leave'`: 用户加入或离开的通知(虽然代码主要依赖 `user_list_update`)。 * `'user_list_update'`: **核心状态同步**。服务器主动推送的最新的、完整的在线用户列表。前端直接使用这个列表来渲染UI,而不是自己维护加入/离开状态,这更健壮。 * `'auth_failed'`: 授权失败(如不在白名单)的通知。 * WebRTC 信令。 * `'debug_log'`: 接收来自 DO 的实时调试日志。 * **HTTP API 通信 (与 `worker.js` 交互)**: * `POST /upload`: 上传图片或音频文件。前端先将文件上传到这个全局 API,获取到返回的 R2 公开 URL 后,再通过 WebSocket 将包含这个 URL 的消息发送出去。 * `POST /ai-explain`: 请求 AI 解释选中的文本。 * `POST /ai-describe-image`: 请求 AI 描述图片内容。 #### 逐函数解读 (JavaScript 部分,按功能模块) 1. **初始化与连接 (`connectWebSocket`, `reconnectLogic`)** * 在页面加载时,获取用户名和房间名,然后调用 `connectWebSocket`。 * `connectWebSocket` 创建 WebSocket 实例并设置 `onopen`, `onmessage`, `onclose`, `onerror` 四个核心事件处理器。 * `onopen`: 连接成功,更新 UI 状态为“已连接”。 * `onmessage`: **消息总分发器**。收到消息后,解析 JSON,根据 `data.type` 调用不同的处理函数(如 `appendChatMessage`, `updateOnlineUserListDisplay`, `handleRtcSignal` 等)。 * `onclose`: 连接关闭。它会调用 `reconnectLogic`,该函数使用**指数退避 (Exponential Backoff)** 策略尝试重连(等待时间从1秒开始,逐渐增加到30秒),避免在服务器故障时对服务器造成冲击。 2. **UI 渲染与更新** * **`updateOnlineUserListDisplay(onlineUsers)`**: 这是个非常好的实践。它不依赖零散的 `user_join`/`user_leave` 消息,而是直接接收后端推送的完整列表来渲染UI。这能保证前端的在线列表始终与后端权威状态一致。 * **`createMessageElement(msg)`**: 消息渲染的核心。它接收一个消息对象,生成对应的 HTML 字符串。它能处理不同类型的消息(文本、图片、音频),并使用 `marked.parse` 渲染 Markdown。 * **`appendChatMessage` & `processMessageQueue`**: 这是性能优化的关键。新消息不会立即插入 DOM,而是被推入一个队列 (`messageQueue`)。`requestAnimationFrame` 会在浏览器下一次重绘前,调用 `processMessageQueue` 将队列中的所有消息一次性地、批量地插入 DOM。这大大减少了 DOM 操作次数,使聊天滚动非常流畅。 * **`processHistoryMessages`**: 处理历史消息时,使用了 `requestIdleCallback`,将大量历史消息的渲染任务分解成小块,在浏览器不忙的时候执行,避免了页面加载时长时间的白屏或卡顿。 3. **用户交互与消息发送** * **`messageForm` 提交事件**: 拦截表单提交,根据输入框内容和是否有待上传文件,调用 `sendImageMessage` 或直接通过 WebSocket 发送文本消息。 * **`sendImageMessage` / `sendAudioMessage`**: 实现了“**先上传,后发送**”的流程。它们先 `POST` 文件到 `/upload` API,拿到 URL 后,再构造一个 `type: 'image'` 或 `type: 'audio'` 的 WebSocket 消息发送给 DO。 * **`handleImageSelection` & `compressImage`**: 在用户选择图片后,**在客户端进行压缩**,减小了上传文件的大小,节省了用户流量和上传时间。 4. **高级功能** * **上下文菜单 (`showContextMenu`, `contextMenu` 事件监听)**: * 通过监听 `contextmenu` 事件(右键点击)来触发。 * `showContextMenu` 负责定位菜单并根据消息类型(文本/图片)和所有者(自己/他人)动态显示/隐藏菜单项(如“删除”只对自己可见)。 * 菜单的点击事件处理器会根据 `data-action` 执行相应操作:调用 AI API、复制文本或发送删除消息的 WebSocket 请求。 * **AI 解释**: * 调用 `/ai-explain` 或 `/ai-describe-image` API。 * 在请求期间,在消息下方动态创建一个“思考中”的 UI。 * 请求成功后,用返回的 Markdown 内容替换掉加载 UI,并添加复制和关闭按钮。 * **WebRTC (`startCall`, `handleRtcSignal`, etc.)**: * `startCall`: 当用户点击呼叫按钮,它会创建 `RTCPeerConnection`,创建 `offer`,设置本地描述,然后通过 WebSocket 将 `offer` 发送给目标用户。 * `handleRtcSignal`: 接收来自对方的信令。如果是 `offer`,就创建 `answer` 并回送;如果是 `answer`,就设置远程描述;如果是 `candidate`,就添加到连接中。这个流程完成了 WebRTC 的“握手”。 * 一旦连接建立,音视频流就直接在两个用户的浏览器之间传输,不再经过服务器。 这个前端应用是一个非常出色的原生 JS 项目范例,它不仅功能完备,而且在架构设计、性能优化和用户体验方面都考虑得非常周到。它与后端的分工清晰,交互模式高效,是学习现代 Web 应用开发的绝佳材料。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章