chenyc
2026-05-14 d41b8c33c18627c17db8f1b013e8826a9be2d292
gxwc
1个文件已修改
167 ■■■■■ 已修改文件
scripts/build.js 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
scripts/build.js
@@ -25,11 +25,11 @@
fs.mkdirSync(DIST, { recursive: true });
for (const key of targets) {
  const t = TARGETS[key];
  const outDir = path.join(DIST, `jms-connection-service-${t.os}`);
  const outDir = path.join(DIST, "jms-connection-service-" + t.os);
  try {
    fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 500 });
  } catch {
    console.log(`  ⚠ ${t.os} 目录清理失败(可能被占用),尝试增量覆盖...`);
    console.log("  ⚠ " + t.os + " 目录清理失败(可能被占用),尝试增量覆盖...");
  }
}
@@ -47,21 +47,21 @@
for (const key of targets) {
  const t = TARGETS[key];
  const baseName = `jms-connection-service-${t.os}`;
  const outDir = path.join(DIST, `jms-connection-service-${t.os}`);
  const baseName = "jms-connection-service-" + t.os;
  const outDir = path.join(DIST, "jms-connection-service-" + t.os);
  fs.mkdirSync(outDir, { recursive: true });
  const outPath = path.join(outDir, baseName);
  console.log(`  编译 ${t.id} → ${path.relative(ROOT, outPath)}${t.ext}`);
  console.log("  编译 " + t.id + " → " + path.relative(ROOT, outPath) + t.ext);
  try {
    execSync(
      `npx pkg . --targets ${t.id} --output "${outPath}"`,
      { cwd: ROOT, stdio: "inherit", env: { ...process.env, PKG_CACHE_PATH: PKG_CACHE } }
      'npx pkg . --targets ' + t.id + ' --output "' + outPath + '"',
      { cwd: ROOT, stdio: "inherit", env: Object.assign({}, process.env, { PKG_CACHE_PATH: PKG_CACHE }) }
    );
    builtFiles.push({ key, outDir, baseName, ext: t.ext, os: t.os });
    builtFiles.push({ key: key, outDir: outDir, baseName: baseName, ext: t.ext, os: t.os });
  } catch (err) {
    console.error(`  ✗ ${t.id} 编译失败: ${err.message}`);
    console.error("  ✗ " + t.id + " 编译失败: " + err.message);
    process.exit(1);
  }
}
@@ -70,7 +70,7 @@
console.log("[4/5] 组装发布包...");
for (const item of builtFiles) {
  const { outDir } = item;
  const outDir = item.outDir;
  // 复制配置模板
  const configSrc = path.join(ROOT, "config.json");
@@ -81,86 +81,135 @@
  fs.mkdirSync(path.join(outDir, "logs"), { recursive: true });
  // 写入使用说明
  const readme = [
    "JMS 透析机 TCP 联机服务",
    "========================",
    "",
    "快速开始:",
    "  1. 编辑 config.json,配置设备 IP、端口、序列号",
    "  2. 命令行启动:",
    `     Windows: .\\jms-connection-service-windows.exe`,
    `     Linux:   ./jms-connection-service-linux`,
    "  3. 浏览器访问 http://localhost:3100 查看监控大屏",
    "",
    "目录结构:",
    `  jms-connection-service-${item.os}/`,
    `  ├── jms-connection-service-${item.os}${item.ext}   # 主程序`,
    "  ├── config.json                                    # 配置文件(可编辑)",
    "  └── logs/                                          # 日志目录(自动创建)",
    "",
    "注册为系统服务(推荐生产环境):",
    `  Windows: sc create JMSConnection binPath= "完整路径\\jms-connection-service-windows.exe" start= auto`,
    "  Linux:   参考 DEPLOY.md 中的 PM2 / systemd 章节",
    "",
    "详细文档见 DEPLOY.md",
  ].join("\n");
  var readmeLines = [];
  readmeLines.push("JMS 透析机 TCP 联机服务");
  readmeLines.push("========================");
  readmeLines.push("");
  readmeLines.push("快速开始:");
  readmeLines.push("  1. 编辑 config.json,配置设备 IP、端口、序列号");
  readmeLines.push("  2. 命令行启动:");
  readmeLines.push("     Windows: .\\jms-connection-service-windows.exe");
  readmeLines.push("     Linux:   ./jms-connection-service-linux");
  readmeLines.push("  3. 浏览器访问 http://localhost:3100 查看监控大屏");
  readmeLines.push("");
  readmeLines.push("──────────────────────────────────────────────────────");
  readmeLines.push("config.json 配置项说明");
  readmeLines.push("──────────────────────────────────────────────────────");
  readmeLines.push("");
  readmeLines.push("【全局参数】");
  readmeLines.push("  pollIntervalMs       = 10000   // K 轮询间隔(毫秒)");
  readmeLines.push("  connectTimeoutMs     = 5000    // TCP 握手超时(毫秒)");
  readmeLines.push("  reconnectBaseMs      = 3000    // 重连退避基数(毫秒)");
  readmeLines.push("  reconnectMaxMs       = 60000   // 重连退避上限(毫秒)");
  readmeLines.push("  logDir       = \"./logs\"       // 日志目录,基于程序所在目录");
  readmeLines.push("  logRetentionDays     = 30      // 日志保留天数");
  readmeLines.push("  dashboardPort        = 3100    // 监控大屏 HTTP 端口");
  readmeLines.push("");
  readmeLines.push("【MQTT 上传(可选,默认关闭)】");
  readmeLines.push("  mqtt.enabled         = false   // 是否启用 MQTT");
  readmeLines.push("  mqtt.brokerUrl       = \"mqtt.ihemodialysis.com\"");
  readmeLines.push("  mqtt.port            = 62283");
  readmeLines.push("  mqtt.username        = \"data\"");
  readmeLines.push("  mqtt.password        = \"data#2018\"");
  readmeLines.push("  mqtt.reconnectPeriod = 5000    // 重连间隔(毫秒)");
  readmeLines.push("  mqtt.clientCode      = \"CLIENT...\"  // 客户端标识");
  readmeLines.push("  mqtt.defaultTopicPrefix = \"touxiji\" // topic = prefix/序列号");
  readmeLines.push("  mqtt.qos             = 1");
  readmeLines.push("");
  readmeLines.push("【阿里云 IoT 上传(可选,默认开启)】");
  readmeLines.push("  aliyun.enabled              = true");
  readmeLines.push("  aliyun.autoRegister         = true    // 设备不存在时自动注册");
  readmeLines.push("  aliyun.tupleApiBaseUrl      = \"https://things.icoldchain.cn/\"");
  readmeLines.push("  aliyun.tupleApiPath         = \"device/info/getAliyunDeviceSecret\"");
  readmeLines.push("  aliyun.tupleRetryCooldownMs = 60000   // 三元组失败冷却(毫秒)");
  readmeLines.push("");
  readmeLines.push("【设备列表(必填)】");
  readmeLines.push("  devices = [");
  readmeLines.push("    {");
  readmeLines.push("      \"ip\":           \"192.168.1.101\", // 设备 IP(必填)");
  readmeLines.push("      \"port\":         10001,           // 端口(GC-110N 默认 10001)");
  readmeLines.push("      \"serialNumber\": \"D001\",          // 序列号(必填,上传标识)");
  readmeLines.push("      \"enabled\":      true             // 是否启用(false=跳过)");
  readmeLines.push("    },");
  readmeLines.push("    ...可添加多台设备");
  readmeLines.push("  ]");
  readmeLines.push("");
  readmeLines.push("──────────────────────────────────────────────────────");
  readmeLines.push("重连策略: 断线后指数退避 3s → 6s → 12s → 24s → 48s → 60s 上限");
  readmeLines.push("连接成功后计数器重置。");
  readmeLines.push("");
  readmeLines.push("目录结构:");
  readmeLines.push("  jms-connection-service-" + item.os + "/");
  readmeLines.push("  ├── jms-connection-service-" + item.os + item.ext + "   # 主程序");
  readmeLines.push("  ├── config.json                                    # 配置文件");
  readmeLines.push("  └── logs/                                          # 日志目录");
  readmeLines.push("");
  readmeLines.push("注册为系统服务(推荐生产环境):");
  readmeLines.push("  Windows: sc create JMSConnection binPath= \"完整路径\\jms-connection-service-windows.exe\" start= auto");
  readmeLines.push("  Linux:   参考 DEPLOY.md 中的 PM2 / systemd 章节");
  readmeLines.push("");
  readmeLines.push("详细文档见 DEPLOY.md");
  var readme = readmeLines.join("\n");
  fs.writeFileSync(path.join(outDir, "README.txt"), readme, "utf-8");
  console.log(`  ${item.os}: ${path.relative(ROOT, outDir)}/ 组装完成`);
  console.log("  " + item.os + ": " + path.relative(ROOT, outDir) + "/ 组装完成");
}
// ── 5. 打包归档 ──
console.log("[5/5] 打包归档...");
for (const item of builtFiles) {
  const { outDir, baseName, os } = item;
  const outDir = item.outDir;
  const baseName = item.baseName;
  const os = item.os;
  const dirName = path.basename(outDir);
  if (os === "windows") {
    const zipPath = path.join(DIST, `${baseName}.zip`);
    const zipPath = path.join(DIST, baseName + ".zip");
    try {
      // 清除旧 zip
      try { fs.unlinkSync(zipPath); } catch {}
      try { fs.unlinkSync(zipPath); } catch (e) {}
      execSync(
        `powershell -Command "Compress-Archive -Path '${outDir}' -DestinationPath '${zipPath}' -Force"`,
        "powershell -Command \"Compress-Archive -Path '" + outDir + "' -DestinationPath '" + zipPath + "' -Force\"",
        { stdio: "ignore" }
      );
      console.log(`  ${path.basename(zipPath)} (${formatSize(fs.statSync(zipPath).size)})`);
    } catch {
      console.log(`  ⚠ PowerShell 不可用,跳过 zip (文件在 ${path.relative(ROOT, outDir)}/)`);
      console.log("  " + path.basename(zipPath) + " (" + formatSize(fs.statSync(zipPath).size) + ")");
    } catch (e) {
      console.log("  ⚠ PowerShell 不可用,跳过 zip (文件在 " + path.relative(ROOT, outDir) + "/)");
    }
  } else {
    const tgzPath = path.join(DIST, `${baseName}.tar.gz`);
    const tgzPath = path.join(DIST, baseName + ".tar.gz");
    try {
      try { fs.unlinkSync(tgzPath); } catch {}
      // 用相对路径避免 Windows 盘符问题
      try { fs.unlinkSync(tgzPath); } catch (e) {}
      execSync(
        `tar -czf "${tgzPath}" -C "${DIST}" "${dirName}"`,
        'tar -czf "' + tgzPath + '" -C "' + DIST + '" "' + dirName + '"',
        { stdio: "ignore", cwd: DIST }
      );
      console.log(`  ${path.basename(tgzPath)} (${formatSize(fs.statSync(tgzPath).size)})`);
    } catch {
      console.log(`  ⚠ tar 不可用,跳过归档 (文件在 ${path.relative(ROOT, outDir)}/)`);
      console.log("  " + path.basename(tgzPath) + " (" + formatSize(fs.statSync(tgzPath).size) + ")");
    } catch (e) {
      console.log("  ⚠ tar 不可用,跳过归档 (文件在 " + path.relative(ROOT, outDir) + "/)");
    }
  }
}
// ── 汇总 ──
console.log("\n======== 打包完成 ========");
console.log(`输出目录: ${path.relative(ROOT, DIST)}/`);
console.log("");
console.log("======== 打包完成 ========");
console.log("输出目录: " + path.relative(ROOT, DIST) + "/");
const distFiles = fs.readdirSync(DIST);
for (const f of distFiles) {
  const full = path.join(DIST, f);
  const stat = fs.statSync(full);
for (var i = 0; i < distFiles.length; i++) {
  var f = distFiles[i];
  var full = path.join(DIST, f);
  var stat = fs.statSync(full);
  if (stat.isDirectory()) {
    const contents = fs.readdirSync(full);
    const exe = contents.find(x => x.endsWith(".exe") || x.startsWith("jms-connection-service-"));
    console.log(`  ${f}/  (${exe ? exe + "、" : ""}config.json、logs/、README.txt)`);
    var contents = fs.readdirSync(full);
    var exe = contents.find(function(x) { return x.endsWith(".exe") || x.startsWith("jms-connection-service-"); });
    console.log("  " + f + "/  (" + (exe || "?") + "、config.json、logs/、README.txt)");
  } else {
    console.log(`  ${f}  (${formatSize(stat.size)})`);
    console.log("  " + f + "  (" + formatSize(stat.size) + ")");
  }
}
console.log("\n下一步: 进入 dist/ 对应目录,编辑 config.json 后启动程序验证。");
console.log("");
console.log("下一步: 进入 dist/ 对应目录,编辑 config.json 后启动程序验证。");
// ── helpers ──
function formatSize(bytes) {