diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx index b96f9f6..4fda387 100644 --- a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx +++ b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx @@ -203,7 +203,9 @@ const AdvancedSettings = ({ onLogout }) => { }, }); - const handleSave = () => { + const [saving, setSaving] = React.useState(false); + + const handleSave = async () => { if (enableMqtt) { if (brokerAddress && !validateIP(brokerAddress)) { message.error('Broker 地址格式不正确,请检查后重试'); @@ -218,7 +220,20 @@ const AdvancedSettings = ({ onLogout }) => { return; } } - saveAllSettings(); + + try { + setSaving(true); + const success = await saveAllSettings(); + + if (!success) { + console.log('保存操作未完全成功'); + } + } catch (error) { + console.error('保存配置时发生异常:', error); + message.error('保存配置时发生异常,请重试'); + } finally { + setSaving(false); + } }; const handleReset = () => { @@ -226,7 +241,7 @@ const AdvancedSettings = ({ onLogout }) => { }; return ( - +
{ icon={} onClick={handleSave} size="large" - disabled={!isConnected || loading} + disabled={!isConnected || loading || saving} + loading={saving} style={{ height: 42, borderRadius: 8, @@ -306,7 +322,7 @@ const AdvancedSettings = ({ onLogout }) => { borderColor: "white", }} > - 保存更改 + {saving ? '保存中...' : '保存更改'} @@ -415,7 +431,7 @@ const AdvancedSettings = ({ onLogout }) => { 启用离线超时告警
- 超过此时长未接收到新数据则触发报警 + 系统将按您设置的间隔周期持续检测:若连续离线时长达到整个周期,则会触发告警 diff --git a/package.json b/package.json index 9d7d508..83a4955 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wuyuanbiaoba-web", - "version": "1.1.2", + "version": "1.1.3", "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/server/tcpProxy/index.js b/server/tcpProxy/index.js index c6d2886..c5eb582 100644 --- a/server/tcpProxy/index.js +++ b/server/tcpProxy/index.js @@ -1,40 +1,54 @@ const net = require('net'); const WebSocket = require('ws'); + // TCP代理配置 -let TCP_HOST = '127.0.0.1'; // 因为下位机和上位机在同一台机器上,所以使用localhost +let TCP_HOST = '127.0.0.1'; const TCP_PORT = 2230; -// 创建独立的WebSocket服务器用于TCP代理 + function setupTcpProxy(conf) { if (conf && conf.flag === 'localdev') { - TCP_HOST = '10.8.30.179' //本地开发配置 + TCP_HOST = '10.8.30.179' } console.log(`TCP代理目标地址: ${TCP_HOST}:${TCP_PORT}`); - // 从配置中获取端口,如果没有则使用默认端口 - const wsPort = (conf && conf.port) ? Number(conf.port) + 1 : 5001; // WebSocket端口比HTTP端口大1 - // console.log(`准备在端口 ${wsPort} 启动WebSocket服务器`); + const wsPort = (conf && conf.port) ? Number(conf.port) + 1 : 5001; const wss = new WebSocket.Server({ port: wsPort, - host: '0.0.0.0', // 监听所有网络接口,允许局域网访问 + host: '0.0.0.0', path: '/tcp-proxy' }); wss.on('connection', (ws, request) => { - // console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); + console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); - // 创建TCP连接 const tcpClient = new net.Socket(); - // TCP数据缓冲区 let tcpDataBuffer = ''; - let tcpConnected = false; // 新增:TCP连接状态标志 + let tcpConnected = false; + let connectionTimeout = null; + + // 设置连接超时 + connectionTimeout = setTimeout(() => { + if (!tcpConnected) { + console.error('TCP连接超时'); + if (ws.readyState === WebSocket.OPEN) { + ws.close(1011, 'TCP连接超时'); + } + tcpClient.destroy(); + } + }, 5000); // 5秒超时 // TCP连接成功 tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => { - // console.log(process.env); - // console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`); + console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`); tcpConnected = true; + // 清除超时定时器 + if (connectionTimeout) { + clearTimeout(connectionTimeout); + connectionTimeout = null; + } + // 向WebSocket客户端发送就绪信号 if (ws.readyState === WebSocket.OPEN) { const readySignal = JSON.stringify({ @@ -51,136 +65,138 @@ function setupTcpProxy(conf) { // TCP接收数据,转发到WebSocket tcpClient.on('data', (data) => { - let textData; - // 尝试解析为文本 try { - textData = data.toString('utf8'); - } catch (e) { - console.log('TCP数据无法解析为文本'); - return; - } - - // 将新数据添加到缓冲区 - tcpDataBuffer += textData; - - // 检查是否有完整的消息(以\n\n结尾) - let endIndex; - while ((endIndex = tcpDataBuffer.indexOf('\n\n')) !== -1) { - // 提取完整消息 - const completeMessage = tcpDataBuffer.substring(0, endIndex); - // 从缓冲区移除已处理的消息 - tcpDataBuffer = tcpDataBuffer.substring(endIndex + 2); - - // console.log('提取到完整消息:', completeMessage.length, '字节'); - - // 转发完整消息到WebSocket - if (ws.readyState === WebSocket.OPEN) { - // console.log('准备发送完整消息到WebSocket:', completeMessage.length, '字节'); - // console.log('消息内容:', completeMessage); - ws.send(completeMessage, (err) => { - if (err) { - console.error(`[${new Date().toLocaleString()}] WebSocket发送数据错误:`, err); - } else { - // console.log('完整消息已转发到WebSocket客户端'); - } - }); + const textData = data.toString('utf8'); + tcpDataBuffer += textData; + + let endIndex; + while ((endIndex = tcpDataBuffer.indexOf('\n\n')) !== -1) { + const completeMessage = tcpDataBuffer.substring(0, endIndex); + tcpDataBuffer = tcpDataBuffer.substring(endIndex + 2); + + if (ws.readyState === WebSocket.OPEN) { + ws.send(completeMessage, (err) => { + if (err) { + console.error(`WebSocket发送数据错误:`, err); + } + }); + } } + } catch (e) { + console.error('TCP数据处理错误:', e); } - - // console.log('缓冲区剩余数据:', tcpDataBuffer.length, '字节'); }); + function compactJson(str) { try { - // 如果是 JSON 字符串,解析后再 stringify(无换行/缩进) - if (typeof str === 'string') return JSON.stringify(JSON.parse(str)); - // 如果是对象/Buffer等,统一转成无格式 JSON 字符串 + if (typeof str === 'string') { + return JSON.stringify(JSON.parse(str)); + } return JSON.stringify(str); } catch { - // 非 JSON 的情况,去掉所有换行,避免触发 \n\n 分隔 return String(str).replace(/\r?\n/g, ' '); } } + // WebSocket接收数据,转发到TCP ws.on('message', (data) => { - // 处理可能的Buffer数据,转换为字符串 - let messageStr; - - if (Buffer.isBuffer(data)) { - messageStr = data.toString('utf8'); - } else if (typeof data === 'string') { - messageStr = data; - } else { - return; - } - messageStr = compactJson(messageStr); - // 转发字符串数据到TCP服务器 - if (tcpClient.writable) { - console.log('发送数据到TCP服务器:', messageStr); - - // 检查数据大小 - // const dataSize = Buffer.byteLength(messageStr, 'utf8'); - // console.log(`数据大小: ${dataSize} bytes`); - - // 直接发送完整数据 - tcpClient.write(messageStr + '\n\n', (err) => { - if (err) { - console.error(`[${new Date().toLocaleString()}] TCP发送数据错误:`, err); - } - }); - } else { - // console.warn('TCP连接不可写,无法发送数据'); + try { + let messageStr; + if (Buffer.isBuffer(data)) { + messageStr = data.toString('utf8'); + } else if (typeof data === 'string') { + messageStr = data; + } else { + return; + } + + messageStr = compactJson(messageStr); + + if (tcpClient.writable && tcpConnected) { + console.log('发送数据到TCP服务器:', messageStr); + tcpClient.write(messageStr + '\n\n', (err) => { + if (err) { + console.error('TCP发送数据错误:', err); + } + }); + } else { + console.warn('TCP连接不可用,无法发送数据'); + } + } catch (e) { + console.error('WebSocket消息处理错误:', e); } }); // TCP连接错误处理 tcpClient.on('error', (err) => { - console.error(`[${new Date().toLocaleString()}] TCP连接错误:`, err); - tcpDataBuffer = ''; // 清理缓冲区 + console.error('TCP连接错误:', err); + tcpConnected = false; + tcpDataBuffer = ''; + + // 清除超时定时器 + if (connectionTimeout) { + clearTimeout(connectionTimeout); + connectionTimeout = null; + } + if (ws.readyState === WebSocket.OPEN) { - ws.close(1011, 'TCP连接错误'); + ws.close(1011, `TCP连接错误: ${err.message}`); } }); // TCP连接关闭 tcpClient.on('close', () => { - // console.log('TCP连接已关闭'); - tcpDataBuffer = ''; // 清理缓冲区 + console.log('TCP连接已关闭'); + tcpConnected = false; + tcpDataBuffer = ''; + + if (connectionTimeout) { + clearTimeout(connectionTimeout); + connectionTimeout = null; + } + if (ws.readyState === WebSocket.OPEN) { - ws.close(); + ws.close(1000, 'TCP连接关闭'); } }); // WebSocket连接关闭 - ws.on('close', () => { - // console.log('WebSocket连接已关闭'); - tcpDataBuffer = ''; // 清理缓冲区 - if (tcpClient.writable) { + ws.on('close', (code, reason) => { + console.log(`WebSocket连接已关闭: ${code} - ${reason}`); + tcpDataBuffer = ''; + + if (connectionTimeout) { + clearTimeout(connectionTimeout); + connectionTimeout = null; + } + + if (tcpClient && !tcpClient.destroyed) { tcpClient.destroy(); } }); // WebSocket错误处理 ws.on('error', (err) => { - console.error(`[${new Date().toLocaleString()}] WebSocket错误:`, err); - if (tcpClient.writable) { + console.error('WebSocket错误:', err); + if (tcpClient && !tcpClient.destroyed) { tcpClient.destroy(); } }); }); wss.on('listening', () => { - // console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); - // console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`); - // console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`); - // console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`); + console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); + console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`); }); wss.on('error', (err) => { - console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err); + console.error('WebSocket服务器错误:', err); + if (err.code === 'EADDRINUSE') { + console.error(`端口 ${wsPort} 已被占用,请检查是否有其他服务在使用该端口`); + } }); return wss; } - module.exports = { setupTcpProxy };