Browse Source

feat: 优化保存设置功能,增加保存状态管理和错误处理,修改超时界面文案

master
qinjian 3 weeks ago
parent
commit
51b6926240
  1. 28
      client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
  2. 2
      package.json
  3. 208
      server/tcpProxy/index.js

28
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 (enableMqtt) {
if (brokerAddress && !validateIP(brokerAddress)) { if (brokerAddress && !validateIP(brokerAddress)) {
message.error('Broker 地址格式不正确,请检查后重试'); message.error('Broker 地址格式不正确,请检查后重试');
@ -218,7 +220,20 @@ const AdvancedSettings = ({ onLogout }) => {
return; 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 = () => { const handleReset = () => {
@ -226,7 +241,7 @@ const AdvancedSettings = ({ onLogout }) => {
}; };
return ( return (
<Spin spinning={loading} tip="正在加载配置..."> <Spin spinning={loading || saving} tip={saving ? "正在保存配置..." : "正在加载配置..."}>
<div <div
style={{ style={{
padding: "12px", padding: "12px",
@ -297,7 +312,8 @@ const AdvancedSettings = ({ onLogout }) => {
icon={<SaveOutlined />} icon={<SaveOutlined />}
onClick={handleSave} onClick={handleSave}
size="large" size="large"
disabled={!isConnected || loading} disabled={!isConnected || loading || saving}
loading={saving}
style={{ style={{
height: 42, height: 42,
borderRadius: 8, borderRadius: 8,
@ -306,7 +322,7 @@ const AdvancedSettings = ({ onLogout }) => {
borderColor: "white", borderColor: "white",
}} }}
> >
保存更改 {saving ? '保存中...' : '保存更改'}
</Button> </Button>
</Space> </Space>
</Col> </Col>
@ -415,7 +431,7 @@ const AdvancedSettings = ({ onLogout }) => {
<Text strong>启用离线超时告警</Text> <Text strong>启用离线超时告警</Text>
<br /> <br />
<Text type="secondary" style={{ fontSize: 12 }}> <Text type="secondary" style={{ fontSize: 12 }}>
超过此时长未接收到新数据则触发报 系统将按您设置的间隔周期持续检测若连续离线时长达到整个周期则会触发告
</Text> </Text>
</Col> </Col>
<Col> <Col>

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "wuyuanbiaoba-web", "name": "wuyuanbiaoba-web",
"version": "1.1.2", "version": "1.1.3",
"main": "index.html", "main": "index.html",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",

208
server/tcpProxy/index.js

@ -1,40 +1,54 @@
const net = require('net'); const net = require('net');
const WebSocket = require('ws'); const WebSocket = require('ws');
// TCP代理配置 // TCP代理配置
let TCP_HOST = '127.0.0.1'; // 因为下位机和上位机在同一台机器上,所以使用localhost let TCP_HOST = '127.0.0.1';
const TCP_PORT = 2230; const TCP_PORT = 2230;
// 创建独立的WebSocket服务器用于TCP代理
function setupTcpProxy(conf) { function setupTcpProxy(conf) {
if (conf && conf.flag === 'localdev') { if (conf && conf.flag === 'localdev') {
TCP_HOST = '10.8.30.179' //本地开发配置 TCP_HOST = '10.8.30.179'
} }
console.log(`TCP代理目标地址: ${TCP_HOST}:${TCP_PORT}`); 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({ const wss = new WebSocket.Server({
port: wsPort, port: wsPort,
host: '0.0.0.0', // 监听所有网络接口,允许局域网访问 host: '0.0.0.0',
path: '/tcp-proxy' path: '/tcp-proxy'
}); });
wss.on('connection', (ws, request) => { wss.on('connection', (ws, request) => {
// console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`);
// 创建TCP连接
const tcpClient = new net.Socket(); const tcpClient = new net.Socket();
// TCP数据缓冲区
let tcpDataBuffer = ''; 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连接成功 // TCP连接成功
tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => { 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; tcpConnected = true;
// 清除超时定时器
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
// 向WebSocket客户端发送就绪信号 // 向WebSocket客户端发送就绪信号
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
const readySignal = JSON.stringify({ const readySignal = JSON.stringify({
@ -51,136 +65,138 @@ function setupTcpProxy(conf) {
// TCP接收数据,转发到WebSocket // TCP接收数据,转发到WebSocket
tcpClient.on('data', (data) => { tcpClient.on('data', (data) => {
let textData;
// 尝试解析为文本
try { try {
textData = data.toString('utf8'); const textData = data.toString('utf8');
} catch (e) { tcpDataBuffer += textData;
console.log('TCP数据无法解析为文本');
return; let endIndex;
} while ((endIndex = tcpDataBuffer.indexOf('\n\n')) !== -1) {
const completeMessage = tcpDataBuffer.substring(0, endIndex);
// 将新数据添加到缓冲区 tcpDataBuffer = tcpDataBuffer.substring(endIndex + 2);
tcpDataBuffer += textData;
if (ws.readyState === WebSocket.OPEN) {
// 检查是否有完整的消息(以\n\n结尾) ws.send(completeMessage, (err) => {
let endIndex; if (err) {
while ((endIndex = tcpDataBuffer.indexOf('\n\n')) !== -1) { console.error(`WebSocket发送数据错误:`, err);
// 提取完整消息 }
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客户端');
}
});
} }
} catch (e) {
console.error('TCP数据处理错误:', e);
} }
// console.log('缓冲区剩余数据:', tcpDataBuffer.length, '字节');
}); });
function compactJson(str) { function compactJson(str) {
try { try {
// 如果是 JSON 字符串,解析后再 stringify(无换行/缩进) if (typeof str === 'string') {
if (typeof str === 'string') return JSON.stringify(JSON.parse(str)); return JSON.stringify(JSON.parse(str));
// 如果是对象/Buffer等,统一转成无格式 JSON 字符串 }
return JSON.stringify(str); return JSON.stringify(str);
} catch { } catch {
// 非 JSON 的情况,去掉所有换行,避免触发 \n\n 分隔
return String(str).replace(/\r?\n/g, ' '); return String(str).replace(/\r?\n/g, ' ');
} }
} }
// WebSocket接收数据,转发到TCP // WebSocket接收数据,转发到TCP
ws.on('message', (data) => { ws.on('message', (data) => {
// 处理可能的Buffer数据,转换为字符串 try {
let messageStr; let messageStr;
if (Buffer.isBuffer(data)) {
if (Buffer.isBuffer(data)) { messageStr = data.toString('utf8');
messageStr = data.toString('utf8'); } else if (typeof data === 'string') {
} else if (typeof data === 'string') { messageStr = data;
messageStr = data; } else {
} else { return;
return; }
}
messageStr = compactJson(messageStr); messageStr = compactJson(messageStr);
// 转发字符串数据到TCP服务器
if (tcpClient.writable) { if (tcpClient.writable && tcpConnected) {
console.log('发送数据到TCP服务器:', messageStr); console.log('发送数据到TCP服务器:', messageStr);
tcpClient.write(messageStr + '\n\n', (err) => {
// 检查数据大小 if (err) {
// const dataSize = Buffer.byteLength(messageStr, 'utf8'); console.error('TCP发送数据错误:', err);
// console.log(`数据大小: ${dataSize} bytes`); }
});
// 直接发送完整数据 } else {
tcpClient.write(messageStr + '\n\n', (err) => { console.warn('TCP连接不可用,无法发送数据');
if (err) { }
console.error(`[${new Date().toLocaleString()}] TCP发送数据错误:`, err); } catch (e) {
} console.error('WebSocket消息处理错误:', e);
});
} else {
// console.warn('TCP连接不可写,无法发送数据');
} }
}); });
// TCP连接错误处理 // TCP连接错误处理
tcpClient.on('error', (err) => { tcpClient.on('error', (err) => {
console.error(`[${new Date().toLocaleString()}] TCP连接错误:`, err); console.error('TCP连接错误:', err);
tcpDataBuffer = ''; // 清理缓冲区 tcpConnected = false;
tcpDataBuffer = '';
// 清除超时定时器
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
ws.close(1011, 'TCP连接错误'); ws.close(1011, `TCP连接错误: ${err.message}`);
} }
}); });
// TCP连接关闭 // TCP连接关闭
tcpClient.on('close', () => { tcpClient.on('close', () => {
// console.log('TCP连接已关闭'); console.log('TCP连接已关闭');
tcpDataBuffer = ''; // 清理缓冲区 tcpConnected = false;
tcpDataBuffer = '';
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
ws.close(); ws.close(1000, 'TCP连接关闭');
} }
}); });
// WebSocket连接关闭 // WebSocket连接关闭
ws.on('close', () => { ws.on('close', (code, reason) => {
// console.log('WebSocket连接已关闭'); console.log(`WebSocket连接已关闭: ${code} - ${reason}`);
tcpDataBuffer = ''; // 清理缓冲区 tcpDataBuffer = '';
if (tcpClient.writable) {
if (connectionTimeout) {
clearTimeout(connectionTimeout);
connectionTimeout = null;
}
if (tcpClient && !tcpClient.destroyed) {
tcpClient.destroy(); tcpClient.destroy();
} }
}); });
// WebSocket错误处理 // WebSocket错误处理
ws.on('error', (err) => { ws.on('error', (err) => {
console.error(`[${new Date().toLocaleString()}] WebSocket错误:`, err); console.error('WebSocket错误:', err);
if (tcpClient.writable) { if (tcpClient && !tcpClient.destroyed) {
tcpClient.destroy(); tcpClient.destroy();
} }
}); });
}); });
wss.on('listening', () => { wss.on('listening', () => {
// console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`);
// console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`); console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`);
// console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`);
// console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`);
}); });
wss.on('error', (err) => { wss.on('error', (err) => {
console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err); console.error('WebSocket服务器错误:', err);
if (err.code === 'EADDRINUSE') {
console.error(`端口 ${wsPort} 已被占用,请检查是否有其他服务在使用该端口`);
}
}); });
return wss; return wss;
} }
module.exports = { setupTcpProxy }; module.exports = { setupTcpProxy };

Loading…
Cancel
Save