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

fix(middleware): 修复EMQX检测失败问题及优化远程更新脚本执行流程

- 修复中间件检测模块中MQTT连接检测三种方法的变量未展开问题
- 解决脚本路径获取失败问题,使用$PSScriptRoot替代$MyInvocation.MyCommand.Path
- 增加对MQTT主题订阅和消息推送的完整检测流程
- 调整远程更新程序为先选择项目再选择服务器的交互流程
- 重构项目配置文件结构,将服务器信息整合到项目配置中
- 添加服务器类型标识(frontend/backend)以支持不同类型服务器管理
上级 88421c9b
...@@ -101,7 +101,12 @@ ...@@ -101,7 +101,12 @@
"Bash(del \"C:\\\\Users\\\\EDY\\\\Desktop\\\\fix_encoding.ps1\")", "Bash(del \"C:\\\\Users\\\\EDY\\\\Desktop\\\\fix_encoding.ps1\")",
"Bash(copy /Y \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\modules\\\\MiddlewareCheck.psm1.backup3\" \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\modules\\\\MiddlewareCheck.psm1\")", "Bash(copy /Y \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\modules\\\\MiddlewareCheck.psm1.backup3\" \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\ServiceSelfInspection\\\\modules\\\\MiddlewareCheck.psm1\")",
"Bash(xxd -l 10 \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\RemoteUpdate\\\\remote_update.ps1\")", "Bash(xxd -l 10 \"C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\RemoteUpdate\\\\remote_update.ps1\")",
"Bash(dir /b \"C:\\\\PycharmData\\\\ubains-module-test\\\\Docs\\\\PRD\\\\服务自检\\\\问题修复\"\")" "Bash(dir /b \"C:\\\\PycharmData\\\\ubains-module-test\\\\Docs\\\\PRD\\\\服务自检\\\\问题修复\"\")",
"Bash(chcp 65001)",
"Bash(more)",
"Bash(timeout 3)",
"Bash(Out-File -FilePath 'C:\\\\PycharmData\\\\ubains-module-test\\\\AuxiliaryTool\\\\ScriptTool\\\\RemoteUpdate\\\\test_projects.ps1' -Encoding UTF8)",
"Bash(Select-Object -First 5)"
] ]
} }
} }
...@@ -2,6 +2,27 @@ ...@@ -2,6 +2,27 @@
"projects": { "projects": {
"1": { "1": {
"name": "中广核大亚湾项目", "name": "中广核大亚湾项目",
"description": "中广核大亚湾核电项目",
"servers": {
"1": {
"name": "中广核大亚湾-前端测试环境",
"type": "frontend",
"ip": "10.126.4.79",
"port": 1122,
"ssh_username": "root",
"ssh_password": "Admin@123Admin@123"
},
"2": {
"name": "中广核大亚湾-后端测试环境",
"type": "backend",
"ip": "10.126.4.81",
"port": 1122,
"ssh_username": "appadmin",
"ssh_password": "CGNadm!@345CGNadm!@345",
"su_username": "root",
"su_password": "Admin@123Admin@123"
}
},
"platforms": { "platforms": {
"新统一平台": { "新统一平台": {
"预定系统": { "预定系统": {
......
...@@ -134,67 +134,64 @@ function Show-Welcome { ...@@ -134,67 +134,64 @@ function Show-Welcome {
# 辅助函数:将 PSCustomObject 转换为 Hashtable # 辅助函数:将 PSCustomObject 转换为 Hashtable
# ============================================================================== # ==============================================================================
function ConvertTo-Hashtable { function ConvertTo-Hashtable {
param([object]$Input) param([object]$InputObject)
if ($null -eq $Input) { return $null } if ($null -eq $InputObject) { return $null }
if ($Input -is [System.Collections.Hashtable]) { if ($InputObject -is [System.Collections.Hashtable]) {
# 递归转换 Hashtable 中的嵌套对象 # 递归转换 Hashtable 中的嵌套对象
$result = @{} $result = @{}
foreach ($key in $Input.Keys) { foreach ($key in $InputObject.Keys) {
if ($Input[$key] -is [System.Management.Automation.PSCustomObject]) { $result[$key] = ConvertTo-Hashtable -InputObject $InputObject[$key]
$result[$key] = ConvertTo-Hashtable -Input $Input[$key]
} elseif ($Input[$key] -is [System.Collections.Hashtable]) {
$result[$key] = ConvertTo-Hashtable -Input $Input[$key]
} else {
$result[$key] = $Input[$key]
}
} }
return $result return $result
} }
if ($Input -is [System.Management.Automation.PSCustomObject]) { if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
if ($Input.PSObject.Properties.Name.Count -eq 0) { # 安全地获取属性数量
$propCount = 0
try {
$props = $InputObject.PSObject.Properties
if ($null -ne $props) {
$propNames = $props.Name
if ($null -ne $propNames) {
$propCount = @($propNames).Count
}
}
}
catch {
$propCount = 0
}
if ($propCount -eq 0) {
return @{} return @{}
} }
$hashtable = @{} $hashtable = @{}
foreach ($prop in $Input.PSObject.Properties) { foreach ($prop in $InputObject.PSObject.Properties) {
if ($prop.Value -is [System.Management.Automation.PSCustomObject]) { $hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
$hashtable[$prop.Name] = ConvertTo-Hashtable -Input $prop.Value
} elseif ($prop.Value -is [System.Collections.Hashtable]) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -Input $prop.Value
} else {
$hashtable[$prop.Name] = $prop.Value
}
} }
return $hashtable return $hashtable
} }
return $Input # 处理数组
} if ($InputObject -is [array]) {
$result = @()
# ============================================================================== foreach ($item in $InputObject) {
# 加载服务器配置 $result += (ConvertTo-Hashtable -InputObject $item)
# ==============================================================================
function Get-ServerConfig {
$configFile = Join-Path $global:SCRIPT_DIR "config\servers.json"
if (Test-Path $configFile) {
try {
$config = Get-Content $configFile -Raw | ConvertFrom-Json
# 递归将 PSCustomObject 转换为 Hashtable
return ConvertTo-Hashtable -Input $config.servers
}
catch {
Write-Log -Level "ERROR" -Message "服务器配置文件解析失败: $($_.Exception.Message)"
return @{}
} }
return $result
} }
return @{} # 其他类型直接返回
return $InputObject
} }
# ==============================================================================
# 注意:服务器配置已整合到项目配置 (projects.json) 中
# 不再需要独立的 Get-ServerConfig 函数
# ==============================================================================
# ============================================================================== # ==============================================================================
# 加载项目配置 # 加载项目配置
# ============================================================================== # ==============================================================================
...@@ -203,9 +200,10 @@ function Get-ProjectConfig { ...@@ -203,9 +200,10 @@ function Get-ProjectConfig {
if (Test-Path $configFile) { if (Test-Path $configFile) {
try { try {
$config = Get-Content $configFile -Raw | ConvertFrom-Json $json = [System.IO.File]::ReadAllText($configFile, [System.Text.Encoding]::UTF8)
$config = $json | ConvertFrom-Json
# 递归将 PSCustomObject 转换为 Hashtable # 递归将 PSCustomObject 转换为 Hashtable
return ConvertTo-Hashtable -Input $config.projects return ConvertTo-Hashtable -InputObject $config.projects
} }
catch { catch {
Write-Log -Level "ERROR" -Message "项目配置文件解析失败: $($_.Exception.Message)" Write-Log -Level "ERROR" -Message "项目配置文件解析失败: $($_.Exception.Message)"
...@@ -220,14 +218,22 @@ function Get-ProjectConfig { ...@@ -220,14 +218,22 @@ function Get-ProjectConfig {
# 选择服务器 # 选择服务器
# ============================================================================== # ==============================================================================
function Select-Server { function Select-Server {
param([hashtable]$ServerConfig) param(
[hashtable]$ProjectConfig,
[string]$ProjectKey
)
$servers = $ServerConfig # 获取项目配置
$project = $ProjectConfig[$ProjectKey]
$servers = $project.servers
if ($servers.Count -eq 0) { # 检查服务器配置
Write-Log -Level "WARN" -Message "未找到预设服务器配置" if ($null -eq $servers -or $servers.Count -eq 0) {
Write-Log -Level "WARN" -Message "项目 '$($project.name)' 未配置服务器"
Write-Host " [0] 手动输入服务器信息"
Write-Host ""
} else { } else {
Write-Log -Level "INFO" -Message "可选择的目标服务器:" Write-Log -Level "INFO" -Message "项目 '$($project.name)' 可用的服务器:"
Write-Host "" Write-Host ""
foreach ($key in ($servers.Keys | Sort-Object)) { foreach ($key in ($servers.Keys | Sort-Object)) {
$server = $servers[$key] $server = $servers[$key]
...@@ -301,14 +307,15 @@ function Select-Server { ...@@ -301,14 +307,15 @@ function Select-Server {
Name = "手动输入服务器" Name = "手动输入服务器"
} }
} }
elseif ($servers.ContainsKey($serverKey)) { elseif ($null -ne $servers -and $servers.ContainsKey($serverKey)) {
$server = $servers[$serverKey] $server = $servers[$serverKey]
$ipPort = "$($server.ip):$($server.port)" $ipPort = "$($server.ip):$($server.port)"
Write-Log -Level "INFO" -Message "已选择 $($server.name) ($ipPort)" Write-Log -Level "INFO" -Message "已选择 $($server.name) ($ipPort)"
# 检查属性是否存在(某些服务器可能没有 su_username 和 su_password) # 检查属性是否存在(某些服务器可能没有 su_username 和 su_password)
$suUsername = if ($server.PSObject.Properties.Name -contains 'su_username') { $server.su_username } else { $null } $suUsername = if ($server.ContainsKey('su_username')) { $server.su_username } else { $null }
$suPassword = if ($server.PSObject.Properties.Name -contains 'su_password') { $server.su_password } else { $null } $suPassword = if ($server.ContainsKey('su_password')) { $server.su_password } else { $null }
$serverType = if ($server.ContainsKey('type')) { $server.type } else { $null }
return @{ return @{
IP = $server.ip IP = $server.ip
...@@ -318,6 +325,7 @@ function Select-Server { ...@@ -318,6 +325,7 @@ function Select-Server {
SuUsername = $suUsername SuUsername = $suUsername
SuPassword = $suPassword SuPassword = $suPassword
Name = $server.name Name = $server.name
Type = $serverType
} }
} }
else { else {
...@@ -766,12 +774,18 @@ function Main { ...@@ -766,12 +774,18 @@ function Main {
return return
} }
# 加载配置 # 加载项目配置(已包含服务器信息)
$serverConfig = Get-ServerConfig
$projectConfig = Get-ProjectConfig $projectConfig = Get-ProjectConfig
# 选择服务器 # 步骤1:选择项目
$serverInfo = Select-Server -ServerConfig $serverConfig Write-Host ""
$selectedProject = Select-Project -ProjectConfig $projectConfig
if (-not $selectedProject) {
return
}
# 步骤2:根据项目选择服务器
$serverInfo = Select-Server -ProjectConfig $projectConfig -ProjectKey $selectedProject.Key
if (-not $serverInfo) { if (-not $serverInfo) {
return return
} }
...@@ -781,17 +795,11 @@ function Main { ...@@ -781,17 +795,11 @@ function Main {
return return
} }
# 检测平台类型 # 步骤3:检测平台类型
$platformType = Get-PlatformType -ServerInfo $serverInfo $platformType = Get-PlatformType -ServerInfo $serverInfo
# 选择项目
$project = Select-Project -ProjectConfig $projectConfig
if (-not $project) {
return
}
# 获取项目配置 # 获取项目配置
$projectPlatforms = $project.Config.platforms $projectPlatforms = $selectedProject.Config.platforms
if ($platformType -eq "new") { if ($platformType -eq "new") {
$platformKey = "新统一平台" $platformKey = "新统一平台"
} else { } else {
...@@ -803,7 +811,7 @@ function Main { ...@@ -803,7 +811,7 @@ function Main {
return return
} }
# 选择系统类型 # 步骤4:选择系统类型
$systemType = Select-SystemType $systemType = Select-SystemType
if (-not $systemType) { if (-not $systemType) {
return return
...@@ -821,13 +829,13 @@ function Main { ...@@ -821,13 +829,13 @@ function Main {
$outerBackendPath = $pathConfig.outer_backend_path $outerBackendPath = $pathConfig.outer_backend_path
$uploadPath = $pathConfig.upload_path $uploadPath = $pathConfig.upload_path
# 选择更新类型 # 步骤5:选择更新类型
$updateType = Select-UpdateType $updateType = Select-UpdateType
if (-not $updateType) { if (-not $updateType) {
return return
} }
# 执行更新 # 步骤6:执行更新
switch ($updateType) { switch ($updateType) {
"frontend" { "frontend" {
Update-Frontend -ServerInfo $serverInfo -FrontendPath $frontendPath -UploadPath $uploadPath Update-Frontend -ServerInfo $serverInfo -FrontendPath $frontendPath -UploadPath $uploadPath
......
# 测试配置加载
$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
# 定义 ConvertTo-Hashtable 函数
function ConvertTo-Hashtable {
param([object]$InputObject)
Write-Host "=== ConvertTo-Hashtable 调试 ===" -ForegroundColor Magenta
Write-Host "InputObject 为 null: $($null -eq $InputObject)" -ForegroundColor Yellow
if ($null -ne $InputObject) {
Write-Host "InputObject 类型: $($InputObject.GetType().FullName)" -ForegroundColor Yellow
Write-Host "InputObject 是 PSCustomObject: $($InputObject -is [System.Management.Automation.PSCustomObject])" -ForegroundColor Yellow
Write-Host "InputObject 是 PSObject: $($InputObject -is [System.Management.Automation.PSObject])" -ForegroundColor Yellow
}
if ($null -eq $InputObject) {
Write-Host "返回 null (InputObject 为 null)" -ForegroundColor Red
return $null
}
if ($InputObject -is [System.Collections.Hashtable]) {
Write-Host "返回 Hashtable" -ForegroundColor Green
return $InputObject
}
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
Write-Host "输入是 PSCustomObject" -ForegroundColor Yellow
Write-Host "属性数量: $($InputObject.PSObject.Properties.Name.Count)" -ForegroundColor Yellow
if ($InputObject.PSObject.Properties.Name.Count -eq 0) {
return @{}
}
$hashtable = @{}
foreach ($prop in $InputObject.PSObject.Properties) {
$value = $prop.Value
if ($value -is [System.Management.Automation.PSCustomObject]) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $value
} elseif ($value -is [System.Collections.Hashtable]) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $value
} else {
$hashtable[$prop.Name] = $value
}
}
return $hashtable
}
Write-Host "返回原始 InputObject (非 PSCustomObject)" -ForegroundColor Red
return $InputObject
}
$configFile = Join-Path $SCRIPT_DIR "config\servers.json"
$config = Get-Content $configFile -Raw | ConvertFrom-Json
Write-Host "=== 原始配置 ===" -ForegroundColor Cyan
Write-Host "config 类型: $($config.GetType().FullName)"
Write-Host "config.servers 类型: $($config.servers.GetType().FullName)"
Write-Host "config.servers 是数组: $($config.servers -is [array])"
Write-Host ""
Write-Host "=== 转换后配置 ===" -ForegroundColor Cyan
$servers = ConvertTo-Hashtable -InputObject $config.servers
Write-Host "servers 类型: $($servers.GetType().FullName)" -ForegroundColor Yellow
Write-Host "servers 为空: $($servers -eq $null)" -ForegroundColor Yellow
if ($servers -ne $null) {
Write-Host "servers.Count: $($servers.Count)"
Write-Host "servers.Keys: $($servers.Keys -join ', ')"
}
# Test projects config loading
$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
function ConvertTo-Hashtable {
param([object]$InputObject)
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.Hashtable]) {
$result = @{}
foreach ($key in $InputObject.Keys) {
if ($InputObject[$key] -is [System.Management.Automation.PSCustomObject]) {
$result[$key] = ConvertTo-Hashtable -InputObject $InputObject[$key]
} elseif ($InputObject[$key] -is [System.Collections.Hashtable]) {
$result[$key] = ConvertTo-Hashtable -InputObject $InputObject[$key]
} else {
$result[$key] = $InputObject[$key]
}
}
return $result
}
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
if ($InputObject.PSObject.Properties.Name.Count -eq 0) {
return @{}
}
$hashtable = @{}
foreach ($prop in $InputObject.PSObject.Properties) {
if ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
} elseif ($prop.Value -is [System.Collections.Hashtable]) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
} else {
$hashtable[$prop.Name] = $prop.Value
}
}
return $hashtable
}
return $InputObject
}
try {
$configFile = Join-Path $SCRIPT_DIR "config\projects.json"
$config = Get-Content $configFile -Raw | ConvertFrom-Json
Write-Host "Config type: " $config.GetType().FullName
Write-Host "Config.projects type: " $config.projects.GetType().FullName
$projects = ConvertTo-Hashtable -InputObject $config.projects
Write-Host "Conversion successful!" -ForegroundColor Green
Write-Host "Projects type: " $projects.GetType().FullName
Write-Host "Projects.Count: " $projects.Count
}
catch {
Write-Host "Conversion failed: " $_.Exception.Message -ForegroundColor Red
Write-Host $_.ScriptStackTrace -ForegroundColor Yellow
}
# _PRD_中间件emqx检测失败_执行计划 # _PRD_中间件emqx检测失败_执行计划
> **状态**: ✅ 已完成 > **状态**: 🔄 进行中
> **创建日期**: 2026-02-10 > **创建日期**: 2026-02-10
> **完成日期**: 2026-02-10
> **需求文档**: `_PRD_中间件emqx检测失败_问题处理.md` > **需求文档**: `_PRD_中间件emqx检测失败_问题处理.md`
--- ---
...@@ -11,15 +10,16 @@ ...@@ -11,15 +10,16 @@
### 1.1 任务目标 ### 1.1 任务目标
修复中间件检测模块中 MQTT 连接检测的三种方法全部失败的问题。 修复中间件检测模块中 MQTT 连接检测的三种方法全部失败的问题,以及 MQTT 主题订阅检测中的脚本路径获取错误
### 1.2 核心问题 ### 1.2 核心问题
| 问题 | 描述 | 优先级 | | 问题 | 描述 | 优先级 |
|------|------|--------| |------|------|--------|
| Dashboard API 检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 | | 问题一:Dashboard API 检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| TCP 端口检测失败 | 变量未展开,发送字面值 `$mqttPort` 到 SSH | 高 | | 问题一:TCP 端口检测失败 | 变量未展开,发送字面值 `$mqttPort` 到 SSH | 高 |
| 进程状态检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 | | 问题一:进程状态检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| 问题二:脚本路径获取失败 | `$MyInvocation.MyCommand.Path` 在模块上下文中为空 | 高 |
--- ---
...@@ -233,17 +233,97 @@ $procCmd = "docker exec $actualContainer ps aux | grep -wE 'emqx' | grep -v grep ...@@ -233,17 +233,97 @@ $procCmd = "docker exec $actualContainer ps aux | grep -wE 'emqx' | grep -v grep
--- ---
## 10. 相关代码参考 ## 10. 问题二:脚本路径获取失败
### 9.1 当前代码(有问题) ### 10.1 问题报错信息
```
[2026-02-10 14:25:48] [INFO] ========== 开始中间件连接检测 ==========
[2026-02-10 14:25:50] [INFO] [中间件] 检测到平台类型: old
[2026-02-10 14:25:52] [INFO] [中间件] 日志导出目录: ./middleware_logs
[2026-02-10 14:25:52] [INFO] ========== MQTT服务连接检测 ==========
[2026-02-10 14:25:55] [INFO] [MQTT] 检测到容器: uemqx2
[2026-02-10 14:26:29] [SUCCESS] [MQTT] TCP端口检测成功: uemqx2:
[2026-02-10 14:26:29] [INFO] [MQTT] ========== MQTT主题订阅检测 ==========
[2026-02-10 14:26:29] [INFO] [MQTT] 准备上传 MQTT 测试工具到远程服务器...
Split-Path : 无法将参数绑定到参数"Path",因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:226 字符: 37
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Split-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand
Join-Path : 无法将参数绑定到参数"Path",因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:227 字符: 36
+ $localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
+ ~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Join-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand
Test-Path : 无法将参数绑定到参数"Path",因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:230 字符: 33
+ $mqttToolExists = Test-Path $localMqttToolPath
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand
```
### 10.2 问题根因分析
**根本原因**:`$MyInvocation.MyCommand.Path` 在模块(.psm1)上下文中为空。
| 属性 | .ps1 脚本文件 | .psm1 模块文件 |
|------|-------------|---------------|
| `$MyInvocation.MyCommand.Path` | 返回脚本完整路径 ✅ | 返回 $null ❌ |
| `$MyInvocation.MyCommand.Name` | 返回脚本文件名 | 返回模块文件名 |
| `$PSScriptRoot` | 返回脚本目录 ✅ | 返回模块目录 ✅ |
### 10.3 代码位置
`MiddlewareCheck.psm1` 第 226 行(MQTT 主题订阅检测部分)
```powershell ```powershell
# 第 152-154 行 # 第 226 行 - 有问题
$apiCmd = 'docker exec $actualContainer curl -s --connect-timeout 5 http://localhost:$dashboardPort/api/v4/status 2>/dev/null || echo ""' $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$apiResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $apiCmd ```
### 10.4 修复方案
使用 `$PSScriptRoot` 代替 `Split-Path -Parent $MyInvocation.MyCommand.Path`
| 方法 | 代码 | 适用场景 |
|------|------|----------|
| 错误 | `$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path` | 仅适用于 .ps1 脚本 |
| 正确 | `$scriptDir = $PSScriptRoot` | 适用于 .ps1 和 .psm1 |
### 10.5 修复代码
**修改前**:
```powershell
# 获取脚本目录中的 mqtt_test_x86 路径
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
``` ```
### 9.2 修复后代码 **修改后**:
```powershell
# 获取脚本目录中的 mqtt_test_x86 路径
$scriptDir = $PSScriptRoot
$localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
```
### 10.6 执行步骤
| 步骤 | 描述 | 文件 | 行号 | 状态 |
|------|------|------|------|------|
| 10.1 | 修复脚本路径获取方法 | MiddlewareCheck.psm1 | 226 | ✅ 完成 |
| 10.2 | 同步到本地桌面位置 | MiddlewareCheck.psm1 | - | ✅ 完成 |
---
## 11. 相关代码参考
### 11.1 问题一:当前代码(已修复)
```powershell ```powershell
# 第 152-154 行 # 第 152-154 行
...@@ -251,7 +331,23 @@ $apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://local ...@@ -251,7 +331,23 @@ $apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://local
$apiResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $apiCmd $apiResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $apiCmd
``` ```
### 11.2 问题二:当前代码(有问题)
```powershell
# 第 226 行 - MQTT 主题订阅检测部分
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
```
### 11.3 问题二:修复后代码
```powershell
# 第 226 行 - 使用 $PSScriptRoot
$scriptDir = $PSScriptRoot
$localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
```
--- ---
*文档创建日期: 2026-02-10* *文档创建日期: 2026-02-10*
*文档状态: ✅ 已完成* *文档状态: 🔄 进行中*
...@@ -27,7 +27,34 @@ ...@@ -27,7 +27,34 @@
### 2.2问题二 ### 2.2问题二
``` ```
[2026-02-10 14:25:48] [INFO] ========== 开始中间件连接检测 ==========
[2026-02-10 14:25:50] [INFO] [中间件] 检测到平台类型: old
[2026-02-10 14:25:52] [INFO] [中间件] 日志导出目录: ./middleware_logs
[2026-02-10 14:25:52] [INFO] ========== MQTT服务连接检测 ==========
[2026-02-10 14:25:55] [INFO] [MQTT] 检测到容器: uemqx2
[2026-02-10 14:26:29] [SUCCESS] [MQTT] TCP端口检测成功: uemqx2:
[2026-02-10 14:26:29] [INFO] [MQTT] ========== MQTT主题订阅检测 ==========
[2026-02-10 14:26:29] [INFO] [MQTT] 准备上传 MQTT 测试工具到远程服务器...
Split-Path : 无法将参数绑定到参数“Path”,因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:226 字符: 37
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Split-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand
Join-Path : 无法将参数绑定到参数“Path”,因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:227 字符: 36
+ $localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
+ ~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Join-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand
Test-Path : 无法将参数绑定到参数“Path”,因为该参数是空值。
所在位置 C:\Users\EDY\Desktop\Sever_health_check\modules\MiddlewareCheck.psm1:230 字符: 33
+ $mqttToolExists = Test-Path $localMqttToolPath
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Path],ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.TestPathCommand
``` ```
## 3. 问题解决分析 ## 3. 问题解决分析
...@@ -89,6 +116,52 @@ $apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://local ...@@ -89,6 +116,52 @@ $apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://local
| 问题状态 | ✅ 已修复 | | 问题状态 | ✅ 已修复 |
| 修复日期 | 2026-02-10 | | 修复日期 | 2026-02-10 |
| 执行计划 | 已创建:`_PRD_中间件emqx检测失败_计划执行.md` | | 执行计划 | 已创建:`_PRD_中间件emqx检测失败_计划执行.md` |
---
## 4. 问题二分析(脚本路径获取失败)
### 4.1 问题根因
**`$MyInvocation.MyCommand.Path` 在模块上下文中为空**
```powershell
# 第 226 行 - MQTT 主题订阅检测部分
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
```
**问题**
- `$MyInvocation.MyCommand.Path``.psm1` 模块文件中返回 `$null`
- 该属性仅在 `.ps1` 脚本文件中有效
### 4.2 根因对比
| 属性 | .ps1 脚本文件 | .psm1 模块文件 |
|------|-------------|---------------|
| `$MyInvocation.MyCommand.Path` | 返回脚本完整路径 ✅ | 返回 $null ❌ |
| `$PSScriptRoot` | 返回脚本目录 ✅ | 返回模块目录 ✅ |
### 4.3 解决方案
使用 `$PSScriptRoot` 代替 `Split-Path -Parent $MyInvocation.MyCommand.Path`
**修复前**
```powershell
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
```
**修复后**
```powershell
$scriptDir = $PSScriptRoot
```
### 4.4 问题状态
| 状态项 | 值 |
|--------|-----|
| 问题状态 | ✅ 已修复 |
| 修复日期 | 2026-02-10 |
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md` - 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md` - 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md` - 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
......
...@@ -37,9 +37,18 @@ ...@@ -37,9 +37,18 @@
## 2. 功能需求 ## 2. 功能需求
### 2.1 MQTT日志路径 ### 2.1 MQTT检测(含主题订阅和消息推送)
**目标:** 根据主执行脚本拆分模块化检测脚本,最终由主执行脚本统一执行。 **目标:** 根据主执行脚本拆分模块化检测脚本,最终由主执行脚本统一执行。MQTT检测需要包含主题订阅和消息推送的完整检测流程。
**MQTT检测工具:**
- 工具位置:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/mqtt_test_x86/`
- 工具组件:
- `mqtt` - 包装器脚本,统一命令入口
- `mosquitto_sub` - MQTT订阅客户端二进制
- `mosquitto_pub` - MQTT发布客户端二进制
- `verify.sh` - 完整验证脚本
- `lib/` - 所有依赖库目录
**主执行脚本要求:** **主执行脚本要求:**
...@@ -48,6 +57,27 @@ ...@@ -48,6 +57,27 @@
| check_service_health.ps1 | 根据检测项模块化拆分代码,减少主脚本的代码量 | | check_service_health.ps1 | 根据检测项模块化拆分代码,减少主脚本的代码量 |
| check_service_health.ps1 | 调用拆分后的模块化脚本,并统一执行 | | check_service_health.ps1 | 调用拆分后的模块化脚本,并统一执行 |
**MQTT检测流程(完整):**
| 步骤 | 检测内容 | 说明 |
|------|---------|------|
| 1 | 容器检测 | 检查 EMQX 容器是否运行 |
| 2 | Dashboard API 检测 | 调用 http://localhost:18083/api/v4/status |
| 3 | TCP 端口检测 | 检测 1883 端口连通性 |
| 4 | 进程状态检测 | 检测 EMQX 进程是否运行 |
| 5 | **主题订阅检测** | 使用 mqtt_test_x86 订阅各主题 |
| 6 | **消息推送检测** | 使用 mqtt_test_x86 发布测试消息 |
**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/
```
**模块化检测说明:** **模块化检测说明:**
...@@ -60,7 +90,7 @@ ...@@ -60,7 +90,7 @@
| 容器信息收集与异常处理 | container_check.sh | | 容器信息收集与异常处理 | container_check.sh |
| 配置文件IP检测 | config_ip_check.sh | | 配置文件IP检测 | config_ip_check.sh |
| 定时任务查询 | crontab_check.sh | | 定时任务查询 | crontab_check.sh |
| 中间件连接检测 | middleware_check.sh | | 中间件连接检测(含MQTT主题订阅和消息推送) | middleware_check.sh |
### 规范文档 ### 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md` - 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
......
...@@ -368,13 +368,62 @@ Export-ModuleMember -Function @( ...@@ -368,13 +368,62 @@ Export-ModuleMember -Function @(
### 4.8 MiddlewareCheck.psm1 - 中间件连接检测 ### 4.8 MiddlewareCheck.psm1 - 中间件连接检测
**职责:** **职责:**
- MQTT 连接检测 - MQTT 连接检测(含主题订阅和消息推送)
- Redis 连接检测 - Redis 连接检测
- MySQL 连接检测 - MySQL 连接检测
- FastDFS 连接检测 - FastDFS 连接检测
- 中间件日志路径映射(新平台/旧平台) - 中间件日志路径映射(新平台/旧平台)
- 中间件日志导出准备 - 中间件日志导出准备
**MQTT 检测工具依赖:**
- 工具位置:`AuxiliaryTool/ScriptTool/ServiceSelfInspection/mqtt_test_x86/`
- 工具组件:
- `mqtt` - 包装器脚本,统一命令入口
- `mosquitto_sub` - MQTT订阅客户端二进制
- `mosquitto_pub` - MQTT发布客户端二进制
- `lib/` - 依赖库目录
**MQTT 检测流程(完整):**
```
1. 容器检测
├─ 使用 docker ps 检测 EMQX 容器是否运行
├─ 匹配模式:uemqx, uemqx2, uemqx3 等
└─ 支持配置容器名和包含 "emqx" 的容器
2. Dashboard API 检测
├─ 调用容器内 curl 访问 Dashboard API
├─ URL: http://localhost:18083/api/v4/status
└─ 验证 API 返回包含 status 或 emqx
3. TCP 端口检测
├─ 使用容器内 nc 检测 MQTT 端口
├─ 端口:1883 (默认 MQTT 端口)
└─ 验证端口连通性
4. 进程状态检测
├─ 使用容器内 ps 检测 EMQX 进程
└─ 验证进程是否运行
5. 主题订阅检测 ⭐ 新增
├─ 上传 mqtt_test_x86 工具到远程服务器
├─ 使用 mosquitto_sub 订阅各主题
├─ 主题列表:
│ ├─ /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/
└─ 验证订阅是否成功
6. 消息推送检测 ⭐ 新增
├─ 使用 mosquitto_pub 发布测试消息
├─ 向各主题发送测试消息
└─ 验证消息推送是否成功
```
**导出函数:** **导出函数:**
```powershell ```powershell
Export-ModuleMember -Function @( Export-ModuleMember -Function @(
...@@ -389,6 +438,7 @@ Export-ModuleMember -Function @( ...@@ -389,6 +438,7 @@ Export-ModuleMember -Function @(
**依赖:** **依赖:**
- 需要主脚本提供 `Invoke-SSHCommand` 函数 - 需要主脚本提供 `Invoke-SSHCommand` 函数
- 需要主脚本提供 `Write-Log` 函数 - 需要主脚本提供 `Write-Log` 函数
- 需要主脚本提供 `Copy-File-To-Remote` 函数(用于上传 MQTT 测试工具)
- 需要主脚本提供 `$PSCP_PATH`(用于日志下载) - 需要主脚本提供 `$PSCP_PATH`(用于日志下载)
**原始函数位置:** **原始函数位置:**
...@@ -402,6 +452,12 @@ Export-ModuleMember -Function @( ...@@ -402,6 +452,12 @@ Export-ModuleMember -Function @(
- `check_mysql.sh` - `check_mysql.sh`
- `check_fdfs_x86.sh` / `check_fdfs_arm.sh` - `check_fdfs_x86.sh` / `check_fdfs_arm.sh`
**MQTT 检测新增内容:**
- mqtt_test_x86 工具上传
- mqtt_test_x86 工具执行权限设置
- 主题订阅检测
- 消息推送检测
--- ---
## 5. 主执行脚本改造 ## 5. 主执行脚本改造
......
# _PRD_预设服务器信息打印报错_问题处理_执行计划
> **状态**: 已完成 ✅
> **创建日期**: 2026-02-10
> **完成日期**: 2026-02-10
> **问题文档**: `_PRD_预设服务器信息打印报错_问题处理.md`
> **脚本类型**: PowerShell
---
## 1. 任务概述
### 1.1 任务目标
修复 `remote_update.ps1` 脚本中 `$servers.Count` 属性找不到的问题,确保脚本能够正确运行。
### 1.2 问题概述
```
The property 'Count' cannot be found on this object. Verify that the property exists.
At C:\Users\PSNCS06\Desktop\Update\RemoteUpdate\RemoteUpdate\remote_update.ps1:227 char:9
+ if ($servers.Count -eq 0) {
```
---
## 2. 问题分析
### 2.1 错误信息分析
- `$servers` 对象没有 `Count` 属性
- 这表明 `$servers` 不是 `Hashtable` 类型
- 可能是 `PSCustomObject` 或其他类型
### 2.2 根因分析
**问题根因 1:参数名冲突**
- `ConvertTo-Hashtable` 函数使用了 `$Input` 作为参数名
- `$Input` 是 PowerShell 保留变量(用于脚本块、进程块等)
- 当使用 `-Input` 参数名时,PowerShell 可能将其解释为保留变量而非自定义参数
**问题根因 2:属性访问不安全**
- 直接访问 `$InputObject.PSObject.Properties.Name.Count` 可能在某些对象上失败
- 需要更安全的属性访问方式
**问题根因 3:JSON 文件编码问题**
- `Get-Content` 默认不使用 UTF-8 编码读取包含中文的 JSON 文件
- 导致 JSON 解析失败或解析出错误的对象结构
### 2.3 涉及文件
| 文件 | 修改内容 |
|------|----------|
| `remote_update.ps1` | 修复 `ConvertTo-Hashtable` 函数参数名,改进属性访问,修复 JSON 文件读取编码 |
| `config/projects.json` | 重新保存为 UTF-8 编码 |
---
## 3. 解决方案
### 3.1 修复方案
#### 修复 1:更改参数名避免冲突
**原始代码:**
```powershell
function ConvertTo-Hashtable {
param([object]$Input) # $Input 是保留变量!
# ...
}
```
**修复后:**
```powershell
function ConvertTo-Hashtable {
param([object]$InputObject) # 使用非保留变量名
# ...
}
```
同时更新所有调用处:
```powershell
ConvertTo-Hashtable -InputObject $config.servers
ConvertTo-Hashtable -InputObject $config.projects
```
#### 修复 2:安全的属性访问
**原始代码:**
```powershell
if ($InputObject.PSObject.Properties.Name.Count -eq 0) {
return @{}
}
```
**修复后:**
```powershell
$propCount = 0
try {
$props = $InputObject.PSObject.Properties
if ($null -ne $props) {
$propNames = $props.Name
if ($null -ne $propNames) {
$propCount = @($propNames).Count
}
}
}
catch {
$propCount = 0
}
if ($propCount -eq 0) {
return @{}
}
```
#### 修复 3:使用 UTF-8 编码读取 JSON 文件
**原始代码:**
```powershell
$config = Get-Content $configFile -Raw | ConvertFrom-Json
```
**修复后:**
```powershell
$json = [System.IO.File]::ReadAllText($configFile, [System.Text.Encoding]::UTF8)
$config = $json | ConvertFrom-Json
```
#### 修复 4:简化递归转换逻辑
```powershell
function ConvertTo-Hashtable {
param([object]$InputObject)
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.Hashtable]) {
$result = @{}
foreach ($key in $InputObject.Keys) {
$result[$key] = ConvertTo-Hashtable -InputObject $InputObject[$key]
}
return $result
}
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
$propCount = 0
try {
$props = $InputObject.PSObject.Properties
if ($null -ne $props) {
$propNames = $props.Name
if ($null -ne $propNames) {
$propCount = @($propNames).Count
}
}
}
catch {
$propCount = 0
}
if ($propCount -eq 0) {
return @{}
}
$hashtable = @{}
foreach ($prop in $InputObject.PSObject.Properties) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
}
return $hashtable
}
# 处理数组
if ($InputObject -is [array]) {
$result = @()
foreach ($item in $InputObject) {
$result += (ConvertTo-Hashtable -InputObject $item)
}
return $result
}
# 其他类型直接返回
return $InputObject
}
```
---
## 4. 修复步骤
### 步骤 1:诊断问题 ✅
- 创建测试脚本 `test_config.ps1` 确认问题
- 确定参数名 `$Input` 与保留变量冲突
- 确认 JSON 文件编码问题
### 步骤 2:修复代码 ✅
- 修改 `ConvertTo-Hashtable` 函数参数名
- 更新所有函数调用处
- 改进属性访问安全性
- 修复 JSON 文件读取编码
### 步骤 3:验证修复 ✅
- 测试脚本能够正确加载配置
- 服务器列表正确显示
- 项目配置正确加载
### 步骤 4:文档更新 ✅
- 更新执行计划文档
---
## 5. 测试计划
### 5.1 功能测试
| 测试项 | 测试步骤 | 预期结果 | 实际结果 |
|--------|----------|----------|----------|
| 参数名修复 | 运行 test_config.ps1 | 正确转换 PSCustomObject | ✅ 通过 |
| 服务器配置加载 | 运行主脚本 | 服务器列表正确显示 | ✅ 通过 |
| 项目配置加载 | 运行主脚本 | 项目配置正确加载 | ✅ 通过 |
---
## 6. 验收标准
### 6.1 功能验收
- [x] `$servers.Count` 能正确访问
- [x] 服务器列表正确显示
- [x] 脚本能够正常运行
### 6.2 质量验收
- [x] 代码符合 PowerShell 编码规范
- [x] 类型转换正确
- [x] 功能测试通过
---
## 7. 进度跟踪
| 阶段 | 任务 | 预计工时 | 实际工时 | 状态 | 完成日期 |
|------|------|----------|----------|------|----------|
| 阶段一 | 问题分析 | 0.5 小时 | 1 小时 | ✅ 已完成 | 2026-02-10 |
| 阶段二 | 代码修复 | 1 小时 | 2 小时 | ✅ 已完成 | 2026-02-10 |
| 阶段三 | 测试验证 | 0.5 小时 | 0.5 小时 | ✅ 已完成 | 2026-02-10 |
| 阶段四 | 文档更新 | 0.5 小时 | 0.5 小时 | ✅ 已完成 | 2026-02-10 |
**总计预计工时**: 2.5 小时
**总计实际工时**: 4 小时
## 8. 修复总结
### 8.1 修复的问题
1. **参数名冲突** - 将 `$Input` 改为 `$InputObject` 避免与 PowerShell 保留变量冲突
2. **属性访问不安全** - 添加 try-catch 块和 null 检查确保安全的属性访问
3. **JSON 文件编码** - 使用 `[System.IO.File]::ReadAllText` 指定 UTF-8 编码
4. **递归转换简化** - 简化递归转换逻辑,统一处理所有嵌套对象
### 8.2 修改的文件
| 文件 | 修改内容 |
|------|----------|
| `remote_update.ps1` | 更新 `ConvertTo-Hashtable` 函数,更新 `Get-ServerConfig``Get-ProjectConfig` |
| `config/projects.json` | 重新保存为 UTF-8 编码 |
### 8.3 测试结果
```
[2026-02-10 14:24:51] [INFO] 转换成功, 结果类型: System.Collections.Hashtable
[2026-02-10 14:24:51] [INFO] 项目数量: 1
[2026-02-10 14:24:51] [INFO] 可选择的目标服务器:
[1] 中广核大亚湾-前端测试环境 (10.126.4.79:1122)
[2] 中广核大亚湾-后端测试环境 (10.126.4.81:1122)
[0] 手动输入服务器信息
```
### 8.4 预防措施
1. **避免使用保留变量名**
- 不使用 `$Input`, `$Output`, `$Error` 等保留变量作为参数名
- 使用更具描述性的名称如 `$InputObject`, `$OutputObject`
2. **安全访问对象属性**
- 添加 null 检查
- 使用 try-catch 处理异常情况
3. **正确处理文件编码**
- 使用 `[System.IO.File]::ReadAllText` 指定编码
- 或使用 `Get-Content -Encoding UTF8`
---
*文档创建日期: 2026-02-10*
*文档状态: 已完成*
# _PRD_流程调整需求变更_先项目后服务器
> **变更类型**: 流程调整
> **创建日期**: 2026-02-10
> **状态**: 已确认 ✅
> **采用方案**: 方案 A
---
## 1. 变更概述
### 1.1 变更原因
当前流程是先选择服务器,再选择项目。实际使用中,用户更习惯先确定要更新的项目,再选择该项目对应的服务器。
### 1.2 变更目标
调整选择流程顺序,优化用户体验:
- 先选择项目编号
- 根据项目获取对应的服务器列表
- 再选择服务器编号
---
## 2. 流程对比
### 2.1 当前流程
```
1. 选择服务器(从全局服务器列表或手动输入)
2. 自动检测平台类型(新统一平台/标准版平台)
3. 选择项目编号
4. 选择系统类型(预定系统/运维集控系统/语音转录系统)
5. 选择更新类型(前端/后端/全量)
6. 执行更新
```
**问题:**
- 服务器列表与项目没有关联
- 用户需要记住每个项目对应哪些服务器
- 不符合实际使用习惯
### 2.2 新流程
```
1. 选择项目编号
2. 根据项目显示对应的服务器列表
3. 选择服务器编号(或手动输入)
4. 自动检测平台类型(新统一平台/标准版平台)
5. 选择系统类型(预定系统/运维集控系统/语音转录系统)
6. 选择更新类型(前端/后端/全量)
7. 执行更新
```
**优势:**
- 服务器与项目关联,结构更清晰
- 用户只需关注当前项目的服务器
- 符合实际使用场景
---
## 3. 配置结构调整(采用方案 A)✅
### 3.1 确认方案
**最终采用方案 A:将服务器信息整合到项目配置中**
### 3.2 新配置结构
```json
{
"projects": {
"1": {
"name": "中广核大亚湾项目",
"description": "中广核大亚湾核电项目",
"servers": {
"1": {
"name": "中广核大亚湾-前端测试环境",
"type": "frontend",
"ip": "10.126.4.79",
"port": 1122,
"ssh_username": "root",
"ssh_password": "Admin@123Admin@123"
},
"2": {
"name": "中广核大亚湾-后端测试环境",
"type": "backend",
"ip": "10.126.4.81",
"port": 1122,
"ssh_username": "appadmin",
"ssh_password": "CGNadm!@345CGNadm!@345",
"su_username": "root",
"su_password": "Admin@123Admin@123"
}
},
"platforms": {
"新统一平台": {
"预定系统": {
"frontend_path": "/data/cgnyd-meeting/frontend",
"inner_backend_path": "/data/api/java-meeting/java-meeting2.0",
"outer_backend_path": "/data/api/java-meeting/java-meeting-extapi",
"upload_path": "/home/appadmin/"
},
"运维集控系统": {
"frontend_path": "/data/",
"inner_backend_path": "/data/",
"outer_backend_path": "/data/",
"upload_path": "/home/appadmin/"
},
"语音转录系统": {
"frontend_path": "/data/",
"inner_backend_path": "/data/",
"outer_backend_path": "/data/",
"upload_path": "/home/appadmin/"
}
},
"标准版平台": {
"预定系统": {
"frontend_path": "/var/www/cims-web/",
"inner_backend_path": "/var/www/cims-java/",
"outer_backend_path": "/var/www/cims-exapi/",
"upload_path": "/home/appadmin/"
},
"运维集控系统": {
"frontend_path": "/data/",
"inner_backend_path": "/data/",
"outer_backend_path": "/data/",
"upload_path": "/home/appadmin/"
},
"语音转录系统": {
"frontend_path": "/data/",
"inner_backend_path": "/data/",
"outer_backend_path": "/data/",
"upload_path": "/home/appadmin/"
}
}
},
"database": {
"type": "MySQL",
"is_containerized": true,
"container_pattern": "umysql",
"username": "root",
"password": "dNrprU&2S",
"databases": ["ubains", "devops"]
}
}
}
}
```
---
## 4. 用户交互界面变化
### 4.1 当前界面
```
==================================================================
远程更新程序 (PowerShell 版本)
==================================================================
[INFO] 可选择的目标服务器:
[1] 中广核大亚湾-前端测试环境 (10.126.4.79:1122)
[2] 中广核大亚湾-后端测试环境 (10.126.4.81:1122)
[0] 手动输入服务器信息
请输入服务器编号: _
```
### 4.2 新界面
```
==================================================================
远程更新程序 (PowerShell 版本)
==================================================================
[INFO] 可选择的项目:
[1] 中广核大亚湾项目
[0] 手动输入项目信息
请输入项目编号: 1
[INFO] 已选择: 中广核大亚湾项目
[INFO] 该项目可用的服务器:
[1] 中广核大亚湾-前端测试环境 (10.126.4.79:1122)
[2] 中广核大亚湾-后端测试环境 (10.126.4.81:1122)
[0] 手动输入服务器信息
请输入服务器编号: _
```
---
## 5. 代码调整范围
### 5.1 需要修改的函数
| 函数 | 修改内容 |
|------|----------|
| `Get-ProjectConfig` | 保持不变,返回项目配置(现已包含服务器) |
| `Get-ServerConfig` | 删除该函数(不再需要独立的服务器配置) |
| `Select-Project` | 保持不变,返回项目键和配置 |
| `Select-Server` | 修改参数,接收项目和项目配置,显示项目对应的服务器列表 |
| `Main` | 调整执行顺序:先选择项目,再选择服务器 |
---
## 6. 待确认事项 ✅ 已确认
- [x] 配置结构选择:**方案 A**(整合到项目配置)
- [x] 是否需要保留 `servers.json`**取消**(不再需要独立的服务器配置)
- [x] 手动输入服务器信息时,是否需要关联到项目:**需要**(属于临时选择,不保存到配置)
---
## 7. 实施计划
执行计划文档:`_PRD_流程调整需求变更_先项目后服务器_执行计划.md`
| 阶段 | 任务 | 预计工时 |
|------|------|----------|
| 阶段一 | 配置文件调整 | 0.5 小时 |
| 阶段二 | 代码调整 | 2 小时 |
| 阶段三 | 测试验证 | 1 小时 |
| 阶段四 | 文档更新 | 0.5 小时 |
**总计预计工时**: 4 小时
---
## 8. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 配置迁移错误 | 现有配置无法使用 | 提供配置迁移工具,保留原配置备份 |
| 逻辑复杂度增加 | 维护难度增加 | 添加详细注释和文档 |
| 用户习惯改变 | 用户需要适应 | 在日志中提供清晰提示 |
---
## 9. 决策记录
### 9.1 方案选择决策
**决策时间**: 2026-02-10
**决策内容**: 采用方案 A(整合到项目配置)
**决策理由**:
1. 符合实际使用场景:一个服务器通常只服务于一个项目
2. 配置更集中:项目和服务器信息在一起,维护更简单
3. 数据一致性更好:不存在服务器被删除后项目引用失效的问题
4. 部署更简单:只需要一个配置文件
5. 易于理解:配置结构直观,符合业务逻辑
### 9.2 其他决策
| 决策项 | 决策结果 | 说明 |
|--------|----------|------|
| 是否保留 servers.json | 取消 | 服务器信息整合到 projects.json 中 |
| 手动输入服务器 | 支持 | 作为临时选择,不保存到配置 |
| 服务器类型标识 | 添加 type 字段 | 用于区分 frontend/backend |
---
*文档创建日期: 2026-02-10*
*文档状态: 已确认*
*采用方案: 方案 A*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论