gx
chenyc
2026-05-24 a43f8991d3f5fa2ef4e0f3eeeca00fb4afc263c0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"use strict";
 
const path = require("path");
const fs = require("fs");
const { createLogger } = require("./lib/logger");
const { DataCache } = require("./lib/data-cache");
const { DeviceManager } = require("./lib/device-manager");
const { UploadManager } = require("./lib/upload");
const { createDashboard } = require("./dashboard/server");
 
// 配置读取:优先从工作目录(CWD),便于打包后用户编辑;开发时回退到 __dirname
let configPath = path.resolve(process.cwd(), "config.json");
if (!fs.existsSync(configPath)) {
  configPath = path.join(__dirname, "config.json");
}
if (!fs.existsSync(configPath)) {
  console.error("缺少 config.json,请先创建配置文件(放置于当前工作目录或程序目录)");
  process.exit(1);
}
 
let config;
try {
  config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
} catch (err) {
  console.error("config.json 解析失败:", err.message);
  process.exit(1);
}
 
// 启动配置校验
if (!Array.isArray(config.devices) || config.devices.length === 0) {
  console.error("config.json 缺少有效的 devices 数组");
  process.exit(1);
}
for (let i = 0; i < config.devices.length; i++) {
  const d = config.devices[i];
  if (!d.ip || !d.port || !d.serialNumber) {
    console.error(`config.json devices[${i}] 缺少必填字段 ip/port/serialNumber`);
    process.exit(1);
  }
}
const activeDevices = config.devices.filter((d) => d.enabled !== false);
console.log(`配置校验通过: ${activeDevices.length}/${config.devices.length} 台设备启用`);
 
const logDir = path.resolve(process.cwd(), config.logDir || "./logs");
const logger = createLogger({
  logDir,
  retentionDays: config.logRetentionDays || 30,
});
 
logger.sys("联机服务启动");
logger.sys(`日志目录: ${logDir}  保留: ${config.logRetentionDays} 天`);
logger.sys(`轮询间隔: ${config.pollIntervalMs / 1000}s  重连退避: ${config.reconnectBaseMs / 1000}s~${config.reconnectMaxMs / 1000}s`);
 
const dataCache = new DataCache();
 
// 上传模块
const uploadManager = new UploadManager({
  mqttConfig: config.mqtt || {},
  aliyunConfig: config.aliyun || {},
  logger,
});
uploadManager.start();
 
const deviceManager = new DeviceManager({
  config,
  dataCache,
  logger,
  uploadManager,
});
 
const dashboard = createDashboard({
  port: config.dashboardPort || 3100,
  dataCache,
  deviceManager,
  logger,
  config,
  uploadManager,
});
 
deviceManager.startAll();
dashboard.start();
 
process.on("SIGINT", () => {
  logger.sys("收到 SIGINT,正在关闭...");
  deviceManager.stopAll();
  uploadManager.stop();
  dashboard.stop();
  logger.close();
  process.exit(0);
});
 
process.on("SIGTERM", () => {
  logger.sys("收到 SIGTERM,正在关闭...");
  deviceManager.stopAll();
  uploadManager.stop();
  dashboard.stop();
  logger.close();
  process.exit(0);
});
 
process.on("uncaughtException", (err) => {
  logger.error("system", `未捕获异常: ${err.message}`, { stack: err.stack });
});
 
process.on("unhandledRejection", (reason) => {
  const msg = reason instanceof Error ? reason.message : String(reason);
  const stack = reason instanceof Error ? reason.stack : undefined;
  logger.error("system", `未处理的 Promise 拒绝: ${msg}`, stack ? { stack } : undefined);
});
 
console.log(`\n联机服务已启动 → Dashboard: http://localhost:${config.dashboardPort || 3100}\n`);