提交 65293bc1 authored 作者: 陈泽健's avatar 陈泽健

feat(middleware): 优化完善中间件检测脚本的容器自动检测功能,完善自检报告输出信息。输出服务自检脚本更新迭代记录。

- 实现容器名模糊匹配功能,支持多种命名模式(如 uredis、uredis2、redis 等)
- 修改脚本上传逻辑为强制覆盖模式,确保使用最新版本检测脚本
- 更新 Redis/MySQL/FastDFS 检测脚本的容器检测实现,移除硬编码容器名参数
- 修复 PowerShell 脚本变量未定义问题,从脚本输出中提取容器名和端口信息
- 补充中间件检测报告信息,显示容器名称、端口、版本等详细信息
- 改进 NTP 检测机制,实现多层级检测避免权限问题
- 修复 SSH 端口配置问题,确保非标准端口连接正常工作
上级 c1946f58
......@@ -6,7 +6,12 @@
"Bash(powershell -NoProfile -Command \"$content = Get-Content ''C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\远程更新程序\\\\remote_program_update.ps1'' -Raw -Encoding UTF8; $utf8BOM = New-Object System.Text.UTF8Encoding $true; [System.IO.File]::WriteAllText\\(''C:\\\\Users\\\\EDY\\\\Desktop\\\\test\\\\remote_program_update.ps1'', $content, $utf8BOM\\)\")",
"Bash(dir \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\")",
"Bash(git checkout -- \"AuxiliaryTool/ScriptTool/远程更新程序/program_update.sh\" \"AuxiliaryTool/ScriptTool/远程更新程序/remote_program_update.ps1\")",
"Bash(ls -la \"C:\\\\PycharmData\\\\ubains-module-test\\\\.claude\\\\skills\\\\prd\"\" 2>/dev/null || echo \"Not found \")"
"Bash(ls -la \"C:\\\\PycharmData\\\\ubains-module-test\\\\.claude\\\\skills\\\\prd\"\" 2>/dev/null || echo \"Not found \")",
"Bash(dir /s /b \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\*.log\")",
"Bash(cmd /c dir \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\issue_handler.sh\")",
"Bash(powershell -Command \"Copy-Item ''C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\问题处理\\\\issue_handler.sh'' ''C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\issue_handler.sh''\")",
"Bash(powershell -Command \"Remove-Item ''C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\issue_handler.sh'' -Force\")",
"Bash(powershell -Command \"$scriptDir = ''C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection''; $localScript = Join-Path $scriptDir ''..\\\\问题处理\\\\issue_handler.sh''; [System.IO.Path]::GetFullPath\\($localScript\\)\")"
]
}
}
#!/bin/bash
# verify_arm_fastdfs.sh
# 自动检测FastDFS容器
CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "utracker|tracker|ustorage|storage" | head -1)
if [[ -z "$CONTAINER" ]]; then
CONTAINER="utracker3" # 默认值
fi
echo "=== ARM服务器FastDFS验证 ==="
echo "部署模式: 单容器 (Tracker+Storage)"
echo "容器名称: utracker3"
echo "检测到容器: $CONTAINER"
echo "网络模式: Host网络"
echo ""
CONTAINER="utracker3"
CLIENT_CONF="/etc/fdfs/client.conf"
# 1. 检查服务状态
......
......@@ -8,14 +8,31 @@ GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
STORAGE_CONTAINER="ustorage"
TRACKER_CONTAINER="utracker"
# 自动检测Storage和Tracker容器
detect_containers() {
# 检测Storage容器
STORAGE_CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "ustorage|storage" | head -1)
if [[ -z "$STORAGE_CONTAINER" ]]; then
STORAGE_CONTAINER="ustorage" # 默认值
fi
# 检测Tracker容器
TRACKER_CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "utracker|tracker" | head -1)
if [[ -z "$TRACKER_CONTAINER" ]]; then
TRACKER_CONTAINER="utracker" # 默认值
fi
}
detect_containers
CLIENT_CONF="/etc/fdfs/client.conf"
echo "=========================================="
echo " FastDFS 最终功能验证"
echo "=========================================="
echo ""
echo "检测到容器: Storage=$STORAGE_CONTAINER, Tracker=$TRACKER_CONTAINER"
echo ""
# 1. 创建测试文件
TEST_FILE="/tmp/fastdfs_final_test_$(date +%s).txt"
......@@ -71,7 +88,9 @@ if [[ $? -eq 0 ]] && [[ -n "$UPLOAD_RESULT" ]] && [[ "$UPLOAD_RESULT" == group*
echo "6. 可选HTTP访问测试:"
# 获取访问URL
FILE_PATH=$(echo "$UPLOAD_RESULT" | sed 's|group1|/group1|')
echo " 访问URL: https://服务器IP$FILE_PATH"
# 获取服务器IP地址
SERVER_IP=$(hostname -I | awk '{print $1}')
echo " 访问URL: https://${SERVER_IP}${FILE_PATH}"
else
echo " ✗ 文件完整性验证失败"
......
#!/bin/bash
# MySQL容器健康检查报告 - 优化版
CONTAINER="${1:-umysql}"
# 容器匹配模式(如果通过参数传递则使用,否则使用默认值)
CONTAINER_PATTERN="${1:-umysql}"
PASSWORD='dNrprU&2S'
# 自动检测MySQL容器(模糊匹配)
DETECTED_CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "${CONTAINER_PATTERN}|mysql" | head -1)
if [[ -n "$DETECTED_CONTAINER" ]]; then
CONTAINER="$DETECTED_CONTAINER"
else
CONTAINER="$CONTAINER_PATTERN"
fi
echo "╔══════════════════════════════════════════════╗"
echo "║ MySQL容器全面健康检查报告 ║"
echo "╠══════════════════════════════════════════════╣"
......
......@@ -54,13 +54,13 @@ log_error() {
detect_redis_container() {
log_info "正在检测 Redis 容器..."
# 查找运行中的 Redis 容器
local containers=$(docker ps --filter "name=${CONTAINER_PATTERN}" --format "{{.Names}}" 2>/dev/null)
# 优先按匹配模式查找运行中的 Redis 容器(使用 grep -E 模糊匹配)
local containers=$(docker ps --format "{{.Names}}" | grep -E "${CONTAINER_PATTERN}|redis" 2>/dev/null)
if [ -z "$containers" ]; then
log_warning "未找到名称包含 '$CONTAINER_PATTERN' 的 Redis 容器"
log_warning "未找到名称包含 '${CONTAINER_PATTERN}' 或 'redis' 的容器"
# 尝试查找任何运行中的 Redis 容器
# 最后尝试:通过镜像名查找任何运行中的 Redis 容器
containers=$(docker ps --format "{{.Names}}" | xargs -I {} sh -c 'docker inspect {} --format "{{.Config.Image}}" 2>/dev/null | grep -qi redis && echo {}')
if [ -z "$containers" ]; then
......
......@@ -87,33 +87,45 @@ if (-not (Test-Path $LOG_DIR)) {
$ServerList = @{
"1" = @{
IP = "192.168.5.48"
Port = 22
User = "root"
Pass = "Ubains@123"
Desc = "标准版预定运维服务器"
};
"2" = @{
IP = "192.168.5.67"
Port = 22
User = "root"
Pass = "Ubains@123"
Desc = "阿曼项目预定服务器"
};
"3" = @{
IP = "192.168.5.47"
Port = 22
User = "root"
Pass = "Ubains@1234"
Desc = "标准版预定运维测试发布服务器"
};
"4" = @{
IP = "192.168.5.44"
Port = 22
User = "root"
Pass = "Ubains@123"
Desc = "新统一平台测试服务器"
};
"5" = @{
IP = "139.159.163.86"
Port = 22
User = "root"
Pass = "hzpassw0RD@KP"
Desc = "云端ARM架构服务器-兰州"
};
"6" = @{
IP = "nat.ubainsyun.com"
Port = 14445
User = "root"
Pass = "Ubains@1234"
Desc = "标准版预定运维测试发布服务器-映射端口14445"
}
}
......@@ -435,7 +447,7 @@ function Invoke-SSHCommand {
[string]$HostName,
[string]$User,
[string]$Pass,
[int]$Port = 22,
[int]$Port,
[string]$Command
)
......@@ -541,8 +553,11 @@ function Select-Server {
}
elseif ($ServerList.ContainsKey($serverKey)) {
$server = $ServerList[$serverKey].Clone()
# 如果预设列表中没有指定端口,使用默认22
if (-not $server.ContainsKey('Port') -or $server.Port -eq 0) {
$server.Port = 22
Write-Log -Level "INFO" -Message "已选择 $($server.Desc) ($($server.IP))"
}
Write-Log -Level "INFO" -Message "已选择 $($server.Desc) ($($server.IP)):$($server.Port)"
return $server
}
else {
......@@ -1644,6 +1659,109 @@ function Repair-ExternalMeetingService {
# ================================
# 服务器容器信息收集
# ================================
# ================================
# 获取单个容器详细信息 (供中间件检测使用)
# ================================
function Get-ContainerDetails {
param(
[hashtable]$Server,
[string]$ContainerName
)
try {
# 镜像
$imgCmd = "docker inspect -f '{{.Config.Image}}' $ContainerName 2>/dev/null"
$imgRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $imgCmd
$image = (($imgRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
if ([string]::IsNullOrWhiteSpace($image)) { $image = "-" }
# 状态
$stateCmd = "docker inspect -f '{{.State.Status}}' $ContainerName 2>/dev/null"
$stateRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $stateCmd
$state = (($stateRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
if ([string]::IsNullOrWhiteSpace($state)) { $state = "-" }
# 重启策略和次数
$restartCmd = "docker inspect -f '{{.HostConfig.RestartPolicy.Name}}|{{.RestartCount}}' $ContainerName 2>/dev/null"
$restartRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $restartCmd
$restartLine = (($restartRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
$restartPolicy = "-"; $restartCount = "0"
if ($restartLine -and $restartLine -match '\|') {
$parts = $restartLine.Trim() -split '\|', 2
if ($parts.Count -ge 1) { $restartPolicy = $parts[0] }
if ($parts.Count -ge 2) { $restartCount = $parts[1] }
}
# IP地址
$ipCmd = "docker inspect -f '{{range `$k,`$v := .NetworkSettings.Networks}}{{if `$v.IPAddress}}{{`$v.IPAddress}} {{end}}{{end}}' $ContainerName 2>/dev/null"
$ipRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $ipCmd
$ip = (($ipRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
$ip = ($ip -replace "`r","").Trim()
if ([string]::IsNullOrWhiteSpace($ip)) { $ip = "-" }
# 端口
$portCmd = "docker port $ContainerName 2>/dev/null | tr '\n' ',' | sed 's/,$//'"
$portRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $portCmd
$ports = (($portRes.Output -split "`n" | Select-Object -First 1) -as [string])
if ([string]::IsNullOrWhiteSpace($ports)) { $ports = "-" }
# 网络
$netCmd = "docker inspect -f '{{range `$k,`$v := .NetworkSettings.Networks}}{{`$k}} {{end}}' $ContainerName 2>/dev/null"
$netRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $netCmd
$networks = (($netRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
$networks = ($networks -replace "`r","").Trim()
if ([string]::IsNullOrWhiteSpace($networks)) { $networks = "-" }
# 挂载
$mntCmd = "docker inspect -f '{{range .Mounts}}{{if .Source}}{{.Source}}{{else}}-{{end}}:{{.Destination}}({{.Mode}}); {{end}}' $ContainerName 2>/dev/null"
$mntRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $mntCmd
$mounts = (($mntRes.Output -split "`n" | Select-Object -First 1) -as [string])
$mounts = ($mounts -replace "`r","").Trim()
if ([string]::IsNullOrWhiteSpace($mounts)) { $mounts = "-" }
# 大小
$sizeCmd = "docker ps -a --size --filter ""name=^/$ContainerName$"" --format ""{{.Size}}"" 2>/dev/null | head -n 1"
$sizeRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $sizeCmd
$sizeLine = (($sizeRes.Output -split "`n" | Select-Object -First 1) -as [string])
$sizeLine = ($sizeLine -replace "`r","").Trim()
$sizeRw = "-"; $sizeRoot = "-"
if (-not [string]::IsNullOrWhiteSpace($sizeLine)) {
$sizeRw = ($sizeLine -split '\s+\(',2)[0]
if ($sizeLine -match '\(virtual\s+([^)]+)\)') { $sizeRoot = $Matches[1] }
}
# 资源使用情况
$statsCmd = "docker stats --no-stream --format ""{{.CPUPerc}} {{.MemUsage}}"" $ContainerName 2>/dev/null"
$statsRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $statsCmd
$statsLine = (($statsRes.Output -split "`n" | Where-Object { $_ -match '\S' } | Select-Object -First 1) -as [string])
$cpuUsage = "-"; $memUsage = "-"
if ($statsLine -match '\S+') {
$statsParts = $statsLine -split '\s+', 2
if ($statsParts.Count -ge 1) { $cpuUsage = $statsParts[0] }
if ($statsParts.Count -ge 2) { $memUsage = $statsParts[1] }
}
return [ordered]@{
Name = $ContainerName
Image = $image
State = $state
RestartPolicy = $restartPolicy
RestartCount = $restartCount
IP = $ip
Ports = $ports
Networks = $networks
Mounts = $mounts
SizeRw = $sizeRw
SizeRoot = $sizeRoot
CpuUsage = $cpuUsage
MemUsage = $memUsage
}
} catch {
Write-Log -Level "ERROR" -Message "获取容器详细信息失败: $($_.Exception.Message)"
return $null
}
}
function Test-ContainerInformation {
param(
[hashtable]$Server,
......@@ -2039,11 +2157,43 @@ function Test-MQTTConnection {
$actualContainer = (@($containerCheck.Output)[0].ToString().Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[MQTT] 检测到容器: $actualContainer"
# 收集容器详细信息
$containerDetails = Get-ContainerDetails -Server $Server -ContainerName $actualContainer
# 2. 多层级降级检测
$detectionMethod = $null
$isConnected = $false
$detailMsg = ""
# 构建详细检测信息
$mqttInfo = @()
if ($containerDetails) {
$mqttInfo += "容器: $($containerDetails.Name)"
$mqttInfo += "镜像: $($containerDetails.Image)"
$mqttInfo += "状态: $($containerDetails.State)"
if ($containerDetails.Ports -ne "-") {
$portList = $containerDetails.Ports -split ','
foreach ($port in $portList) {
$mqttInfo += "端口: $port"
}
}
if ($containerDetails.Networks -ne "-") {
$mqttInfo += "网络: $($containerDetails.Networks)"
}
if ($containerDetails.Mounts -ne "-") {
$mountList = $containerDetails.Mounts -split ';' | Select-Object -First 3
foreach ($mount in $mountList) {
if ($mount.Trim()) { $mqttInfo += "挂载: $mount" }
}
}
if ($containerDetails.CpuUsage -ne "-") {
$mqttInfo += "CPU: $($containerDetails.CpuUsage)"
}
if ($containerDetails.MemUsage -ne "-") {
$mqttInfo += "内存: $($containerDetails.MemUsage)"
}
}
# 方案A: 尝试 EMQX Dashboard API
try {
$apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://localhost:$dashboardPort/api/v4/status 2>/dev/null || echo 'API_FAIL'"
......@@ -2100,13 +2250,41 @@ function Test-MQTTConnection {
}
}
# 添加MQTT服务检测结果(包含容器详细信息)
$mqttDetailInfo = "$detailMsg | 检测方式: $detectionMethod | 容器: $actualContainer | Dashboard端口: $dashboardPort"
if ($containerDetails) {
$mqttDetailInfo += " | 镜像: $($containerDetails.Image) | 状态: $($containerDetails.State)"
if ($containerDetails.CpuUsage -ne "-") { $mqttDetailInfo += " | CPU: $($containerDetails.CpuUsage)" }
if ($containerDetails.MemUsage -ne "-") { $mqttDetailInfo += " | 内存: $($containerDetails.MemUsage)" }
}
$results += @{
Check = "MQTT服务检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = "$detailMsg | 检测方式: $detectionMethod"
Details = $mqttDetailInfo
Success = $isConnected
}
# 如果有容器详细信息,添加为单独的检测结果项
if ($containerDetails) {
$containerInfo = "容器名称: $($containerDetails.Name) | 镜像: $($containerDetails.Image) | 状态: $($containerDetails.State) | 重启: $($containerDetails.RestartPolicy)/$($containerDetails.RestartCount)"
if ($containerDetails.IP -ne "-") { $containerInfo += " | IP: $($containerDetails.IP)" }
if ($containerDetails.Networks -ne "-") { $containerInfo += " | 网络: $($containerDetails.Networks)" }
if ($containerDetails.Ports -ne "-") {
$portList = $containerDetails.Ports -split ','
$containerInfo += " | 端口: $($portList[0])"
if ($portList.Count -gt 1) { $containerInfo += " 等 $($portList.Count) 个端口" }
}
if ($containerDetails.SizeRw -ne "-") { $containerInfo += " | 磁盘: $($containerDetails.SizeRw)" }
$results += @{
Check = "MQTT容器详细信息"
Status = "完成"
Details = $containerInfo
Success = $true
}
}
# 3. MQTT标准版主题订阅检测(PRD 2.1要求)
# 仅在服务连接成功时执行主题订阅检测
if ($isConnected) {
......@@ -2218,7 +2396,7 @@ function Test-MQTTConnection {
$results += @{
Check = "MQTT消息收发检测"
Status = "正常"
Details = "消息发送与接收测试均成功"
Details = "消息发送与接收测试均成功 | 测试主题: $testTopic | 测试消息: $testMessage"
Success = $true
}
Write-Log -Level "SUCCESS" -Message "[MQTT] ========== MQTT消息收发测试完成 =========="
......@@ -2226,7 +2404,7 @@ function Test-MQTTConnection {
$results += @{
Check = "MQTT消息收发检测"
Status = "正常"
Details = "消息发送成功"
Details = "消息发送成功 | 测试主题: $testTopic | 测试消息: $testMessage"
Success = $true
}
Write-Log -Level "SUCCESS" -Message "[MQTT] ========== MQTT消息收发测试完成 =========="
......@@ -2234,7 +2412,7 @@ function Test-MQTTConnection {
$results += @{
Check = "MQTT消息收发检测"
Status = "异常"
Details = "消息发送失败"
Details = "消息发送失败 | 测试主题: $testTopic"
Success = $false
}
Write-Log -Level "ERROR" -Message "[MQTT] ========== MQTT消息收发测试: 失败 =========="
......@@ -2249,7 +2427,8 @@ function Test-MQTTConnection {
}
}
# 添加主题订阅检测结果
# 添加主题订阅检测结果(摘要)
$topicListStr = $topicList -join ', '
$results += @{
Check = "MQTT主题订阅检测"
Status = "正常"
......@@ -2257,6 +2436,29 @@ function Test-MQTTConnection {
Success = $true
}
# 添加标准版主题列表详情
$topicDescriptions = @{
"/androidPanel/" = "安卓面板主题"
"/iot/v1/conference/service/request/" = "会议服务请求主题"
"message/paperLessService/callService" = "无纸化服务调用主题"
"message/paperLessAuthor/onthewall" = "无纸化上墙主题"
"/meeting/message/" = "会议消息主题"
"/iot/v1/device/event/request/" = "设备事件请求主题"
"/iot/v1/device/service/request/" = "设备服务请求主题"
}
$topicCounter = 1
foreach ($topic in $topicList) {
$topicDesc = if ($topicDescriptions[$topic]) { $topicDescriptions[$topic] } else { "自定义主题" }
$results += @{
Check = "MQTT主题 $topicCounter"
Status = "正常"
Details = "主题: $topic | 描述: $topicDesc | 订阅通道正常"
Success = $true
}
$topicCounter++
}
Write-Log -Level "SUCCESS" -Message "[MQTT] 标准版主题订阅检测完成: 检测 $topicCount 个主题"
} else {
......@@ -2290,21 +2492,14 @@ function Test-RedisConnection {
# 在远程服务器上,check_redis.sh与check_server_health.sh在同一目录
$scriptPath = 'check_redis.sh'
# 检查check_redis.sh脚本是否存在(在脚本目录下)
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/check_redis.sh && echo `${script_dir}/check_redis.sh EXISTS || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
if ($checkOutput -notmatch 'EXISTS') {
Write-Log -Level "WARN" -Message "[Redis] check_redis.sh脚本不存在,尝试自动上传"
# 强制上传check_redis.sh脚本(覆盖远程旧版本)
Write-Log -Level "INFO" -Message "[Redis] 开始上传/更新check_redis.sh脚本..."
# 获取远程脚本目录(使用更可靠的方式)
# 通过which查找check_server_health.sh的位置,然后获取其目录
# 获取远程脚本目录
$getRemoteDirCmd = "which check_server_health.sh 2>/dev/null | xargs dirname 2>/dev/null || echo ''"
$remoteDirResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd
$remoteScriptDir = ($remoteDirResult.Output -join "").Trim()
# 如果which找不到,尝试使用BASH_SOURCE方式
if (-not $remoteScriptDir -or $remoteScriptDir -eq '') {
$getRemoteDirCmd2 = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" 2>/dev/null && pwd); echo `$script_dir"
$remoteDirResult2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd2
......@@ -2314,39 +2509,37 @@ function Test-RedisConnection {
Write-Log -Level "INFO" -Message "[Redis] 检测到远程脚本目录: $remoteScriptDir"
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
# 本地脚本路径
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) "check_redis.sh"
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[Redis] 本地脚本: $localScriptPath"
Write-Log -Level "INFO" -Message "[Redis] 上传到远程目录: $remoteScriptDir"
Write-Log -Level "INFO" -Message "[Redis] 本地脚本: $localScriptPath,上传到远程 (强制覆盖)"
# 上传脚本
$uploadOk = Copy-File-To-Remote -LocalPath $localScriptPath -Server $Server -RemoteDir $remoteScriptDir
if ($uploadOk) {
Write-Log -Level "SUCCESS" -Message "[Redis] check_redis.sh上传成功"
$remoteScriptPath = "$remoteScriptDir/check_redis.sh"
# 修复换行并赋权
$fixCmd = "cd '$remoteScriptDir' && dos2unix check_redis.sh 2>/dev/null || true && chmod +x check_redis.sh"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = $remoteScriptPath
} else {
Write-Log -Level "WARN" -Message "[Redis] 脚本上传失败,使用降级检测"
# 降级检测逻辑
$actualScriptPath = "$remoteScriptDir/check_redis.sh"
}
} else {
Write-Log -Level "WARN" -Message "[Redis] 本地脚本不存在: $localScriptPath,使用降级检测"
}
} else {
Write-Log -Level "WARN" -Message "[Redis] 无法获取远程脚本目录,使用降级检测"
}
# 如果上传失败或无法获取远程目录,使用降级检测
# 如果上传失败,尝试使用远程已有脚本或降级检测
if (-not $actualScriptPath) {
Write-Log -Level "WARN" -Message "[Redis] 上传失败,尝试使用远程已有脚本"
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/check_redis.sh && echo `$script_dir/check_redis.sh || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
if ($checkOutput -notmatch 'NOT_FOUND') {
$actualScriptPath = $checkOutput.Trim()
Write-Log -Level "INFO" -Message "[Redis] 使用远程已有脚本: $actualScriptPath"
}
}
# 如果仍无脚本可用,使用降级检测
if (-not $actualScriptPath) {
# 降级:执行简单的redis-cli ping测试
Write-Log -Level "WARN" -Message "[Redis] 无可用脚本,使用降级检测"
$containerName = $MiddlewareConfig.Redis.ContainerName
$redisPort = $MiddlewareConfig.Redis.Port
$redisPassword = $MiddlewareConfig.Redis.Password
......@@ -2377,18 +2570,12 @@ function Test-RedisConnection {
}
return $results
}
} else {
# 提取脚本完整路径
$actualScriptPath = ($checkOutput -replace ' EXISTS','').Trim()
}
# 使用check_redis.sh脚本进行完整检测(PRD 2.2要求)
Write-Log -Level "INFO" -Message "[Redis] 使用check_redis.sh脚本进行完整检测: $actualScriptPath"
# 直接传递容器名作为参数(脚本内部使用硬编码密码)
# 使用单引号包裹路径,避免特殊字符问题
$containerName = $MiddlewareConfig.Redis.ContainerName
$redisCmd = "bash '$actualScriptPath' '$containerName' 2>&1"
# 不传递容器名参数,让脚本自己自动检测容器(支持模糊匹配)
$redisCmd = "bash '$actualScriptPath' 2>&1"
$redisResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $redisCmd
$outputText = $redisResult.Output -join "`n"
......@@ -2398,6 +2585,24 @@ function Test-RedisConnection {
$isConnected = $redisResult.ExitCode -eq 0
$detailMsg = ""
# 提取Redis版本信息
$redisVersion = ""
if ($outputText -match 'redis_version\s*:\s*([\d.]+)') {
$redisVersion = $matches[1]
}
# 提取内存使用信息
$redisMemory = ""
if ($outputText -match 'used_memory_human\s*:\s*(\S+)') {
$redisMemory = $matches[1]
}
# 提取连接数信息
$redisClients = ""
if ($outputText -match 'connected_clients\s*:\s*(\d+)') {
$redisClients = $matches[1]
}
if ($isConnected) {
# 提取关键信息
if ($outputText -match '所有核心测试通过') {
......@@ -2417,13 +2622,100 @@ function Test-RedisConnection {
Write-Log -Level "ERROR" -Message "[Redis] check_redis.sh检测失败"
}
# 从输出中提取容器名和端口
$detectedContainer = "uredis"
$detectedPort = "6379"
if ($outputText -match '容器名:\s*(\S+)') {
$detectedContainer = $matches[1]
}
if ($outputText -match '端口:\s*(\d+)') {
$detectedPort = $matches[1]
}
# 提取更多Redis详细信息
$redisKeys = ""
if ($outputText -match '数据库键数:\s*(\d+)') {
$redisKeys = $matches[1]
}
$redisOps = ""
if ($outputText -match '每秒操作数:\s*(\d+)') {
$redisOps = $matches[1]
}
$redisHitRate = ""
if ($outputText -match '缓存命中率:\s*([\d.]+)%') {
$redisHitRate = $matches[1]
}
$redisUptime = ""
if ($outputText -match '运行天数:\s*(\d+)') {
$redisUptime = $matches[1]
}
# 构建详细信息
$detailInfo = $detailMsg
if ($isConnected) {
$detailInfo += " | 容器: $detectedContainer | 端口: $detectedPort"
if ($redisVersion) { $detailInfo += " | 版本: $redisVersion" }
if ($redisMemory) { $detailInfo += " | 内存: $redisMemory" }
if ($redisClients) { $detailInfo += " | 连接数: $redisClients" }
if ($redisKeys) { $detailInfo += " | 键数: $redisKeys" }
if ($redisOps -and $redisOps -ne "0") { $detailInfo += " | QPS: $redisOps" }
if ($redisHitRate) { $detailInfo += " | 命中率: $redisHitRate%" }
if ($redisUptime) { $detailInfo += " | 运行: $redisUptime 天" }
} else {
$detailInfo += " | 容器: $detectedContainer | 端口: $detectedPort"
# 如果是密码解析错误,添加错误详情
if ($outputText -match '2S') {
$detailInfo += " | 错误: 密码解析失败(特殊字符问题)"
}
}
$results += @{
Check = "Redis连接检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = $detailMsg
Details = $detailInfo
Success = $isConnected
}
# 如果检测成功,添加Redis详细信息作为单独的结果项
if ($isConnected) {
# 收集容器详细信息
$containerDetails = Get-ContainerDetails -Server $Server -ContainerName $detectedContainer
if ($containerDetails) {
$containerInfo = "容器名称: $($containerDetails.Name) | 镜像: $($containerDetails.Image) | 状态: $($containerDetails.State)"
if ($containerDetails.Networks -ne "-") { $containerInfo += " | 网络: $($containerDetails.Networks)" }
if ($containerDetails.CpuUsage -ne "-") { $containerInfo += " | CPU: $($containerDetails.CpuUsage)" }
if ($containerDetails.MemUsage -ne "-") { $containerInfo += " | 内存: $($containerDetails.MemUsage)" }
if ($containerDetails.SizeRw -ne "-") { $containerInfo += " | 磁盘: $($containerDetails.SizeRw)" }
$results += @{
Check = "Redis容器详细信息"
Status = "完成"
Details = $containerInfo
Success = $true
}
}
# 添加Redis功能测试结果
$testResults = @()
if ($outputText -match '✓ 连接测试通过') { $testResults += "连接测试: 通过" }
if ($outputText -match '✓ 读写功能测试通过') { $testResults += "读写测试: 通过" }
if ($outputText -match '✓ 删除功能测试通过') { $testResults += "删除测试: 通过" }
if ($outputText -match '✓ Redis 信息获取成功') { $testResults += "信息收集: 通过" }
if ($testResults.Count -gt 0) {
$results += @{
Check = "Redis功能测试结果"
Status = "完成"
Details = ($testResults -join ' | ')
Success = $true
}
}
}
return $results
}
......@@ -2436,24 +2728,14 @@ function Test-MySQLConnection {
$results = @()
$actualScriptPath = $null
# 检查check_mysql.sh脚本是否存在(在脚本目录下)
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/check_mysql.sh && echo `${script_dir}/check_mysql.sh EXISTS || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
# 强制上传check_mysql.sh脚本(覆盖远程旧版本)
Write-Log -Level "INFO" -Message "[MySQL] 开始上传/更新check_mysql.sh脚本..."
if ($checkOutput -match 'EXISTS') {
# 提取脚本完整路径
$actualScriptPath = ($checkOutput -replace ' EXISTS','').Trim()
} else {
Write-Log -Level "WARN" -Message "[MySQL] check_mysql.sh脚本不存在,尝试自动上传"
# 获取远程脚本目录(使用更可靠的方式)
# 通过which查找check_server_health.sh的位置,然后获取其目录
# 获取远程脚本目录
$getRemoteDirCmd = "which check_server_health.sh 2>/dev/null | xargs dirname 2>/dev/null || echo ''"
$remoteDirResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd
$remoteScriptDir = ($remoteDirResult.Output -join "").Trim()
# 如果which找不到,尝试使用BASH_SOURCE方式
if (-not $remoteScriptDir -or $remoteScriptDir -eq '') {
$getRemoteDirCmd2 = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" 2>/dev/null && pwd); echo `$script_dir"
$remoteDirResult2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd2
......@@ -2463,37 +2745,40 @@ function Test-MySQLConnection {
Write-Log -Level "INFO" -Message "[MySQL] 检测到远程脚本目录: $remoteScriptDir"
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
# 本地脚本路径
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) "check_mysql.sh"
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[MySQL] 本地脚本: $localScriptPath"
Write-Log -Level "INFO" -Message "[MySQL] 上传到远程目录: $remoteScriptDir"
Write-Log -Level "INFO" -Message "[MySQL] 本地脚本: $localScriptPath,上传到远程 (强制覆盖)"
# 上传脚本
$uploadOk = Copy-File-To-Remote -LocalPath $localScriptPath -Server $Server -RemoteDir $remoteScriptDir
if ($uploadOk) {
Write-Log -Level "SUCCESS" -Message "[MySQL] check_mysql.sh上传成功"
$remoteScriptPath = "$remoteScriptDir/check_mysql.sh"
# 修复换行并赋权
$fixCmd = "cd '$remoteScriptDir' && dos2unix check_mysql.sh 2>/dev/null || true && chmod +x check_mysql.sh"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = $remoteScriptPath
$actualScriptPath = "$remoteScriptDir/check_mysql.sh"
}
}
}
# 如果上传失败,尝试使用远程已有脚本
if (-not $actualScriptPath) {
Write-Log -Level "WARN" -Message "[MySQL] 上传失败,尝试使用远程已有脚本"
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/check_mysql.sh && echo `$script_dir/check_mysql.sh || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
if ($checkOutput -notmatch 'NOT_FOUND') {
$actualScriptPath = $checkOutput.Trim()
Write-Log -Level "INFO" -Message "[MySQL] 使用远程已有脚本: $actualScriptPath"
}
}
# 如果有check_mysql.sh脚本,使用脚本进行完整检测
if ($actualScriptPath) {
Write-Log -Level "INFO" -Message "[MySQL] 使用check_mysql.sh脚本进行完整检测: $actualScriptPath"
# 设置环境变量并执行检测脚本
$containerName = $MiddlewareConfig.MySQL.ContainerName
$mysqlCmd = "bash `"$actualScriptPath`" $containerName 2>&1"
# 不传递容器名参数,让脚本自己自动检测容器(支持模糊匹配)
$mysqlCmd = "bash `"$actualScriptPath`" 2>&1"
$mysqlResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $mysqlCmd
$outputText = $mysqlResult.Output -join "`n"
......@@ -2501,7 +2786,68 @@ function Test-MySQLConnection {
# 解析检测结果
$isConnected = $mysqlResult.ExitCode -eq 0
# 提取MySQL版本信息
$mysqlVersion = ""
if ($outputText -match '(\d+\.\d+\.\d+[-a-zA-Z0-9]*)') {
$mysqlVersion = $matches[1]
}
# 从输出中提取容器名和端口
$detectedContainer = "umysql"
$detectedPort = "3306"
if ($outputText -match '容器:\s*(\S+)') {
$detectedContainer = $matches[1]
}
if ($outputText -match '端口:\s*(\d+)') {
$detectedPort = $matches[1]
}
# 提取更多MySQL详细信息
$mysqlConnections = ""
if ($outputText -match '当前连接:\s*(\d+)') {
$mysqlConnections = $matches[1]
}
$mysqlQuestions = ""
if ($outputText -match '总查询数:\s*([\d,]+)') {
$mysqlQuestions = $matches[1] -replace ',', ''
}
$mysqlQPS = ""
if ($outputText -match '平均QPS:\s*(\d+)') {
$mysqlQPS = $matches[1]
}
$mysqlDbCount = ""
if ($outputText -match '用户数据库:\s*(\d+)') {
$mysqlDbCount = $matches[1]
}
$mysqlTableCount = ""
if ($outputText -match '数据表总数:\s*(\d+)') {
$mysqlTableCount = $matches[1]
}
$mysqlMaxConn = ""
if ($outputText -match '最大连接数:\s*(\d+)') {
$mysqlMaxConn = $matches[1]
}
$mysqlBufferSize = ""
if ($outputText -match '缓冲池大小:\s*(\d+)MB') {
$mysqlBufferSize = $matches[1]
}
$mysqlCharset = ""
if ($outputText -match '字符集:\s*(\S+)') {
$mysqlCharset = $matches[1]
}
# 构建详细信息
$detailMsg = if ($isConnected) { "MySQL完整检测通过" } else { "MySQL检测失败" }
$detailMsg += " | 容器: $detectedContainer | 端口: $detectedPort"
if ($mysqlVersion) { $detailMsg += " | 版本: $mysqlVersion" }
if ($isConnected) {
Write-Log -Level "SUCCESS" -Message "[MySQL] check_mysql.sh检测成功"
......@@ -2515,6 +2861,47 @@ function Test-MySQLConnection {
Details = $detailMsg
Success = $isConnected
}
# 如果检测成功,添加MySQL详细信息作为单独的结果项
if ($isConnected) {
# 收集容器详细信息
$containerDetails = Get-ContainerDetails -Server $Server -ContainerName $detectedContainer
if ($containerDetails) {
$containerInfo = "容器名称: $($containerDetails.Name) | 镜像: $($containerDetails.Image) | 状态: $($containerDetails.State)"
if ($containerDetails.Networks -ne "-") { $containerInfo += " | 网络: $($containerDetails.Networks)" }
if ($containerDetails.CpuUsage -ne "-") { $containerInfo += " | CPU: $($containerDetails.CpuUsage)" }
if ($containerDetails.MemUsage -ne "-") { $containerInfo += " | 内存: $($containerDetails.MemUsage)" }
if ($containerDetails.SizeRw -ne "-") { $containerInfo += " | 磁盘: $($containerDetails.SizeRw)" }
$results += @{
Check = "MySQL容器详细信息"
Status = "完成"
Details = $containerInfo
Success = $true
}
}
# 添加MySQL性能指标
$perfInfo = ""
if ($mysqlConnections) { $perfInfo += "当前连接: $mysqlConnections" }
if ($mysqlMaxConn) { $perfInfo += " | 最大连接: $mysqlMaxConn" }
if ($mysqlQPS) { $perfInfo += " | QPS: $mysqlQPS" }
if ($mysqlDbCount) { $perfInfo += " | 数据库: $mysqlDbCount 个" }
if ($mysqlTableCount) { $perfInfo += " | 数据表: $mysqlTableCount 个" }
if ($mysqlBufferSize) { $perfInfo += " | 缓冲池: ${mysqlBufferSize}MB" }
if ($mysqlCharset) { $perfInfo += " | 字符集: $mysqlCharset" }
if ($perfInfo) {
$results += @{
Check = "MySQL性能指标"
Status = "完成"
Details = $perfInfo
Success = $true
}
}
}
return $results
}
......@@ -2608,23 +2995,14 @@ function Test-FastDFSConnection {
Write-Log -Level "INFO" -Message "[FastDFS] 检测到x86架构,使用 $fdfsScriptName"
}
# 检查check_fdfs脚本是否存在(在脚本目录下)
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/$fdfsScriptName && echo `${script_dir}/$fdfsScriptName EXISTS || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
if ($checkOutput -match 'EXISTS') {
# 提取脚本完整路径
$actualScriptPath = ($checkOutput -replace ' EXISTS','').Trim()
} else {
Write-Log -Level "WARN" -Message "[FastDFS] $fdfsScriptName 脚本不存在,尝试自动上传"
# 强制上传fdfs脚本(覆盖远程旧版本)
Write-Log -Level "INFO" -Message "[FastDFS] 开始上传/更新 $fdfsScriptName 脚本..."
# 获取远程脚本目录(使用更可靠的方式)
# 获取远程脚本目录
$getRemoteDirCmd = "which check_server_health.sh 2>/dev/null | xargs dirname 2>/dev/null || echo ''"
$remoteDirResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd
$remoteScriptDir = ($remoteDirResult.Output -join "").Trim()
# 如果which找不到,尝试使用BASH_SOURCE方式
if (-not $remoteScriptDir -or $remoteScriptDir -eq '') {
$getRemoteDirCmd2 = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" 2>/dev/null && pwd); echo `$script_dir"
$remoteDirResult2 = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $getRemoteDirCmd2
......@@ -2634,27 +3012,31 @@ function Test-FastDFSConnection {
Write-Log -Level "INFO" -Message "[FastDFS] 检测到远程脚本目录: $remoteScriptDir"
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
# 本地脚本路径(根据架构选择)
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) $fdfsScriptName
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[FastDFS] 本地脚本: $localScriptPath"
Write-Log -Level "INFO" -Message "[FastDFS] 上传到远程目录: $remoteScriptDir"
Write-Log -Level "INFO" -Message "[FastDFS] 本地脚本: $localScriptPath,上传到远程 (强制覆盖)"
# 上传脚本
$uploadOk = Copy-File-To-Remote -LocalPath $localScriptPath -Server $Server -RemoteDir $remoteScriptDir
if ($uploadOk) {
Write-Log -Level "SUCCESS" -Message "[FastDFS] $fdfsScriptName 上传成功"
$remoteScriptPath = "$remoteScriptDir/$fdfsScriptName"
# 修复换行并赋权
$fixCmd = "cd '$remoteScriptDir' && dos2unix $fdfsScriptName 2>/dev/null || true && chmod +x $fdfsScriptName"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = $remoteScriptPath
$actualScriptPath = "$remoteScriptDir/$fdfsScriptName"
}
}
}
# 如果上传失败,尝试使用远程已有脚本
if (-not $actualScriptPath) {
Write-Log -Level "WARN" -Message "[FastDFS] 上传失败,尝试使用远程已有脚本"
$checkScriptCmd = "script_dir=`$(cd `"`${BASH_SOURCE[0]%/*}`" && pwd); test -f `${script_dir}/$fdfsScriptName && echo `$script_dir/$fdfsScriptName || echo NOT_FOUND"
$scriptCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkScriptCmd
$checkOutput = $scriptCheck.Output -join ""
if ($checkOutput -notmatch 'NOT_FOUND') {
$actualScriptPath = $checkOutput.Trim()
Write-Log -Level "INFO" -Message "[FastDFS] 使用远程已有脚本: $actualScriptPath"
}
}
......@@ -2673,6 +3055,39 @@ function Test-FastDFSConnection {
$isConnected = $fdfsResult.ExitCode -eq 0
$detailMsg = ""
# 确定架构类型
$archType = if ($remoteArch -match 'arm|aarch64') { "ARM" } else { "x86" }
# 提取上传文件大小信息
$uploadSize = ""
if ($outputText -match '上传文件大小\s*:\s*(\S+)') {
$uploadSize = $matches[1]
}
# 提取文件ID信息
$fileId = ""
if ($outputText -match '文件ID:\s*(group\d+/M\d+/.+)') {
$fileId = $matches[1]
}
# 提取访问URL信息
$accessUrl = ""
if ($outputText -match '访问URL:\s*(https?://\S+)') {
$accessUrl = $matches[1]
}
# 提取Storage容器名
$storageContainer = $storageContainerName
if ($outputText -match '检测到Storage容器:\s*(\S+)') {
$storageContainer = $matches[1]
}
# 提取Tracker容器名
$trackerContainer = "utracker"
if ($outputText -match '检测到Tracker容器:\s*(\S+)') {
$trackerContainer = $matches[1]
}
if ($isConnected) {
# 检查是否所有核心功能验证通过
if ($outputText -match '所有核心功能验证通过' -or $outputText -match '所有核心测试通过') {
......@@ -2692,12 +3107,90 @@ function Test-FastDFSConnection {
Write-Log -Level "ERROR" -Message "[FastDFS] $fdfsScriptName 检测失败"
}
# 构建详细信息
$detailInfo = "$detailMsg | Storage: $storageContainer | 架构: $archType"
if ($uploadSize) { $detailInfo += " | 测试文件: $uploadSize" }
if ($fileId) { $detailInfo += " | 文件ID: $fileId" }
if ($accessUrl) { $detailInfo += " | URL: $accessUrl" }
$results += @{
Check = "FastDFS连接检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = $detailMsg
Details = $detailInfo
Success = $isConnected
}
# 如果检测成功,添加FastDFS详细信息作为单独的结果项
if ($isConnected) {
# 收集Storage容器详细信息
$storageDetails = Get-ContainerDetails -Server $Server -ContainerName $storageContainer
if ($storageDetails) {
$storageInfo = "Storage容器: $($storageDetails.Name) | 镜像: $($storageDetails.Image) | 状态: $($storageDetails.State)"
if ($storageDetails.Networks -ne "-") { $storageInfo += " | 网络: $($storageDetails.Networks)" }
if ($storageDetails.CpuUsage -ne "-") { $storageInfo += " | CPU: $($storageDetails.CpuUsage)" }
if ($storageDetails.MemUsage -ne "-") { $storageInfo += " | 内存: $($storageDetails.MemUsage)" }
if ($storageDetails.SizeRw -ne "-") { $storageInfo += " | 磁盘: $($storageDetails.SizeRw)" }
$results += @{
Check = "FastDFS Storage容器信息"
Status = "完成"
Details = $storageInfo
Success = $true
}
}
# 添加Tracker容器信息
$trackerDetails = Get-ContainerDetails -Server $Server -ContainerName $trackerContainer
if ($trackerDetails) {
$trackerInfo = "Tracker容器: $($trackerDetails.Name) | 镜像: $($trackerDetails.Image) | 状态: $($trackerDetails.State)"
if ($trackerDetails.Networks -ne "-") { $trackerInfo += " | 网络: $($trackerDetails.Networks)" }
if ($trackerDetails.CpuUsage -ne "-") { $trackerInfo += " | CPU: $($trackerDetails.CpuUsage)" }
if ($trackerDetails.MemUsage -ne "-") { $trackerInfo += " | 内存: $($trackerDetails.MemUsage)" }
if ($trackerDetails.SizeRw -ne "-") { $trackerInfo += " | 磁盘: $($trackerDetails.SizeRw)" }
$results += @{
Check = "FastDFS Tracker容器信息"
Status = "完成"
Details = $trackerInfo
Success = $true
}
}
# 添加功能测试结果详情
$testResults = @()
if ($outputText -match '✓ 文件上传') { $testResults += "文件上传: 通过" }
if ($outputText -match '✓ 文件下载') { $testResults += "文件下载: 通过" }
if ($outputText -match '✓ 文件完整性验证通过') { $testResults += "完整性验证: 通过" }
if ($outputText -match '✓ Tracker服务') { $testResults += "Tracker服务: 正常" }
if ($outputText -match '✓ Storage服务') { $testResults += "Storage服务: 正常" }
if ($outputText -match '✓ 网络通信') { $testResults += "网络通信: 正常" }
if ($testResults.Count -gt 0) {
$results += @{
Check = "FastDFS功能测试结果"
Status = "完成"
Details = ($testResults -join ' | ')
Success = $true
}
}
# 添加文件访问信息
if ($fileId -or $accessUrl) {
$fileInfo = ""
if ($fileId) { $fileInfo += "文件ID: $fileId" }
if ($accessUrl) { $fileInfo += " | 访问URL: $accessUrl" }
$results += @{
Check = "FastDFS文件访问信息"
Status = "完成"
Details = $fileInfo
Success = $true
}
}
}
return $results
}
......@@ -3744,7 +4237,28 @@ function Show-HealthReport {
if ($MiddlewareResults -and $MiddlewareResults.Count -gt 0) {
Write-Host "【中间件连接检测】" -ForegroundColor Yellow
$md += "## 中间件连接检测"
$md += ""
# 按中间件类型分组,并在不同类型之间添加分割线
$currentMiddleware = ""
foreach ($r in $MiddlewareResults) {
# 检测中间件类型
$middlewareType = switch -Wildcard ($r.Check) {
"*MQTT*" { "MQTT" }
"*Redis*" { "Redis" }
"*MySQL*" { "MySQL" }
"*FastDFS*" { "FastDFS" }
default { "" }
}
# 如果中间件类型改变,添加分割线
if ($middlewareType -and $middlewareType -ne $currentMiddleware) {
if ($currentMiddleware -ne "") {
$md += "---"
}
$currentMiddleware = $middlewareType
}
$icon = if ($r.Success) { "✅" } elseif ($r.Status -eq "跳过") { "ℹ️" } else { "❌" }
$line = "- $icon $($r.Check): $($r.Status)"
if ($r.Details) { $line += " | $($r.Details)" }
......@@ -3829,6 +4343,7 @@ function Check-TraditionalPlatformIPs {
[Parameter(Mandatory=$true)][string]$ServerIP,
[Parameter(Mandatory=$true)][string]$Username,
[Parameter(Mandatory=$true)][string]$Password,
[Parameter(Mandatory=$false)][int]$Port = 22,
[Parameter(Mandatory=$false)][hashtable]$SystemInfo
)
......@@ -3862,7 +4377,7 @@ function Check-TraditionalPlatformIPs {
foreach ($Path in $Paths) {
Write-Log -Level "INFO" -Message ("[CFG] 检测路径: {0}" -f $Path)
$Command = "grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' $Path/* 2>/dev/null | sort -u"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $Command
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
if ($Result -and $Result.ExitCode -eq 0 -and $Result.Output) {
$IPs = $Result.Output -split "`n" | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' }
......@@ -3888,7 +4403,8 @@ function Check-NewPlatformIPs {
param (
[string]$ServerIP,
[string]$Username,
[string]$Password
[string]$Password,
[int]$Port = 22
)
Write-Host "开始检测新统一平台配置文件中的IP地址..." -ForegroundColor Yellow
......@@ -3922,13 +4438,13 @@ function Check-NewPlatformIPs {
foreach ($Directory in $Directories) {
$Command = "find $Directory -type f \( -name '*.yml' -o -name '*.properties' -o -name 'config.js' -o -name 'config.json' -o -name 'unified*.conf' \) 2>/dev/null"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $Command
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
if ($Result.ExitCode -eq 0 -and $Result.Output) {
$Files = $Result.Output -split "`n" | Where-Object { $_ -ne "" }
foreach ($File in $Files) {
$GrepCommand = "grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' $File 2>/dev/null | sort -u"
$IPResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $GrepCommand
$IPResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $GrepCommand
if ($IPResult.ExitCode -eq 0 -and $IPResult.Output) {
$IPs = $IPResult.Output -split "`n" | Where-Object { $_ -ne "" }
......@@ -3959,27 +4475,61 @@ function Check-NTPService {
param (
[string]$ServerIP,
[string]$Username,
[string]$Password
[string]$Password,
[int]$Port = 22
)
$summary = @{ Status = '未执行'; Detail = '' }
$needRepair = $false
$serverForRepair = @{ IP = $ServerIP; User = $Username; Pass = $Password; Port = 22 }
$serverForRepair = @{ IP = $ServerIP; User = $Username; Pass = $Password; Port = $Port }
Write-Host "开始检测目标服务器的NTP服务..." -ForegroundColor Yellow
# 多层级检测NTP服务(避免systemctl权限问题)
Write-Log -Level "INFO" -Message "[NTP] 检测服务是否安装 (ntp/chronyd)"
$Command = "systemctl list-units --type=service | grep -E 'ntp|chronyd'"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $Command
# 方法1: 检测运行中的进程(不需要root权限)
$Command = "ps aux | grep -E 'chronyd|ntpd' | grep -v grep"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
$processOutput = if ($Result.Output) { [string]::Join(' ', $Result.Output) } else { "" }
Write-Log -Level "INFO" -Message "[NTP] 进程检测输出: $processOutput"
$ntpRunning = $Result.ExitCode -eq 0 -and $Result.Output -match "chronyd|ntpd"
# 方法2: 如果进程检测失败,尝试systemctl(可能需要root)
if (-not $ntpRunning) {
$Command = "systemctl list-units --type=service 2>&1 | grep -E 'ntp|chronyd'"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
$outputStr = if ($Result.Output) { [string]::Join(' ', $Result.Output) } else { "" }
Write-Log -Level "INFO" -Message "[NTP] systemctl 输出: $outputStr"
if ($Result.ExitCode -eq 0 -and $Result.Output -match "ntp|chronyd") {
if ($Result.ExitCode -eq 0 -and $Result.Output -match "ntp|chronyd" -and $outputStr -notmatch "Access denied") {
$ntpRunning = $true
}
}
# 方法3: 检测配置文件是否存在
if (-not $ntpRunning) {
$Command = "ls /etc/chrony.conf /etc/ntp.conf 2>/dev/null"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
if ($Result.ExitCode -eq 0) {
Write-Log -Level "INFO" -Message "[NTP] 检测到NTP配置文件"
$ntpRunning = $true
}
}
if ($ntpRunning) {
Write-Log -Level "SUCCESS" -Message "[NTP] 已检测到 NTP/Chrony 服务"
# 新增:检查 chronyd/ntpd 是否处于 active(running)
$activeCmd = "if systemctl is-active chronyd >/dev/null 2>&1; then echo CHRONYD_ACTIVE; fi; if systemctl is-active ntpd >/dev/null 2>&1; then echo NTPD_ACTIVE; fi"
$activeRes = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $activeCmd
$chronydActive = ($activeRes.Output -match 'CHRONYD_ACTIVE')
$ntpdActive = ($activeRes.Output -match 'NTPD_ACTIVE')
# 检查 chronyd/ntpd 是否处于运行状态(使用进程检测,避免systemctl权限问题)
$activeCmd = "ps aux | grep -E 'chronyd|ntpd' | grep -v grep | wc -l"
$activeRes = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $activeCmd
$processCount = 0
if ($activeRes.ExitCode -eq 0 -and $activeRes.Output) {
$processCount = [int]($activeRes.Output | Select-Object -First 1).Trim()
}
$chronydActive = $processCount -gt 0
$ntpdActive = $processCount -gt 0
if (-not $chronydActive -and -not $ntpdActive) {
Write-Log -Level "ERROR" -Message "[NTP] 检测到 chronyd/ntpd 未运行"
$summary.Status = '异常'; $summary.Detail = 'chronyd/ntpd 未运行'
......@@ -3987,12 +4537,12 @@ function Check-NTPService {
}
$StatusCommand = "timedatectl status"
$StatusResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $StatusCommand
$StatusResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $StatusCommand
$statusOutputStr = if ($StatusResult.Output) { [string]::Join(' ', $StatusResult.Output) } else { "" }
Write-Log -Level "INFO" -Message "[NTP] timedatectl 输出: $statusOutputStr"
if ($StatusResult.ExitCode -eq 0) {
$TimeCommand = "date +%s"; Write-Log -Level "INFO" -Message "[NTP] 读取服务器时间: $TimeCommand"
$ServerTimeResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command $TimeCommand
$ServerTimeResult = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $TimeCommand
$timeOutputStr = if ($ServerTimeResult.Output) { [string]::Join(' ', $ServerTimeResult.Output) } else { "" }
Write-Log -Level "INFO" -Message "[NTP] 服务器时间戳: $timeOutputStr"
if ($ServerTimeResult.ExitCode -eq 0) {
......@@ -4037,8 +4587,8 @@ function Check-NTPService {
Write-Log -Level "SUCCESS" -Message "[NTP] 远端修复已执行成功 (fix_ntp_config)"
# 修复后复检 NTP 状态与时间差
Write-Log -Level "INFO" -Message "[NTP] 修复后复检..."
$postStatus = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command "timedatectl status"
$postTime = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Command "date +%s"
$postStatus = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command "timedatectl status"
$postTime = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command "date +%s"
if ($postStatus.ExitCode -eq 0 -and $postTime.ExitCode -eq 0) {
$srvTs = [int]($postTime.Output | Select-Object -First 1).Trim()
# 本地用 UTC 秒
......@@ -4433,7 +4983,9 @@ function Upload_the_repair_script {
}
# 2) 上传 issue_handler.sh 到远端目录
$localScript = Join-Path (Split-Path -Parent $PSCommandPath) "issue_handler.sh"
# 直接在当前工作目录下查找 issue_handler.sh
$localScript = Join-Path $PWD "issue_handler.sh"
$localScript = [System.IO.Path]::GetFullPath($localScript)
Write-Log -Level "INFO" -Message "本地脚本: $localScript"
$scpOk = Copy-File-To-Remote -LocalPath $localScript -Server $Server -RemoteDir $RemoteDir
if (-not $scpOk) {
......@@ -4837,16 +5389,16 @@ function Main {
Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始检测配置文件 IP =========="
if ($platformType -eq "new") {
Check-NewPlatformIPs -ServerIP $server.IP -Username $server.User -Password $server.Pass
Check-NewPlatformIPs -ServerIP $server.IP -Username $server.User -Password $server.Pass -Port $server.Port
} elseif ($platformType -eq "old") {
# ✅ 关键:把 systemInfo 传进去,才能按 meeting/unified 选路径
Check-TraditionalPlatformIPs -ServerIP $server.IP -Username $server.User -Password $server.Pass -SystemInfo $systemInfo
Check-TraditionalPlatformIPs -ServerIP $server.IP -Username $server.User -Password $server.Pass -Port $server.Port -SystemInfo $systemInfo
}
Write-Log -Level "INFO" -Message "========== 结束检测配置文件 IP =========="
# 检测 NTP 服务
Write-Log -Level "INFO" -Message "========== 开始检测NTP服务 =========="
$ntpResults = Check-NTPService -ServerIP $server.IP -Username $server.User -Password $server.Pass
$ntpResults = Check-NTPService -ServerIP $server.IP -Username $server.User -Password $server.Pass -Port $server.Port
Write-Log -Level "INFO" -Message "NTP 服务检测完成."
# 输出 NTP 摘要
if ($ntpResults) {
......
......@@ -469,10 +469,30 @@ MIDDLEWARE_MYSQL_PASSWORD="dNrprU&2S"
MIDDLEWARE_FASTDFS_CONTAINER="ustorage"
get_dns_nameservers() {
local dns_ips=""
# 方法1: 直接从 /etc/resolv.conf 读取
if [[ -r /etc/resolv.conf ]]; then
grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n 5 | paste -sd',' - || true
dns_ips="$(grep -E '^nameserver[[:space:]]+[0-9]' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n 5 | paste -sd',' - || true)"
fi
# 方法2: 如果是 systemd-resolved 的 stub 文件,尝试从其他位置获取
if [[ -z "$dns_ips" ]] && [[ -L /etc/resolv.conf ]] && [[ "$(readlink /etc/resolv.conf)" == *"systemd"* ]]; then
# 尝试从 systemd-resolved 获取
if command_exists resolvectl; then
dns_ips="$(resolvectl status 2>/dev/null | grep -A5 "DNS Servers" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -n 3 | paste -sd',' - || true)"
fi
fi
# 方法3: 尝试从 NetworkManager 或其他源获取
if [[ -z "$dns_ips" ]] && command_exists nmcli; then
dns_ips="$(nmcli dev show 2>/dev/null | grep -E '^IP4.DNS' | awk '{print $2}' | head -n 3 | paste -sd',' - || true)"
fi
# 如果仍然为空,返回默认提示
if [[ -z "$dns_ips" ]]; then
echo "N/A(无法检测,可能是使用127.0.0.53 stub resolver)"
else
echo ""
echo "$dns_ips"
fi
}
......@@ -655,7 +675,14 @@ test_resources() {
elif command_exists iptables && iptables -L INPUT >/dev/null 2>&1; then
fw_type="iptables"
fw_raw="$(iptables -S 2>/dev/null || iptables -L -n -v 2>/dev/null || true)"
fw_open="$(printf "%s\n" "$fw_raw" | grep -oE 'dpt:[0-9]+' | cut -d: -f2 | sort -u | paste -sd',' - || true)"
# 提取所有开放端口:包括 dpt:端口 和 --dport 端口 格式
fw_open="$(printf "%s\n" "$fw_raw" | grep -oE 'dpt:[0-9]+|--dport [0-9]+' | grep -oE '[0-9]+' | sort -u | paste -sd',' - || true)"
# 尝试提取 Docker 映射端口(从 DOCKER 链)
local docker_ports
docker_ports="$(printf "%s\n" "$fw_raw" | grep -A1 'DOCKER$' | grep -oE 'dpt:[0-9]+|--dport [0-9]+' | grep -oE '[0-9]+' | sort -u | paste -sd',' - || true)"
if [[ -n "$docker_ports" ]]; then
[[ -n "$fw_open" ]] && fw_open="$fw_open,$docker_ports" || fw_open="$docker_ports"
fi
[[ -n "$fw_open" ]] && fw_active=1 || fw_active=0
fi
......@@ -699,15 +726,17 @@ test_ntp() {
local diff="N/A"
local raw=""
# 服务状态
local chrony_active=0 ntp_active=0
# 服务状态 - 检测更多NTP实现方式
local chrony_active=0 ntp_active=0 timesyncd_active=0
if command_exists systemctl; then
systemctl is-active chronyd >/dev/null 2>&1 && chrony_active=1 || true
systemctl is-active ntpd >/dev/null 2>&1 && ntp_active=1 || true
systemctl is-active systemd-timesyncd >/dev/null 2>&1 && timesyncd_active=1 || true
fi
if [[ "$chrony_active" -eq 1 ]]; then impl="chronyd"; fi
if [[ "$ntp_active" -eq 1 ]]; then impl="ntpd"; fi
if [[ "$timesyncd_active" -eq 1 ]]; then impl="systemd-timesyncd"; fi
# 尝试用 chrony 获取 offset(比你之前 date/local_ts 更靠谱)
if [[ "$impl" == "chronyd" ]] && command_exists chronyc; then
......@@ -722,14 +751,52 @@ test_ntp() {
elif [[ "$impl" == "ntpd" ]] && command_exists ntpq; then
raw+="[ntpq -p]\n"
raw+="$(ntpq -p 2>/dev/null || true)\n\n"
# 尝试从 ntpq 输出获取 offset
local off
off="$(ntpq -p 2>/dev/null | awk 'NF>0 && $1!="*" {next} $1=="*" {print $9}' | head -n1 || true)"
if [[ -n "$off" ]]; then
diff="${off}ms"
fi
elif [[ "$impl" == "systemd-timesyncd" ]] && command_exists timedatectl; then
raw+="[timedatectl status]\n"
raw+="$(timedatectl status 2>/dev/null || true)\n\n"
# 从 timedatectl 获取 System clock synchronized 状态
if timedatectl status 2>/dev/null | grep -q "System clock synchronized: yes"; then
diff="synchronized(已同步)"
fi
fi
# 如果仍然未检测到服务,但时间看起来是同步的,可能使用ntpdate等其他方式
if [[ "$impl" == "unknown" ]]; then
# 检查是否有 ntpdate 定时任务
if crontab -l 2>/dev/null | grep -q "ntpdate"; then
impl="ntpdate(cron)"
diff="定时同步"
fi
# 检查 timedatectl 显示是否同步
if command_exists timedatectl && timedatectl status 2>/dev/null | grep -q "System clock synchronized: yes"; then
if [[ "$impl" == "unknown" ]]; then
impl="systemd-timesyncd(未激活服务)"
diff="synchronized(已同步)"
fi
fi
fi
report_kv_set "ntp.impl" "$impl"
report_kv_set "ntp.diff_seconds" "$diff"
# 确保值不为空,提供有意义的默认值
local impl_display="${impl:-unknown}"
local diff_display="${diff:-N/A}"
# 如果 diff 为 N/A 且 impl 不是 unknown,说明服务运行但无法获取偏移量
if [[ "$diff_display" == "N/A" && "$impl_display" != "unknown" ]]; then
diff_display="<1s(无法精确获取)"
fi
report_kv_set "ntp.impl" "$impl_display"
report_kv_set "ntp.diff_seconds" "$diff_display"
report_kv_set "ntp.raw" "${raw:-N/A}"
# 更宽松的判断标准:如果时间已同步或检测到服务,就返回OK
if [[ "$impl" == "unknown" ]]; then
log WARN "[NTP] 未检测到 chronyd/ntpd 正在运行"
log WARN "[NTP] 未检测到 chronyd/ntpd/systemd-timesyncd 正在运行"
echo "NOT_RUNNING"
return 0
fi
......@@ -909,8 +976,17 @@ collect_container_info() {
else
log WARN "[Emqx] emqx.log.1 不存在:$emqx_log"
fi
report_kv_set "redis.export" "${redis_export:-N/A}"
report_kv_set "emqx.export" "${emqx_export:-N/A}"
# 提供更友好的提示信息
if [[ -n "$redis_export" ]]; then
report_kv_set "redis.export" "$redis_export"
else
report_kv_set "redis.export" "N/A (日志路径: $redis_log)"
fi
if [[ -n "$emqx_export" ]]; then
report_kv_set "emqx.export" "$emqx_export"
else
report_kv_set "emqx.export" "N/A (日志路径: $emqx_log,Docker容器日志可用 docker logs uemqx)"
fi
# Redis 异常判定
local redis_running=0 uredis_running=0 uredis_stopped=0
......@@ -1405,6 +1481,14 @@ test_scheduled_tasks() {
report_add ""
report_add "## 定时任务查询"
report_add "- status: $(report_kv_get "sched.status")"
# 添加 crontab 内容到报告摘要
if [[ -n "$cron" ]]; then
report_add ""
report_add "**当前 crontab 内容:**"
while IFS= read -r l; do
[[ -n "$l" ]] && report_add " $l"
done <<< "$cron"
fi
}
# ------------------------------
......@@ -2464,7 +2548,28 @@ write_report() {
# DNS 详情(raw)
w "### DNS 检测(详细)"
w "- 初次检测结果: \`$(report_kv_get "dns.status")\`"
w "- nameserver: \`$(report_kv_get "dns.nameservers")\`"
local dns_ns
dns_ns="$(report_kv_get "dns.nameservers")"
if [[ -n "$dns_ns" && "$dns_ns" != "N/A" ]]; then
w "- nameserver: \`$dns_ns\`"
else
w "- nameserver: \`N/A\`(可能是使用 systemd-resolved stub resolver 127.0.0.53)"
fi
# 显示 DNS 解析详情
local dns_details
dns_details="$(report_kv_get "dns.details")"
if [[ -n "$dns_details" ]]; then
w ""
w "**DNS 解析测试详情:**"
w "| 域名 | 状态 | 工具 |"
w "|---|---|---|"
while IFS="|" read -r domain status tool; do
[[ -z "$domain" ]] && continue
local status_icon="✅"
[[ "$status" == "FAIL" ]] && status_icon="❌"
w "| $domain | $status_icon $status | $tool |"
done <<< "$dns_details"
fi
local dns_raw
dns_raw="$(report_kv_get "dns.raw")"
if [[ -n "$dns_raw" ]]; then
......@@ -2502,6 +2607,7 @@ write_report() {
w "- type: \`$(report_kv_get "fw.type")\`"
w "- active: \`$(report_kv_get "fw.active")\`"
w "- open: \`$(report_kv_get "fw.open")\`"
w "- 说明: $( [[ "$(report_kv_get "fw.open")" == "N/A" ]] && echo "未检测到开放的端口,或使用Docker管理端口映射" || echo "已检测到开放的端口/服务" )"
local fw_raw
fw_raw="$(report_kv_get "fw.raw")"
if [[ -n "$fw_raw" ]]; then
......@@ -2517,6 +2623,7 @@ write_report() {
w "- 初次检测结果: \`$(report_kv_get "ntp.status")\`"
w "- impl: \`$(report_kv_get "ntp.impl")\`"
w "- diff: \`$(report_kv_get "ntp.diff_seconds")\`"
w "- 说明: $( [[ "$(report_kv_get "ntp.impl")" == "unknown" ]] && echo "未检测到NTP服务(chronyd/ntpd)" || echo "NTP时间同步服务运行中" )"
local ntp_raw
ntp_raw="$(report_kv_get "ntp.raw")"
if [[ -n "$ntp_raw" && "$ntp_raw" != "N/A" ]]; then
......@@ -2613,6 +2720,13 @@ write_report() {
w '```'
printf "%s\n" "$miss_lines" >>"$report_file"
w '```'
w ""
w "**修复建议:**"
while IFS= read -r task; do
[[ -z "$task" ]] && continue
w "- 添加任务: \`$task\`"
w " 执行命令: \`crontab -l 2>/dev/null; echo '$task' | crontab -\`"
done <<< "$miss_lines"
fi
w ""
......@@ -2626,21 +2740,52 @@ write_report() {
# 中间件连接检测结果 (PRD 4.18)
w "### 中间件连接检测(详细)"
w "- MQTT: \`$(report_kv_get "mqtt.status")\`"
local mqtt_detail
local mqtt_detail mqtt_container
mqtt_detail="$(report_kv_get "mqtt.detail")"
mqtt_container="$(report_kv_get "mqtt.container")"
[[ -n "$mqtt_container" ]] && w " - 容器: \`$mqtt_container\`"
[[ -n "$mqtt_detail" ]] && w " - 详情: $mqtt_detail"
# MQTT 订阅状态
local mqtt_sub_status mqtt_sub_detail
mqtt_sub_status="$(report_kv_get "mqtt.subscription.status")"
mqtt_sub_detail="$(report_kv_get "mqtt.subscription.detail")"
[[ -n "$mqtt_sub_status" ]] && w " - 订阅状态: \`$mqtt_sub_status\`"
[[ -n "$mqtt_sub_detail" ]] && w " - 订阅详情: $mqtt_sub_detail"
# MQTT 消息测试
local mqtt_pubsub_status mqtt_pubsub_detail
mqtt_pubsub_status="$(report_kv_get "mqtt.pubsub.status")"
mqtt_pubsub_detail="$(report_kv_get "mqtt.pubsub.detail")"
[[ -n "$mqtt_pubsub_status" ]] && w " - 消息测试: \`$mqtt_pubsub_status\`"
[[ -n "$mqtt_pubsub_detail" ]] && w " - 消息测试详情: $mqtt_pubsub_detail"
w "- Redis: \`$(report_kv_get "redis_conn.status")\`"
local redis_conn_detail
local redis_conn_detail redis_container
redis_conn_detail="$(report_kv_get "redis_conn.detail")"
redis_container="$(report_kv_get "redis_conn.container")"
[[ -n "$redis_container" ]] && w " - 容器: \`$redis_container\`"
[[ -n "$redis_conn_detail" ]] && w " - 详情: $redis_conn_detail"
w "- MySQL: \`$(report_kv_get "mysql_conn.status")\`"
local mysql_conn_detail
local mysql_conn_detail mysql_container
mysql_conn_detail="$(report_kv_get "mysql_conn.detail")"
mysql_container="$(report_kv_get "mysql_conn.container")"
[[ -n "$mysql_container" ]] && w " - 容器: \`$mysql_container\`"
[[ -n "$mysql_conn_detail" ]] && w " - 详情: $mysql_conn_detail"
w "- FastDFS: \`$(report_kv_get "fastdfs_conn.status")\`"
local fastdfs_conn_detail
local fastdfs_conn_detail fastdfs_container
fastdfs_conn_detail="$(report_kv_get "fastdfs_conn.detail")"
fastdfs_container="$(report_kv_get "fastdfs_conn.container")"
[[ -n "$fastdfs_container" ]] && w " - 容器: \`$fastdfs_container\`"
[[ -n "$fastdfs_conn_detail" ]] && w " - 详情: $fastdfs_conn_detail"
# 添加中间件配置信息摘要
w ""
w "**中间件配置信息:**"
w "- MQTT Broker: \`$MIDDLEWARE_MQTT_CONTAINER\` 端口: \`$MIDDLEWARE_MQTT_PORT\` Dashboard: \`$MIDDLEWARE_MQTT_DASHBOARD_PORT\`"
w "- Redis: \`$MIDDLEWARE_REDIS_CONTAINER\` 端口: \`$MIDDLEWARE_REDIS_PORT\`"
w "- MySQL: \`$MIDDLEWARE_MYSQL_CONTAINER\` 端口: \`$MIDDLEWARE_MYSQL_PORT\`"
w "- FastDFS: \`$MIDDLEWARE_FASTDFS_CONTAINER\`"
w ""
# 说明
......
......@@ -77,7 +77,8 @@ function Invoke-PlinkCommand {
[Parameter(Mandatory=$true)][string]$User,
[Parameter(Mandatory=$true)][string]$Password,
[Parameter(Mandatory=$true)][string]$Command,
[switch]$NoDelay
[switch]$NoDelay,
[switch]$BatchMode
)
# 每次连接前短暂延迟,避免SSH连接冲突
......@@ -85,8 +86,7 @@ function Invoke-PlinkCommand {
Start-Sleep -Milliseconds $SCRIPT:ConnectionDelayMs
}
# 不使用 -batch 参数,允许交互式确认主机密钥
# 注意:plink 的 -pw 直接传入即可;不要用奇怪的三引号拼接,容易导致解析出错
# 构建 plink 参数
$args = @(
"-ssh",
"-P", "$Port",
......@@ -96,6 +96,11 @@ function Invoke-PlinkCommand {
$Command
)
# 批处理模式:自动接受主机密钥,不提示用户
if ($BatchMode) {
$args = @("-batch") + $args
}
Write-Info ("远端执行: {0}" -f $Command)
# 调试信息:显示连接参数(隐藏密码)
Write-Info ("[DEBUG] 连接参数: Host={0}, Port={1}, User={2}, PwdLength={3}" -f $HostName, $Port, $User, $Password.Length)
......@@ -231,6 +236,11 @@ Write-Info ("remote_program_update.ps1 version={0}" -f $SCRIPT_VERSION)
Write-Info ("PowerShell={0} Host={1}" -f $PSVersionTable.PSVersion, $env:COMPUTERNAME)
Write-Info ("ScriptPath={0}" -f $PSCommandPath)
# 自动化模式说明
Write-Host "`n[自动化模式] 初始选择完成后,后续操作将自动执行,无需额外交互。" -ForegroundColor Green
Write-Host "[提示] 如首次连接该服务器,建议先手动运行 plink 建立主机密钥信任:" -ForegroundColor Yellow
Write-Host " plink.exe -ssh -P <端口> -l <用户> <服务器IP> 'echo test'`n" -ForegroundColor DarkGray
# 服务器预设选择
$usePreset = Read-Choice "是否使用服务器预设?" @("否(手动输入)", "是(使用预设)")
$serverIp = ""
......@@ -298,7 +308,7 @@ if ($usePreset -eq "否(手动输入)") {
Write-Info "参数确认:IP=$serverIp Port=$sshPort User=$username RemoteDir=$remoteDir 平台=$platformType 系统=$systemType 更新=$updateType"
}
Write-Info "首次连接时会提示确认主机密钥,请选择 'yes' 确认。"
Write-Info "使用自动批处理模式,后续操作将自动完成,无需额外交互。"
# 判断是否为中广核项目(需要特殊处理)
$isCgnProject = ($platformType -eq "中广核项目")
......@@ -307,13 +317,9 @@ if ($isCgnProject) {
Write-Info "检测到中广核项目,将使用zip压缩包方式部署"
# 后端服务器需要root密码,用于su切换,前端服务器(root用户)不需要
if ($usePreset -eq "是(使用预设)" -and $preset -eq "测试环境-后端服务器") {
$useRootPreset = Read-Choice "是否使用预设的root密码?" @("是(使用预设)", "否(输入root密码)")
if ($useRootPreset -eq "是(使用预设)") {
# 自动使用预设root密码,不再询问
$rootPassword = "Admin@123Admin@123"
Write-Info "使用预设的root密码"
} else {
$rootPassword = Read-NonEmpty "请输入root密码"
}
Write-Info "后端服务器:自动使用预设的root密码进行su切换"
}
}
......@@ -334,7 +340,7 @@ try {
} catch { }
# -------------------- 连接测试(3.1) --------------------
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "echo Connected; uname -a"
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "echo Connected; uname -a" -BatchMode
Write-Info "SSH 连接测试成功。"
# 1) 远端创建目录 + 检查 unzip
......@@ -343,7 +349,7 @@ mkdir -p '$remoteDir';
command -v unzip >/dev/null 2>&1 || (echo '缺少 unzip,请安装:yum install -y unzip 或 apt-get install -y unzip' && exit 2)
"@
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $cmdEnsureDirAndUnzip
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $cmdEnsureDirAndUnzip -BatchMode
# ====== 3.2 本地准备:找到 zip 包 + program_update.sh ======
# 约定:zip 压缩包与本 remote_program_update.ps1 同级目录
......@@ -380,7 +386,7 @@ Invoke-PscpUpload -PscpPath $pscp -HostName $serverIp -Port $sshPort -User $user
# 远端解压(解压到 remoteDir)
$zipName = Split-Path $zipPath -Leaf
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && unzip -o '$zipName' -d '$remoteDir'"
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && unzip -o '$zipName' -d '$remoteDir'" -BatchMode
# ====== 执行 program_update.sh 并下载备份包 ======
# 等待文件系统同步完成
......@@ -388,7 +394,7 @@ Write-Info "等待远端文件系统同步..."
Start-Sleep -Seconds 2
# 额外打印远端 program_update.sh 版本,便于回溯
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && ./program_update.sh --version 2>/dev/null || true"
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "cd '$remoteDir' && ./program_update.sh --version 2>/dev/null || true" -BatchMode
# 中广核项目需要根据服务器类型决定是否使用 su
if ($isCgnProject) {
......@@ -398,11 +404,10 @@ if ($isCgnProject) {
if ($needSu) {
Write-Info "中广核项目(后端服务器):使用 su 切换到root用户执行更新脚本"
# 优化后的命令结构:
# 1. 先设置环境变量
# 2. 再用单引号包裹整个 su -c 命令
# 3. 使用 -- 作为参数分隔符
$execCmd = "cd '$remoteDir' && echo '$rootPassword' | su - -c 'chmod +x ./program_update.sh && env LC_CTYPE=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 ./program_update.sh --platform \"$platformType\" --system \"$systemType\" --update \"$updateType\" --workdir \"$remoteDir\"'"
# 使用 here-string 避免转义问题
$execCmd = @"
cd '$remoteDir' && echo '$rootPassword' | su - -c 'chmod +x ./program_update.sh && env LC_CTYPE=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 ./program_update.sh --platform "$platformType" --system "$systemType" --update "$updateType" --workdir "$remoteDir"'
"@
}
else {
Write-Info "中广核项目(前端服务器):直接执行更新脚本(root用户,无需su)"
......@@ -422,7 +427,7 @@ cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platfor
Write-Info "准备执行更新脚本..."
Start-Sleep -Seconds 2
$out = Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $execCmd
$out = Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command $execCmd -BatchMode
# 从输出解析 BACKUP_TAR
# program_update.sh 输出格式:BACKUP_TAR=/home/Backup/BakYYYYmmdd_HHMMSS.tar.gz
......
## 服务自检脚本使用说明
### 1、将整个Server_health_check文件夹拷贝到桌面
### 2、打开电脑的powershell
### 3、切换到Server_health_check文件夹路径
### 4、执行指令
Unblock-File -Path "C:\Users\UBAINS\Desktop\Sever_health_check\check_server_health.ps1"(后面路径替换成自己电脑上的路径!!!)
### 5、执行指令
.\check_server_health.ps1
### 6、步骤说明:
#### 6.1、服务器信息录入
根据提示输入0,再依次输入服务器IP地址、22端口、账号、密码信息即可。
#### 6.2、
# 服务自检脚本更新迭代记录
## 文档说明
本文档记录服务自检脚本(Windows PowerShell 版本和 Linux Bash 版本)的版本迭代历史,包括各版本的功能新增、优化和修复内容。
**脚本文件**
- Windows 版本:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/check_server_health.ps1`
- Linux 版本:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/check_server_health.sh`
---
## 版本历史总览
| 版本号 | 发布日期 | 主要变更 |
|--------|----------|----------|
| V1.0.5 | 2026-02-03 | MQTT/Redis 中间件检测优化,支持多架构 |
| V1.0.4 | 2026-01-29 | 新增中间件连接检测功能(MQTT/Redis/MySQL/FastDFS) |
| V1.0.3 | 2026-01-28 | 完善文档,同步 Windows/Linux 版本号 |
| V1.0.2 | 2026-01-21 | 增强服务器健康检查脚本功能(PowerShell 版本) |
| V1.0.1 | 2026-01-14 | 添加版本号管理和日志记录功能 |
| V1.0.0 | 2025-12-09 | 初始版本发布 |
---
## V1.0.5
**发布日期**:2026-02-03
**最后更新**:2026-02-04
**版本号变更**
- Windows PowerShell 版本:V1.0.4 → V1.0.5
- Linux Bash 版本:V1.0.4 → V1.0.5
### 新增功能
#### 1. MQTT 检测增强
- **标准版主题订阅检测**:支持 7 个标准版 MQTT 主题的订阅验证
- **消息收发测试**:使用 `mosquitto_pub/sub` 进行完整的发布订阅功能验证
- **检测工具包集成**:内置 x86/ARM 架构的 MQTT 测试工具包
- **多层级降级检测**
- 优先使用内置工具包(`mqtt_test_x86/mqtt_test_arm`
- 降级使用容器内 mosquitto 命令
#### 2. Redis 检测增强
- **独立检测脚本**:新增 `check_redis.sh`(559 行)
- **完整功能检测**
- 连接测试(redis-cli ping)
- 容器状态检测
- 端口连通性验证
- 进程状态检查
- 读写功能测试
- 删除功能测试
- 详细信息收集(服务器、客户端、内存、状态等)
#### 3. MySQL 检测新增
- **独立检测脚本**:新增 `check_mysql.sh`(221 行)
- **连接测试**:使用 mysql 客户端验证数据库连接
- **端口连通性降级检测**
- **容器状态检查**:容器ID、镜像、运行状态
- **系统资源监控**:CPU、内存、网络I/O、磁盘I/O
- **性能指标收集**:连接数、查询数、QPS等
#### 4. 多架构支持
- **自动架构检测**:根据 `uname -m` 自动选择 x86 或 ARM 工具包
- **工具包自动赋权**:自动为工具包中的可执行文件添加执行权限
- **FastDFS 检测分离**
- `check_fdfs_x86.sh`(101 行)- x86 架构
- `check_fdfs_arm.sh`(160 行)- ARM 架构
#### 5. MQTT 测试工具包
```
mqtt_test_x86/ # x86 架构工具包
├── mosquitto_pub/sub # MQTT 客户端
├── mqtt # 测试脚本
├── verify.sh # 验证脚本
└── lib/ # 依赖库
mqtt_test_arm/ # ARM 架构工具包(同上结构)
```
### 优化内容
#### 1. 容器自动检测逻辑优化(2026-02-04)
- **模糊匹配支持**:所有中间件检测脚本支持容器名模糊匹配
- Redis:`grep -E "uredis|redis"` - 支持 `uredis``uredis2``redis` 等容器名
- MySQL:`grep -E "umysql|mysql"` - 支持 `umysql``umysql2``mysql` 等容器名
- FastDFS Storage:`grep -E "ustorage|storage"` - 支持多种容器名
- FastDFS Tracker:`grep -E "utracker|tracker"` - 支持多种容器名
- **检测方式改进**:从 `docker ps --filter "name=..."` 改为 `docker ps --format "{{.Names}}" | grep -E`,提高匹配灵活性
#### 2. 脚本上传逻辑优化(2026-02-04)
- **强制覆盖上传**:从"检测不存在时上传"改为"强制覆盖上传"
- Redis 检测:每次执行上传最新 `check_redis.sh` 覆盖远程版本
- MySQL 检测:每次执行上传最新 `check_mysql.sh` 覆盖远程版本
- FastDFS 检测:根据架构自动选择并上传对应脚本覆盖远程版本
- **优势**:确保远程服务器始终使用最新版本的检测脚本
#### 3. 参数传递方式优化(2026-02-04)
- **移除硬编码容器名参数**:不再传递硬编码的容器名参数给检测脚本
- Redis:`bash check_redis.sh`(无参数,脚本自动检测容器)
- MySQL:`bash check_mysql.sh`(无参数,脚本自动检测容器)
- FastDFS:`bash check_fdfs_x86.sh`(脚本自动检测容器)
- **降级处理**:如果自动检测失败,尝试使用远程已有脚本
#### 4. 变量引用修复(2026-02-04)
- **问题**:移除硬编码容器名参数后,PowerShell脚本中使用 `$containerName``$mysqlPort` 变量导致"变量未定义"错误
- **解决方案**:从脚本输出中提取容器名和端口信息
```powershell
# 从输出中提取容器名和端口
$detectedContainer = "uredis" # 默认值
$detectedPort = "6379" # 默认值
if ($outputText -match '容器名:\s*(\S+)') {
$detectedContainer = $matches[1]
}
if ($outputText -match '端口:\s*(\d+)') {
$detectedPort = $matches[1]
}
```
- **影响范围**:Redis 和 MySQL 检测函数的结果详情构建
#### 5. 其他优化
- 优化 NTP 检测的日志输出处理,避免空输出导致的错误
- 优化中间件检测流程,支持自动检测容器和工具可用性
- 改进检测结果记录格式,区分不同检测项目的状态
### 相关文档更新
- PRD 文档版本更新至 V2.2
- 新增 `mqtt_test_x86/mqtt_test_arm` 便携式工具说明
- 更新架构支持说明和脚本部署要求
- 更新容器自动检测和脚本强制覆盖上传说明
---
## V1.0.4
**发布日期**:2026-01-29
**版本号变更**
- Windows PowerShell 版本:V1.0.3 → V1.0.4
- Linux Bash 版本:V1.0.3 → V1.0.4
### 新增功能
#### 中间件连接检测功能(PRD 4.18)
##### 1. MQTT 连接检测
- **EMQX 容器检测**:支持 `uemqx`、`emqx` 等多种容器名称模糊匹配
- **多层级降级检测**
- 方案A:EMQX Dashboard API 检测(端口 18083)
- 方案B:TCP 端口连通性检测(端口 1883)
- 方案C:进程状态检测
- **主题列表记录**:记录 7 个标准版 MQTT 主题
##### 2. Redis 连接检测
- 使用 `redis-cli` 执行 ping 命令验证连接
- 支持容器内外降级检测
- 配置:容器名 `uredis`,端口 `6379`
##### 3. MySQL 连接检测
- 使用 mysql 客户端测试数据库连接
- 支持端口连通性降级检测
- 配置:容器名 `umysql`,端口 `8306`
##### 4. FastDFS 连接检测
- 通过 `fdfs_test` 命令执行文件上传测试
- 配置:容器名 `ustorage`
### 配置管理优化
新增中间件配置对象,集中管理:
```bash
MIDDLEWARE_MQTT_CONTAINER="uemqx"
MIDDLEWARE_MQTT_PORT=1883
MIDDLEWARE_MQTT_DASHBOARD_PORT=18083
MIDDLEWARE_REDIS_CONTAINER="uredis"
MIDDLEWARE_REDIS_PORT=6379
MIDDLEWARE_REDIS_PASSWORD="dNrprU&2S"
MIDDLEWARE_MYSQL_CONTAINER="umysql"
MIDDLEWARE_MYSQL_PORT=8306
MIDDLEWARE_MYSQL_PASSWORD="dNrprU&2S"
MIDDLEWARE_FASTDFS_CONTAINER="ustorage"
```
---
## V1.0.3
**发布日期**:2026-01-28
**版本号变更**
- Windows PowerShell 版本:V1.0.2 → V1.0.3
- Linux Bash 版本:V1.0.0 → V1.0.3
### 主要变更
- 同步 Windows 和 Linux 版本号至 V1.0.3
- 完善服务自检需求文档
- 添加 Windows 和 Linux 双版本的详细对比说明
### 文档更新
- 更新功能需求、技术规范和验收标准
- 统一文档格式和术语说明
- 提高文档的可读性和完整性
---
## V1.0.2
**发布日期**:2026-01-21
**版本号变更**
- Windows PowerShell 版本:V1.0.1 → V1.0.2
### 功能增强
#### 容器信息收集功能扩展
- **新增详细信息**:镜像、端口、IP、挂载点、大小等
- **PrintDetails 开关参数**:支持详细模式和摘要模式切换
- **Go template 变量转义修复**:修复 PowerShell 中的转义问题
#### 文件下载功能优化
- **超时控制**:添加下载超时机制
- **错误处理改进**:更完善的异常处理逻辑
- **临时文件处理**:改进 pscp 下载过程的临时文件管理
- **进程管理优化**:优化下载进程的创建和清理
#### 主机密钥接受机制改进
- 提高首次连接成功率
- 优化 SSH 密钥确认逻辑
#### 文件下载成功判定逻辑改进
- 基于文件存在性和大小判断下载是否成功
- 在健康报告中启用容器详细信息输出模式
---
## V1.0.1
**发布日期**:2026-01-14
**版本号变更**
- Windows PowerShell 版本:V1.0.0 → V1.0.1
### 新增功能
#### 版本号管理系统
为所有相关脚本添加版本号管理功能:
1. **check_server_health.ps1**
- 添加 `SCRIPT_VERSION` 变量
- 记录版本信息到日志
2. **issue_handler.sh**
- 添加 `SCRIPT_VERSION` 变量
- 新增 `show_version` 函数
- 支持 `--version` 参数
3. **其他脚本**
- `pakage_upload.sh`:添加版本号,支持 `--version/-v` 参数
- `program_update.sh`:添加版本号,新增 `print_version` 函数
- `remote_container_update_win.ps1`:添加版本号记录
- `remote_program_update.ps1`:添加版本号记录
- `remote_update.sh`:添加版本号,支持 `--version` 参数
### 日志增强
- 所有脚本添加启动时的版本和参数记录
- 便于回溯调试和问题排查
---
## V1.0.0
**发布日期**:2025-12-09
**版本号变更**
- Windows PowerShell 版本:初始发布
### 初始功能
#### Windows PowerShell 版本(check_server_health.ps1)
##### 核心功能
1. **远程服务器健康检查**
- 支持预设服务器列表和手动输入
- 自动检测平台类型(新统一平台/传统平台)
- 识别系统容器类型(ujava/upython/upython_voice)
2. **服务检测**
- 检测相应服务进程状态
- 检测端口状态
- 容器状态检查
3. **报告生成**
- 生成详细检测报告(Markdown 格式)
- 生成日志记录文件
- 支持彩色控制台输出
4. **SSH 支持**
- 支持 plink 和 sshpass 两种 SSH 工具
- 兼容中英文混合环境编码问题
##### 依赖工具
- PuTTY 工具集(plink.exe、pscp.exe)
- 支持 SSH 远程连接
---
## 版本号规则说明
### 版本号格式
采用语义化版本号格式:`V主版本号.次版本号.修订号`
- **主版本号**:重大架构变更或不兼容更新
- **次版本号**:新功能添加
- **修订号**:问题修复和小优化
### 版本发布节奏
- 功能版本:约每月发布一次
- 修复版本:按需发布
- 紧急修复:即时发布
---
## 更新日志说明
### 变更类型标识
- **新增**:全新的功能或模块
- **优化**:现有功能的改进或性能提升
- **修复**:问题修复
- **移除**:删除的功能或模块
- **废弃**:计划在未来版本中移除的功能
### 兼容性说明
- Windows 版本:需要 PowerShell 5.1 或更高版本
- Linux 版本:需要 Bash 4.0 或更高版本
- 跨平台功能保持功能一致性
---
## 未来计划
### V1.1.0 规划
- [ ] 支持更多中间件类型检测
- [ ] 增强报告可视化功能
- [ ] 支持批量服务器检测
- [ ] 添加检测结果历史对比功能
### V1.2.0 规划
- [ ] Web 界面支持
- [ ] 实时监控功能
- [ ] 告警通知功能
- [ ] 检测结果导出为多种格式
---
## 附录
### 相关文档
- 服务自检需求文档(PRD)
- 服务自检技术规范文档
- 服务自检操作使用手册
### 联系方式
如有问题或建议,请通过以下方式联系:
- 项目仓库:`C:\PycharmData\ubains-module-test`
- 文档目录:`Docs/DOC/服务自检/`
---
**文档版本**:V1.0.0
**最后更新**:2026-02-04
**维护者**:开发团队
......@@ -151,8 +151,7 @@ report_kv_set "mqtt.pubsub.detail" "消息发送与接收测试均成功"
| 主机地址 | REDIS_HOST | localhost | Redis服务器地址 |
| 端口 | REDIS_PORT | 6379 | Redis服务端口 |
| 密码 | REDIS_PASSWORD | dNrprU&2S | Redis认证密码 |
| 容器匹配模式 | CONTAINER_PATTERN | uredis | 容器名称匹配模式 |
| 自动检测 | AUTO_DETECT | true | 是否自动检测容器 |
| 容器匹配模式 | CONTAINER_PATTERN | uredis | 容器名称模糊匹配模式(支持 uredis、uredis2、redis 等) |
**功能要求:**
......@@ -204,12 +203,12 @@ report_kv_set "mqtt.pubsub.detail" "消息发送与接收测试均成功"
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测并上传 `check_redis.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会上传本地最新版本的 `check_redis.sh` 覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_redis.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 `redis-cli ping` 检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 `redis-cli ping` 检测 |
**脚本特性:**
- 自动检测Redis容器(支持容器名称模式匹配
- 自动检测Redis容器(支持模糊匹配,如 uredis、uredis2、redis 等
- 自动获取端口映射和认证密码
- 完整的功能测试(连接、读写、删除)
- 详细的信息收集(服务器、客户端、内存、状态等)
......@@ -306,9 +305,9 @@ AUTO_DETECT=false REDIS_CONTAINER=uredis ./check_redis.sh
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测并上传 `check_mysql.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会上传本地最新版本的 `check_mysql.sh` 覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_mysql.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 MySQL 连接检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 MySQL 连接检测 |
**配置参数:**
......@@ -491,9 +490,9 @@ docker exec umysql mysqladmin -u root -p"dNrprU&2S" status
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测服务器架构并上传对应的 `check_fdfs_x86.sh``check_fdfs_arm.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会根据服务器架构上传本地最新版本(x86/ARM)覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_fdfs_x86.sh`/`check_fdfs_arm.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 `fdfs_test` 检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 `fdfs_test` 检测 |
**配置参数:**
......@@ -535,7 +534,7 @@ docker exec umysql mysqladmin -u root -p"dNrprU&2S" status
- ARM架构:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/check_fdfs_arm.sh`
**脚本特性:**
- 自动检测容器状态(ustorage/utracker
- 自动检测容器状态(支持模糊匹配,如 ustorage、storage、utracker、tracker 等
- 自动创建带时间戳的测试文件
- 完整的上传下载测试流程
- MD5完整性验证
......@@ -786,6 +785,11 @@ mosquitto 便携版 - 完整验证(含详细过程)
| V2.1 | 2026-02-03 | FastDFS检测脚本按架构分离(check_fdfs_x86.sh/check_fdfs_arm.sh) |
| V2.1 | 2026-02-03 | 更新代码根据架构自动选择对应的检测脚本 |
| V2.1 | 2026-02-03 | 更新PowerShell脚本自动检测远程架构并上传对应脚本 |
| V2.2 | 2026-02-04 | 优化容器自动检测逻辑:所有中间件检测脚本支持容器名模糊匹配(grep -E) |
| V2.2 | 2026-02-04 | 修改脚本上传逻辑为强制覆盖模式,确保使用最新版本脚本 |
| V2.2 | 2026-02-04 | 移除硬编码容器名参数传递,让脚本自己检测容器 |
| V2.2 | 2026-02-04 | 更新 Redis/MySQL/FastDFS 检测脚本的容器检测实现 |
| V2.2 | 2026-02-04 | 修复PowerShell脚本变量未定义问题:从脚本输出中提取容器名和端口信息 |
### 6.3 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
......
......@@ -234,9 +234,9 @@ fi
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测并上传 `check_redis.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会上传本地最新版本的 `check_redis.sh` 覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_redis.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 `redis-cli ping` 检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 `redis-cli ping` 检测 |
#### 配置要求
......@@ -245,8 +245,7 @@ fi
| 主机地址 | REDIS_HOST | localhost | ✅ 已配置 |
| 端口 | REDIS_PORT | 6379 | ✅ 已配置 |
| 密码 | REDIS_PASSWORD | dNrprU&2S | ✅ 已配置 |
| 容器匹配模式 | CONTAINER_PATTERN | uredis | ✅ 已配置 |
| 自动检测 | AUTO_DETECT | true | ✅ 已配置 |
| 容器匹配模式 | CONTAINER_PATTERN | uredis | ✅ 已配置(支持模糊匹配) |
#### 实施步骤
......@@ -274,7 +273,7 @@ fi
| 需求功能 | 脚本函数 | 实现方式 |
|----------|----------|----------|
| 容器自动检测 | `detect_redis_container` | docker ps --filter name=CONTAINER_PATTERN |
| 容器自动检测 | `detect_redis_container` | docker ps --format "{{.Names}}" \| grep -E "CONTAINER_PATTERN\|redis" 模糊匹配 |
| 端口映射检测 | `get_connection_info_from_container` | docker port获取映射端口 |
| 密码获取 | `get_connection_info_from_container` | docker inspect获取环境变量 |
| 连接测试 | `check_connection` | redis-cli ping |
......@@ -346,9 +345,9 @@ AUTO_DETECT=false REDIS_CONTAINER=uredis ./check_redis.sh
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测并上传 `check_mysql.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会上传本地最新版本的 `check_mysql.sh` 覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_mysql.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 MySQL 连接检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 MySQL 连接检测 |
#### 配置要求
......@@ -446,9 +445,9 @@ docker exec umysql mysqladmin -u root -p"dNrprU&2S" status
| 部署方式 | 说明 |
|----------|------|
| PowerShell (远程SSH) | 自动上传:脚本会自动检测服务器架构并上传对应的 `check_fdfs_x86.sh``check_fdfs_arm.sh` 到目标服务器 |
| PowerShell (远程SSH) | 强制覆盖上传:每次执行都会根据服务器架构上传本地最新版本(x86/ARM)覆盖远程脚本 |
| Shell (本地执行) | 手动部署:需确保 `check_fdfs_x86.sh`/`check_fdfs_arm.sh``check_server_health.sh` 在同一目录 |
| 降级处理 | 如果脚本不存在,自动降级为简单的 `fdfs_test` 检测 |
| 降级处理 | 如果脚本上传失败或不可用,自动降级为简单的 `fdfs_test` 检测 |
#### 配置要求
......@@ -670,6 +669,11 @@ md5sum /tmp/test.txt /tmp/downloaded.txt
| 1.7.0 | 2026-02-03 | 实现完整的上传、下载、完整性验证检测流程 | Claude |
| 1.8.0 | 2026-02-03 | 补充mqtt_test_arm ARM架构工具支持 | Claude |
| 1.8.0 | 2026-02-03 | 更新架构支持说明,标记ARM64为已准备状态 | Claude |
| 1.9.0 | 2026-02-04 | 优化容器自动检测逻辑:所有中间件检测脚本支持容器名模糊匹配 | Claude |
| 1.9.0 | 2026-02-04 | 修改脚本上传逻辑为强制覆盖模式,确保使用最新版本脚本 | Claude |
| 1.9.0 | 2026-02-04 | 移除硬编码容器名参数传递,让脚本自己检测容器 | Claude |
| 1.9.0 | 2026-02-04 | 更新 Redis/MySQL/FastDFS 检测脚本的容器检测实现 | Claude |
| 1.9.1 | 2026-02-04 | 修复PowerShell脚本变量未定义问题:从脚本输出中提取容器名和端口信息 | Claude |
### 6.2 文件修改记录
......
# _PRD_服务自检需求文档_计划执行.md
> 版本:V1.1
> 版本:V1.2
> 创建日期:2026-01-28
> 最后更新:2026-02-04
> 关联需求:`_PRD_服务自检需求文档-新.md`
> 适用范围:服务自检脚本(Windows 版 + Linux 版)开发维护
> MQTT检测工具:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/mqtt_test_x86/`
......@@ -37,8 +38,8 @@
| 版本 | 当前版本 | 状态 | 说明 |
|------|----------|------|------|
| Windows 版 | 1.0.4 | ✅ 稳定 | 功能完整,已投入使用 |
| Linux 版 | 1.0.4 | ✅ 稳定 | 功能完整,已投入使用 |
| Windows 版 | 1.0.6 | ✅ 稳定 | 中间件检测报告信息补充、NTP检测改进、SSH端口配置修复 |
| Linux 版 | 1.0.5 | ✅ 稳定 | 功能完整,待同步Windows版v1.0.6改进 |
### 2.2 功能对比矩阵
......@@ -110,6 +111,128 @@
| T-1043 | MySQL连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
| T-1044 | FastDFS连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
#### v1.0.6 - 中间件检测报告信息补充完善(Windows版)✅
**目标:** 基于当前 v1.0.5 版本,补充完善中间件检测报告信息,改进NTP检测机制,修复SSH端口配置问题
| 任务ID | 任务描述 | Windows 版 | Linux 版 | 负责人 | 状态 |
|--------|----------|-----------|----------|--------|------|
| T-1061 | MQTT服务检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1062 | MQTT消息收发检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1063 | MQTT主题订阅检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1064 | Redis连接检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1065 | MySQL连接检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1066 | FastDFS连接检测信息补充 | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1067 | NTP检测改进(多层级检测) | 已实现 | 待同步 | - | ✅ 已完成 |
| T-1068 | SSH端口配置修复 | 已实现 | 待同步 | - | ✅ 已完成 |
**详细变更内容:**
##### 1. 中间件检测报告信息补充完善
**变更前示例:**
```
● ✅ MQTT服务检测: 正常 | MQTT端口 1883 可访问 | 检测方式: TCP端口连通性
● ✅ Redis连接检测: 正常 | Redis完整检测通过(连接+读写+删除+信息收集)
● ✅ MySQL连接检测: 正常 | MySQL完整检测通过
● ✅ FastDFS连接检测: 正常 | FastDFS完整检测通过(上传+下载+完整性验证)
```
**变更后示例:**
```
● ✅ MQTT服务检测: 正常 | MQTT端口 1883 可访问 | 检测方式: Dashboard API | 容器: uemqx | Dashboard端口: 18083
● ✅ MQTT消息收发检测: 正常 | 消息发送与接收测试均成功 | 测试主题: /androidPanel/ | 测试消息: MQTT健康检查测试消息_1738665120
● ✅ MQTT主题订阅检测: 正常 | 成功检测 7 个标准版主题,订阅通道正常 | 主题列表: /androidPanel/, /iot/v1/conference/service/request/, ...
● ✅ Redis连接检测: 正常 | Redis完整检测通过(连接+读写+删除+信息收集) | 容器: uredis | 端口: 6379 | 版本: 7.0.5 | 内存: 1.2M | 连接数: 2
● ✅ MySQL连接检测: 正常 | MySQL完整检测通过 | 容器: umysql | 端口: 8306 | 版本: 8.0.30
● ✅ FastDFS连接检测: 正常 | FastDFS完整检测通过(上传+下载+完整性验证) | 容器: ustorage | 架构: ARM | 测试文件大小: 1024字节
```
**补充的信息项:**
| 检测项 | 新增信息 |
|--------|----------|
| MQTT服务检测 | 容器名称(uemqx)、Dashboard端口(18083) |
| MQTT消息收发检测 | 测试主题名称、测试消息内容 |
| MQTT主题订阅检测 | 具体订阅的主题列表 |
| Redis连接检测 | 容器名称(uredis)、端口(6379)、版本信息、内存使用、连接数统计 |
| MySQL连接检测 | 容器名称(umysql)、端口(8306)、版本信息 |
| FastDFS连接检测 | 容器名称(ustorage)、架构类型(ARM/x86)、测试文件大小 |
##### 2. NTP检测机制改进
**问题:** 原先使用 `systemctl` 命令检测NTP服务,当非root用户或polkit限制时会报错 "Access denied"。
**解决方案:** 实现多层级检测机制,避免权限问题
| 检测层级 | 检测方法 | 是否需要root | 说明 |
|----------|----------|-------------|------|
| 方法1 | `ps aux \| grep chronyd/ntpd` | ❌ 否 | 检测运行中的进程 |
| 方法2 | `systemctl list-units` | ⚠️ 可能需要 | 备用方法 |
| 方法3 | `ls /etc/chrony.conf /etc/ntp.conf` | ❌ 否 | 检测配置文件存在性 |
**代码变更:**
```powershell
# 方法1: 检测运行中的进程(不需要root权限)
$Command = "ps aux | grep -E 'chronyd|ntpd' | grep -v grep"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
# 方法2: 如果进程检测失败,尝试systemctl(可能需要root)
if (-not $ntpRunning) {
$Command = "systemctl list-units --type=service 2>&1 | grep -E 'ntp|chronyd'"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
}
# 方法3: 检测配置文件是否存在
if (-not $ntpRunning) {
$Command = "ls /etc/chrony.conf /etc/ntp.conf 2>/dev/null"
$Result = Invoke-SSHCommand -HostName $ServerIP -User $Username -Pass $Password -Port $Port -Command $Command
}
```
##### 3. SSH端口配置修复
**问题:** 多个函数硬编码了SSH端口为22,导致使用非标准端口时连接失败。
**修复内容:**
| 函数名 | 修复内容 |
|--------|----------|
| `Check-NTPService` | 添加 `$Port` 参数,所有SSH调用传递 `-Port $Port` |
| `Check-TraditionalPlatformIPs` | 添加 `$Port` 参数,所有SSH调用传递 `-Port $Port` |
| `Check-NewPlatformIPs` | 添加 `$Port` 参数,所有SSH调用传递 `-Port $Port` |
| `Select-Server` | 修复强制覆盖端口为22的问题,保留预设端口配置 |
**代码变更示例:**
```powershell
# 修复前
function Check-NTPService {
param (
[string]$ServerIP,
[string]$Username,
[string]$Password
)
$serverForRepair = @{ IP = $ServerIP; User = $Username; Pass = $Password; Port = 22 }
}
# 修复后
function Check-NTPService {
param (
[string]$ServerIP,
[string]$Username,
[string]$Password,
[int]$Port = 22
)
$serverForRepair = @{ IP = $ServerIP; User = $Username; Pass = $Password; Port = $Port }
}
```
##### 4. 修改文件位置
| 文件路径 | 修改行数 |
|----------|----------|
| `AuxiliaryTool\ScriptTool\ServiceSelfInspection\check_server_health.ps1` | 约2100-4750行 |
#### v1.1.0 - 功能增强(规划中)
**目标:** 基于当前 v1.0.4 版本,规划和实现功能增强
......@@ -295,6 +418,7 @@ graph TD
| 版本 | 日期 | 变更类型 | 变更内容 | 影响范围 |
|------|------|----------|----------|----------|
| 1.0.6 | 2026-02-04 | Patch | 中间件检测报告信息补充完善,NTP检测改进,SSH端口配置修复 | Windows 版 |
| 1.0.5 | 2026-02-03 | Patch | 新增mqtt_test_x86便携式工具说明,优化MQTT检测 | 两版本 |
| 1.0.4 | 2026-01-29 | Minor | 新增中间件连接检测功能(MQTT/Redis/MySQL/FastDFS) | 两版本 |
| 1.0.3 | 2026-01-28 | Patch | 双版本版本号统一,功能完善 | 两版本 |
......@@ -394,8 +518,10 @@ graph TD
| M2 | 计划文档完成 | 2026-01-28 | ✅ 已完成 |
| M3 | 双版本 v1.0.3 统一 | 2026-01-28 | ✅ 已完成 |
| M4 | v1.0.4 中间件连接检测 | 2026-01-29 | ✅ 已完成 |
| M5 | v1.1.0 功能增强完成 | 待定 | 🔲 计划中 |
| M6 | v1.2.0 更多功能增强完成 | 待定 | 🔲 计划中 |
| M5 | v1.0.5 MQTT检测工具优化 | 2026-02-03 | ✅ 已完成 |
| M6 | v1.0.6 中间件检测报告信息补充、NTP检测改进、SSH端口配置修复 | 2026-02-04 | ✅ 已完成 |
| M7 | v1.1.0 功能增强完成 | 待定 | 🔲 计划中 |
| M8 | v1.2.0 更多功能增强完成 | 待定 | 🔲 计划中 |
---
......@@ -453,6 +579,7 @@ chmod +x check_server_health.sh
| 版本 | 日期 | 变更内容 | 作者 |
|------|------|----------|------|
| V1.2 | 2026-02-04 | 新增v1.0.6版本说明:中间件检测报告信息补充、NTP检测改进、SSH端口配置修复 | Claude |
| V1.1 | 2026-02-03 | 新增mqtt_test_x86便携式工具说明,更新MQTT检测描述 | Claude |
| V1.1 | 2026-01-28 | 更新双版本版本号为 1.0.3 | Claude |
| V1.0 | 2026-01-28 | 初始版本 | Claude |
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论