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