第40章 Cloudreve 第40章:文件变更事件(SSE 实时通知)
文件变更事件 Cloudreve 提供基于 Server-Sent Events (SSE) 的实时文件变更通知服务。客户端可订阅文件变更事件,在文件或文件夹被创建、修改、重命名或删除时接收通知。
端点 GET /api/v4/file/events 完整的 API 架构请参阅 Events 流 API 文档。 认证:需要 JWT
参数 类型 位置 必填 描述 uri string query 否 要监听的文件夹 URI,例如:cloudreve://my X-Cr-Client-Id string header 否 唯一的客户端标识符(必须是 UUID),用于订阅管理
Server-Sent Events
该端点返回 text/event-stream 响应。连接保持打开状态,服务器在事件发生时将其推送给客户端。每个事件遵循 SSE 格式:
event:
事件类型 事件类型 描述 subscribed 初始订阅建立成功 resumed 重连后恢复订阅(使用相同的 X-Cr-Client-Id) event 文件变更通知,包含一个或多个变更事件 keep-alive 用于维持连接的心跳包 reconnect-required 服务器要求客户端断开并重新建立连接
客户端标识 X-Cr-Client-Id 请求头具有多重用途:
过滤自生成事件 当客户端对文件做出更改(上传、重命名、删除等)时,这些更改会触发事件。通过提供一致的 X-Cr-Client-Id,服务器会过滤掉源自同一客户端的事件,防止冗余处理。
订阅恢复 如果客户端断开连接(由于网络问题、应用进入后台等),服务器会临时存储该客户端 ID 的事件。当客户端使用相同的 X-Cr-Client-Id 重新连接时: 服务器发送 resumed 事件而非 subscribed 将存储的事件投递给客户端 客户端可以无缝继续,不会错过任何变更
提示:为每个客户端实例生成一个持久化的 UUID,并在重连时重用,以充分利用订阅恢复功能。
重连策略 客户端应实现带有指数退避的自动重连: 在连接失败或意外关闭时,等待一段时间后重连 从短时间延迟开始(例如,1 秒) 每次失败尝试后延迟加倍,直至达到上限(例如,32 秒) 在成功连接后重置延迟
当接收到 reconnect-required 事件时,客户端应: 优雅地关闭当前连接 立即重连,不进行退避延迟 继续使用相同的 X-Cr-Client-Id 以恢复订阅
事件防抖与合并 服务器实现了智能的事件防抖和合并,以减少噪音和带宽消耗: 防抖:对同一文件的快速连续变更进行批处理 延迟:事件会短暂保持,以便将相关操作分组 合并:冗余或已被取代的事件将被整合(例如,多次修改合并为一个事件,创建后跟删除则相互抵消)
这意味着客户端接收到的事件数量可能少于实际操作次数,但每个事件都反映了受影响文件的有意义最终状态。
示例 javascript const clientId = localStorage.getItem('clientId') || crypto.randomUUID(); localStorage.setItem('clientId', clientId);
function connect() { const url = new URL('/api/v4/file/events', 'https://your-site.com'); url.searchParams.set('uri', 'cloudreve://my');
const eventSource = new EventSource(url, { headers: { 'Authorization': `Bearer ${accessToken}`, 'X-Cr-Client-Id': clientId } });
eventSource.addEventListener('subscribed', (e) => { console.log('Subscription established'); });
eventSource.addEventListener('resumed', (e) => { console.log('Subscription resumed'); });
eventSource.addEventListener('event', (e) => { const events = JSON.parse(e.data); for (const event of events) { console.log(`${event.type}: ${event.from} -> ${event.to}`); } });
eventSource.addEventListener('reconnect-required', (e) => { eventSource.close(); connect(); // 立即重连 });
eventSource.onerror = (e) => { eventSource.close(); setTimeout(connect, 5000); // 延迟重连 }; }
connect();