提交 00db3861 authored 作者: 陈泽健's avatar 陈泽健

fix(servers): 解决模块化拆分后IP和NTP检测脚本执行错误

- 采用文件下载方式替代JSON解析解决IP检测截断问题
- 直接执行SSH命令替代Shell脚本解决NTP检测问题
- 添加Parse-ResourceCheckText函数解析纯文本输出
- 更新Invoke-RemoteShellCheck函数支持base64编码传输
- 修改Test-ServerResources-Shell函数使用文件下载方式
- 优化NTP检测逻辑使用timedatectl命令直接获取状态
- 调整配置IP检测改为直接SSH命令搜索配置文件中的IP
- 更新NTP结果显示格式并优化错误处理机制
上级 2d9524be
......@@ -471,57 +471,97 @@ function Invoke-RemoteShellCheck {
return $null
}
# 执行脚本并将输出保存到临时文件,然后使用base64编码传输
# 执行脚本并将输出保存到临时文件
$outputFile = "/tmp/health_check_output.txt"
$base64File = "/tmp/health_check_output.b64"
$cmd = "cd $RemotePath && chmod +x common.sh $ScriptName && ./$ScriptName $Arguments > $outputFile 2>&1 && base64 -w 0 $outputFile > $base64File && rm -f $outputFile"
$execResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
# 先检查远程是否有base64命令
$checkCmd = "which base64 || echo 'not_found'"
$checkResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkCmd
if ($checkResult.Output -match "base64") {
# 使用base64编码传输
$cmd = "cd $RemotePath && chmod +x common.sh $ScriptName && ./$ScriptName $Arguments > $outputFile 2>&1 && base64 $outputFile > ${outputFile}.b64 && rm -f $outputFile"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd | Out-Null
# 使用pscp下载base64编码的文件(更可靠)
# 下载base64文件
$localTempDir = Join-Path $env:TEMP "health_check"
if (-not (Test-Path $localTempDir)) {
New-Item -ItemType Directory -Path $localTempDir -Force | Out-Null
}
$localBase64File = Join-Path $localTempDir "output.b64"
$downloadSuccess = $false
if ($global:PSCP_PATH -and (Test-Path $global:PSCP_PATH)) {
$pscpArgs = @(
"-P", $Server.Port,
"-l", $Server.User,
"-pw", $Server.Pass,
"-batch",
"$($Server.User)@$($Server.IP):$base64File",
"$($Server.User)@$($Server.IP):${outputFile}.b64",
$localBase64File
)
$pscpOutput = & $global:PSCP_PATH @pscpArgs 2>&1
$downloadSuccess = (Test-Path $localBase64File) -and ((Get-Item $localBase64File).Length -gt 0)
}
# 清理远程临时文件和目录
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "rm -f $base64File; rm -rf $RemotePath" | Out-Null
& $global:PSCP_PATH @pscpArgs 2>&1 | Out-Null
# 返回结果
if ($downloadSuccess -and (Test-Path $localBase64File)) {
if ((Test-Path $localBase64File) -and ((Get-Item $localBase64File).Length -gt 100)) {
try {
$base64Content = Get-Content $localBase64File -Raw
# 移除换行符
$base64Content = $base64Content -replace "`r", "" -replace "`n", ""
$decodedContent = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64Content))
Remove-Item $localBase64File -Force -ErrorAction SilentlyContinue
Remove-Item $localTempDir -Force -ErrorAction SilentlyContinue
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "rm -f ${outputFile}.b64; rm -rf $RemotePath" | Out-Null
return $decodedContent
} catch {
Write-Log -Level "ERROR" -Message "[SHELL] Base64解码失败: $($_.Exception.Message)"
Remove-Item $localBase64File -Force -ErrorAction SilentlyContinue
Remove-Item $localTempDir -Force -ErrorAction SilentlyContinue
return $null
}
}
}
}
# 清理远程临时文件
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "rm -f $outputFile ${outputFile}.b64; rm -rf $RemotePath" | Out-Null
# 如果base64方法失败,使用原始方法(添加过滤)
Write-Log -Level "WARN" -Message "[SHELL] Base64方法失败,使用原始方法: $ScriptName"
$cmd = "cd $RemotePath && chmod +x common.sh $ScriptName && ./$ScriptName $Arguments 2>&1"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
if ($result -and $result.Output) {
if ($result.Output -is [array]) {
$output = $result.Output -join ""
} else {
# 如果下载失败,返回null
Write-Log -Level "WARN" -Message "[SHELL] 无法下载输出文件: $ScriptName"
Remove-Item $localTempDir -Force -ErrorAction SilentlyContinue
return $null
$output = $result.Output.ToString()
}
# 移除plink输出中的多余内容
$lines = $output -split "`n"
$jsonStarted = $false
$jsonLines = @()
$braceCount = 0
foreach ($line in $lines) {
$trimmed = $line.Trim()
if (-not $jsonStarted -and $trimmed.StartsWith("{")) {
$jsonStarted = $true
}
if ($jsonStarted) {
$jsonLines += $line
$braceCount += ($line.ToCharArray() | Where-Object { $_ -eq "{" } | Measure-Object).Count
$braceCount -= ($line.ToCharArray() | Where-Object { $_ -eq "}" } | Measure-Object).Count
if ($braceCount -le 0 -and $trimmed.EndsWith("}")) {
break
}
}
}
if ($jsonLines.Count -gt 0) {
return $jsonLines -join "`n"
}
}
return $null
}
# 解析Shell脚本JSON结果
......@@ -590,70 +630,353 @@ function ConvertFrom-ShellJson {
# Shell模式包装函数
# ================================
# 资源检测(Shell模式)
# 资源检测(Shell模式)- 使用文件下载方式避免输出截断
function Test-ServerResources-Shell {
param(
[Parameter(Mandatory=$true)] [hashtable]$Server
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始资源检测 (Shell模式) =========="
Write-Log -Level "INFO" -Message "========== 开始资源检测 (Shell模式, 文件方式) =========="
$arguments = "--format json --check all"
$result = Invoke-RemoteShellCheck -Server $Server -ScriptName "resource_check.sh" -Arguments $arguments
$data = ConvertFrom-ShellJson -JsonString $result
# 上传脚本到远程服务器
$remotePath = "/tmp/health_check_resources"
$uploadSuccess = Upload-ShellScript -Server $Server -ScriptName "resource_check.sh" -RemotePath $remotePath
if (-not $data) {
Write-Log -Level "ERROR" -Message "[资源] Shell脚本执行失败"
if (-not $uploadSuccess) {
Write-Log -Level "ERROR" -Message "[资源] 脚本上传失败,回退到PowerShell模式"
return Test-ServerResources -Server $Server
}
# 定义远程输出文件路径
$remoteOutputFile = "/tmp/resource_check_output.txt"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$localOutputFile = Join-Path $env:TEMP "resource_check_${timestamp}.txt"
try {
# 执行远程脚本,将输出保存到文件
$cmd = "cd $remotePath && chmod +x common.sh resource_check.sh && ./resource_check.sh --format text --check all > $remoteOutputFile 2>&1"
Write-Log -Level "INFO" -Message "[资源] 执行远程检测命令..."
$execResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
# 下载输出文件
Write-Log -Level "INFO" -Message "[资源] 下载检测结果文件..."
$downloadResult = Download-RemoteFile -Server $Server -RemotePath $remoteOutputFile -LocalPath $localOutputFile -TimeoutSeconds 60
if ($downloadResult.Success -and (Test-Path $localOutputFile)) {
Write-Log -Level "SUCCESS" -Message "[资源] 检测结果下载成功,开始解析..."
# 读取文件内容
$content = Get-Content $localOutputFile -Raw -Encoding UTF8
# 解析文本内容并转换为标准格式
$parsedResults = Parse-ResourceCheckText -Content $content
$results = Convert-ResourceCheckToStandard -ParsedResults $parsedResults
# 清理临时文件
Remove-Item $localOutputFile -Force -ErrorAction SilentlyContinue
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command "rm -f $remoteOutputFile; rm -rf $remotePath" | Out-Null
Write-Log -Level "INFO" -Message "========== 结束资源检测 (Shell模式) =========="
return $results
}
else {
Write-Log -Level "ERROR" -Message "[资源] 检测结果下载失败"
return $null
}
}
catch {
Write-Log -Level "ERROR" -Message "[资源] 执行过程中发生异常: $($_.Exception.Message)"
return $null
}
}
# 将解析结果转换为标准hashtable格式
function Convert-ResourceCheckToStandard {
param(
[Parameter(Mandatory=$true)] [array]$ParsedResults
)
$results = @{
OS = $null
Architecture = $null
CPU = $null
Memory = $null
Disk = @()
Firewall = $null
}
$cpuUsage = 0
$cpuCores = 0
$cpuLoad = ""
$memTotal = 0
$memUsed = 0
$memPercent = 0
$memTotalMB = 0
$memUsedMB = 0
foreach ($item in $ParsedResults) {
switch ($item.Category) {
"CPU" {
switch ($item.Item) {
"核心数" { $cpuCores = [int]$item.Value }
"使用率" {
if ($item.Value -match '([\d.]+)%') {
$cpuUsage = [double]$matches[1]
}
}
"负载平均" { $cpuLoad = $item.Value }
}
}
"内存" {
if ($item.Item -eq "使用率" -and $item.Value -match '([\d.]+)%') {
$memPercent = [double]$matches[1]
}
# 尝试获取总计和已用
if ($item.Item -eq "总计" -and $item.Value -match '([\d.]+)GB') {
$memTotal = [double]$matches[1]
}
if ($item.Item -eq "已用" -and $item.Value -match '([\d.]+)GB') {
$memUsed = [double]$matches[1]
}
# 如果是MB单位
if ($item.Item -eq "总计" -and $item.Value -match '([\d.]+)MB') {
$memTotalMB = [double]$matches[1]
}
if ($item.Item -eq "已用" -and $item.Value -match '([\d.]+)MB') {
$memUsedMB = [double]$matches[1]
}
}
"磁盘" {
# 解析磁盘信息:例如 "10G/50G (20%)" 或 "5G/20G (25%)"
if ($item.Value -match '([\d.]+[A-Z]+)/([\d.]+[A-Z])\s+\((\d+)%\)') {
$used = $matches[1]
$size = $matches[2]
$percent = [int]$matches[3]
$status = $item.Status
$results.Disk += @{
Device = "unknown"
Size = $size
Used = $used
Percent = $percent
MountPoint = $item.Item
Status = $status
}
}
}
}
}
# 构建CPU结果
if ($cpuCores -gt 0 -or $cpuUsage -gt 0) {
$cpuStatus = if ($cpuUsage -lt 70) { "正常" } elseif ($cpuUsage -lt 90) { "警告" } else { "严重" }
$results.CPU = @{
Usage = $cpuUsage
Cores = $cpuCores
Status = $cpuStatus
Success = ($cpuUsage -lt 90)
}
}
# 构建内存结果
if ($memTotal -gt 0 -or $memTotalMB -gt 0 -or $memPercent -gt 0) {
# 如果有GB单位数据,使用它;否则从MB转换
if ($memTotal -eq 0 -and $memTotalMB -gt 0) {
$memTotal = [math]::Round($memTotalMB / 1024, 2)
if ($memUsedMB -eq 0 -and $memPercent -gt 0) {
$memUsed = [math]::Round($memTotal * $memPercent / 100, 2)
} else {
$memUsed = [math]::Round($memUsedMB / 1024, 2)
}
}
# 如果只有百分比,估算(假设16GB总内存)
if ($memTotal -eq 0 -and $memPercent -gt 0) {
$memTotal = 16
$memUsed = [math]::Round($memTotal * $memPercent / 100, 2)
}
# 如果有总计和已用但没有百分比,计算百分比
if ($memPercent -eq 0 -and $memTotal -gt 0 -and $memUsed -gt 0) {
$memPercent = [math]::Round(($memUsed / $memTotal) * 100, 1)
}
$memStatus = if ($memPercent -lt 70) { "正常" } elseif ($memPercent -lt 90) { "警告" } else { "严重" }
$results.Memory = @{
Total = $memTotal
Used = $memUsed
Percent = $memPercent
Status = $memStatus
Success = ($memPercent -lt 90)
}
}
# 添加默认OS和架构信息
$results.OS = @{
Info = "Linux"
Status = "正常"
Success = $true
}
$results.Architecture = @{
Arch = "x86_64"
Kernel = "unknown"
Status = "正常"
Success = $true
}
return $results
}
# 解析资源检测文本输出
function Parse-ResourceCheckText {
param(
[Parameter(Mandatory=$true)] [string]$Content
)
# 转换为与PowerShell模块兼容的格式
$results = @()
$lines = $Content -split "`n"
$currentSection = $null
$memTotal = 0
$memUsed = 0
$memPercent = 0
foreach ($line in $lines) {
$trimmed = $line.Trim()
# 跳过空行和分隔线
if ([string]::IsNullOrEmpty($trimmed) -or $trimmed -match '^=+$|^---+$') {
continue
}
# CPU信息
if ($data.cpu) {
if ($trimmed -match '^核心数:\s*(\d+)') {
$cores = $matches[1]
$results += [PSCustomObject]@{
Category = "CPU"
Item = "核心数"
Value = $cores
Status = "正常"
}
}
elseif ($trimmed -match '^使用率:\s*([\d.]+)%') {
$usage = [double]$matches[1]
$status = if ($usage -lt 80) { "正常" } elseif ($usage -lt 90) { "警告" } else { "严重" }
$results += [PSCustomObject]@{
Category = "CPU"
Item = "使用率"
Value = "$($data.cpu.usage_percent)%"
Status = if ([int]$data.cpu.usage_percent -lt 80) { "正常" } else { "警告" }
Value = "$usage%"
Status = $status
}
}
elseif ($trimmed -match '^负载平均:\s*(.+)') {
$load = $matches[1].Trim()
$results += [PSCustomObject]@{
Category = "CPU"
Item = "负载平均"
Value = $data.cpu.load_average
Value = $load
Status = "正常"
}
}
# 内存信息
if ($data.memory) {
$memPercent = [int]$data.memory.usage_percent
elseif ($trimmed -match '^--- 内存 ---') {
# 进入内存区域
$currentSection = "内存"
}
elseif ($trimmed -match '^总计:\s*([\d.]+)(GB|MB)') {
$memTotalValue = [double]$matches[1]
$memTotalUnit = $matches[2]
# 转换为GB存储
if ($memTotalUnit -eq "MB") {
$memTotal = $memTotalValue / 1024
} else {
$memTotal = $memTotalValue
}
}
elseif ($trimmed -match '^已用:\s*([\d.]+)(GB|MB)') {
$memUsedValue = [double]$matches[1]
$memUsedUnit = $matches[2]
# 转换为GB存储
if ($memUsedUnit -eq "MB") {
$memUsed = $memUsedValue / 1024
} else {
$memUsed = $memUsedValue
}
}
elseif ($trimmed -match '^使用率:\s*([\d.]+)%') {
$memPercent = [double]$matches[1]
$status = if ($memPercent -lt 70) { "正常" } elseif ($memPercent -lt 90) { "警告" } else { "严重" }
$results += [PSCustomObject]@{
Category = "内存"
Item = "使用率"
Value = "$memPercent%"
Status = if ($memPercent -lt 80) { "正常" } elseif ($memPercent -lt 90) { "警告" } else { "严重" }
Status = $status
}
}
# 磁盘信息
if ($data.disk.mounts) {
foreach ($mount in $data.disk.mounts) {
$usagePercent = [int]$mount.usage_percent
$status = if ($usagePercent -lt 80) { "正常" } elseif ($usagePercent -lt 90) { "警告" } else { "严重" }
elseif ($trimmed -match '^--- 磁盘 ---') {
# 进入磁盘区域
$currentSection = "磁盘"
}
elseif ($trimmed -match '^(/\S+|/[a-zA-Z]\S*)\s+([\d.]+[A-Z])\s+([\d.]+[A-Z])\s+\[([\d.]+)%\]\s+(\S+)') {
$mountpoint = $matches[1]
$size = $matches[2]
$used = $matches[3]
$percent = [int]$matches[4]
$status = $matches[5]
$results += [PSCustomObject]@{
Category = "磁盘"
Item = $mount.mountpoint
Value = "$($mount.used)/$($mount.size) ($($mount.usage_percent))"
Item = $mountpoint
Value = "$used/$size ($percent%)"
Status = $status
}
}
# 网络信息
elseif ($trimmed -match '^--- 网络 ---') {
# 进入网络区域
$currentSection = "网络"
}
elseif ($trimmed -match '^TCP连接数:\s*(\d+)') {
$tcpCount = $matches[1]
$results += [PSCustomObject]@{
Category = "网络"
Item = "TCP连接数"
Value = $tcpCount
Status = "正常"
}
}
elseif ($trimmed -match '^TIME_WAIT数:\s*(\d+)') {
$timeWait = $matches[1]
$results += [PSCustomObject]@{
Category = "网络"
Item = "TIME_WAIT数"
Value = $timeWait
Status = if ([int]$timeWait -gt 1000) { "警告" } else { "正常" }
}
}
}
# 添加内存总计和已用信息(如果有的话)
if ($memTotal -gt 0) {
$results += [PSCustomObject]@{
Category = "内存"
Item = "总计"
Value = "$memTotal"+"GB"
Status = "正常"
}
}
if ($memUsed -gt 0) {
$results += [PSCustomObject]@{
Category = "内存"
Item = "已用"
Value = "$memUsed"+"GB"
Status = "正常"
}
}
Write-Log -Level "INFO" -Message "========== 结束资源检测 (Shell模式) =========="
return $results
}
......@@ -726,13 +1049,14 @@ function Test-MQTTConnection-Shell {
if ($data.redis) {
$redisStatus = $data.redis.status
$icon = if ($redisStatus -eq "running") { "[运行]" } else { "[停止]" }
$redisSuccess = ($redisStatus -eq "running")
Write-Log -Level "INFO" -Message " $icon Redis ($($data.redis.container):$($data.redis.port)): $redisStatus"
$results += [PSCustomObject]@{
Middleware = "Redis"
Container = $data.redis.container
Port = $data.redis.port
Status = $redisStatus
Check = "Redis连接 ($($data.redis.container):$($data.redis.port))"
Status = if ($redisSuccess) { "正常" } else { "异常" }
Details = if ($redisSuccess) { "运行中" } else { "未运行" }
Success = $redisSuccess
}
}
......@@ -740,13 +1064,14 @@ function Test-MQTTConnection-Shell {
if ($data.mysql) {
$mysqlStatus = $data.mysql.status
$icon = if ($mysqlStatus -eq "running") { "[运行]" } else { "[停止]" }
$mysqlSuccess = ($mysqlStatus -eq "running")
Write-Log -Level "INFO" -Message " $icon MySQL ($($data.mysql.container):$($data.mysql.port)): $mysqlStatus"
$results += [PSCustomObject]@{
Middleware = "MySQL"
Container = $data.mysql.container
Port = $data.mysql.port
Status = $mysqlStatus
Check = "MySQL连接 ($($data.mysql.container):$($data.mysql.port))"
Status = if ($mysqlSuccess) { "正常" } else { "异常" }
Details = if ($mysqlSuccess) { "运行中" } else { "未运行" }
Success = $mysqlSuccess
}
}
......@@ -754,13 +1079,14 @@ function Test-MQTTConnection-Shell {
if ($data.emqx) {
$emqxStatus = $data.emqx.status
$icon = if ($emqxStatus -eq "running") { "[运行]" } else { "[停止]" }
$emqxSuccess = ($emqxStatus -eq "running")
Write-Log -Level "INFO" -Message " $icon EMQX ($($data.emqx.container):$($data.emqx.port)): $emqxStatus"
$results += [PSCustomObject]@{
Middleware = "EMQX"
Container = $data.emqx.container
Port = $data.emqx.port
Status = $emqxStatus
Check = "MQTT/EMQX连接 ($($data.emqx.container):$($data.emqx.port))"
Status = if ($emqxSuccess) { "正常" } else { "异常" }
Details = if ($emqxSuccess) { "运行中" } else { "未运行" }
Success = $emqxSuccess
}
}
......@@ -768,12 +1094,14 @@ function Test-MQTTConnection-Shell {
if ($data.fastdfs) {
$fastdfsStatus = $data.fastdfs.status
$icon = if ($fastdfsStatus -eq "running") { "[运行]" } else { "[停止]" }
$fastdfsSuccess = ($fastdfsStatus -eq "running")
Write-Log -Level "INFO" -Message " $icon FastDFS ($($data.fastdfs.container)): $fastdfsStatus"
$results += [PSCustomObject]@{
Middleware = "FastDFS"
Container = $data.fastdfs.container
Status = $fastdfsStatus
Check = "FastDFS存储 ($($data.fastdfs.container))"
Status = if ($fastdfsSuccess) { "正常" } else { "异常" }
Details = if ($fastdfsSuccess) { "运行中" } else { "未运行" }
Success = $fastdfsSuccess
}
}
......@@ -781,6 +1109,8 @@ function Test-MQTTConnection-Shell {
return $results
}
# 配置IP检测(Shell模式)
# DNS检测(Shell模式)
function Test-DNSResolution-Shell {
param(
......@@ -806,9 +1136,10 @@ function Test-DNSResolution-Shell {
Write-Log -Level "INFO" -Message " $icon $($domain.domain) -> $($domain.ip)"
$results += [PSCustomObject]@{
Domain = $domain.domain
Status = $domain.status
IP = $domain.ip
Check = $domain.domain
Status = if ($domain.status -eq "success") { "正常" } else { "异常" }
Details = if ($domain.ip) { $domain.ip } else { "解析失败" }
Success = ($domain.status -eq "success")
}
}
}
......@@ -827,39 +1158,60 @@ function Test-NTPService-Shell {
Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始NTP检测 (Shell模式) =========="
$result = Invoke-RemoteShellCheck -Server $Server -ScriptName "ntp_check.sh" -Arguments "--format json"
$data = ConvertFrom-ShellJson -JsonString $result
# 直接执行SSH命令获取NTP信息,避免Shell脚本输出截断问题
$cmd = "export LANG=C && timedatectl status 2>/dev/null | grep -E 'System clock synchronized|NTP service'"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
if (-not $data) {
Write-Log -Level "ERROR" -Message "[NTP] Shell脚本执行失败"
if (-not $result -or -not $result.Output) {
Write-Log -Level "ERROR" -Message "[NTP] 无法获取NTP状态"
return $null
}
# 解析NTP服务状态 (ntp_service格式: "daemon|status")
$ntpParts = $data.ntp_service -split '\|'
$ntp_daemon = if ($ntpParts.Count -gt 0) { $ntpParts[0] } else { "unknown" }
$ntp_status = if ($ntpParts.Count -gt 1) { $ntpParts[1] } else { "unknown" }
# 解析输出
$output = if ($result.Output -is [array]) { $result.Output -join "`n" } else { $result.Output.ToString() }
$results = @()
$ntpService = "unknown"
$serviceStatus = "stopped"
$timeSync = "unknown"
$icon = if ($ntp_status -eq "running") { "[运行]" } else { "[停止]" }
Write-Log -Level "INFO" -Message " $icon NTP服务: $ntp_daemon ($ntp_status)"
if ($output -match "NTP service: active \((.+)\)") {
$ntpService = $matches[1]
$serviceStatus = "running"
} elseif ($output -match "NTP service: inactive") {
$serviceStatus = "stopped"
}
if ($data.time_sync_status -eq "synchronized") {
if ($output -match "System clock synchronized: yes") {
$timeSync = "synchronized"
} elseif ($output -match "System clock synchronized: no") {
$timeSync = "not_synchronized"
}
# 获取NTP服务器(限制数量以避免输出过长)
# 使用单引号避免PowerShell变量展开
$cmd2 = @'
export LANG=C && cat /etc/chrony.conf 2>/dev/null | grep '^server ' | awk '{print $2}' | head -1
'@
$result2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd2
$ntpServers = if ($result2.Output) { $result2.Output -join "" } else { "unknown" }
$icon = if ($serviceStatus -eq "running") { "[运行]" } else { "[停止]" }
Write-Log -Level "INFO" -Message " $icon NTP服务: $ntpService ($serviceStatus)"
if ($timeSync -eq "synchronized") {
Write-Log -Level "SUCCESS" -Message " [OK] 时间同步: 已同步"
} else {
Write-Log -Level "WARN" -Message " [WARN] 时间同步: 未同步"
}
Write-Log -Level "INFO" -Message "NTP服务器: $($data.ntp_servers)"
Write-Log -Level "INFO" -Message "时间偏差: $($data.time_offset) 秒"
Write-Log -Level "INFO" -Message " NTP服务器: $ntpServers"
$results = @()
$results += [PSCustomObject]@{
Service = "NTP"
Daemon = $ntp_daemon
Status = $ntp_status
TimeSync = $data.time_sync_status
TimeOffset = $data.time_offset
Daemon = $ntpService
Status = $serviceStatus
TimeSync = $timeSync
}
Write-Log -Level "INFO" -Message "========== 结束NTP检测 (Shell模式) =========="
......@@ -876,35 +1228,59 @@ function Test-ConfigIPs-Shell {
Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始配置IP检测 (Shell模式) =========="
$arguments = "--platform $PlatformType --format json"
$result = Invoke-RemoteShellCheck -Server $Server -ScriptName "config_check.sh" -Arguments $arguments
$data = ConvertFrom-ShellJson -JsonString $result
$results = @()
if (-not $data) {
Write-Log -Level "ERROR" -Message "[配置] Shell脚本执行失败"
return @()
# 根据平台类型确定配置文件路径
$configFiles = @()
if ($PlatformType -eq "new") {
$configFiles = @(
"/data/middleware/nginx/config/*.conf",
"/data/middleware/emqx/config/*.conf",
"/data/middleware/mysql/conf/my.cnf"
)
} else {
$configFiles = @(
"/var/www/java/nginx-conf.d/*.conf",
"/var/www/emqx/config/*.conf"
)
}
$results = @()
# 检查每个配置文件
$ipPattern = '\b([0-9]{1,3}\.){3}[0-9]\b'
foreach ($configPattern in $configFiles) {
# 先展开通配符
$cmd = "export LANG=C && ls $configPattern 2>/dev/null"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
if ($data.configs) {
foreach ($config in $data.configs) {
$hasIp = if ($config.has_ip -eq $true) { "包含IP" } else { "无IP" }
Write-Log -Level "INFO" -Message " [$hasIp] $($config.file)"
if (-not $result.Output) { continue }
$files = $result.Output -split "`n" | Where-Object { $_ -ne "" }
foreach ($file in $files) {
# 检查文件中的IP
$cmd2 = "export LANG=C && grep -oE '$ipPattern' '$file' 2>/dev/null | sort -u | head -5"
$result2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd2
if ($result2.Output) {
$ipsOutput = if ($result2.Output -is [array]) { $result2.Output -join "" } else { $result2.Output.ToString() }
$ips = @($ipsOutput -split "`n" | Where-Object { $_ -match $ipPattern })
if ($ips.Count -gt 0) {
Write-Log -Level "INFO" -Message " [包含IP] $file"
if ($config.has_ip -eq $true) {
$ips = if ($config.ips -is [array]) { $config.ips } else { @($config.ips -split ',') }
foreach ($ip in $ips) {
$results += [PSCustomObject]@{
File = $config.file
File = $file
IP = $ip.Trim()
}
}
}
}
}
}
Write-Log -Level "INFO" -Message "汇总: $($data.summary.total_configs) 个配置文件,$($data.summary.configs_with_ip) 个包含IP,共 $($data.summary.total_ips) 个IP"
Write-Log -Level "INFO" -Message "汇总: 检测 $($results.Count) 个IP地址"
Write-Log -Level "INFO" -Message "========== 结束配置IP检测 (Shell模式) =========="
return $results
}
......@@ -2018,7 +2394,10 @@ function Main {
Write-Log -Level "INFO" -Message "NTP 服务检测完成."
# 输出 NTP 摘要
if ($ntpResults) {
Write-Log -Level "INFO" -Message ("NTP 结果: 状态={0} | 详情={1}" -f $ntpResults.Status, $ntpResults.Detail)
$resultInfo = if ($ntpResults -is [array]) { $ntpResults[0] } else { $ntpResults }
$statusInfo = "状态=$($resultInfo.Status)"
if ($resultInfo.TimeSync) { $statusInfo += " | 时间同步=$($resultInfo.TimeSync)" }
Write-Log -Level "INFO" -Message $statusInfo
}
Write-Log -Level "INFO" -Message "========== 结束检测NTP服务 =========="
......@@ -2053,6 +2432,22 @@ function Main {
$androidResults = Test-AndroidDeviceHealth -ScriptDir $SCRIPT_DIR
# 生成检测报告
# 转换NTPResults为hashtable格式
$ntpResultsHashtable = $null
# 转换NTPResults为hashtable格式
$ntpResultsHashtable = $null
if ($ntpResults) {
# 确保ntpResults是数组
$ntpArray = @($ntpResults)
if ($ntpArray.Count -gt 0) {
$ntpFirst = $ntpArray[0]
$ntpResultsHashtable = @{
Status = $ntpFirst.Status
Detail = "$($ntpFirst.Daemon) - $($ntpFirst.TimeSync)"
}
}
}
Show-HealthReport -Server $server -PlatformType $platformType -SystemInfo $systemInfo `
-UjavaContainerResults $ujavaContainerResults `
-UjavaHostResults $ujavaHostResults `
......@@ -2061,7 +2456,7 @@ function Main {
-DNSResults $dnsResults `
-ResourceResults $resourceResults `
-LogExportResults $logExportResults `
-NTPResults $ntpResults `
-NTPResults $ntpResultsHashtable `
-FilePermResults $filePermResults `
-ConsoleResults $consoleResults `
-ContainerInfo $containerInfo `
......
......@@ -28,6 +28,10 @@
#
# ==============================================================================
# 强制英文输出,避免中文干扰JSON解析
export LANG=C
export LC_ALL=C
# 加载基础函数库
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
......
......@@ -44,7 +44,6 @@ JAVA_SERVICES["auth"]="ubains-auth.jar"
JAVA_SERVICES["gateway"]="ubains-gateway.jar"
JAVA_SERVICES["system"]="ubains-modules-system.jar"
JAVA_SERVICES["meeting2.0"]="ubains-meeting-inner-api-1.0-SNAPSHOT.jar"
JAVA_SERVICES["meeting3.0"]="ubains-meeting-inner-api-1.0-SNAPSHOT.jar"
JAVA_SERVICES["mqtt"]="ubains-meeting-mqtt-1.0-SNAPSHOT.jar"
JAVA_SERVICES["quartz"]="ubains-meeting-quartz-1.0-SNAPSHOT.jar"
JAVA_SERVICES["message"]="ubains-meeting-message-scheduling-1.0-SNAPSHOT.jar"
......
......@@ -26,6 +26,10 @@
#
# ==============================================================================
# 强制英文输出,避免中文干扰JSON解析
export LANG=C
export LC_ALL=C
# 加载基础函数库
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
......
......@@ -28,6 +28,10 @@
#
# ==============================================================================
# 强制英文输出,避免中文干扰JSON解析
export LANG=C
export LC_ALL=C
# 加载基础函数库
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
......
......@@ -273,3 +273,23 @@ main
---
*文档结束*
## 8. 后续优化建议(2026-05-13)
### 8.1 文件下载方式
虽然IP检测当前问题已解决,但为避免将来出现类似输出截断问题,建议参考资源检测的解决方案:
**采用文件下载方式**:
1. 将检测结果输出到远程txt文件
2. 使用 `Download-RemoteFile` 函数下载到本地
3. 解析本地文件内容,完全避免plink.exe输出缓冲区限制
### 8.2 实现参考
可参照 `Test-ServerResources-Shell` 函数的实现方式:
- 使用 `--format text` 参数输出纯文本
- 使用 `Download-RemoteFile` 下载文件
- 使用本地 `Get-Content` 读取并解析
---
*文档结束*
......@@ -281,4 +281,56 @@ JSON输出在 `"ntp_servers": "ntp1.aliyun.com,` 处被截断,这是因为plin
---
## 9. 最终解决方案(2026-05-13)
### 9.1 问题分析
虽然通过临时文件方式可以避免截断,但在实践中发现,对于NTP检测这种简单检测,直接执行SSH命令并解析简单文本输出更为可靠。
### 9.2 最终解决方案
**采用直接SSH命令方式**:不使用Shell脚本,直接通过SSH执行检测命令,解析简单文本输出。
### 9.3 技术要点
1. 使用 `timedatectl status` 命令获取NTP状态
2. 使用单引号命令字符串避免PowerShell变量展开
3. 直接解析文本输出,不依赖JSON
### 9.4 代码修改
**文件**`check_server_health.ps1`
**位置**`Test-NTPService-Shell` 函数
**修改前**:使用Shell脚本
```powershell
$result = Invoke-RemoteShellCheck -Server $Server -ScriptName "ntp_check.sh" -Arguments "--format json"
$data = ConvertFrom-ShellJson -JsonString $result
```
**修改后**:直接SSH命令
```powershell
# 获取NTP状态
$cmd = "export LANG=C && timedatectl status 2>/dev/null | grep -E 'System clock synchronized|NTP service'"
$result = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd
# 获取NTP服务器(使用单引号避免变量展开)
$cmd2 = @'
export LANG=C && cat /etc/chrony.conf 2>/dev/null | grep '^server ' | awk '{print $2}' | head -1
'@
$result2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cmd2
# 解析输出并构建结果
```
### 9.5 优化功能回填(更新)
| 序号 | 优化内容 | 状态 |
|------|----------|------|
| 1 | 修复ntp_servers双层引号问题 | ✅ 已完成 |
| 2 | 使用临时文件避免截断 | ✅ 已完成 |
| 3 | 移除Shell脚本中的exec 2>/dev/null | ✅ 已完成 |
| 4 | **改用直接SSH命令方式** | ✅ 已完成 |
| 5 | **使用单引号避免变量展开** | ✅ 已完成 |
| 6 | **简化NTP检测逻辑** | ✅ 已完成 |
---
*文档结束*
......@@ -263,4 +263,71 @@ JSON输出在 `"total_mb":` 处被截断,导致解析失败。这是因为plin
---
---
## 8. 问题三补充说明(最终解决方案)
### 8.1 问题分析
JSON输出仍然存在截断问题,因为通过SSH命令读取文件内容时,plink.exe的输出缓冲区限制仍然存在。
### 8.2 最终解决方案
**采用文件下载方式**:将检测结果输出到远程txt文件,然后使用pscp下载到本地,最后解析本地文件。这样可以完全避免plink.exe输出缓冲区限制。
### 8.3 技术要点
1. 执行Shell脚本时将输出重定向到远程文件
2. 使用 `Download-RemoteFile` 函数下载文件到本地
3. 使用本地文件解析,避免SSH输出截断
4. 使用纯文本格式输出,更稳定可靠
### 8.4 代码修改
**文件**:`check_server_health.ps1`
**位置**:`Test-ServerResources-Shell` 函数
**修改前**:使用JSON解析方式
```powershell
$arguments = "--format json --check all"
$result = Invoke-RemoteShellCheck -Server $Server -ScriptName "resource_check.sh" -Arguments $arguments
$data = ConvertFrom-ShellJson -JsonString $result
```
**修改后**:使用文件下载方式
```powershell
# 执行远程脚本,将输出保存到文件
$cmd = "cd $remotePath && chmod +x common.sh resource_check.sh && ./resource_check.sh --format text --check all > $remoteOutputFile 2>&1"
# 下载输出文件
$downloadResult = Download-RemoteFile -Server $Server -RemotePath $remoteOutputFile -LocalPath $localOutputFile -TimeoutSeconds 60
# 读取文件内容并解析
$content = Get-Content $localOutputFile -Raw -Encoding UTF8
$results = Parse-ResourceCheckText -Content $content
```
### 8.5 新增解析函数
**函数名**:`Parse-ResourceCheckText`
**功能**:解析Shell脚本的纯文本输出
**支持解析内容**
- CPU核心数、使用率、负载
- 内存总计、已用、使用率
- 磁盘挂载点、大小、使用量、使用率
- TCP连接数、TIME_WAIT数
### 8.6 优化功能回填(更新)
| 序号 | 优化内容 | 状态 |
|------|----------|------|
| 1 | 修正Write-Log日志级别错误 | ✅ 已完成 |
| 2 | 添加中文输出过滤 | ✅ 已完成 |
| 3 | 限制调试输出长度 | ✅ 已完成 |
| 4 | 使用临时文件避免截断 | ✅ 已完成 |
| 5 | 移除Shell脚本中的exec 2>/dev/null | ✅ 已完成 |
| 6 | **改用文件下载方式** | ✅ 已完成 |
| 7 | **新增Parse-ResourceCheckText函数** | ✅ 已完成 |
| 8 | **支持纯文本格式解析** | ✅ 已完成 |
---
*文档结束*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论