From 7885cede659f3255be56f77c1eef2ada7387d6f1 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期日, 22 三月 2026 16:23:21 +0800
Subject: [PATCH] 初始化项目

---
 src/tcpServer.js |  156 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 156 insertions(+), 0 deletions(-)

diff --git a/src/tcpServer.js b/src/tcpServer.js
new file mode 100644
index 0000000..b38b734
--- /dev/null
+++ b/src/tcpServer.js
@@ -0,0 +1,156 @@
+const net = require("net");
+const config = require("./config");
+const logger = require("./logger");
+const DeviceManager = require("./deviceManager");
+const ProtocolParser = require("./protocol");
+
+// 将解析后的数据整理成人类可读的中文列表,便于在日志中快速查看关键参数
+function formatFrameSummary(data) {
+  const lines = [];
+
+  lines.push(`机器号: ${data.n || ""}`);
+  lines.push(`机型: ${data.deviceType || ""}`);
+  lines.push(`运行模式: ${data.jqyxms || ""}`);
+  // SetTreatmentTime 和 K 现在已在解析阶段转换为“分钟”
+  lines.push(
+    `治疗时间(设/已)(分): ${data.SetTreatmentTime ?? ""} / ${data.K ?? ""}`
+  );
+  // A/B/C 已在解析阶段转换为“升(L)”或“升每小时(L/h)”并保留三位小数
+  lines.push(`超滤总量A/已超滤量B(L): ${data.A ?? ""} / ${data.B ?? ""}`);
+  lines.push(`超滤率C(L/h): ${data.C ?? ""}`);
+  lines.push(`血泵流量D(ml/min): ${data.D ?? ""}`);
+  lines.push(`透析液流量L(ml/min): ${data.L ?? ""}`);
+  lines.push(
+    `动/静脉压/跨膜压(mmHg): ${data.o ?? ""} / ${data.H ?? ""} / ${
+      data.J ?? ""
+    }`
+  );
+  if (data.N != null || data.O != null || data.P != null || data.BPMPJDMY != null) {
+    lines.push(
+      `血压N/O/P/平均压(mmHg,bpm): ${data.N ?? ""} / ${data.O ?? ""} / ${
+        data.P ?? ""
+      } / ${data.BPMPJDMY ?? ""}`
+    );
+  }
+  if (data.alarmCode != null || data.bjlx != null || data.bjsj) {
+    lines.push(
+      `报警编号/类型/时间: ${data.alarmCode ?? ""} / ${
+        data.bjlx ?? ""
+      } / ${data.bjsj || ""}`
+    );
+  }
+    if (data.pyzl != null || data.ypyzhyl != null) {
+      lines.push(
+        `补液总量pyzl/已补入置换液量(ml): ${data.pyzl ?? ""} / ${
+          data.ypyzhyl ?? ""
+        }`
+      );
+    }
+
+  return lines.join("; ");
+}
+
+function createTcpServer({ dataCache, mqttPublisher, aliyunReporter }) {
+  const protocolParser = new ProtocolParser();
+
+  const deviceManager = new DeviceManager({
+    idleTimeoutMs: config.tcp.idleTimeoutMs,
+    maxBufferBytes: config.tcp.maxBufferBytes,
+    maxFramesPerChunk: config.tcp.maxFramesPerChunk,
+    onFrameParsed: ({ deviceNumber, data }) => {
+      try {
+        // 每次成功解析一帧透析机数据时,记录一条带中文说明的概要日志,方便排查问题
+        logger.info("Dialysis frame parsed", {
+          deviceNumber,
+          frameType: data.frameType,
+          runMode: data.jqyxms,
+          ip: data.IPAddress,
+          suedtime: data.suedtime,
+          summary: formatFrameSummary(data)
+        });
+
+        // 同时输出完整解析结果,便于查看所有字段
+        logger.debug("Dialysis frame payload", { deviceNumber, payload: data });
+
+        if (dataCache) {
+          dataCache.setDeviceData(deviceNumber, data);
+        }
+        if (mqttPublisher) {
+          try {
+            mqttPublisher(deviceNumber, data);
+          } catch (err) {
+            logger.error("mqttPublisher error", {
+              deviceNumber,
+              error: err.message || err
+            });
+          }
+        }
+        if (aliyunReporter) {
+          // 阿里云上报是异步的,这里不等待其完成
+          Promise.resolve(aliyunReporter(deviceNumber, data)).catch((err) => {
+            logger.error("aliyunReporter error", {
+              deviceNumber,
+              error: err.message || err
+            });
+          });
+        }
+      } catch (err) {
+        logger.error("onFrameParsed handler error", err.message || err);
+      }
+    }
+  });
+
+  const server = net.createServer((socket) => {
+    const key = `${socket.remoteAddress}:${socket.remotePort}`;
+    logger.info("Incoming TCP connection", { key });
+
+    deviceManager.addConnection(socket);
+
+    socket.setNoDelay(true);
+    socket.setKeepAlive(true, 30 * 1000);
+
+    socket.on("data", (chunk) => {
+      // 记录收到的原始 TCP 数据(十六进制),便于现场抓包对比
+      try {
+        const hex = chunk.toString("hex").match(/.{1,2}/g)?.join(" ") || "";
+        logger.debug("Raw TCP data received", { key, length: chunk.length, hex });
+      } catch (e) {
+        logger.error("Failed to log raw TCP data", { key, error: e.message || e });
+      }
+
+      try {
+        deviceManager.handleData(socket, chunk, protocolParser);
+      } catch (err) {
+        logger.error("handleData crashed", { key, error: err.message || err });
+      }
+    });
+
+    socket.on("close", () => {
+      deviceManager.removeConnection(socket);
+    });
+
+    socket.on("error", (err) => {
+      logger.error("Socket error", { key, error: err.message || err });
+      deviceManager.removeConnection(socket);
+    });
+  });
+
+  server.on("error", (err) => {
+    logger.error("TCP server error", err.message || err);
+  });
+
+  if (config.tcp.maxConnections) {
+    server.maxConnections = config.tcp.maxConnections;
+  }
+
+  server.listen(config.tcp.port, config.tcp.host, () => {
+    logger.info("TCP server listening", {
+      host: config.tcp.host,
+      port: config.tcp.port
+    });
+  });
+
+  return { server, deviceManager };
+}
+
+module.exports = createTcpServer;

--
Gitblit v1.8.0