From 747a86bb94006aaca721cfc0c0ce7061643a9ea6 Mon Sep 17 00:00:00 2001
From: chenyc <501753378@qq.com>
Date: 星期四, 20 八月 2026 12:19:35 +0800
Subject: [PATCH] gx

---
 build/ncc/index.js |  193 +++++++++++++++++++++++++++++++++--------------
 1 files changed, 134 insertions(+), 59 deletions(-)

diff --git a/build/ncc/index.js b/build/ncc/index.js
index 83c5471..ab4b510 100644
--- a/build/ncc/index.js
+++ b/build/ncc/index.js
@@ -67332,7 +67332,7 @@
     const normalized = this.normalizeEventPayload(errorLike);
 
     if (normalized instanceof Error) {
-      return normalized.message || normalized.name || 'unknown-error';
+      return normalized.message || normalized.name || '未知错误';
     }
 
     if (Array.isArray(normalized)) {
@@ -67340,7 +67340,7 @@
         .map((item) => this.formatErrorMessage(item))
         .filter(Boolean);
 
-      return parts.length > 0 ? parts.join(' | ') : 'unknown-error';
+      return parts.length > 0 ? parts.join(' | ') : '未知错误';
     }
 
     if (normalized && typeof normalized === 'object') {
@@ -67356,7 +67356,7 @@
     }
 
     if (normalized === undefined || normalized === null || normalized === '') {
-      return 'unknown-error';
+      return '未知错误';
     }
 
     return String(normalized);
@@ -67408,7 +67408,7 @@
       return this.aliyunSdk;
     }
 
-    // eslint-disable-next-line global-require
+    // 延迟加载 SDK,避免未启用阿里云时引入额外依赖。
     this.aliyunSdk = __nccwpck_require__(9871);
     return this.aliyunSdk;
   }
@@ -67488,7 +67488,7 @@
     try {
       responseBody = JSON.parse(rawText);
     } catch (_error) {
-      throw new Error('三元组接口返回不是有效 JSON');
+      throw new Error('三元组接口返回的不是有效 JSON');
     }
 
     return this.extractTuple(responseBody, deviceId);
@@ -67510,7 +67510,7 @@
 
     if (context.lastRegisterAttempt > 0 && (now - context.lastRegisterAttempt) < retryMs && context.lastRegisterError) {
       this.logger.warn(`[ALIYUN] 三元组请求冷却中 deviceId=${device.deviceId} retryMs=${retryMs} lastError=${context.lastRegisterError}`);
-      throw new Error(`阿里云三元组重试冷却中 ${context.lastRegisterError}`);
+      throw new Error(`阿里云三元组重试冷却中: ${context.lastRegisterError}`);
     }
 
     context.lastRegisterAttempt = now;
@@ -67774,6 +67774,7 @@
   error: 40,
 };
 
+// 统一生成本地日志时间,兼顾控制台和文件日志格式。
 function formatLocalTimestamp(date = new Date()) {
   const year = date.getFullYear();
   const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -67860,7 +67861,7 @@
       try {
         fs.appendFileSync(logFilePath, `${line}\n`, 'utf8');
       } catch (error) {
-        console.error(`[LOGGER] Failed to append log file: ${error.message}`);
+        console.error(`[LOGGER] 写入日志文件失败: ${error.message}`);
       }
     }
   }
