From a43f8991d3f5fa2ef4e0f3eeeca00fb4afc263c0 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期日, 24 五月 2026 17:08:18 +0800
Subject: [PATCH] gx

---
 config.json                 |    2 
 lib/obfuscate.js            |   34 +++++++++++
 tools/decode-log.js         |   36 ++++++++++++
 scripts/build.js            |    2 
 dashboard/public/index.html |   13 ---
 dashboard/server.js         |    6 +
 lib/logger.js               |    5 +
 package.json                |    3 
 .claude/settings.local.json |    4 +
 DEPLOY.md                   |   16 ++--
 10 files changed, 95 insertions(+), 26 deletions(-)

diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index eb50cf5..578d4fc 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -7,7 +7,9 @@
       "Bash(netstat -ano)",
       "Bash(powershell *)",
       "Bash(curl -s -o nul -w \"%{http_code}\" http://localhost:3100/)",
-      "Bash(node scripts/build.js --linux)"
+      "Bash(node scripts/build.js --linux)",
+      "Bash(node tools/decode-log.js --stdin)",
+      "Bash(timeout 3 node -e \"require\\('./lib/logger'\\)\")"
     ]
   }
 }
diff --git a/DEPLOY.md b/DEPLOY.md
index d0ab685..a9f15a6 100644
--- a/DEPLOY.md
+++ b/DEPLOY.md
@@ -2,12 +2,12 @@
 
 ## 1. 系统概述
 
