完善websocket消息确认机制

This commit is contained in:
2026-09-29 11:14:09 +08:00
parent 5e57920039
commit 9dc511f08b
2 changed files with 91 additions and 42 deletions
+1 -1
View File
@@ -189,7 +189,7 @@ WebSocket 挂载在 HTTP Server 上,客户端连接地址的形式为 `ws://<h
4. **WebSocket 数据库写入字段不匹配**:`websocket/index.js` 插入 `message_id`,但 `db/sql.txt` 的 `messages` 表没有该列。需统一消息 ID 设计,例如新增唯一字段及迁移,或使用现有自增 `id` 并调整协议和查询。(已解决)
5. **发送确认早于持久化结果**:`sendSuccess` 和接收方推送发生在异步 `db.query` 回调之前。应将成功确认和实时推送放到成功回调中;失败时只返回错误,不推送为已发送消息。(已解决)
6. **连接管理器广播引用未定义的 `WebSocket`**:`connectionManager.js` 的 `broadcast` 检查 `WebSocket.OPEN`,但文件中没有导入 `ws`。当广播函数被调用时可能抛出 `ReferenceError`。应在此模块引入库常量,或由连接对象状态采用明确且可用的检查方式。(已解决)
7. **ACK 参数调用不一致**:消息分发通过 `handleAck(data)` 调用,而函数签名是 `handleAck(receiverName, data)`;函数内部随后读取第二个参数,可能因 `data` 未定义而失败。应统一函数参数并以当前连接用户名作为 ACK 接收者身份。
7. **ACK 参数调用不一致**:消息分发通过 `handleAck(data)` 调用,而函数签名是 `handleAck(receiverName, data)`;函数内部随后读取第二个参数,可能因 `data` 未定义而失败。应统一函数参数并以当前连接用户名作为 ACK 接收者身份。(已解决)
8. **重复登录连接的关闭竞态**:同一用户名的新连接会覆盖 Map 中旧连接;旧连接关闭时无条件按用户名删除,可能把新连接也从 Map 移除。移除连接时应确认 Map 中仍是即将关闭的那个 WebSocket。
9. **分页和输入校验不足**:`contactMessagesInfo` 未验证 `contactName`、`limit`、`offset` 的类型与范围,也未约束最大页大小。建议验证请求参数并设置默认值和上限。
10. **在线状态及可靠性受单进程限制**:Map 不支持多实例共享、服务重启恢复、离线消息投递或持久化 ACK 状态。若部署多实例,需要共享在线状态/消息协调机制;若要求可靠消息,应定义消息状态、幂等键、重试及离线投递策略。
+89 -40
View File
@@ -43,6 +43,7 @@ function initWebSocket(server) {
console.log("WebSocket用户:", user);
const username = user.username;
ws.username = username;
// 保存连接
addConnection(username, ws);
@@ -54,15 +55,16 @@ function initWebSocket(server) {
// 接收客户端消息
ws.on("message", (message) => {
try {
const data = JSON.parse(message.toString());
console.log("收到WebSocket消息:", data);
try {
const data = JSON.parse(message.toString());
console.log("收到WebSocket消息:", data);
handleMessage(username, data);
} catch (error) {
console.error("WebSocket消息处理失败:", error);
}
});
// websocket收到消息,必须通过解析data内容才知道谁是发送者谁是接收者
handleMessage(data, ws);
} catch (error) {
console.error("WebSocket消息处理失败:", error);
}
});
// 连接关闭
ws.on("close", () => {
@@ -87,13 +89,13 @@ function initWebSocket(server) {
}
// 处理WebSocket消息
function handleMessage(username, data) {
function handleMessage(data, ws) {
switch (data.type) {
case "sendMessage":
handleSendMessage(username, data);
handleSendMessage(data, ws);
break;
case "ack":
handleAck(data);
handleAck(data, ws);
break;
default:
@@ -102,21 +104,36 @@ function handleMessage(username, data) {
}
// 处理发送消息
function handleSendMessage(senderName, data) {
function handleSendMessage(data, ws) {
const senderName = ws.username;
const messageId = data.messageId;
const receiverName = data.receiverName;
const content = data.content;
if (!messageId) {
console.log("消息缺少 messageId");
return;
}
if (!receiverName) {
console.log("消息缺少接收者");
return;
}
if (!content) {
console.log("消息内容为空");
return;
}
console.log(
`${senderName} → ${receiverName}:${content}`
);
const sql = `
INSERT INTO messages
(message_id, sender_name, receiver_name, content)
VALUES (?, ?, ?, ?)
`;
INSERT INTO messages
(message_id, sender_name, receiver_name, content)
VALUES (?, ?, ?, ?)
`;
db.query(sql, [
messageId,
senderName,
@@ -138,6 +155,7 @@ function handleSendMessage(senderName, data) {
}
return;
}
// 数据库确认写入后,才确认发送并推送给接收者
const senderWs = getConnection(senderName);
if (senderWs) {
@@ -169,36 +187,67 @@ function handleSendMessage(senderName, data) {
});
}
function handleAck(receiverName, data) {
function handleAck(data, ws) {
const messageId = data.messageId;
const senderName = data.senderName;
console.log(
`${receiverName} 收到消息 ${messageId},发送 ACK`
);
// 当前 WebSocket 对应的真实用户
const ackUser = ws.username;
// 找到原发送者
const senderWs = getConnection(senderName);
if (!senderWs) {
console.log(
`${senderName} 当前不在线`
);
if (!messageId) {
console.log("ACK 缺少 messageId");
return;
}
// 把 ACK 转给原发送者
senderWs.send(
JSON.stringify({
type: "ack",
messageId:
messageId
})
);
const sql = `
SELECT sender_name, receiver_name
FROM messages
WHERE message_id = ?
`;
db.query(sql, [messageId], (error, results) => {
if (error) {
console.error("查询消息失败:", error);
return;
}
console.log(
`ACK ${messageId} 已发送给 ${senderName}`
);
if (results.length === 0) {
console.log(`消息 ${messageId} 不存在`);
return;
}
const message = results[0];
// 当前用户必须是这条消息的接收者
if (message.receiver_name !== ackUser) {
console.log(
`${ackUser} 无权确认消息 ${messageId}`
);
return;
}
// 原消息发送者
const originalSender = message.sender_name;
console.log(
`${ackUser} 已收到来自 ${originalSender} 的消息 ${messageId},发送 ACK`
);
const senderWs = getConnection(originalSender);
if (!senderWs) {
console.log(
`${originalSender} 当前不在线`
);
return;
}
senderWs.send(
JSON.stringify({
type: "ack",
messageId: messageId
})
);
console.log(
`ACK ${messageId} 已发送给 ${originalSender}`
);
});
}