多用户实时更新
本文介绍如何为 DHTMLX Gantt 的实时更新模块实现服务器端支持。
原理
DHTMLX Gantt 提供了 RemoteEvents 助手,用于在多人之间实现实时同步。
关键工作流
- 当 Gantt 初始化时,
RemoteEvents客户端会打开一个 WebSocket 连接。 - 用户的变更(即 "create"、"edit" 或 "delete" 事件)通过
DataProcessor使用 REST API 发送到服务器。 - 服务器在处理完变更后,通过 WebSocket 将更新广播给所有已连接的客户端。
RemoteEvents客户端接收这些更新并将其应用到 Gantt,以确保跨用户的同步。
该设计使该后端模块能够在同一个应用中支持多个 DHTMLX 小部件(例如 Kanban、Gantt、Scheduler)。共享的格式简化了数据同步,而无需为每个小部件设置独立的后端。
前端集成
在加载 Gantt 数据的同一段代码中初始化 RemoteEvents 并设置 DataProcessor。
const AUTH_TOKEN = "token";
gantt.init("gantt_here");
gantt.parse("/data");
const dp = gantt.createDataProcessor({
url: "/data",
mode: "REST-JSON",
headers: {
"Remote-Token": AUTH_TOKEN,
},
});
const { RemoteEvents, remoteUpdates } = gantt.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
关键细节
RemoteEvents构造函数需要一个授权令牌,该令牌将在服务器验证时通过 "Remote-Token" 头部发送。- 第一个参数指定
WebSocket端点(例如,/api/v1)。 remoteUpdates助手处理来自 WebSocket 的消息并同步 Gantt 数据。
后端实现
本节描述如何构建支持实时更新的后端。
简化示例
要测试实现:
- 使用
npm install和npm run start命令提取并运行后端项目。 - 在两个独立的浏览器标签页中打开前端示例。
- 在其中一个标签页修改任务,修改应出现在第二个标签页中。
服务器端工作流
1. 握手请求
初始化时,RemoteEvents 向服务器发送一个 GET 请求以建立连接。
示例:
GET /api/v1
Remote-Token: AUTH_TOKEN
响应:
{"api":{},"data":{},"websocket":true}
2. WebSocket 连接
收到响应后,RemoteEvents 使用提供的端点建立 websocket 连接。
示例:
ws://${URL}?token=${token}&ws=1
服务器验证令牌并返回如下消息:
{"action":"start","body":"connectionId"}
示例实现:
app.get('/api/v1', (req, res) => {
const token = req.headers['remote-token'];
if (!token || !verifyAuthHeader(token)) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json({ api: {}, data: {}, websocket: true });
});
wss.on('connection', (ws, req) => {
const token = new URLSearchParams(req.url.split('?')[1]).get('token');
if (!token || !verifyAuthToken(token)) {
ws.close(1008, 'Unauthorized');
return;
}
const connectionId = generateConnectionId();
ws.send(JSON.stringify({ action: 'start', body: connectionId }));
});
3. 订阅
连接建立后,RemoteEvents 会订阅 Gantt 的特定实体(在 Task 和 Links 的情况下):
- 对于 tasks
{"action":"subscribe","name":"tasks"}
- 对于 links
{"action":"subscribe","name":"links"}
如要取消订阅:
- 对于 tasks
{"action":"unsubscribe","name":"tasks"}
- 对于 links
{"action":"unsubscribe","name":"links"}
注释
该格式支持应用程序同时使用多个 DHTMLX 小部件的场景。每个小部件仅订阅其数据相关的更新。
示例:
ws.on('message', function(message) {
try {
const msg = JSON.parse(message);
const client = clients.get(connectionId);
if (!client) return;
if (msg.action === 'subscribe') {
client.subscriptions.add(msg.name);
} else if (msg.action === 'unsubscribe') {
client.subscriptions.delete(msg.name);
}
} catch (err) {
console.error('Error parsing WebSocket message:', err);
}
});