-JMS 联机服务用于管理与监控多台 **GC-110N 透析设备**。通过持久 TCP 长连接 + 定时 K 指令轮询,实时采集 32 项治疗参数,经 MQTT / 阿里云 IoT 上传至云端,并提供本地 Web 监控大屏。
+JMS 联机服务用于管理与监控多台 **GC-110N 透析设备**。通过持久 TCP 长连接 + 定时指令轮询,实时采集 32 项治疗参数,经 MQTT / 阿里云 IoT 上传至云端,并提供本地 Web 监控大屏。
 
 ```
 ┌──────────┐   TCP:10001    ┌─────────────────┐    MQTT     ┌──────────────┐
 │ GC-110N  │←──────────────→│  jms-connection  │───────────→│  MQTT Broker  │
-│ 透析机 1  │   K 轮询 (10s)  │     -service     │            └──────────────┘
+│ 透析机 1  │   轮询 (10s)    │     -service     │            └──────────────┘
 └──────────┘                │                  │
      ···                    │  dashboard:3100  │   HTTP     ┌──────────────┐
 ┌──────────┐                │  ← 浏览器访问      │───────────→│ 阿里云 IoT     │
@@ -233,7 +233,7 @@
 ```jsonc
 {
   // ── 全局参数 ──
-  "pollIntervalMs": 10000,       // K 轮询间隔(毫秒),默认 10s
+  "pollIntervalMs": 10000,       // 轮询间隔(毫秒),默认 10s
   "connectTimeoutMs": 5000,      // TCP 握手超时(毫秒)
   "reconnectBaseMs": 3000,       // 重连退避基数(毫秒)
   "reconnectMaxMs": 60000,       // 重连退避上限(毫秒)
@@ -414,7 +414,7 @@
 
 | 目标 | 协议 | 端口 | 用途 |
 |------|------|------|------|
-| GC-110N 设备 | TCP | 10001(可配置) | K 指令轮询 |
+| GC-110N 设备 | TCP | 10001(可配置) | 指令轮询 |
 | MQTT Broker | TCP | 62283(可配置) | MQTT 上传 |
 | 阿里云 IoT | TCP | 443 (TLS) | 属性上报 |
 | 三元组 API | HTTPS | 443 | 获取设备凭证 |
@@ -475,8 +475,8 @@
 ### 9.2 收不到数据 / 字段数为 0
 
 1. 确认设备已进入治疗状态(待机状态可能返回有限数据)
-2. 查看日志中 `←` 行,确认报文以 `K` 开头 + 4 位状态码
-3. 确认设备固件支持 K 格式协议
+2. 查看日志中 `←` 行,确认收到设备报文(混淆格式可通过 `node tools/decode-log.js --stdin` 解码查看)
+3. 确认设备固件协议版本兼容
 
 ### 9.3 阿里云上传失败
 
@@ -519,8 +519,8 @@
 │   ├── logger.js                  # 日志模块(按天滚动)
 │   ├── data-cache.js              # 内存数据缓存(Map<ip, deviceData>)
 │   ├── device-manager.js          # 设备管理(遍历创建连接)
-│   ├── device-connection.js       # 单设备 TCP 长连接 + K 轮询 + 重连
-│   ├── protocol.js                # GC-110N K 格式解析器
+│   ├── device-connection.js       # 单设备 TCP 长连接 + 轮询 + 重连
+│   ├── protocol.js                # GC-110N 协议解析器
 │   └── upload/
 │       ├── index.js               # 上传总控(顺序:MQTT → 阿里云)
 │       ├── mqtt-uploader.js       # MQTT 单例客户端
diff --git a/config.json b/config.json
index 9add152..8218e68 100644
--- a/config.json
+++ b/config.json
@@ -26,7 +26,7 @@
   },
   "devices": [
     {
-      "ip": "192.168.160.1",
+      "ip": "169.254.233.58",
       "port": 10001,
       "serialNumber": "xy123",
       "enabled": true
diff --git a/dashboard/public/index.html b/dashboard/public/index.html
index 3fd17f0..17a4699 100644
--- a/dashboard/public/index.html
+++ b/dashboard/public/index.html
@@ -80,16 +80,9 @@
   padding: 4px 8px; border-radius: 4px; background: #161b22;
   font-size: 12px;
 }
-.detail-panel .field .fid { color: #58a6ff; font-weight: 600; min-width: 18px; }
 .detail-panel .field .fname { color: #8b949e; flex: 1; margin: 0 8px; }
 .detail-panel .field .fval { color: #f0f6fc; font-family: 'Consolas', monospace; }
 .detail-panel .field .funit { color: #484f58; margin-left: 4px; font-size: 11px; }
-.detail-panel .raw-frame {
-  grid-column: 1 / -1; margin-top: 8px; padding: 8px 12px;
-  background: #0d1117; border: 1px solid #30363d; border-radius: 4px;
-  font-family: 'Consolas', monospace; font-size: 13px; color: #d2a8ff;
-  word-break: break-all; max-height: 100px; overflow-y: auto;
-}
 
 .clickable { cursor: pointer; user-select: none; }
 .clickable td:first-child { color: #58a6ff; }
@@ -104,7 +97,7 @@
 <div class="header">
   <div>
     <h1>JMS GC-110N 联机服务监控</h1>
-    <div class="info">协议 Ver.3.0 &middot; K 格式状态轮询</div>
+    <div class="info">TCP 长连接 &middot; 定时轮询</div>
   </div>
   <div class="info">
     <span class="dot live"></span>
@@ -236,13 +229,11 @@
           <div class="detail-panel">
             ${fields.length === 0 ? '<div style="color:#484f58;grid-column:1/-1;">暂无解析数据</div>' :
               fields.map(f => `<div class="field">
-                <span class="fid">${f.id}</span>
                 <span class="fname">${f.name||''}</span>
                 <span class="fval">${f.displayValue||f.value||'(空)'}</span>
                 <span class="funit">${f.unit||''}</span>
               </div>`).join('')}
-            ${dev.rawFrame ? `<div class="raw-frame">${dev.rawFrame}</div>` : ''}
-          </div>
+                      </div>
         </td>
       </tr>`;
     }
diff --git a/dashboard/server.js b/dashboard/server.js
index 6fbd7d4..3a3b354 100644
--- a/dashboard/server.js
+++ b/dashboard/server.js
@@ -32,11 +32,15 @@
   }
 
   function buildSnapshot() {
+    const devices = dataCache.getAll().map((dev) => {
+      const { rawFrame, ...rest } = dev;
+      return rest;
+    });
     return {
       type: "snapshot",
       timestamp: new Date().toISOString(),
       summary: dataCache.getSummary(),
-      devices: dataCache.getAll(),
+      devices,
       config: {
         pollIntervalMs: config.pollIntervalMs,
         reconnectBaseMs: config.reconnectBaseMs,
diff --git a/lib/logger.js b/lib/logger.js
index 1105e3d..80a5be0 100644
--- a/lib/logger.js
+++ b/lib/logger.js
@@ -2,6 +2,7 @@
 
 const fs = require("fs");
 const path = require("path");
+const { obfuscate } = require("./obfuscate");
 
 const C = { reset: "\x1b[0m", dim: "\x1b[2m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", white: "\x1b[37m" };
 
@@ -79,10 +80,10 @@
 
     // ── 数据收发 ──
     sendK(ip) {
-      log(ip, C.yellow, "→", `发送 K 请求`);
+      log(ip, C.yellow, "→", `发送轮询请求`);
     },
     recvK(ip, rawFrame, fieldCount, statusCode) {
-      log(ip, C.green, "←", `${rawFrame}`);
+      log(ip, C.green, "←", `${obfuscate(rawFrame)}`);
     },
 
     // ── 异常 ──
diff --git a/lib/obfuscate.js b/lib/obfuscate.js
new file mode 100644
index 0000000..a82547b
--- /dev/null
+++ b/lib/obfuscate.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const DEFAULT_KEY = "jms-gc110n-log-obfuscate-default";
+
+function getKey() {
+  return process.env.JMS_LOG_KEY || DEFAULT_KEY;
+}
+
+function obfuscate(text) {
+  if (!text) return text;
+  const key = Buffer.from(getKey(), "utf-8");
+  const data = Buffer.from(text, "utf-8");
+  const out = Buffer.alloc(data.length);
+  for (let i = 0; i < data.length; i++) {
+    out[i] = data[i] ^ key[i % key.length];
+  }
+  return "[OBF]" + out.toString("base64");
+}
+
+function deobfuscate(encoded) {
+  if (!encoded) return encoded;
+  if (encoded.startsWith("[OBF]")) {
+    encoded = encoded.slice(5);
+  }
+  const key = Buffer.from(getKey(), "utf-8");
+  const data = Buffer.from(encoded, "base64");
+  const out = Buffer.alloc(data.length);
+  for (let i = 0; i < data.length; i++) {
+    out[i] = data[i] ^ key[i % key.length];
+  }
+  return out.toString("utf-8");
+}
+
+module.exports = { obfuscate, deobfuscate };
diff --git a/package.json b/package.json
index e8fad98..de5a458 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
     "dev": "node index.js",
     "build": "node scripts/build.js",
     "build:win": "node scripts/build.js --win",
-    "build:linux": "node scripts/build.js --linux"
+    "build:linux": "node scripts/build.js --linux",
+    "decode-log": "node tools/decode-log.js logs/service.2026-05-18.log"
   },
   "pkg": {
     "assets": [
diff --git a/scripts/build.js b/scripts/build.js
index 4423024..5e10890 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -97,7 +97,7 @@
   readmeLines.push("──────────────────────────────────────────────────────");
   readmeLines.push("");
   readmeLines.push("【全局参数】");
-  readmeLines.push("  pollIntervalMs       = 10000   // K 轮询间隔(毫秒)");
+  readmeLines.push("  pollIntervalMs       = 10000   // 轮询间隔(毫秒)");
   readmeLines.push("  connectTimeoutMs     = 5000    // TCP 握手超时(毫秒)");
   readmeLines.push("  reconnectBaseMs      = 3000    // 重连退避基数(毫秒)");
   readmeLines.push("  reconnectMaxMs       = 60000   // 重连退避上限(毫秒)");
diff --git a/tools/decode-log.js b/tools/decode-log.js
new file mode 100644
index 0000000..4464b58
--- /dev/null
+++ b/tools/decode-log.js
@@ -0,0 +1,36 @@
+"use strict";
+
+const fs = require("fs");
+const { deobfuscate } = require("../lib/obfuscate");
+
+function decode(text) {
+  return text.replace(/\[OBF\][A-Za-z0-9+/=]+/g, (match) => {
+    return deobfuscate(match);
+  });
+}
+
+function decodeStdin() {
+  const chunks = [];
+  process.stdin.setEncoding("utf-8");
+  process.stdin.on("data", (chunk) => chunks.push(chunk));
+  process.stdin.on("end", () => {
+    process.stdout.write(decode(chunks.join("")));
+  });
+}
+
+function decodeFile(filePath) {
+  const content = fs.readFileSync(filePath, "utf-8");
+  process.stdout.write(decode(content));
+}
+
+if (process.argv.includes("--stdin")) {
+  decodeStdin();
+} else if (process.argv[2]) {
+  decodeFile(process.argv[2]);
+} else {
+  console.log("用法:");
+  console.log("  node tools/decode-log.js <日志文件>    解码日志文件");
+  console.log("  node tools/decode-log.js --stdin       解码标准输入");
+  console.log("");
+  console.log("密钥: JMS_LOG_KEY 环境变量 (未设置时使用默认值)");
+}

--
Gitblit v1.8.0