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