@@ -67909,23 +67910,23 @@
 }
 
 function printHelp() {
-  console.log(`JHM TCP Socket Gateway
+  console.log(`JHM TCP Socket 网关服务
 
-Usage:
+用法:
   node app.js
   node app.js --config ./config.json
   jhm-service.exe --config .\\runtime\\config.json
   ./jhm-service --config ./runtime/config.json
 
-Options:
-  --config <path>    set config file path
-  --help, -h         show help
+参数:
+  --config <path>    指定配置文件路径
+  --help, -h         显示帮助
 `);
 }
 
 function loadConfig(configPath) {
   if (!fs.existsSync(configPath)) {
-    throw new Error(`config file not found: ${configPath}`);
+    throw new Error(`未找到配置文件: ${configPath}`);
   }
 
   const content = fs.readFileSync(configPath, 'utf8');
@@ -67967,66 +67968,67 @@
 
   return {
     publishTime: bloodPressure ? bloodPressure.publishTime !== false : true,
+    flushImmediately: bloodPressure ? bloodPressure.flushImmediately !== false : true,
   };
 }
 
 function validateConfig(config) {
   if (!config.tcp || !Array.isArray(config.devices)) {
-    throw new Error('config.json must include tcp and devices');
+    throw new Error('config.json 必须包含 tcp 和 devices');
   }
 
   if (!config.tcp.host || !config.tcp.port) {
-    throw new Error('config.json must include tcp.host and tcp.port');
+    throw new Error('config.json 必须包含 tcp.host 和 tcp.port');
   }
 
   const channels = getSendChannels(config);
 
   if (channels.length === 0) {
-    throw new Error('config.json send.channels must enable at least one channel');
+    throw new Error('config.json 的 send.channels 至少要启用一个通道');
   }
 
   if (channels.includes('mqtt')) {
     if (!config.mqtt) {
-      throw new Error('config.json enabled mqtt but missing mqtt config');
+      throw new Error('config.json 已启用 mqtt,但缺少 mqtt 配置');
     }
 
     const hasBrokerUrl = Boolean(config.mqtt.brokerUrl);
     const hasHostMode = Boolean(config.mqtt.protocol && config.mqtt.host && config.mqtt.port);
 
     if (!hasBrokerUrl && !hasHostMode) {
-      throw new Error('config.json mqtt requires brokerUrl or protocol/host/port');
+      throw new Error('config.json 的 mqtt 必须配置 brokerUrl 或 protocol/host/port');
     }
 
     if (!config.mqtt.topicTemplate && !config.mqtt.defaultTopicPrefix) {
-      throw new Error('config.json mqtt requires topicTemplate or defaultTopicPrefix');
+      throw new Error('config.json 的 mqtt 必须配置 topicTemplate 或 defaultTopicPrefix');
     }
   }
 
   if (channels.includes('aliyun')) {
     if (!config.aliyun) {
-      throw new Error('config.json enabled aliyun but missing aliyun config');
+      throw new Error('config.json 已启用 aliyun,但缺少 aliyun 配置');
     }
 
     if (!config.aliyun.tupleApiBaseUrl && !config.aliyun.tupleApiUrl) {
-      throw new Error('config.json aliyun requires tupleApiBaseUrl or tupleApiUrl');
+      throw new Error('config.json 的 aliyun 必须配置 tupleApiBaseUrl 或 tupleApiUrl');
     }
   }
 
   if (!config.protocol || !config.protocol.alModelPath) {
-    throw new Error('config.json protocol.alModelPath is required');
+    throw new Error('config.json 必须配置 protocol.alModelPath');
   }
 
   const sendOptions = getSendOptions(config);
 
   if (sendOptions.flushIntervalMs <= 0) {
-    throw new Error('config.json send.flushIntervalMs must be > 0');
+    throw new Error('config.json 的 send.flushIntervalMs 必须大于 0');
   }
 
   if (config.logging && config.logging.level) {
     const supportedLevels = ['debug', 'info', 'warn', 'error'];
 
     if (!supportedLevels.includes(String(config.logging.level).toLowerCase())) {
-      throw new Error(`config.json logging.level only supports ${supportedLevels.join(', ')}`);
+      throw new Error(`config.json 的 logging.level 仅支持 ${supportedLevels.join('、')}`);
     }
   }
 }
@@ -68037,6 +68039,29 @@
   }
 
   return path.join(path.dirname(configFilePath), targetPath);
+}
+
+function createOnMetricHandler({ logger, aggregator, sendOptions, bloodPressureOptions }) {
+  return function onMetric(device, metric, result) {
+    logger.info(`[APP] 指标已缓存 deviceId=${device.deviceId} mode=${sendOptions.mode} metric=${JSON.stringify(metric)}`);
+    return aggregator.ingest(device, metric)
+      .then(() => {
+        if (
+          sendOptions.mode === 'batch'
+          && bloodPressureOptions.flushImmediately
+          && result
+          && result.protocol === 'blood-pressure'
+        ) {
+          logger.info(`[APP] 血压报文触发整包立即发送 deviceId=${device.deviceId}`);
+          return aggregator.flush({ reason: 'blood-pressure', deviceId: device.deviceId });
+        }
+
+        return null;
+      })
+      .catch((error) => {
+        logger.error(`[APP] 指标处理失败 deviceId=${device.deviceId}: ${error.message}`);
+      });
+  };
 }
 
 async function main() {
@@ -68051,7 +68076,7 @@
     enabled: false,
     console: true,
   });
-  bootstrapLogger.info(`[APP] Startup arguments parsed config=${options.configPath}`);
+  bootstrapLogger.info(`[APP] 启动参数解析完成 config=${options.configPath}`);
 
   const config = loadConfig(options.configPath);
   const logDir = resolveConfigPath(options.configPath, (config.logging && config.logging.dir) || './logs');
@@ -68065,21 +68090,22 @@
   const alModelPath = resolveConfigPath(options.configPath, config.protocol.alModelPath);
 
   if (config.logging && config.logging.enabled === false) {
-    logger.info('[APP] File logging is disabled; console output only');
+    logger.info('[APP] 文件日志已禁用,仅输出到控制台');
   } else {
-    logger.info(`[APP] Local logging enabled dir=${logDir} file=${logger.getLogFilePath()} level=${normalizeLogLevel(config.logging && config.logging.level)}`);
+    logger.info(`[APP] 本地日志已启用 dir=${logDir} file=${logger.getLogFilePath()} level=${normalizeLogLevel(config.logging && config.logging.level)}`);
   }
 
-  logger.info(`[APP] Config loaded tcp=${config.tcp.host}:${config.tcp.port} devices=${config.devices.length} channels=${sendChannels.join(',')} sendMode=${sendOptions.mode} flushIntervalMs=${sendOptions.flushIntervalMs} alModel=${alModelPath}`);
+  logger.info(`[APP] 配置加载完成 tcp=${config.tcp.host}:${config.tcp.port} devices=${config.devices.length} channels=${sendChannels.join(',')} sendMode=${sendOptions.mode} flushIntervalMs=${sendOptions.flushIntervalMs} alModel=${alModelPath}`);
 
   const mqttService = sendChannels.includes('mqtt') ? new MqttService(config.mqtt, logger) : null;
   const aliyunService = sendChannels.includes('aliyun') ? new AliyunService(config.aliyun, logger) : null;
 
+  // 聚合器负责把多次单点指标合并成一次完整物模型上报。
   const aggregator = new MetricAggregator({
     logger,
     ...sendOptions,
     onFlush: async (device, payload, meta) => {
-      logger.info(`[APP] Batch dispatch deviceId=${device.deviceId} channels=${sendChannels.join(',')} reason=${meta.reason} payload=${JSON.stringify(payload)}`);
+      logger.info(`[APP] 开始批量分发 deviceId=${device.deviceId} channels=${sendChannels.join(',')} reason=${meta.reason} payload=${JSON.stringify(payload)}`);
 
       let ok = true;
 
@@ -68088,7 +68114,7 @@
           mqttService.publish(device, payload);
         } catch (error) {
           ok = false;
-          logger.error(`[APP] MQTT batch dispatch failed deviceId=${device.deviceId}: ${error.message}`);
+          logger.error(`[APP] MQTT 批量分发失败 deviceId=${device.deviceId}: ${error.message}`);
         }
       }
 
@@ -68098,14 +68124,14 @@
 
           if (result && result.skipped) {
             ok = false;
-            logger.warn(`[APP] Aliyun batch dispatch skipped deviceId=${device.deviceId} reason=${result.reason}`);
+            logger.warn(`[APP] 阿里云批量分发已跳过 deviceId=${device.deviceId} reason=${result.reason}`);
           } else if (result && result.ok === false) {
             ok = false;
-            logger.error(`[APP] Aliyun batch dispatch failed deviceId=${device.deviceId}: ${result.reason}`);
+            logger.error(`[APP] 阿里云批量分发失败 deviceId=${device.deviceId}: ${result.reason}`);
           }
         } catch (error) {
           ok = false;
-          logger.error(`[APP] Aliyun batch dispatch exception deviceId=${device.deviceId}: ${error.message}`);
+          logger.error(`[APP] 阿里云批量分发异常 deviceId=${device.deviceId}: ${error.message}`);
         }
       }
 
@@ -68119,27 +68145,27 @@
     alModelPath,
     publishBloodPressureTime: bloodPressureOptions.publishTime,
     logger,
-    onMetric(device, metric) {
-      logger.info(`[APP] Metric cached deviceId=${device.deviceId} mode=${sendOptions.mode} metric=${JSON.stringify(metric)}`);
-      aggregator.ingest(device, metric).catch((error) => {
-        logger.error(`[APP] Metric cache failed deviceId=${device.deviceId}: ${error.message}`);
-      });
-    },
+    onMetric: createOnMetricHandler({
+      logger,
+      aggregator,
+      sendOptions,
+      bloodPressureOptions,
+    }),
   });
 
   if (mqttService) {
-    logger.info('[APP] Starting MQTT channel');
+    logger.info('[APP] 正在启动 MQTT 通道');
     mqttService.start();
   }
 
   if (aliyunService) {
-    logger.info('[APP] Starting Aliyun channel');
+    logger.info('[APP] 正在启动阿里云通道');
     aliyunService.start();
   }
 
   aggregator.start();
   await tcpService.start();
-  logger.info('[APP] All services started');
+  logger.info('[APP] 所有服务已启动');
 
   let shuttingDown = false;
 
@@ -68149,7 +68175,7 @@
     }
 
     shuttingDown = true;
-    logger.warn(`[APP] Received ${signal}, shutting down`);
+    logger.warn(`[APP] 收到 ${signal},开始关闭服务`);
 
     await tcpService.stop();
     await aggregator.stop();
@@ -68162,7 +68188,7 @@
       await aliyunService.stop();
     }
 
-    logger.info('[APP] Service shutdown completed');
+    logger.info('[APP] 服务已完成关闭');
     await logger.close();
     process.exit(0);
   }
@@ -68176,11 +68202,11 @@
   });
 
   process.on('uncaughtException', (error) => {
-    logger.error(`[APP] Uncaught exception: ${error.stack || error.message}`);
+    logger.error(`[APP] 未捕获异常: ${error.stack || error.message}`);
   });
 
   process.on('unhandledRejection', (reason) => {
-    logger.error(`[APP] Unhandled promise rejection: ${reason}`);
+    logger.error(`[APP] 未处理的 Promise 拒绝: ${reason}`);
   });
 }
 
@@ -68199,6 +68225,7 @@
   getSendOptions,
   loadConfig,
   main,
+  createOnMetricHandler,
   normalizeLogLevel,
   parseArgs,
   printHelp,
@@ -68605,7 +68632,7 @@
     this.timer = null;
     this.started = false;
     this.flushing = false;
-    this.flushQueued = false;
+    this.queuedFlushOptions = null;
   }
 
   start() {
@@ -68640,8 +68667,15 @@
 
     const entry = this.getOrCreateEntry(device);
     entry.device = device;
+    const now = Date.now();
+
+    // 首次进入脏状态时记录时间,便于控制“缓存满一轮再发送”。
+    if (!entry.dirty) {
+      entry.dirtySinceAt = now;
+    }
+
     entry.dirty = true;
-    entry.lastUpdateAt = Date.now();
+    entry.lastUpdateAt = now;
 
     if (this.includeDeviceIdField) {
       entry.payload[this.deviceIdField] = device.deviceId;
@@ -68662,6 +68696,7 @@
         device,
         payload: {},
         dirty: false,
+        dirtySinceAt: 0,
         firstSeenAt: Date.now(),
         lastUpdateAt: 0,
         lastFlushAt: 0,
@@ -68691,7 +68726,7 @@
 
     this.timer = setTimeout(() => {
       this.flush({ reason: 'timer' }).catch((error) => {
-        this.logger.error(`[APP] Aggregator flush failed: ${error.message}`);
+        this.logger.error(`[APP] 聚合器刷新失败: ${error.message}`);
       });
     }, this.computeDelayMs());
 
@@ -68700,12 +68735,36 @@
     }
   }
 
+  mergeQueuedFlushOptions(options = {}) {
+    const nextOptions = {
+      reason: options.reason || 'manual',
+      deviceId: options.deviceId || '',
+    };
+
+    if (!this.queuedFlushOptions) {
+      this.queuedFlushOptions = nextOptions;
+      return;
+    }
+
+    const currentOptions = this.queuedFlushOptions;
+    const currentIsTimer = currentOptions.reason === 'timer' || currentOptions.reason === 'queued';
+    const nextIsTimer = nextOptions.reason === 'timer' || nextOptions.reason === 'queued';
+
+    if (currentIsTimer && !nextIsTimer) {
+      currentOptions.reason = nextOptions.reason;
+    }
+
+    if (!currentOptions.deviceId || !nextOptions.deviceId || currentOptions.deviceId !== nextOptions.deviceId) {
+      currentOptions.deviceId = '';
+    }
+  }
+
   async flush(options = {}) {
     const reason = options.reason || 'manual';
     const deviceId = options.deviceId || '';
 
     if (this.flushing) {
-      this.flushQueued = true;
+      this.mergeQueuedFlushOptions({ reason, deviceId });
       return false;
     }
 
@@ -68726,10 +68785,20 @@
           return false;
         }
 
+        // 定时触发时要求数据至少缓存满一个周期,避免刚收到就被单独发出。
+        if ((reason === 'timer' || reason === 'queued') && entry.dirtySinceAt > 0) {
+          const dirtyAgeMs = Date.now() - entry.dirtySinceAt;
+
+          if (dirtyAgeMs < this.flushIntervalMs) {
+            return false;
+          }
+        }
+
         return true;
       });
 
       for (const entry of entries) {
+        const flushUpdateAt = entry.lastUpdateAt;
         const payload = {
           ...entry.payload,
         };
@@ -68750,10 +68819,14 @@
             continue;
           }
 
-          entry.dirty = false;
+          if (entry.lastUpdateAt === flushUpdateAt) {
+            entry.dirty = false;
+            entry.dirtySinceAt = 0;
+          }
+
           entry.lastFlushAt = Date.now();
         } catch (error) {
-          this.logger.error(`[APP] Aggregator device flush failed deviceId=${entry.device.deviceId}: ${error.message}`);
+          this.logger.error(`[APP] 聚合器设备刷新失败 deviceId=${entry.device.deviceId}: ${error.message}`);
         }
       }
 
@@ -68765,9 +68838,10 @@
         this.scheduleNextFlush();
       }
 
-      if (this.flushQueued) {
-        this.flushQueued = false;
-        await this.flush({ reason: 'queued' });
+      if (this.queuedFlushOptions) {
+        const queuedOptions = this.queuedFlushOptions;
+        this.queuedFlushOptions = null;
+        await this.flush(queuedOptions);
       }
     }
   }
@@ -71058,6 +71132,7 @@
 const fs = __nccwpck_require__(9896);
 const path = __nccwpck_require__(6928);
 
+// 按候选路径顺序寻找默认配置文件,兼容源码运行和打包运行两种场景。
 function fileExists(filePath) {
   try {
     return fs.statSync(filePath).isFile();
@@ -71154,7 +71229,7 @@
 
       map.set(normalizeIp(device.ip), {
         ...device,
-        name: device.name || device['备注'] || device.deviceId,
+        name: device.name || device.备注 || device['澶囨敞'] || device.deviceId,
       });
     }
 
@@ -71173,7 +71248,7 @@
     this.server.maxConnections = this.tcpConfig.maxConnections || 100;
 
     this.server.on('error', (error) => {
-      this.logger.error(`[TCP] 服务错误: ${error.message}`);
+      this.logger.error(`[TCP] 服务异常: ${error.message}`);
     });
 
     await new Promise((resolve, reject) => {
@@ -71186,7 +71261,7 @@
       });
     });
 
-    this.logger.info(`[TCP] 已监听 ${this.tcpConfig.host}:${this.tcpConfig.port} devices=${this.deviceMap.size} maxConnections=${this.server.maxConnections}`);
+    this.logger.info(`[TCP] 已开始监听 ${this.tcpConfig.host}:${this.tcpConfig.port} devices=${this.deviceMap.size} maxConnections=${this.server.maxConnections}`);
   }
 
   handleConnection(socket) {
@@ -71250,7 +71325,7 @@
     const results = session.decoder.push(chunk);
 
     if (results.length === 0) {
-      this.logger.warn(`[TCP] 当前数据片段未形成完整报文 deviceId=${session.device.deviceId} bytes=${chunk.length}`);
+      this.logger.warn(`[TCP] 当前数据片段尚未组成完整报文 deviceId=${session.device.deviceId} bytes=${chunk.length}`);
     }
 
     for (const result of results) {

--
Gitblit v1.8.0