Files
NetMesh/electron/bridges/inspection/adapters/windowsServerAdapter.cjs
zhaolei c64e00d835 feat: 新增设备巡检模块实现完整的巡检告警功能
新增设备巡检模块核心功能,包括:
- 巡检数据存储与调度系统
- 华为/H3C交换机及Linux服务器采集器
- 设备扩展配置与告警规则管理
- 主机详情页巡检配置入口
- 产品名称自定义设置功能
- 网络设备SSH exec通道兼容处理
2026-10-09 10:37:48 +08:00

238 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
/**
* Windows 服务器采集器
*
* 采集指标:
* - cpu_usage CPU 使用率(%)
* - memory_usage 内存使用率(%)
* - disk_usage 磁盘使用率(%)
* - process_count 进程数
* - uptime 运行时间
* - service_status 关键服务状态
*
* 实现说明:
* 使用 PowerShell Base64 EncodedCommand 方式执行,与 NetMesh System Manager 一致。
* 传入 exec 函数执行远程命令(由调度器注入)。
*/
const BaseAdapter = require("./baseAdapter.cjs");
class WindowsServerAdapter extends BaseAdapter {
constructor(connectionInfo, options = {}) {
super(connectionInfo, options);
this.adapterName = "windows_server";
if (options.execFn) {
this.execute = options.execFn;
}
}
getSupportedMetrics() {
return [
"cpu_usage",
"memory_usage",
"disk_usage",
"process_count",
"uptime",
"service_status",
];
}
async collectMetrics() {
const metrics = {};
const raw = {};
const DEBUG = process.env.INSPECTION_DEBUG === "1";
// 1. CPU + 内存 + 运行时间(快速,独立执行)
try {
const script = this._buildFastStatsScript();
const cmd = this._buildPowerShellCmd(script);
if (DEBUG) console.log("[WinAdapter] 执行命令:", cmd);
raw.stats = await this.execute(cmd);
if (DEBUG) console.log("[WinAdapter] 原始输出:", raw.stats);
const parsed = this._parseStats(raw.stats);
if (DEBUG) console.log("[WinAdapter] 解析结果:", parsed);
Object.assign(metrics, parsed);
} catch (e) {
raw.stats_error = e.message;
if (DEBUG) console.error("[WinAdapter] 采集失败:", e.message);
}
// 2. 磁盘使用率(较慢,单独执行,超时放宽到 30s)
try {
const diskScript = this._buildDiskScript();
const diskCmd = this._buildPowerShellCmd(diskScript);
if (DEBUG) console.log("[WinAdapter] 磁盘命令:", diskCmd);
raw.disk = await this.execute(diskCmd);
if (DEBUG) console.log("[WinAdapter] 磁盘原始输出:", raw.disk);
metrics.disk_usage = this._parseDisk(raw.disk);
} catch (e) {
raw.disk_error = e.message;
if (DEBUG) console.error("[WinAdapter] 磁盘采集失败:", e.message);
}
// 3. 关键服务状态
try {
const serviceScript = this._buildServiceScript();
const cmd = this._buildPowerShellCmd(serviceScript);
if (DEBUG) console.log("[WinAdapter] 服务命令:", cmd);
raw.services = await this.execute(cmd);
if (DEBUG) console.log("[WinAdapter] 服务原始输出:", raw.services);
metrics.service_status = this._parseServices(raw.services);
} catch (e) {
metrics.service_status = [];
raw.services_error = e.message;
if (DEBUG) console.error("[WinAdapter] 服务采集失败:", e.message);
}
return metrics;
}
/**
* 快速统计:CPU + 内存(使用更快的 WMI 查询)
*/
_buildFastStatsScript() {
return `
try { $cpu = (Get-WmiObject Win32_Processor).LoadPercentage; if (-not $cpu) { $cpu = 0 } } catch { $cpu = 0 }
try { $os = Get-WmiObject Win32_OperatingSystem; $total = [math]::Round($os.TotalVisibleMemorySize/1MB,0); $free = [math]::Round($os.FreePhysicalMemory/1MB,0); $usage = if ($total -gt 0) { [math]::Round(($total-$free)/$total*100,1) } else { 0 } } catch { $total = 0; $usage = 0; $free = 0 }
@{ cpu = $cpu; mem_usage = $usage; mem_total = $total; mem_free = $free } | ConvertTo-Json -Compress
`.trim();
}
/** 磁盘使用率(只查 C: 盘) */
_buildDiskScript() {
return `
try {
$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'"
if ($disk -and $disk.Size -gt 0) {
$s = [math]::Round($disk.Size/1GB,1); $f = [math]::Round($disk.FreeSpace/1GB,1); $u = [math]::Round(($disk.Size-$disk.FreeSpace)/$disk.Size*100,1)
@{ d='C:'; s=$s; f=$f; u=$u } | ConvertTo-Json -Compress
} else { 'null' }
} catch { 'null' }
`.trim();
}
_parseDisk(stdout) {
try {
const json = this._extractJson(stdout);
if (!json || json === "null" || json === null) return [];
return [{
drive: json.d || "C:",
size_gb: json.s || 0,
free_gb: json.f || 0,
usage_percent: json.u || 0,
}];
} catch {
return [];
}
}
/**
* 构建 PowerShell 命令:UTF-16LE Base64 编码 + -EncodedCommand
* Node.js Buffer.from(str, "utf16le") 是最可靠的编码方式
*/
_buildPowerShellCmd(script) {
// 用 Node.js 原生 Buffer 进行 UTF-16LE 编码,再转 Base64
// UTF-16LE 编码(不带 BOM),然后 Base64
const utf16le = Buffer.from(script, "utf16le");
const encoded = utf16le.toString("base64");
return `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
}
_buildServiceScript() {
return `
$ErrorActionPreference = 'SilentlyContinue'
# 关键服务
$services = @(
"wuauserv", # Windows Update
"Spooler", # Print Spooler
"TermService", # Remote Desktop
"DHCP", # DHCP Client
"DNScache" # DNS Client
)
$results = foreach ($svc in $services) {
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
if ($s) {
[PSCustomObject]@{
name = $s.Name
display_name = $s.DisplayName
status = $s.Status.ToString()
start_type = $s.StartType.ToString()
}
}
}
$results | ConvertTo-Json -Depth 3
`.trim();
}
_parseStats(stdout) {
try {
const json = this._extractJson(stdout);
if (!json) return {};
const result = {};
if (json.cpu !== undefined) result.cpu_usage = json.cpu;
if (json.mem_usage !== undefined) result.memory_usage = json.mem_usage;
if (json.mem_total !== undefined && json.mem_total > 0) {
result.memory = {
total_mb: json.mem_total,
used_mb: Math.round((json.mem_usage / 100) * json.mem_total),
};
}
return result;
} catch (e) {
return {};
}
}
_parseServices(stdout) {
try {
const json = this._extractJson(stdout);
if (!json) return [];
return Array.isArray(json) ? json : [json];
} catch (e) {
return [];
}
}
/**
* 从 PowerShell 输出中提取 JSON(可能有多余的警告/文本)
*/
_extractJson(text) {
if (!text) return null;
const start = text.indexOf("{");
const startArr = text.indexOf("[");
const realStart = startArr === -1 ? start : (start === -1 ? startArr : Math.min(start, startArr));
if (realStart === -1) return null;
let depth = 0;
let end = -1;
const open = text[realStart];
const close = open === "{" ? "}" : "]";
for (let i = realStart; i < text.length; i++) {
if (text[i] === open) depth++;
else if (text[i] === close) {
depth--;
if (depth === 0) {
end = i;
break;
}
}
}
if (end === -1) return null;
try {
return JSON.parse(text.substring(realStart, end + 1));
} catch (e) {
return null;
}
}
}
module.exports = WindowsServerAdapter;