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

feat(server): 新增中间件连接检测功能

- 实现MQTT主题连接检测,支持EMQX容器检测、Dashboard API、TCP端口连通性和进程状态多层检测
- 实现Redis连接检测,通过redis-cli在容器内执行ping命令验证连接
- 实现MySQL连接检测,使用mysql客户端测试数据库连接并支持端口连通性降级检测
- 实现FastDFS连接检测,通过fdfs_test命令执行文件上传测试验证服务可用性
- 更新文档版本号从1.0.3到1.0.4,添加中间件检测相关功能说明和配置信息
- 重构脚本架构,新增中间件配置对象和检测函数模块化设计
上级 a2afe2d5
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(ls -la \"E:\\\\GithubData\\\\ubains-module-test\\\\自动化部署脚本\\\\x86架构\\\\新统一平台\\\\定时脚本\"\" 2>/dev/null || dir \"E:GithubDataubains-module-test自动化部署脚本x86架构新统一平台定时脚本\"\")", "Bash(ls -la \"E:\\\\GithubData\\\\ubains-module-test\\\\自动化部署脚本\\\\x86架构\\\\新统一平台\\\\定时脚本\"\" 2>/dev/null || dir \"E:GithubDataubains-module-test自动化部署脚本x86架构新统一平台定时脚本\"\")",
"Bash(dir \"E:\\\\GithubData\\\\ubains-module-test\\\\自动化部署脚本\\\\x86架构\\\\新统一平台\" /s /b)" "Bash(dir \"E:\\\\GithubData\\\\ubains-module-test\\\\自动化部署脚本\\\\x86架构\\\\新统一平台\" /s /b)",
"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\\)\")"
] ]
} }
} }
...@@ -64,7 +64,7 @@ $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path ...@@ -64,7 +64,7 @@ $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
$SSH_TIMEOUT = 30 $SSH_TIMEOUT = 30
# 脚本版本号(用于日志与报告) # 脚本版本号(用于日志与报告)
$SCRIPT_VERSION = "1.0.3" $SCRIPT_VERSION = "1.0.4"
# PuTTY 工具路径 # PuTTY 工具路径
$script:PLINK_PATH = $null $script:PLINK_PATH = $null
...@@ -179,6 +179,47 @@ $UpythonOldPlatformPorts = @( ...@@ -179,6 +179,47 @@ $UpythonOldPlatformPorts = @(
$DNSTestDomains = @( $DNSTestDomains = @(
"www.baidu.com" "www.baidu.com"
"www.qq.com" "www.qq.com"
)
# ================================
# 中间件连接检测配置 (PRD 4.18)
# ================================
# MQTT 主题列表(用于记录参考,实际检测依赖端口连通性)
$MQTTTopics = @(
"/androidPanel/"
"/iot/v1/conference/service/request/"
"message/paperLessService/callService"
"message/paperLessAuthor/onthewall"
"/meeting/message/"
"/iot/v1/device/event/request/"
"/iot/v1/device/service/request/"
)
# 中间件连接信息
$MiddlewareConfig = @{
Redis = @{
ContainerName = "uredis"
Port = 6379
Password = "dNrprU&2S"
Description = "Redis 缓存服务"
}
MySQL = @{
ContainerName = "umysql"
Port = 8306
Password = "dNrprU&2S"
Description = "MySQL 数据库服务"
}
MQTT = @{
ContainerName = "uemqx"
Port = 1883
DashboardPort = 18083
Description = "MQTT 消息服务"
}
FastDFS = @{
ContainerName = "ustorage"
Description = "FastDFS 文件存储服务"
}
"www.aliyun.com" "www.aliyun.com"
) )
...@@ -1957,6 +1998,338 @@ function Test-ContainerInformation { ...@@ -1957,6 +1998,338 @@ function Test-ContainerInformation {
} }
# ================================
# 中间件连接检测功能 (PRD 4.18)
# ================================
function Test-MQTTConnection {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "========== MQTT主题连接检测 =========="
$results = @()
$containerName = $MiddlewareConfig.MQTT.ContainerName
$mqttPort = $MiddlewareConfig.MQTT.Port
$dashboardPort = $MiddlewareConfig.MQTT.DashboardPort
# 1. 检测EMQX容器是否存在
$checkContainerCmd = "docker ps --format '{{.Names}}' | grep -E '$containerName|emqx' | head -n 1"
$containerCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkContainerCmd
if ($containerCheck.ExitCode -ne 0 -or -not $containerCheck.Output) {
Write-Log -Level "WARN" -Message "[MQTT] 未检测到EMQX容器($containerName),跳过MQTT连接检测"
$results += @{
Check = "MQTT服务检测"
Status = "跳过"
Details = "未检测到EMQX容器"
Success = $false
}
return $results
}
$actualContainer = ($containerCheck.Output[0].Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[MQTT] 检测到容器: $actualContainer"
# 2. 多层级降级检测
$detectionMethod = $null
$isConnected = $false
$detailMsg = ""
# 方案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'"
$apiResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $apiCmd
if ($apiResult.ExitCode -eq 0 -and $apiResult.Output -and -not ($apiResult.Output -match 'API_FAIL')) {
$apiOutput = $apiResult.Output -join ""
if ($apiOutput -match 'status' -or $apiOutput -match 'emqx') {
$isConnected = $true
$detectionMethod = "Dashboard API"
$detailMsg = "EMQX Dashboard API响应正常 (端口 $dashboardPort)"
Write-Log -Level "SUCCESS" -Message "[MQTT] Dashboard API检测成功: $actualContainer"
}
}
} catch {
Write-Log -Level "DEBUG" -Message "[MQTT] Dashboard API检测失败: $($_.Exception.Message)"
}
# 方案B: TCP端口连通性检测(如果方案A失败)
if (-not $isConnected) {
try {
$portCmd = "docker exec $actualContainer nc -zv -w 3 localhost $mqttPort 2>&1 || echo 'PORT_FAIL'"
$portResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $portCmd
if ($portResult.ExitCode -eq 0 -or ($portResult.Output -match 'succeeded')) {
$isConnected = $true
$detectionMethod = "TCP端口连通性"
$detailMsg = "MQTT端口 $mqttPort 可访问"
Write-Log -Level "SUCCESS" -Message "[MQTT] TCP端口检测成功: $actualContainer:$mqttPort"
}
} catch {
Write-Log -Level "DEBUG" -Message "[MQTT] TCP端口检测失败: $($_.Exception.Message)"
}
}
# 方案C: 检查EMQX进程状态(如果前两方案都失败)
if (-not $isConnected) {
try {
$procCmd = "docker exec $actualContainer ps aux | grep -e 'emqx' | grep -v grep | head -n 1 || echo 'PROC_FAIL'"
$procResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $procCmd
if ($procResult.ExitCode -eq 0 -and $procResult.Output -and -not ($procResult.Output -match 'PROC_FAIL')) {
$isConnected = $true
$detectionMethod = "进程状态"
$detailMsg = "EMQX进程运行中"
Write-Log -Level "SUCCESS" -Message "[MQTT] 进程检测成功: EMQX进程运行"
} else {
$detailMsg = "所有检测方法均失败,MQTT服务可能异常"
Write-Log -Level "ERROR" -Message "[MQTT] 所有检测方法均失败"
}
} catch {
$detailMsg = "检测异常: $($_.Exception.Message)"
Write-Log -Level "ERROR" -Message "[MQTT] 检测异常: $($_.Exception.Message)"
}
}
$results += @{
Check = "MQTT服务检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = "$detailMsg | 检测方式: $detectionMethod"
Success = $isConnected
}
# 输出MQTT主题列表(仅供参考)
Write-Log -Level "INFO" -Message "[MQTT] 监听主题列表: $($MQTTTopics -join ', ')"
return $results
}
function Test-RedisConnection {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "========== Redis连接检测 =========="
$results = @()
$containerName = $MiddlewareConfig.Redis.ContainerName
$redisPort = $MiddlewareConfig.Redis.Port
$redisPassword = $MiddlewareConfig.Redis.Password
# 1. 检测Redis容器
$checkContainerCmd = "docker ps --format '{{.Names}}' | grep -E '$containerName|redis' | head -n 1"
$containerCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkContainerCmd
if ($containerCheck.ExitCode -ne 0 -or -not $containerCheck.Output) {
Write-Log -Level "WARN" -Message "[Redis] 未检测到Redis容器($containerName),跳过Redis连接检测"
$results += @{
Check = "Redis连接检测"
Status = "跳过"
Details = "未检测到Redis容器"
Success = $false
}
return $results
}
$actualContainer = ($containerCheck.Output[0].Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[Redis] 检测到容器: $actualContainer"
# 2. 执行redis-cli ping测试
$isConnected = $false
$detailMsg = ""
try {
# 在容器内执行 redis-cli ping
$redisCmd = "docker exec $actualContainer redis-cli -h localhost -p $redisPort -a `"$redisPassword`" --no-auth-warning ping 2>&1"
$redisResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $redisCmd
if ($redisResult.ExitCode -eq 0 -and $redisResult.Output -match 'PONG') {
$isConnected = $true
$detailMsg = "连接成功,PING响应: PONG"
Write-Log -Level "SUCCESS" -Message "[Redis] redis-cli PING测试成功"
} else {
$outputText = $redisResult.Output -join ""
$detailMsg = "连接失败: $outputText"
Write-Log -Level "ERROR" -Message "[Redis] redis-cli PING测试失败: $outputText"
}
} catch {
$detailMsg = "检测异常: $($_.Exception.Message)"
Write-Log -Level "ERROR" -Message "[Redis] 检测异常: $($_.Exception.Message)"
}
$results += @{
Check = "Redis连接检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = $detailMsg
Success = $isConnected
}
return $results
}
function Test-MySQLConnection {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "========== MySQL连接检测 =========="
$results = @()
$containerName = $MiddlewareConfig.MySQL.ContainerName
$mysqlPort = $MiddlewareConfig.MySQL.Port
$mysqlPassword = $MiddlewareConfig.MySQL.Password
# 1. 检测MySQL容器
$checkContainerCmd = "docker ps --format '{{.Names}}' | grep -E '$containerName|mysql' | head -n 1"
$containerCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkContainerCmd
if ($containerCheck.ExitCode -ne 0 -or -not $containerCheck.Output) {
Write-Log -Level "WARN" -Message "[MySQL] 未检测到MySQL容器($containerName),跳过MySQL连接检测"
$results += @{
Check = "MySQL连接检测"
Status = "跳过"
Details = "未检测到MySQL容器"
Success = $false
}
return $results
}
$actualContainer = ($containerCheck.Output[0].Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[MySQL] 检测到容器: $actualContainer"
# 2. 执行连接测试
$isConnected = $false
$detailMsg = ""
try {
# 尝试使用mysql客户端测试
$mysqlCmd = "docker exec $actualContainer mysql -h localhost -P $mysqlPort -uroot -p`"$mysqlPassword`" -e 'SELECT 1' 2>&1"
$mysqlResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $mysqlCmd
if ($mysqlResult.ExitCode -eq 0) {
$isConnected = $true
$detailMsg = "连接成功,端口 $mysqlPort 可访问"
Write-Log -Level "SUCCESS" -Message "[MySQL] mysql客户端连接测试成功"
} else {
# 降级:使用nc测试端口连通性
$portCmd = "docker exec $actualContainer nc -zv -w 3 localhost $mysqlPort 2>&1 || echo 'PORT_FAIL'"
$portResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $portCmd
if ($portResult.ExitCode -eq 0 -or ($portResult.Output -match 'succeeded')) {
$isConnected = $true
$detailMsg = "端口连通性测试成功 (端口 $mysqlPort)"
Write-Log -Level "SUCCESS" -Message "[MySQL] TCP端口检测成功"
} else {
$outputText = $mysqlResult.Output -join ""
$detailMsg = "连接失败: $outputText"
Write-Log -Level "ERROR" -Message "[MySQL] 连接测试失败: $outputText"
}
}
} catch {
$detailMsg = "检测异常: $($_.Exception.Message)"
Write-Log -Level "ERROR" -Message "[MySQL] 检测异常: $($_.Exception.Message)"
}
$results += @{
Check = "MySQL连接检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = $detailMsg
Success = $isConnected
}
return $results
}
function Test-FastDFSConnection {
param(
[hashtable]$Server
)
Write-Log -Level "INFO" -Message "========== FastDFS连接检测 =========="
$results = @()
$containerName = $MiddlewareConfig.FastDFS.ContainerName
# 1. 检测ustorage容器
$checkContainerCmd = "docker ps --format '{{.Names}}' | grep -E '$containerName|storage' | head -n 1"
$containerCheck = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkContainerCmd
if ($containerCheck.ExitCode -ne 0 -or -not $containerCheck.Output) {
Write-Log -Level "WARN" -Message "[FastDFS] 未检测到ustorage容器($containerName),跳过FastDFS连接检测"
$results += @{
Check = "FastDFS连接检测"
Status = "跳过"
Details = "未检测到ustorage容器"
Success = $false
}
return $results
}
$actualContainer = ($containerCheck.Output[0].Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[FastDFS] 检测到容器: $actualContainer"
# 2. 执行文件上传测试
$isConnected = $false
$detailMsg = ""
try {
# 创建临时测试文件
$testFileName = "test_fdfs_$(Get-Date -Format 'yyyyMMddHHmmss').txt"
$remoteTestFile = "/tmp/$testFileName"
# 在容器内创建测试文件
$createCmd = "docker exec $actualContainer bash -c 'echo \"FastDFS test file\" > $remoteTestFile 2>&1'"
$createResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $createCmd
if ($createResult.ExitCode -eq 0) {
# 执行 fdfs_test 上传
$uploadCmd = "docker exec $actualContainer bash -c 'cd /home && fdfs_test /etc/fdfs/client.conf upload $remoteTestFile 2>&1' || echo 'FDFS_FAIL'"
$uploadResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $uploadCmd
$uploadOutput = $uploadResult.Output -join ""
if ($uploadOutput -match 'SUCCESS' -or $uploadOutput -match 'file_url') {
$isConnected = $true
$detailMsg = "文件上传测试成功"
Write-Log -Level "SUCCESS" -Message "[FastDFS] 文件上传测试成功"
} else {
# 降级:检查fdfs_test命令是否存在
$checkCmd = "docker exec $actualContainer which fdfs_test 2>&1 || echo 'NOT_FOUND'"
$checkResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $checkCmd
$checkOutput = $checkResult.Output -join ""
if ($checkOutput -match 'NOT_FOUND') {
$detailMsg = "fdfs_test命令不存在,跳过上传测试"
Write-Log -Level "WARN" -Message "[FastDFS] fdfs_test命令不存在"
} else {
$detailMsg = "文件上传测试失败: $uploadOutput"
Write-Log -Level "ERROR" -Message "[FastDFS] 文件上传测试失败"
}
}
# 清理临时文件
$cleanupCmd = "docker exec $actualContainer rm -f $remoteTestFile 2>/dev/null || true"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cleanupCmd | Out-Null
} else {
$detailMsg = "创建测试文件失败"
Write-Log -Level "ERROR" -Message "[FastDFS] 创建测试文件失败"
}
} catch {
$detailMsg = "检测异常: $($_.Exception.Message)"
Write-Log -Level "ERROR" -Message "[FastDFS] 检测异常: $($_.Exception.Message)"
}
$results += @{
Check = "FastDFS连接检测"
Status = $(if ($isConnected) { "正常" } else { "异常" })
Details = $detailMsg
Success = $isConnected
}
return $results
}
# ================================
# Nginx日志导出功能
# ================================
function Export-NginxErrorLogFromContainer { function Export-NginxErrorLogFromContainer {
param( param(
[Parameter(Mandatory=$true)] [hashtable]$Server, [Parameter(Mandatory=$true)] [hashtable]$Server,
...@@ -2636,7 +3009,8 @@ function Show-HealthReport { ...@@ -2636,7 +3009,8 @@ function Show-HealthReport {
[hashtable]$NTPResults, [hashtable]$NTPResults,
[hashtable]$FilePermResults, [hashtable]$FilePermResults,
[array]$ContainerInfo, [array]$ContainerInfo,
[array]$AndroidResults [array]$AndroidResults,
[array]$MiddlewareResults
) )
if (-not $SCRIPT_DIR -or [string]::IsNullOrWhiteSpace($SCRIPT_DIR)) { $SCRIPT_DIR = (Get-Location).Path } if (-not $SCRIPT_DIR -or [string]::IsNullOrWhiteSpace($SCRIPT_DIR)) { $SCRIPT_DIR = (Get-Location).Path }
...@@ -2920,6 +3294,21 @@ function Show-HealthReport { ...@@ -2920,6 +3294,21 @@ function Show-HealthReport {
} }
$md += "" $md += ""
# 中间件连接检测结果 (PRD 4.18)
if ($MiddlewareResults -and $MiddlewareResults.Count -gt 0) {
Write-Host "【中间件连接检测】" -ForegroundColor Yellow
$md += "## 中间件连接检测"
foreach ($r in $MiddlewareResults) {
$icon = if ($r.Success) { "✅" } elseif ($r.Status -eq "跳过") { "ℹ️" } else { "❌" }
$line = "- $icon $($r.Check): $($r.Status)"
if ($r.Details) { $line += " | $($r.Details)" }
$md += $line
Write-Host " $line"
}
$md += ""
Write-Host ""
}
# 安卓设备自检结果 # 安卓设备自检结果
if ($AndroidResults -and $AndroidResults.Count -gt 0) { if ($AndroidResults -and $AndroidResults.Count -gt 0) {
Write-Host "【安卓设备自检】" -ForegroundColor Yellow Write-Host "【安卓设备自检】" -ForegroundColor Yellow
...@@ -3972,6 +4361,29 @@ function Main { ...@@ -3972,6 +4361,29 @@ function Main {
} }
$md += "" $md += ""
# 中间件连接检测 (PRD 4.18)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始中间件连接检测 =========="
$middlewareResults = @()
# MQTT连接检测
$mqttResults = Test-MQTTConnection -Server $server
if ($mqttResults) { $middlewareResults += $mqttResults }
# Redis连接检测
$redisConnResults = Test-RedisConnection -Server $server
if ($redisConnResults) { $middlewareResults += $redisConnResults }
# MySQL连接检测
$mysqlConnResults = Test-MySQLConnection -Server $server
if ($mysqlConnResults) { $middlewareResults += $mysqlConnResults }
# FastDFS连接检测
$fastdfsConnResults = Test-FastDFSConnection -Server $server
if ($fastdfsConnResults) { $middlewareResults += $fastdfsConnResults }
Write-Log -Level "INFO" -Message "========== 中间件连接检测完成 =========="
# 检测配置文件中的IP地址 # 检测配置文件中的IP地址
Write-Host "" Write-Host ""
Write-Log -Level "INFO" -Message "========== 开始检测配置文件 IP ==========" Write-Log -Level "INFO" -Message "========== 开始检测配置文件 IP =========="
...@@ -4035,7 +4447,8 @@ function Main { ...@@ -4035,7 +4447,8 @@ function Main {
-NTPResults $ntpResults ` -NTPResults $ntpResults `
-FilePermResults $filePermResults ` -FilePermResults $filePermResults `
-ContainerInfo $containerInfo ` -ContainerInfo $containerInfo `
-AndroidResults $androidResults -AndroidResults $androidResults `
-MiddlewareResults $middlewareResults
} }
# 执行主函数 # 执行主函数
......
...@@ -20,7 +20,7 @@ set -euo pipefail ...@@ -20,7 +20,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ✅ 确保相对路径(./linux_x86_adb/adb 等)以脚本目录为基准 # ✅ 确保相对路径(./linux_x86_adb/adb 等)以脚本目录为基准
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
SCRIPT_VERSION="1.0.3" SCRIPT_VERSION="1.0.4"
SSH_TIMEOUT=30 # 占位:与 ps1 一致,这里不使用 SSH_TIMEOUT=30 # 占位:与 ps1 一致,这里不使用
LOG_DIR="$SCRIPT_DIR/logs" LOG_DIR="$SCRIPT_DIR/logs"
...@@ -438,6 +438,36 @@ UPYTHON_PORTS_OLD=( ...@@ -438,6 +438,36 @@ UPYTHON_PORTS_OLD=(
# ------------------------------ # ------------------------------
DNS_TEST_DOMAINS=("www.baidu.com" "www.qq.com" "www.aliyun.com") DNS_TEST_DOMAINS=("www.baidu.com" "www.qq.com" "www.aliyun.com")
# ------------------------------
# 中间件连接检测配置 (PRD 4.18)
# ------------------------------
# MQTT 主题列表(用于记录参考)
MQTT_TOPICS=(
"/androidPanel/"
"/iot/v1/conference/service/request/"
"message/paperLessService/callService"
"message/paperLessAuthor/onthewall"
"/meeting/message/"
"/iot/v1/device/event/request/"
"/iot/v1/device/service/request/"
)
# 中间件容器名和配置
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"
get_dns_nameservers() { get_dns_nameservers() {
if [[ -r /etc/resolv.conf ]]; then if [[ -r /etc/resolv.conf ]]; then
grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n 5 | paste -sd',' - || true grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n 5 | paste -sd',' - || true
...@@ -1430,6 +1460,274 @@ check_emqx_container_exception() { ...@@ -1430,6 +1460,274 @@ check_emqx_container_exception() {
fi fi
} }
# ------------------------------
# 中间件连接检测功能 (PRD 4.18)
# ------------------------------
# MQTT 连接检测
test_mqtt_connection() {
section "MQTT主题连接检测"
local container_name="$MIDDLEWARE_MQTT_CONTAINER"
local mqtt_port="$MIDDLEWARE_MQTT_PORT"
local dashboard_port="$MIDDLEWARE_MQTT_DASHBOARD_PORT"
# 1. 检测EMQX容器
local actual_container=""
actual_container="$(docker ps --format '{{.Names}}' | grep -E "${container_name}|emqx" | head -n 1 || true)"
if [[ -z "$actual_container" ]]; then
log WARN "[MQTT] 未检测到EMQX容器(${container_name}),跳过MQTT连接检测"
report_kv_set "mqtt.status" "SKIP"
report_kv_set "mqtt.detail" "未检测到EMQX容器"
return 0
fi
log INFO "[MQTT] 检测到容器: $actual_container"
# 2. 多层级降级检测
local detection_method=""
local is_connected=0
local detail_msg=""
# 方案A: EMQX Dashboard API
local api_output
api_output="$(docker exec "$actual_container" curl -s --connect-timeout 5 "http://localhost:${dashboard_port}/api/v4/status" 2>/dev/null || echo "API_FAIL")"
if [[ "$api_output" != "API_FAIL" ]] && [[ "$api_output" =~ status|emqx ]]; then
is_connected=1
detection_method="Dashboard API"
detail_msg="EMQX Dashboard API响应正常 (端口 ${dashboard_port})"
log SUCCESS "[MQTT] Dashboard API检测成功: $actual_container"
fi
# 方案B: TCP端口连通性(如果方案A失败)
if [[ $is_connected -eq 0 ]]; then
local port_output
port_output="$(docker exec "$actual_container" nc -zv -w 3 "localhost" "$mqtt_port" 2>&1 || echo "PORT_FAIL")"
if [[ "$port_output" =~ succeeded ]]; then
is_connected=1
detection_method="TCP端口连通性"
detail_msg="MQTT端口 ${mqtt_port} 可访问"
log SUCCESS "[MQTT] TCP端口检测成功: ${actual_container}:${mqtt_port}"
fi
fi
# 方案C: 进程状态(如果前两方案都失败)
if [[ $is_connected -eq 0 ]]; then
local proc_output
proc_output="$(docker exec "$actual_container" ps aux | grep -e 'emqx' | grep -v grep | head -n 1 || echo "PROC_FAIL")"
if [[ "$proc_output" != "PROC_FAIL" ]]; then
is_connected=1
detection_method="进程状态"
detail_msg="EMQX进程运行中"
log SUCCESS "[MQTT] 进程检测成功: EMQX进程运行"
else
detail_msg="所有检测方法均失败,MQTT服务可能异常"
log ERROR "[MQTT] 所有检测方法均失败"
fi
fi
if [[ $is_connected -eq 1 ]]; then
report_kv_set "mqtt.status" "OK"
else
report_kv_set "mqtt.status" "FAIL"
fi
report_kv_set "mqtt.detail" "${detail_msg} | 检测方式: ${detection_method}"
report_kv_set "mqtt.container" "$actual_container"
# 输出MQTT主题列表(仅供参考)
log INFO "[MQTT] 监听主题列表: ${MQTT_TOPICS[*]}"
}
# Redis 连接检测
test_redis_connection() {
section "Redis连接检测"
local container_name="$MIDDLEWARE_REDIS_CONTAINER"
local redis_port="$MIDDLEWARE_REDIS_PORT"
local redis_password="$MIDDLEWARE_REDIS_PASSWORD"
# 1. 检测Redis容器
local actual_container=""
actual_container="$(docker ps --format '{{.Names}}' | grep -E "${container_name}|redis" | head -n 1 || true)"
if [[ -z "$actual_container" ]]; then
log WARN "[Redis] 未检测到Redis容器(${container_name}),跳过Redis连接检测"
report_kv_set "redis_conn.status" "SKIP"
report_kv_set "redis_conn.detail" "未检测到Redis容器"
return 0
fi
log INFO "[Redis] 检测到容器: $actual_container"
# 2. 执行redis-cli ping测试
local is_connected=0
local detail_msg=""
local redis_output
redis_output="$(docker exec "$actual_container" redis-cli -h "localhost" -p "$redis_port" -a "${redis_password}" --no-auth-warning ping 2>&1 || echo "REDIS_FAIL")"
if [[ "$redis_output" =~ PONG ]]; then
is_connected=1
detail_msg="连接成功,PING响应: PONG"
log SUCCESS "[Redis] redis-cli PING测试成功"
else
detail_msg="连接失败: ${redis_output}"
log ERROR "[Redis] redis-cli PING测试失败: ${redis_output}"
fi
if [[ $is_connected -eq 1 ]]; then
report_kv_set "redis_conn.status" "OK"
else
report_kv_set "redis_conn.status" "FAIL"
fi
report_kv_set "redis_conn.detail" "$detail_msg"
report_kv_set "redis_conn.container" "$actual_container"
}
# MySQL 连接检测
test_mysql_connection() {
section "MySQL连接检测"
local container_name="$MIDDLEWARE_MYSQL_CONTAINER"
local mysql_port="$MIDDLEWARE_MYSQL_PORT"
local mysql_password="$MIDDLEWARE_MYSQL_PASSWORD"
# 1. 检测MySQL容器
local actual_container=""
actual_container="$(docker ps --format '{{.Names}}' | grep -E "${container_name}|mysql" | head -n 1 || true)"
if [[ -z "$actual_container" ]]; then
log WARN "[MySQL] 未检测到MySQL容器(${container_name}),跳过MySQL连接检测"
report_kv_set "mysql_conn.status" "SKIP"
report_kv_set "mysql_conn.detail" "未检测到MySQL容器"
return 0
fi
log INFO "[MySQL] 检测到容器: $actual_container"
# 2. 执行连接测试
local is_connected=0
local detail_msg=""
local mysql_output
mysql_output="$(docker exec "$actual_container" mysql -h "localhost" -P "$mysql_port" -uroot -p"${mysql_password}" -e 'SELECT 1' 2>&1 || echo "MYSQL_FAIL")"
if [[ "$mysql_output" != "MYSQL_FAIL" ]]; then
is_connected=1
detail_msg="连接成功,端口 ${mysql_port} 可访问"
log SUCCESS "[MySQL] mysql客户端连接测试成功"
else
# 降级:使用nc测试端口
local port_output
port_output="$(docker exec "$actual_container" nc -zv -w 3 "localhost" "$mysql_port" 2>&1 || echo "PORT_FAIL")"
if [[ "$port_output" =~ succeeded ]]; then
is_connected=1
detail_msg="端口连通性测试成功 (端口 ${mysql_port})"
log SUCCESS "[MySQL] TCP端口检测成功"
else
detail_msg="连接失败: ${mysql_output}"
log ERROR "[MySQL] 连接测试失败: ${mysql_output}"
fi
fi
if [[ $is_connected -eq 1 ]]; then
report_kv_set "mysql_conn.status" "OK"
else
report_kv_set "mysql_conn.status" "FAIL"
fi
report_kv_set "mysql_conn.detail" "$detail_msg"
report_kv_set "mysql_conn.container" "$actual_container"
}
# FastDFS 连接检测
test_fastdfs_connection() {
section "FastDFS连接检测"
local container_name="$MIDDLEWARE_FASTDFS_CONTAINER"
# 1. 检测ustorage容器
local actual_container=""
actual_container="$(docker ps --format '{{.Names}}' | grep -E "${container_name}|storage" | head -n 1 || true)"
if [[ -z "$actual_container" ]]; then
log WARN "[FastDFS] 未检测到ustorage容器(${container_name}),跳过FastDFS连接检测"
report_kv_set "fastdfs_conn.status" "SKIP"
report_kv_set "fastdfs_conn.detail" "未检测到ustorage容器"
return 0
fi
log INFO "[FastDFS] 检测到容器: $actual_container"
# 2. 执行文件上传测试
local is_connected=0
local detail_msg=""
# 创建临时测试文件
local test_filename="test_fdfs_$(date '+%Y%m%d%H%M%S').txt"
local remote_test_file="/tmp/${test_filename}"
# 在容器内创建测试文件
if docker exec "$actual_container" bash -c "echo \"FastDFS test file\" > ${remote_test_file} 2>&1"; then
# 执行 fdfs_test 上传
local upload_output
upload_output="$(docker exec "$actual_container" bash -c "cd /home && fdfs_test /etc/fdfs/client.conf upload ${remote_test_file} 2>&1" || echo "FDFS_FAIL")"
if [[ "$upload_output" =~ SUCCESS ]] || [[ "$upload_output" =~ file_url ]]; then
is_connected=1
detail_msg="文件上传测试成功"
log SUCCESS "[FastDFS] 文件上传测试成功"
else
# 降级:检查fdfs_test命令是否存在
local check_output
check_output="$(docker exec "$actual_container" which fdfs_test 2>&1 || echo "NOT_FOUND")"
if [[ "$check_output" =~ NOT_FOUND ]]; then
detail_msg="fdfs_test命令不存在,跳过上传测试"
log WARN "[FastDFS] fdfs_test命令不存在"
else
detail_msg="文件上传测试失败: ${upload_output}"
log ERROR "[FastDFS] 文件上传测试失败"
fi
fi
# 清理临时文件
docker exec "$actual_container" rm -f "$remote_test_file" >/dev/null 2>&1 || true
else
detail_msg="创建测试文件失败"
log ERROR "[FastDFS] 创建测试文件失败"
fi
if [[ $is_connected -eq 1 ]]; then
report_kv_set "fastdfs_conn.status" "OK"
else
report_kv_set "fastdfs_conn.status" "FAIL"
fi
report_kv_set "fastdfs_conn.detail" "$detail_msg"
report_kv_set "fastdfs_conn.container" "$actual_container"
}
# 中间件连接检测统一入口
test_middleware_connections() {
section "中间件连接检测"
# MQTT连接检测
test_mqtt_connection
# Redis连接检测
test_redis_connection
# MySQL连接检测
test_mysql_connection
# FastDFS连接检测
test_fastdfs_connection
}
# ------------------------------ # ------------------------------
# 17) 现场数据备份(对齐 ps1:目录复制 + 可选 mysqldump + 打包) # 17) 现场数据备份(对齐 ps1:目录复制 + 可选 mysqldump + 打包)
# - 本机版不做“下载到本地”,只在本机生成 tar.gz 并写入报告 # - 本机版不做“下载到本地”,只在本机生成 tar.gz 并写入报告
...@@ -1992,6 +2290,26 @@ write_report() { ...@@ -1992,6 +2290,26 @@ write_report() {
[[ -n "$emqx_recheck" ]] && w "- recheck: \`$emqx_recheck\`" [[ -n "$emqx_recheck" ]] && w "- recheck: \`$emqx_recheck\`"
w "" w ""
# 中间件连接检测结果 (PRD 4.18)
w "### 中间件连接检测(详细)"
w "- MQTT: \`$(report_kv_get "mqtt.status")\`"
local mqtt_detail
mqtt_detail="$(report_kv_get "mqtt.detail")"
[[ -n "$mqtt_detail" ]] && w " - 详情: $mqtt_detail"
w "- Redis: \`$(report_kv_get "redis_conn.status")\`"
local redis_conn_detail
redis_conn_detail="$(report_kv_get "redis_conn.detail")"
[[ -n "$redis_conn_detail" ]] && w " - 详情: $redis_conn_detail"
w "- MySQL: \`$(report_kv_get "mysql_conn.status")\`"
local mysql_conn_detail
mysql_conn_detail="$(report_kv_get "mysql_conn.detail")"
[[ -n "$mysql_conn_detail" ]] && w " - 详情: $mysql_conn_detail"
w "- FastDFS: \`$(report_kv_get "fastdfs_conn.status")\`"
local fastdfs_conn_detail
fastdfs_conn_detail="$(report_kv_get "fastdfs_conn.detail")"
[[ -n "$fastdfs_conn_detail" ]] && w " - 详情: $fastdfs_conn_detail"
w ""
# 说明 # 说明
w "## 说明" w "## 说明"
w "" w ""
...@@ -2214,6 +2532,9 @@ main() { ...@@ -2214,6 +2532,9 @@ main() {
# ✅ 补齐:Emqx 异常检测(你已经实现但 main 没调用) # ✅ 补齐:Emqx 异常检测(你已经实现但 main 没调用)
check_emqx_container_exception check_emqx_container_exception
# 9.5) 中间件连接检测 (PRD 4.18)
test_middleware_connections
report_add "" report_add ""
report_add "## 容器与中间件" report_add "## 容器与中间件"
report_add "- docker: $(report_kv_get "docker.available")" report_add "- docker: $(report_kv_get "docker.available")"
......
...@@ -66,6 +66,9 @@ function Require-PuttyTools { ...@@ -66,6 +66,9 @@ function Require-PuttyTools {
return @{ plink=$plink; pscp=$pscp } return @{ plink=$plink; pscp=$pscp }
} }
# 全局变量:连接间隔时间(毫秒)
$SCRIPT:ConnectionDelayMs = 1000
function Invoke-PlinkCommand { function Invoke-PlinkCommand {
param( param(
[Parameter(Mandatory=$true)][string]$PlinkPath, [Parameter(Mandatory=$true)][string]$PlinkPath,
...@@ -73,10 +76,17 @@ function Invoke-PlinkCommand { ...@@ -73,10 +76,17 @@ function Invoke-PlinkCommand {
[Parameter(Mandatory=$true)][int]$Port, [Parameter(Mandatory=$true)][int]$Port,
[Parameter(Mandatory=$true)][string]$User, [Parameter(Mandatory=$true)][string]$User,
[Parameter(Mandatory=$true)][string]$Password, [Parameter(Mandatory=$true)][string]$Password,
[Parameter(Mandatory=$true)][string]$Command [Parameter(Mandatory=$true)][string]$Command,
[switch]$NoDelay
) )
# 每次连接前短暂延迟,避免SSH连接冲突
if (-not $NoDelay) {
Start-Sleep -Milliseconds $SCRIPT:ConnectionDelayMs
}
# 不使用 -batch 参数,允许交互式确认主机密钥 # 不使用 -batch 参数,允许交互式确认主机密钥
# 注意:plink 的 -pw 直接传入即可;不要用奇怪的三引号拼接,容易导致解析出错
$args = @( $args = @(
"-ssh", "-ssh",
"-P", "$Port", "-P", "$Port",
...@@ -86,7 +96,10 @@ function Invoke-PlinkCommand { ...@@ -86,7 +96,10 @@ function Invoke-PlinkCommand {
$Command $Command
) )
Write-Info "远端执行: $Command" Write-Info ("远端执行: {0}" -f $Command)
# 调试信息:显示连接参数(隐藏密码)
Write-Info ("[DEBUG] 连接参数: Host={0}, Port={1}, User={2}, PwdLength={3}" -f $HostName, $Port, $User, $Password.Length)
Write-Info ("[DEBUG] plink.exe 路径: {0}" -f $PlinkPath)
# 使用临时文件捕获输出 # 使用临时文件捕获输出
$tmpOut = Join-Path $env:TEMP ("plink_{0}.out.log" -f ([guid]::NewGuid().ToString("N"))) $tmpOut = Join-Path $env:TEMP ("plink_{0}.out.log" -f ([guid]::NewGuid().ToString("N")))
...@@ -102,16 +115,35 @@ function Invoke-PlinkCommand { ...@@ -102,16 +115,35 @@ function Invoke-PlinkCommand {
if (Test-Path $tmpOut) { $outText = (Get-Content $tmpOut -Raw -Encoding utf8) } if (Test-Path $tmpOut) { $outText = (Get-Content $tmpOut -Raw -Encoding utf8) }
if (Test-Path $tmpErr) { $errText = (Get-Content $tmpErr -Raw -Encoding utf8) } if (Test-Path $tmpErr) { $errText = (Get-Content $tmpErr -Raw -Encoding utf8) }
# 输出标准输出(调试用)
if (-not [string]::IsNullOrWhiteSpace($outText)) {
Write-Info ("[DEBUG] 标准输出: {0}" -f $outText.Trim())
}
# 输出错误信息(如果有) # 输出错误信息(如果有)
if (-not [string]::IsNullOrWhiteSpace($errText)) { if (-not [string]::IsNullOrWhiteSpace($errText)) {
Write-Host $errText Write-Err ("[DEBUG] 标准错误: {0}" -f $errText.Trim())
} }
# 显示 ExitCode
Write-Info ("[DEBUG] ExitCode: {0}" -f $p.ExitCode)
# 临时文件保留用于调试(取消注释以下行可保留日志)
# Write-Info ("[DEBUG] 日志文件: {0}, {1}" -f $tmpOut, $tmpErr)
# 清理临时文件 # 清理临时文件
Remove-Item -ErrorAction SilentlyContinue $tmpOut, $tmpErr Remove-Item -ErrorAction SilentlyContinue $tmpOut, $tmpErr
if ($p.ExitCode -ne 0) { if ($p.ExitCode -ne 0) {
throw "plink 执行失败,ExitCode=$($p.ExitCode)。命令:$Command" # 常见 ExitCode 含义
$exitCodeMeaning = switch ($p.ExitCode) {
1 { "一般错误(可能是密码错误、命令执行失败等)" }
2 { "连接被拒绝" }
5 { "SSH 协议版本不匹配" }
6 { "密钥文件协商失败" }
default { "未知错误" }
}
throw "plink 执行失败,ExitCode=$($p.ExitCode) ($exitCodeMeaning)。命令:$Command"
} }
return $outText return $outText
...@@ -139,6 +171,9 @@ function Invoke-PscpUpload { ...@@ -139,6 +171,9 @@ function Invoke-PscpUpload {
$remoteTarget = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteDir) $remoteTarget = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteDir)
foreach ($lf in $LocalFiles) { foreach ($lf in $LocalFiles) {
# 每次上传前短暂延迟
Start-Sleep -Milliseconds $SCRIPT:ConnectionDelayMs
$args = @( $args = @(
"-P", "$Port", "-P", "$Port",
"-pw", $Password, "-pw", $Password,
...@@ -169,6 +204,9 @@ function Invoke-PscpDownload { ...@@ -169,6 +204,9 @@ function Invoke-PscpDownload {
New-Item -ItemType Directory -Path $LocalDir | Out-Null New-Item -ItemType Directory -Path $LocalDir | Out-Null
} }
# 下载前短暂延迟
Start-Sleep -Milliseconds $SCRIPT:ConnectionDelayMs
$remoteSpec = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteFile) $remoteSpec = ("{0}@{1}:{2}" -f $User, $HostName, $RemoteFile)
$args = @( $args = @(
...@@ -204,22 +242,33 @@ $remoteDir = "" ...@@ -204,22 +242,33 @@ $remoteDir = ""
if ($usePreset -eq "是(使用预设)") { if ($usePreset -eq "是(使用预设)") {
$preset = Read-Choice "请选择服务器预设:" @("测试环境-前端服务器", "测试环境-后端服务器") $preset = Read-Choice "请选择服务器预设:" @("测试环境-前端服务器", "测试环境-后端服务器")
$usePresetPassword = Read-Choice "是否使用预设密码?" @("是(使用预设)", "否(手动输入)")
if ($preset -eq "测试环境-前端服务器") { if ($preset -eq "测试环境-前端服务器") {
$serverIp = "10.126.4.79" $serverIp = "10.126.4.79"
$sshPortStr = "1122" $sshPortStr = "1122"
$sshPort = 1122 $sshPort = 1122
$username = "root" $username = "root"
if ($usePresetPassword -eq "是(使用预设)") {
$plainPwd = "Admin@123Admin@123" $plainPwd = "Admin@123Admin@123"
} else {
$securePwd = Read-Host "请输入密码" -AsSecureString
$plainPwd = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd))
}
$remoteDir = "/home/appadmin/" $remoteDir = "/home/appadmin/"
Write-Info "已选择测试环境-前端服务器预设" Write-Info "已选择测试环境-前端服务器"
} else { } else {
$serverIp = "10.126.4.81" $serverIp = "10.126.4.81"
$sshPortStr = "1122" $sshPortStr = "1122"
$sshPort = 1122 $sshPort = 1122
$username = "appadmin" $username = "appadmin"
if ($usePresetPassword -eq "是(使用预设)") {
$plainPwd = "CGNadm!@345CGNadm!@345" $plainPwd = "CGNadm!@345CGNadm!@345"
} else {
$securePwd = Read-Host "请输入密码" -AsSecureString
$plainPwd = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd))
}
$remoteDir = "/home/appadmin/" $remoteDir = "/home/appadmin/"
Write-Info "已选择测试环境-后端服务器预设" Write-Info "已选择测试环境-后端服务器"
} }
Write-Info "参数确认:IP=$serverIp Port=$sshPort User=$username RemoteDir=$remoteDir" Write-Info "参数确认:IP=$serverIp Port=$sshPort User=$username RemoteDir=$remoteDir"
} else { } else {
...@@ -253,15 +302,18 @@ Write-Info "首次连接时会提示确认主机密钥,请选择 'yes' 确认 ...@@ -253,15 +302,18 @@ Write-Info "首次连接时会提示确认主机密钥,请选择 'yes' 确认
# 判断是否为中广核项目(需要特殊处理) # 判断是否为中广核项目(需要特殊处理)
$isCgnProject = ($platformType -eq "中广核项目") $isCgnProject = ($platformType -eq "中广核项目")
$sudoPassword = "" $rootPassword = ""
if ($isCgnProject) { if ($isCgnProject) {
Write-Info "检测到中广核项目,将使用zip压缩包方式部署" Write-Info "检测到中广核项目,将使用zip压缩包方式部署"
# 如果使用预设且是后端服务器,则已经有密码了,不需要再输入 # 后端服务器需要root密码,用于su切换,前端服务器(root用户)不需要
if ($usePreset -eq "是(使用预设)" -and $preset -eq "测试环境-后端服务器") { if ($usePreset -eq "是(使用预设)" -and $preset -eq "测试环境-后端服务器") {
$sudoPassword = $plainPwd $useRootPreset = Read-Choice "是否使用预设的root密码?" @("是(使用预设)", "否(输入root密码)")
Write-Info "使用预设的sudo密码" if ($useRootPreset -eq "是(使用预设)") {
$rootPassword = "Admin@123Admin@123"
Write-Info "使用预设的root密码"
} else { } else {
$sudoPassword = Read-NonEmpty "请输入sudo密码" "CGNadm!@345CGNadm!@345" $rootPassword = Read-NonEmpty "请输入root密码"
}
} }
} }
...@@ -286,7 +338,13 @@ Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $ ...@@ -286,7 +338,13 @@ Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $
Write-Info "SSH 连接测试成功。" Write-Info "SSH 连接测试成功。"
# 1) 远端创建目录 + 检查 unzip # 1) 远端创建目录 + 检查 unzip
Invoke-PlinkCommand -PlinkPath $plink -HostName $serverIp -Port $sshPort -User $username -Password $plainPwd -Command "mkdir -p '$remoteDir'; command -v unzip >/dev/null 2>&1 || (echo '缺少 unzip,请安装:yum install -y unzip 或 apt-get install -y unzip' && exit 2)" $cmdEnsureDirAndUnzip = @"
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
# ====== 3.2 本地准备:找到 zip 包 + program_update.sh ====== # ====== 3.2 本地准备:找到 zip 包 + program_update.sh ======
# 约定:zip 压缩包与本 remote_program_update.ps1 同级目录 # 约定:zip 压缩包与本 remote_program_update.ps1 同级目录
$zipFiles = @( $zipFiles = @(
...@@ -325,16 +383,45 @@ $zipName = Split-Path $zipPath -Leaf ...@@ -325,16 +383,45 @@ $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'"
# ====== 执行 program_update.sh 并下载备份包 ====== # ====== 执行 program_update.sh 并下载备份包 ======
# 等待文件系统同步完成
Write-Info "等待远端文件系统同步..."
Start-Sleep -Seconds 2
# 额外打印远端 program_update.sh 版本,便于回溯 # 额外打印远端 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"
# 中广核项目需要通过 sudo 切换到 root 用户执行 # 中广核项目需要根据服务器类型决定是否使用 su
if ($isCgnProject) { if ($isCgnProject) {
Write-Info "中广核项目:使用 sudo 执行更新脚本" # 前端服务器使用 root 用户,不需要 su;后端服务器使用 appadmin 用户,需要通过 su 切换到 root
$execCmd = "cd '$remoteDir' && echo '$sudoPassword' | sudo -S chmod +x ./program_update.sh && echo '$sudoPassword' | sudo -S env LC_CTYPE=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'" $needSu = ($usePreset -eq "是(使用预设)" -and $preset -eq "测试环境-后端服务器")
} else {
$execCmd = "cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'" 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\"'"
}
else {
Write-Info "中广核项目(前端服务器):直接执行更新脚本(root用户,无需su)"
$execCmd = @"
cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'
"@
}
} }
else {
# 非中广核项目:直接执行
$execCmd = @"
cd '$remoteDir' && chmod +x ./program_update.sh && ./program_update.sh --platform '$platformType' --system '$systemType' --update '$updateType' --workdir '$remoteDir'
"@
}
# 执行更新脚本前等待,确保之前的操作完成
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
# 从输出解析 BACKUP_TAR # 从输出解析 BACKUP_TAR
......
...@@ -790,6 +790,38 @@ ls -la /etc/cron.d/ 2>/dev/null ...@@ -790,6 +790,38 @@ ls -la /etc/cron.d/ 2>/dev/null
--- ---
### 4.18 中间件连接检测
#### 4.18.1 MQTT主题连接检测
- 连接当前服务器的MQTT服务
- 订阅标准版主题信息:
- /androidPanel/
- /iot/v1/conference/service/request/
- message/paperLessService/callService
- message/paperLessAuthor/onthewall
- /meeting/message/
- /iot/v1/device/event/request/
- /iot/v1/device/service/request/
- /iot/v1/conference/service/request/
- 主题发送消息检测:
每个主题订阅后补充消息推送监测。
#### 4.18.2 Redis连接检测
- 连接当前服务器的Redis服务,端口为6379,账号为root,密码为:dNrprU&2S
#### 4.18.3 Mysql连接检测
- 连接当前服务器的Mysql服务,端口为8306,账号为root,密码为:dNrprU&2S
#### 4.18.4 Fastfds连接检测
- 连接当前Fastfds服务
- 测试文件上传功能(可固定文件名称为:test.png)
- 指令如下:
- docker cp ./test.png ustorage:/home
- docker exec -it ustorage bash
- cd /home/
- fdfs_test /etc/fdfs/client.conf upload test.png
- 检查返回结果
### 4.18 报告生成 ### 4.18 报告生成
#### 4.18.1 报告格式 #### 4.18.1 报告格式
......
...@@ -36,8 +36,8 @@ ...@@ -36,8 +36,8 @@
| 版本 | 当前版本 | 状态 | 说明 | | 版本 | 当前版本 | 状态 | 说明 |
|------|----------|------|------| |------|----------|------|------|
| Windows 版 | 1.0.3 | ✅ 稳定 | 功能完整,已投入使用 | | Windows 版 | 1.0.4 | ✅ 稳定 | 功能完整,已投入使用 |
| Linux 版 | 1.0.3 | ✅ 稳定 | 功能完整,已投入使用 | | Linux 版 | 1.0.4 | ✅ 稳定 | 功能完整,已投入使用 |
### 2.2 功能对比矩阵 ### 2.2 功能对比矩阵
...@@ -64,6 +64,10 @@ ...@@ -64,6 +64,10 @@
| 日志导出 | ✅ | ✅ | ⚠️ 差异 (导出方式) | | 日志导出 | ✅ | ✅ | ⚠️ 差异 (导出方式) |
| 数据备份 | ✅ | ✅ | ⚠️ 差异 (下载方式) | | 数据备份 | ✅ | ✅ | ⚠️ 差异 (下载方式) |
| 定时任务查询 | ❌ | ✅ | ⚠️ Linux 独有 | | 定时任务查询 | ❌ | ✅ | ⚠️ Linux 独有 |
| MQTT主题连接检测 | ✅ | ✅ | ✅ 一致 |
| Redis连接检测 | ✅ | ✅ | ✅ 一致 |
| MySQL连接检测 | ✅ | ✅ | ✅ 一致 |
| FastDFS连接检测 | ✅ | ✅ | ✅ 一致 |
**图例:** **图例:**
- ✅ 已实现 - ✅ 已实现
...@@ -75,7 +79,7 @@ ...@@ -75,7 +79,7 @@
| 优先级 | 任务 | Windows 版 | Linux 版 | | 优先级 | 任务 | Windows 版 | Linux 版 |
|--------|------|-----------|----------| |--------|------|-----------|----------|
| 🟢 低 | 版本号已统一为 1.0.3 | 1.0.3 | 1.0.3 | | 🟢 低 | 版本号已统一为 1.0.4 | 1.0.4 | 1.0.4 |
| 🟢 低 | 差异说明文档化 | 已完成 | 已完成 | | 🟢 低 | 差异说明文档化 | 已完成 | 已完成 |
--- ---
...@@ -84,14 +88,24 @@ ...@@ -84,14 +88,24 @@
### 3.1 版本规划 ### 3.1 版本规划
#### v1.0.4 - 中间件连接检测(已发布)✅
**目标:** 基于 v1.0.3 版本,实现中间件连接检测功能
| 任务ID | 任务描述 | Windows 版 | Linux 版 | 负责人 | 状态 |
|--------|----------|-----------|----------|--------|------|
| T-1041 | MQTT主题连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
| T-1042 | Redis连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
| T-1043 | MySQL连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
| T-1044 | FastDFS连接检测 | 已实现 | 已实现 | - | ✅ 已完成 |
#### v1.1.0 - 功能增强(规划中) #### v1.1.0 - 功能增强(规划中)
**目标:** 基于当前 v1.0.3 版本,规划和实现功能增强 **目标:** 基于当前 v1.0.4 版本,规划和实现功能增强
| 任务ID | 任务描述 | Windows 版 | Linux 版 | 负责人 | 状态 | | 任务ID | 任务描述 | Windows 版 | Linux 版 | 负责人 | 状态 |
|--------|----------|-----------|----------|--------|------| |--------|----------|-----------|----------|--------|------|
| T-1101 | 定时任务查询功能移植到 Windows 版 | 待开发 | 已实现 | - | 🔲 待开始 | | T-1101 | 定时任务查询功能移植到 Windows 版 | 待开发 | 已实现 | - | 🔲 待开始 |
| T-1102 | 增加更多中间件检测 | 待开发 | 待开发 | - | 🔲 待开始 |
| T-1103 | 支持配置文件外部化 | 待开发 | 待开发 | - | 🔲 待开始 | | T-1103 | 支持配置文件外部化 | 待开发 | 待开发 | - | 🔲 待开始 |
#### v1.2.0 - 更多功能增强(规划中) #### v1.2.0 - 更多功能增强(规划中)
...@@ -270,6 +284,7 @@ graph TD ...@@ -270,6 +284,7 @@ graph TD
| 版本 | 日期 | 变更类型 | 变更内容 | 影响范围 | | 版本 | 日期 | 变更类型 | 变更内容 | 影响范围 |
|------|------|----------|----------|----------| |------|------|----------|----------|----------|
| 1.0.4 | 2026-01-29 | Minor | 新增中间件连接检测功能(MQTT/Redis/MySQL/FastDFS) | 两版本 |
| 1.0.3 | 2026-01-28 | Patch | 双版本版本号统一,功能完善 | 两版本 | | 1.0.3 | 2026-01-28 | Patch | 双版本版本号统一,功能完善 | 两版本 |
| 1.0.3 | 2026-01-XX | Patch | Windows 版功能完善 | Windows 版 | | 1.0.3 | 2026-01-XX | Patch | Windows 版功能完善 | Windows 版 |
| 1.0.0 | 2026-01-XX | Major | 初始版本 | 两版本 | | 1.0.0 | 2026-01-XX | Major | 初始版本 | 两版本 |
...@@ -366,8 +381,9 @@ graph TD ...@@ -366,8 +381,9 @@ graph TD
| M1 | 需求文档完成 | 2026-01-28 | ✅ 已完成 | | M1 | 需求文档完成 | 2026-01-28 | ✅ 已完成 |
| M2 | 计划文档完成 | 2026-01-28 | ✅ 已完成 | | M2 | 计划文档完成 | 2026-01-28 | ✅ 已完成 |
| M3 | 双版本 v1.0.3 统一 | 2026-01-28 | ✅ 已完成 | | M3 | 双版本 v1.0.3 统一 | 2026-01-28 | ✅ 已完成 |
| M4 | v1.1.0 功能增强完成 | 待定 | 🔲 计划中 | | M4 | v1.0.4 中间件连接检测 | 2026-01-29 | ✅ 已完成 |
| M5 | v1.2.0 更多功能增强完成 | 待定 | 🔲 计划中 | | M5 | v1.1.0 功能增强完成 | 待定 | 🔲 计划中 |
| M6 | v1.2.0 更多功能增强完成 | 待定 | 🔲 计划中 |
--- ---
......
...@@ -62,10 +62,14 @@ ...@@ -62,10 +62,14 @@
| 中广核项目 | ubains | 1套(cims-web) | 1套(cims-java) | 中广核项目 | | 中广核项目 | ubains | 1套(cims-web) | 1套(cims-java) | 中广核项目 |
#### 2.1.3 服务器预设信息 #### 2.1.3 服务器预设信息
| 项目 | 环境 | 服务器类型 | IP地址 | SSH端口 | 用户名 | 密码 | 存放目录 | | 项目 | 环境 | 服务器类型 | IP地址 | SSH端口 | 用户名 | 密码 | root密码 | 存放目录 |
|------|------|-----------|--------|---------|--------|------|---------| |------|------|-----------|--------|---------|--------|------|---------|---------|
| 中广核项目 | 测试环境 | 前端服务器 | 10.126.4.79 | 1122 | root | Admin@123Admin@123 | /home/appadmin/ | | 中广核项目 | 测试环境 | 前端服务器 | 10.126.4.79 | 1122 | root | Admin@123Admin@123 | - | /home/appadmin/ |
| 中广核项目 | 测试环境 | 后端服务器 | 10.126.4.81 | 1122 | appadmin | CGNadm!@345CGNadm!@345 | /home/appadmin/ | | 中广核项目 | 测试环境 | 后端服务器 | 10.126.4.81 | 1122 | appadmin | CGNadm!@345CGNadm!@345 | Admin@123Admin@123 | /home/appadmin/ |
**说明**
- 前端服务器:使用root用户直接登录,无需切换
- 后端服务器:使用appadmin用户登录,通过 `su -` 切换到root用户执行命令,root密码为 `Admin@123Admin@123`
#### 2.1.3 更新类型 #### 2.1.3 更新类型
| 更新类型 | 说明 | 需要重启 | | 更新类型 | 说明 | 需要重启 |
......
...@@ -743,6 +743,7 @@ tail -n 50 /var/www/html/log/uinfo.log ...@@ -743,6 +743,7 @@ tail -n 50 /var/www/html/log/uinfo.log
| 日期 | 版本 | 变更内容 | 变更人 | | 日期 | 版本 | 变更内容 | 变更人 |
|------|------|----------|--------| |------|------|----------|--------|
| 2026-01-28 | V2.6 | 后端服务器从使用sudo改为使用su切换到root用户;root密码设置为Admin@123Admin@123 | Claude |
| 2026-01-28 | V2.5 | 修复Invoke-PlinkCommand函数输出捕获问题;修复sudo中文参数乱码问题;优化正则表达式匹配;前端更新测试通过 | Claude | | 2026-01-28 | V2.5 | 修复Invoke-PlinkCommand函数输出捕获问题;修复sudo中文参数乱码问题;优化正则表达式匹配;前端更新测试通过 | Claude |
| 2026-01-28 | V2.4 | 中广核项目部署方式改为使用zip压缩包(cims-update.zip),统一部署流程;移除旧版本plink不支持的参数 | Claude | | 2026-01-28 | V2.4 | 中广核项目部署方式改为使用zip压缩包(cims-update.zip),统一部署流程;移除旧版本plink不支持的参数 | Claude |
| 2026-01-28 | V2.3 | 中广核项目从系统类型改为独立平台类型,增加服务器预设功能(测试环境前端/后端服务器) | Claude | | 2026-01-28 | V2.3 | 中广核项目从系统类型改为独立平台类型,增加服务器预设功能(测试环境前端/后端服务器) | Claude |
...@@ -754,7 +755,30 @@ tail -n 50 /var/www/html/log/uinfo.log ...@@ -754,7 +755,30 @@ tail -n 50 /var/www/html/log/uinfo.log
## 13.2 技术实现细节与问题解决 ## 13.2 技术实现细节与问题解决
### 13.2.1 Invoke-PlinkCommand输出捕获问题 ### 13.2.1 中广核项目后端服务器su切换方式
**实现方式变更**
- 原方式:使用 `sudo -S` 切换到root(sudo密码与appadmin登录密码相同)
- 新方式:使用 `su -` 切换到root(root密码为 `Admin@123Admin@123`
**变更原因**
- appadmin用户密码包含特殊字符,导致plink验证失败
- 改用root用户登录密码,避免特殊字符问题
**实现代码**
```powershell
if ($needSu) {
Write-Info "中广核项目(后端服务器):使用 su 切换到root用户执行更新脚本"
$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\"'"
}
```
**说明**
- `su -` 切换到root用户,并使用 `-c` 执行命令
- 在命令中设置 `LC_CTYPE``LC_ALL``zh_CN.UTF-8` 支持中文参数
- root密码通过 `echo` 传递给 `su` 命令
### 13.2.2 Invoke-PlinkCommand输出捕获问题
**问题描述** **问题描述**
函数直接返回空字符串,导致无法解析远端脚本输出的 `BACKUP_TAR` 变量。 函数直接返回空字符串,导致无法解析远端脚本输出的 `BACKUP_TAR` 变量。
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论