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

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

- 修复中间件检测模块中MQTT连接检测三种方法的变量未展开问题
- 解决脚本路径获取失败问题,使用$PSScriptRoot替代$MyInvocation.MyCommand.Path
- 增加对MQTT主题订阅和消息推送的完整检测流程
- 调整远程更新程序为先选择项目再选择服务器的交互流程
- 重构项目配置文件结构,将服务器信息整合到项目配置中
- 添加服务器类型标识(frontend/backend)以支持不同类型服务器管理
上级 88421c9b
......@@ -101,7 +101,12 @@
"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(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 @@
"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": {
"新统一平台": {
"预定系统": {
......
......@@ -134,67 +134,64 @@ function Show-Welcome {
# 辅助函数:将 PSCustomObject 转换为 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 中的嵌套对象
$result = @{}
foreach ($key in $Input.Keys) {
if ($Input[$key] -is [System.Management.Automation.PSCustomObject]) {
$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]
}
foreach ($key in $InputObject.Keys) {
$result[$key] = ConvertTo-Hashtable -InputObject $InputObject[$key]
}
return $result
}
if ($Input -is [System.Management.Automation.PSCustomObject]) {
if ($Input.PSObject.Properties.Name.Count -eq 0) {
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 $Input.PSObject.Properties) {
if ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
$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
}
foreach ($prop in $InputObject.PSObject.Properties) {
$hashtable[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
}
return $hashtable
}
return $Input
}
# ==============================================================================
# 加载服务器配置
# ==============================================================================
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 @{}
# 处理数组
if ($InputObject -is [array]) {
$result = @()
foreach ($item in $InputObject) {
$result += (ConvertTo-Hashtable -InputObject $item)
}
return $result
}
return @{}
# 其他类型直接返回
return $InputObject
}
# ==============================================================================
# 注意:服务器配置已整合到项目配置 (projects.json) 中
# 不再需要独立的 Get-ServerConfig 函数
# ==============================================================================
# ==============================================================================
# 加载项目配置
# ==============================================================================
......@@ -203,9 +200,10 @@ function Get-ProjectConfig {
if (Test-Path $configFile) {
try {
$config = Get-Content $configFile -Raw | ConvertFrom-Json
$json = [System.IO.File]::ReadAllText($configFile, [System.Text.Encoding]::UTF8)
$config = $json | ConvertFrom-Json
# 递归将 PSCustomObject 转换为 Hashtable
return ConvertTo-Hashtable -Input $config.projects
return ConvertTo-Hashtable -InputObject $config.projects
}
catch {
Write-Log -Level "ERROR" -Message "项目配置文件解析失败: $($_.Exception.Message)"
......@@ -220,14 +218,22 @@ function Get-ProjectConfig {
# 选择服务器
# ==============================================================================
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 {
Write-Log -Level "INFO" -Message "可选择的目标服务器:"
Write-Log -Level "INFO" -Message "项目 '$($project.name)' 可用的服务器:"
Write-Host ""
foreach ($key in ($servers.Keys | Sort-Object)) {
$server = $servers[$key]
......@@ -301,14 +307,15 @@ function Select-Server {
Name = "手动输入服务器"
}
}
elseif ($servers.ContainsKey($serverKey)) {
elseif ($null -ne $servers -and $servers.ContainsKey($serverKey)) {
$server = $servers[$serverKey]
$ipPort = "$($server.ip):$($server.port)"
Write-Log -Level "INFO" -Message "已选择 $($server.name) ($ipPort)"
# 检查属性是否存在(某些服务器可能没有 su_username 和 su_password)
$suUsername = if ($server.PSObject.Properties.Name -contains 'su_username') { $server.su_username } else { $null }
$suPassword = if ($server.PSObject.Properties.Name -contains 'su_password') { $server.su_password } else { $null }
$suUsername = if ($server.ContainsKey('su_username')) { $server.su_username } else { $null }
$suPassword = if ($server.ContainsKey('su_password')) { $server.su_password } else { $null }
$serverType = if ($server.ContainsKey('type')) { $server.type } else { $null }
return @{
IP = $server.ip
......@@ -318,6 +325,7 @@ function Select-Server {
SuUsername = $suUsername
SuPassword = $suPassword
Name = $server.name
Type = $serverType
}
}
else {
......@@ -766,12 +774,18 @@ function Main {
return
}
# 加载配置
$serverConfig = Get-ServerConfig
# 加载项目配置(已包含服务器信息)
$projectConfig = Get-ProjectConfig
# 选择服务器
$serverInfo = Select-Server -ServerConfig $serverConfig
# 步骤1:选择项目
Write-Host ""
$selectedProject = Select-Project -ProjectConfig $projectConfig
if (-not $selectedProject) {
return
}
# 步骤2:根据项目选择服务器
$serverInfo = Select-Server -ProjectConfig $projectConfig -ProjectKey $selectedProject.Key
if (-not $serverInfo) {
return
}
......@@ -781,17 +795,11 @@ function Main {
return
}
# 检测平台类型
# 步骤3:检测平台类型
$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") {
$platformKey = "新统一平台"
} else {
......@@ -803,7 +811,7 @@ function Main {
return
}
# 选择系统类型
# 步骤4:选择系统类型
$systemType = Select-SystemType
if (-not $systemType) {
return
......@@ -821,13 +829,13 @@ function Main {
$outerBackendPath = $pathConfig.outer_backend_path
$uploadPath = $pathConfig.upload_path
# 选择更新类型
# 步骤5:选择更新类型
$updateType = Select-UpdateType
if (-not $updateType) {
return
}
# 执行更新
# 步骤6:执行更新
switch ($updateType) {
"frontend" {
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
}
......@@ -194,13 +194,155 @@ function Test-MQTTConnection {
$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)"
Write-Log -Level "INFO" -Message "[MQTT] 进程检测失败: $($_.Exception.Message)"
}
}
# 方法D:MQTT主题订阅检测(补充检测)
Write-Log -Level "INFO" -Message "[MQTT] ========== MQTT主题订阅检测 =========="
$topicSubResults = @()
$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/"
)
# 上传 mqtt_test_x86 工具到远程服务器
Write-Log -Level "INFO" -Message "[MQTT] 准备上传 MQTT 测试工具到远程服务器..."
$mqttToolDir = "/tmp/mqtt_test"
$mqttToolSrc = "mqtt_test_x86"
# 创建远程工具目录
$createDirCmd = "mkdir -p $mqttToolDir 2>/dev/null || true"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $createDirCmd | Out-Null
# 获取脚本目录中的 mqtt_test_x86 路径
$scriptDir = $PSScriptRoot
$localMqttToolPath = Join-Path $scriptDir $mqttToolSrc
# 检查本地工具是否存在
$mqttToolExists = Test-Path $localMqttToolPath
if ($mqttToolExists) {
Write-Log -Level "INFO" -Message "[MQTT] 本地 MQTT 工具路径: $localMqttToolPath"
# 上传 mqtt 工具二进制文件和库
$mqttFiles = @("mqtt", "mosquitto_sub", "mosquitto_pub")
foreach ($file in $mqttFiles) {
$localFile = Join-Path $localMqttToolPath $file
if (Test-Path $localFile) {
$uploadCmd = "cd '$mqttToolDir' && cat > $file 2>/dev/null"
# 使用 pscp 或 base64 方式上传文件
# 这里使用 base64 编码方式上传二进制文件
$fileContent = [System.IO.File]::ReadAllBytes($localFile)
$base64Content = [System.Convert]::ToBase64String($fileContent)
$uploadCmd = "echo '$base64Content' | base64 -d > '$mqttToolDir/$file' && chmod +x '$mqttToolDir/$file'"
$uploadResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $uploadCmd
}
}
# 上传 lib 目录中的所有库文件
$libDir = Join-Path $localMqttToolPath "lib"
if (Test-Path $libDir) {
$libFiles = Get-ChildItem -Path $libDir -File
foreach ($libFile in $libFiles) {
$libContent = [System.IO.File]::ReadAllBytes($libFile.FullName)
$base64Content = [System.Convert]::ToBase64String($libContent)
$uploadCmd = "echo '$base64Content' | base64 -d > '$mqttToolDir/lib/$($libFile.Name)' && chmod +x '$mqttToolDir/lib/$($libFile.Name)'"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $uploadCmd | Out-Null
}
}
Write-Log -Level "SUCCESS" -Message "[MQTT] MQTT 测试工具上传完成"
} else {
Write-Log -Level "WARN" -Message "[MQTT] 本地 MQTT 测试工具不存在: $localMqttToolPath"
Write-Log -Level "INFO" -Message "[MQTT] 跳过主题订阅检测"
}
# 执行主题订阅检测
if ($mqttToolExists) {
$subscribedTopics = 0
foreach ($topic in $mqttTopics) {
# 订阅主题(在后台运行5秒后退出)
$subCmd = "cd '$mqttToolDir' && timeout 5 ./mqtt sub -h localhost -t '$topic' -v 2>&1 || echo 'TIMEOUT'"
$subResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $subCmd
# 检查订阅是否成功(查找 "Connected" 或成功订阅的标志)
if ($subResult.Output -and ($subResult.Output -match 'Connected' -or $subResult.Output -match 'Subscribed')) {
$subscribedTopics++
Write-Log -Level "SUCCESS" -Message "[MQTT] 主题订阅成功: $topic"
$topicSubResults += @{
Topic = $topic
Status = "订阅成功"
Success = $true
}
} else {
Write-Log -Level "WARN" -Message "[MQTT] 主题订阅失败或超时: $topic"
$topicSubResults += @{
Topic = $topic
Status = "订阅失败"
Success = $false
}
}
}
Write-Log -Level "INFO" -Message "[MQTT] 主题订阅完成: 成功 $subscribedTopics/$($mqttTopics.Count) 个"
# 方法E:MQTT消息推送检测
Write-Log -Level "INFO" -Message "[MQTT] ========== MQTT消息推送检测 =========="
$topicPubResults = @()
$publishedTopics = 0
# 选择一个主题进行消息推送测试
$testTopic = "/androidPanel/"
$testMessage = "test_message_from_health_check_$(Get-Date -Format 'yyyyMMddHHmmss')"
Write-Log -Level "INFO" -Message "[MQTT] 测试推送主题: $testTopic"
Write-Log -Level "INFO" -Message "[MQTT] 测试消息内容: $testMessage"
# 发布测试消息
$pubCmd = "cd '$mqttToolDir' && ./mqtt pub -h localhost -t '$testTopic' -m '$testMessage' 2>&1"
$pubResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $pubCmd
# 检查推送是否成功
if ($pubResult.ExitCode -eq 0 -or ($pubResult.Output -match 'sent successfully' -or $pubResult.Output -match 'Connection closed')) {
$publishedTopics++
Write-Log -Level "SUCCESS" -Message "[MQTT] 消息推送成功: $testTopic"
$topicPubResults += @{
Topic = $testTopic
Message = $testMessage
Status = "推送成功"
Success = $true
}
} else {
Write-Log -Level "WARN" -Message "[MQTT] 消息推送失败: $testTopic"
$topicPubResults += @{
Topic = $testTopic
Message = $testMessage
Status = "推送失败"
Success = $false
}
}
Write-Log -Level "INFO" -Message "[MQTT] 消息推送检测完成"
}
# 汇总结果
if ($isConnected) {
$detailMsg += " | 主题订阅: $subscribedTopics/$($mqttTopics.Count) 成功"
if ($publishedTopics -gt 0) {
$detailMsg += " | 消息推送: 成功"
}
} else {
$detailMsg += " | 主题订阅: $subscribedTopics/$($mqttTopics.Count) 成功"
if ($publishedTopics -gt 0) {
$detailMsg += " | 消息推送: 成功"
}
}
......@@ -302,7 +444,8 @@ function Test-RedisConnection {
}
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) $scriptName
# 使用 $PWD 而不是 $PSCommandPath,因为模块中 $PSCommandPath 可能为空
$localScriptPath = Join-Path $PWD $scriptName
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[Redis] 加载脚本: $localScriptPath,上传至远程 (强制覆盖)"
......@@ -315,6 +458,8 @@ function Test-RedisConnection {
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = "$remoteScriptDir/$scriptName"
}
} else {
Write-Log -Level "WARN" -Message "[Redis] 本地脚本不存在: $localScriptPath"
}
}
......@@ -530,7 +675,8 @@ function Test-MySQLConnection {
}
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) $scriptName
# 使用 $PWD 而不是 $PSCommandPath,因为模块中 $PSCommandPath 可能为空
$localScriptPath = Join-Path $PWD $scriptName
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[MySQL] 加载脚本: $localScriptPath,上传至远程 (强制覆盖)"
......@@ -543,6 +689,8 @@ function Test-MySQLConnection {
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = "$remoteScriptDir/$scriptName"
}
} else {
Write-Log -Level "WARN" -Message "[MySQL] 本地脚本不存在: $localScriptPath"
}
}
......@@ -768,7 +916,8 @@ function Test-FastDFSConnection {
$actualScriptPath = $null
if ($remoteScriptDir -and $remoteScriptDir -ne '') {
$localScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) $fdfsScriptName
# 使用 $PWD 而不是 $PSCommandPath,因为模块中 $PSCommandPath 可能为空
$localScriptPath = Join-Path $PWD $fdfsScriptName
if (Test-Path $localScriptPath) {
Write-Log -Level "INFO" -Message "[FastDFS] 加载脚本: $localScriptPath,上传至远程 (强制覆盖)"
......@@ -781,6 +930,8 @@ function Test-FastDFSConnection {
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
$actualScriptPath = "$remoteScriptDir/$fdfsScriptName"
}
} else {
Write-Log -Level "WARN" -Message "[FastDFS] 本地脚本不存在: $localScriptPath"
}
}
......
# _PRD_中间件emqx检测失败_执行计划
> **状态**: ✅ 已完成
> **状态**: 🔄 进行中
> **创建日期**: 2026-02-10
> **完成日期**: 2026-02-10
> **需求文档**: `_PRD_中间件emqx检测失败_问题处理.md`
---
......@@ -11,15 +10,16 @@
### 1.1 任务目标
修复中间件检测模块中 MQTT 连接检测的三种方法全部失败的问题。
修复中间件检测模块中 MQTT 连接检测的三种方法全部失败的问题,以及 MQTT 主题订阅检测中的脚本路径获取错误
### 1.2 核心问题
| 问题 | 描述 | 优先级 |
|------|------|--------|
| Dashboard API 检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| TCP 端口检测失败 | 变量未展开,发送字面值 `$mqttPort` 到 SSH | 高 |
| 进程状态检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| 问题一:Dashboard API 检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| 问题一:TCP 端口检测失败 | 变量未展开,发送字面值 `$mqttPort` 到 SSH | 高 |
| 问题一:进程状态检测失败 | 变量未展开,发送字面值 `$actualContainer` 到 SSH | 高 |
| 问题二:脚本路径获取失败 | `$MyInvocation.MyCommand.Path` 在模块上下文中为空 | 高 |
---
......@@ -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
# 第 152-154 行
$apiCmd = 'docker exec $actualContainer curl -s --connect-timeout 5 http://localhost:$dashboardPort/api/v4/status 2>/dev/null || echo ""'
$apiResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $apiCmd
# 第 226 行 - 有问题
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
```
### 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
# 第 152-154 行
......@@ -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
```
### 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*
*文档状态: ✅ 已完成*
*文档状态: 🔄 进行中*
......@@ -27,7 +27,34 @@
### 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. 问题解决分析
......@@ -89,6 +116,52 @@ $apiCmd = "docker exec $actualContainer curl -s --connect-timeout 5 http://local
| 问题状态 | ✅ 已修复 |
| 修复日期 | 2026-02-10 |
| 执行计划 | 已创建:`_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`
......
......@@ -37,9 +37,18 @@
## 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 @@
| 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 @@
| 容器信息收集与异常处理 | container_check.sh |
| 配置文件IP检测 | config_ip_check.sh |
| 定时任务查询 | crontab_check.sh |
| 中间件连接检测 | middleware_check.sh |
| 中间件连接检测(含MQTT主题订阅和消息推送) | middleware_check.sh |
### 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
......
......@@ -368,13 +368,62 @@ Export-ModuleMember -Function @(
### 4.8 MiddlewareCheck.psm1 - 中间件连接检测
**职责:**
- MQTT 连接检测
- MQTT 连接检测(含主题订阅和消息推送)
- Redis 连接检测
- MySQL 连接检测
- 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
Export-ModuleMember -Function @(
......@@ -389,6 +438,7 @@ Export-ModuleMember -Function @(
**依赖:**
- 需要主脚本提供 `Invoke-SSHCommand` 函数
- 需要主脚本提供 `Write-Log` 函数
- 需要主脚本提供 `Copy-File-To-Remote` 函数(用于上传 MQTT 测试工具)
- 需要主脚本提供 `$PSCP_PATH`(用于日志下载)
**原始函数位置:**
......@@ -402,6 +452,12 @@ Export-ModuleMember -Function @(
- `check_mysql.sh`
- `check_fdfs_x86.sh` / `check_fdfs_arm.sh`
**MQTT 检测新增内容:**
- mqtt_test_x86 工具上传
- mqtt_test_x86 工具执行权限设置
- 主题订阅检测
- 消息推送检测
---
## 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*
# _PRD_流程调整需求变更_先项目后服务器_执行计划
> **状态**: 待执行
> **创建日期**: 2026-02-10
> **需求变更文档**: `_PRD_流程调整需求变更_先项目后服务器.md`
> **脚本类型**: PowerShell
---
## 1. 任务概述
### 1.1 任务目标
`remote_update.ps1` 脚本的用户交互流程从"先选择服务器,再选择项目"调整为"先选择项目,再选择项目对应的服务器"。
### 1.2 变更范围
| 变更项 | 变更说明 |
|--------|----------|
| 配置文件 | 取消 `servers.json`,将服务器信息整合到 `projects.json` |
| 用户流程 | 调整选择顺序:项目 → 服务器 |
| 函数调整 | 修改相关函数以支持新流程 |
---
## 2. 配置文件调整
### 2.1 当前配置结构
**servers.json**(将被取消):
```json
{
"servers": {
"1": { "name": "...", "ip": "...", ... },
"2": { "name": "...", "ip": "...", ... }
}
}
```
**projects.json**(当前版本):
```json
{
"projects": {
"1": {
"name": "中广核大亚湾项目",
"platforms": { ... },
"database": { ... }
}
}
}
```
### 2.2 新配置结构(方案 A)
**projects.json**(新版本):
```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": {
"新统一平台": { ... },
"标准版平台": { ... }
},
"database": {
"type": "MySQL",
"is_containerized": true,
"container_pattern": "umysql",
"username": "root",
"password": "dNrprU&2S",
"databases": ["ubains", "devops"]
}
}
}
}
```
---
## 3. 代码调整方案
### 3.1 需要修改的函数
| 函数名 | 修改内容 | 文件位置 |
|--------|----------|----------|
| `Get-ProjectConfig` | 保持不变,返回项目配置(现已包含服务器) | remote_update.ps1 |
| `Get-ServerConfig` | 删除该函数(不再需要独立的服务器配置) | remote_update.ps1 |
| `Select-Project` | 保持不变 | remote_update.ps1 |
| `Select-Server` | 修改参数,接收项目和服务器配置 | remote_update.ps1 |
| `Main` | 调整执行顺序 | remote_update.ps1 |
### 3.2 函数详细调整
#### 3.2.1 删除 `Get-ServerConfig` 函数
**原因**:服务器信息现在包含在项目配置中,不再需要独立的服务器配置加载函数。
**删除代码**
```powershell
# 删除以下函数
function Get-ServerConfig {
$configFile = Join-Path $global:SCRIPT_DIR "config\servers.json"
# ...
}
```
#### 3.2.2 修改 `Select-Server` 函数
**原函数签名**
```powershell
function Select-Server {
param([hashtable]$ServerConfig)
# ...
}
```
**新函数签名**
```powershell
function Select-Server {
param(
[hashtable]$ProjectConfig,
[string]$ProjectKey
)
# ...
}
```
**函数实现调整**
```powershell
function Select-Server {
param(
[hashtable]$ProjectConfig,
[string]$ProjectKey
)
# 获取项目配置
$project = $ProjectConfig[$ProjectKey]
$servers = $project.servers
# 检查服务器配置
if ($null -eq $servers -or $servers.Count -eq 0) {
Write-Log -Level "WARN" -Message "项目 '$($project.name)' 未配置服务器"
return $null
}
# 显示服务器列表
Write-Log -Level "INFO" -Message "项目 '$($project.name)' 可用的服务器:"
Write-Host ""
foreach ($key in ($servers.Keys | Sort-Object)) {
$server = $servers[$key]
$ipPort = "$($server.ip):$($server.port)"
Write-Host " [$key] $($server.name) ($ipPort)"
}
Write-Host " [0] 手动输入服务器信息"
Write-Host ""
$serverKey = Read-Host "请输入服务器编号"
# 处理手动输入
if ($serverKey -eq "0") {
Write-Log -Level "INFO" -Message "进入手动输入模式"
# ... 手动输入逻辑 ...
return $manualServerInfo
}
# 处理预设服务器选择
if (-not $servers.ContainsKey($serverKey)) {
Write-Log -Level "ERROR" -Message "编号 $serverKey 不存在,请重新运行脚本"
return $null
}
$server = $servers[$serverKey]
$ipPort = "$($server.ip):$($server.port)"
Write-Log -Level "INFO" -Message "已选择 $($server.name) ($ipPort)"
# 构建服务器信息
return @{
IP = $server.ip
Port = $server.port
SshUsername = $server.ssh_username
SshPassword = $server.ssh_password
SuUsername = if ($server.su_username) { $server.su_username } else { $null }
SuPassword = if ($server.su_password) { $server.su_password } else { $null }
Name = $server.name
Type = $server.type
}
}
```
#### 3.2.3 修改 `Main` 函数执行顺序
**原流程**
```powershell
function Main {
# 加载配置
$serverConfig = Get-ServerConfig
$projectConfig = Get-ProjectConfig
# 选择服务器
$serverInfo = Select-Server -ServerConfig $serverConfig
# 检测平台类型
$platformType = Get-PlatformType -ServerInfo $serverInfo
# 选择项目
$project = Select-Project -ProjectConfig $projectConfig
# ...
}
```
**新流程**
```powershell
function Main {
# 加载配置
$projectConfig = Get-ProjectConfig
# 选择项目
$selectedProject = Select-Project -ProjectConfig $projectConfig
if (-not $selectedProject) {
return
}
# 选择服务器(传入项目和项目配置)
$serverInfo = Select-Server -ProjectConfig $projectConfig -ProjectKey $selectedProject.Key
if (-not $serverInfo) {
return
}
# 检测平台类型
$platformType = Get-PlatformType -ServerInfo $serverInfo
# 选择系统类型
$systemType = Select-SystemType
# ...
}
```
---
## 4. 实施步骤
### 步骤 1:配置文件调整(预计 0.5 小时)
| 任务 | 说明 | 输出 |
|------|------|------|
| 备份当前配置 | 备份 `servers.json``projects.json` | 备份文件 |
| 更新 `projects.json` | 将服务器信息整合到项目配置中 | 新配置文件 |
| 删除 `servers.json` | 删除或重命名旧的服务器配置文件 | - |
### 步骤 2:代码调整(预计 2 小时)
| 任务 | 说明 | 输出 |
|------|------|------|
| 删除 `Get-ServerConfig` 函数 | 移除不再需要的服务器配置加载函数 | - |
| 修改 `Select-Server` 函数 | 调整参数和实现逻辑 | 更新的函数 |
| 修改 `Main` 函数 | 调整执行顺序 | 更新的主函数 |
| 更新 `Select-Project` 返回值 | 确保返回项目键和配置 | 更新的函数 |
### 步骤 3:测试验证(预计 1 小时)
| 测试项 | 测试步骤 | 预期结果 |
|--------|----------|----------|
| 项目列表显示 | 运行脚本,查看项目列表 | 正确显示项目 |
| 服务器列表显示 | 选择项目后查看服务器列表 | 正确显示该项目对应的服务器 |
| 服务器选择 | 选择预设服务器 | 正确获取服务器信息 |
| 手动输入服务器 | 选择手动输入模式 | 正常输入服务器信息 |
| SSH 连接测试 | 选择服务器后测试连接 | 连接成功 |
### 步骤 4:文档更新(预计 0.5 小时)
| 任务 | 说明 | 输出 |
|------|------|------|
| 更新需求文档 | 已完成 | `_RPD_远程更新程序需求文档.md` |
| 更新执行计划 | 已完成 | `_PRD_远程更新程序需求文档_执行计划.md` |
| 创建变更文档 | 已完成 | `_PRD_流程调整需求变更_先项目后服务器.md` |
---
## 5. 代码变更详情
### 5.1 需要删除的代码
**位置**`remote_update.ps1` 中的 `Get-ServerConfig` 函数
```powershell
# ========== 以下代码需要删除 ==========
function Get-ServerConfig {
$configFile = Join-Path $global:SCRIPT_DIR "config\servers.json"
if (Test-Path $configFile) {
try {
$json = [System.IO.File]::ReadAllText($configFile, [System.Text.Encoding]::UTF8)
$config = $json | ConvertFrom-Json
# 递归将 PSCustomObject 转换为 Hashtable
return ConvertTo-Hashtable -InputObject $config.servers
}
catch {
Write-Log -Level "ERROR" -Message "服务器配置文件解析失败: $($_.Exception.Message)"
return @{}
}
}
return @{}
}
# ========== 删除结束 ==========
```
### 5.2 需要修改的代码
**文件**`remote_update.ps1`
#### 修改点 1:`Select-Server` 函数
**修改前**
```powershell
function Select-Server {
param([hashtable]$ServerConfig)
$servers = $ServerConfig
# ...
}
```
**修改后**
```powershell
function Select-Server {
param(
[hashtable]$ProjectConfig,
[string]$ProjectKey
)
$project = $ProjectConfig[$ProjectKey]
$servers = $project.servers
# ...
}
```
#### 修改点 2:`Main` 函数
**修改前**
```powershell
function Main {
# ...
$serverConfig = Get-ServerConfig
$projectConfig = Get-ProjectConfig
$serverInfo = Select-Server -ServerConfig $serverConfig
# ...
}
```
**修改后**
```powershell
function Main {
# ...
$projectConfig = Get-ProjectConfig
$selectedProject = Select-Project -ProjectConfig $projectConfig
# ...
$serverInfo = Select-Server -ProjectConfig $projectConfig -ProjectKey $selectedProject.Key
# ...
}
```
---
## 6. 测试用例
### 6.1 功能测试
| 用例编号 | 测试场景 | 测试步骤 | 预期结果 |
|----------|----------|----------|----------|
| TC-001 | 项目列表显示 | 运行脚本,查看项目列表 | 正确显示所有项目 |
| TC-002 | 服务器列表显示 | 选择项目后查看服务器列表 | 正确显示该项目对应的服务器 |
| TC-003 | 预设服务器选择 | 选择预设服务器编号 | 正确获取服务器信息 |
| TC-004 | 手动输入服务器 | 选择手动输入,输入服务器信息 | 正常输入并继续 |
| TC-005 | SSH连接测试 | 选择服务器后测试SSH连接 | 连接成功 |
| TC-006 | 平台检测 | 连接后自动检测平台类型 | 正确识别平台类型 |
| TC-007 | 系统类型选择 | 选择系统类型 | 正常显示系统类型列表 |
| TC-008 | 更新类型选择 | 选择更新类型 | 正常显示更新类型列表 |
### 6.2 异常测试
| 用例编号 | 测试场景 | 测试步骤 | 预期结果 |
|----------|----------|----------|----------|
| TE-001 | 项目配置不存在 | 删除或重命名 `projects.json` | 提示配置文件不存在 |
| TE-002 | 项目无服务器配置 | 配置项目但无服务器 | 提示项目未配置服务器 |
| TE-003 | 服务器编号错误 | 输入不存在的服务器编号 | 提示编号不存在 |
| TE-004 | SSH连接失败 | 输入错误的密码 | 连接失败,提示重新选择 |
---
## 7. 验收标准
### 7.1 功能验收
- [ ] 配置文件已更新,服务器信息整合到 `projects.json`
- [ ] `servers.json` 已删除或标记为废弃
- [ ] 脚本启动后首先显示项目列表
- [ ] 选择项目后显示该项目对应的服务器列表
- [ ] 支持从服务器列表中选择预设服务器
- [ ] 支持手动输入服务器信息
- [ ] 后续功能(平台检测、系统选择、更新执行)正常工作
### 7.2 质量验收
- [ ] 代码符合现有编码规范
- [ ] 错误处理完善
- [ ] 日志输出清晰
- [ ] 功能测试全部通过
### 7.3 文档验收
- [ ] 需求文档已更新
- [ ] 执行计划文档已更新
- [ ] 变更文档已创建
---
## 8. 进度跟踪
| 阶段 | 任务 | 预计工时 | 状态 | 完成日期 |
|------|------|----------|------|----------|
| 阶段一 | 配置文件调整 | 0.5 小时 | 待执行 | |
| 阶段二 | 代码调整 | 2 小时 | 待执行 | |
| 阶段三 | 测试验证 | 1 小时 | 待执行 | |
| 阶段四 | 文档更新 | 0.5 小时 | ✅ 已完成 | 2026-02-10 |
**总计预计工时**: 4 小时
---
## 9. 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 配置迁移错误 | 现有配置无法使用 | 提供配置迁移脚本,保留原配置备份 |
| 函数调用错误 | 脚本无法正常运行 | 全面测试所有调用路径 |
| 用户习惯改变 | 用户需要适应新流程 | 在日志和界面中提供清晰提示 |
---
## 10. 回滚方案
如果新流程存在问题,可以快速回滚到原流程:
1. 恢复 `servers.json` 配置文件
2. 恢复 `Get-ServerConfig` 函数
3. 恢复 `Select-Server` 函数的原参数
4. 恢复 `Main` 函数的原执行顺序
---
*文档创建日期: 2026-02-10*
*文档状态: 待执行*
......@@ -4,6 +4,7 @@
> **创建日期**: 2026-02-10
> **需求文档**: `_RPD_远程更新程序需求文档.md`
> **脚本类型**: PowerShell / Shell
> **版本**: 2.0 (流程调整:先项目后服务器)
---
......@@ -17,9 +18,9 @@
| 功能模块 | 描述 | 优先级 |
|----------|------|--------|
| SSH 连接管理 | 支持预设服务器和手动输入服务器信息 | P0 |
| 项目配置管理 | 项目-服务器关联配置、路径映射 | P0 |
| SSH 连接管理 | 支持项目内服务器选择和手动输入 | P0 |
| 平台类型检测 | 自动检测新统一平台/标准版平台 | P0 |
| 项目配置管理 | 项目路径映射、数据库配置 | P0 |
| 前端更新 | 文件备份、更新、回滚 | P0 |
| 后端更新 | Jar 文件备份、更新、服务重启、回滚 | P0 |
| 全量更新 | 前端+后端顺序更新 | P0 |
......@@ -31,7 +32,7 @@
|--------|------|
| `remote_update.ps1` | Windows PowerShell 版本主脚本 |
| `remote_update.sh` | Linux Shell 版本主脚本 |
| 配置文件模板 | 项目配置模板文件 |
| `config/projects.json` | 项目配置文件(含服务器信息)|
| 测试用例文档 | 功能测试用例 |
| 用户使用文档 | 脚本使用说明文档 |
......@@ -67,7 +68,7 @@
| 子任务 | 描述 | 输出 |
|--------|------|------|
| 创建目录结构 | 创建脚本目录、配置目录、日志目录 | 目录结构 |
| 定义配置文件格式 | 设计项目配置、服务器配置的数据结构 | 配置模板 |
| 定义配置文件格式 | 设计项目-服务器关联配置的数据结构 | 配置模板 |
| 定义日志格式 | 设计日志输出格式和存储方式 | 日志模板 |
**目录结构:**
......@@ -76,9 +77,9 @@ remote_update/
├── remote_update.ps1 # PowerShell 主脚本
├── remote_update.sh # Shell 主脚本
├── config/
│ ├── servers.json # 服务器配置
│ └── projects.json # 项目配置
│ └── projects.json # 项目配置(含服务器信息)
├── lib/
│ ├── log.ps1 # 日志模块
│ ├── ssh.ps1 # SSH 连接模块
│ ├── file.ps1 # 文件操作模块
│ ├── backup.ps1 # 备份恢复模块
......@@ -101,6 +102,7 @@ remote_update/
# 服务器信息数据结构
$ServerInfo = @{
Name = "服务器名称"
Type = "frontend" # frontend 或 backend
IP = "192.168.1.1"
Port = 22
SshUsername = "root" # SSH 连接账号
......@@ -132,8 +134,8 @@ function Invoke-RemoteCommand {
| 子任务 | 描述 | 输出 |
|--------|------|------|
| 服务器配置加载 | 加载预设服务器信息 | 函数 |
| 项目配置加载 | 加载项目路径映射配置 | 函数 |
| 项目配置加载 | 加载项目配置(含服务器信息) | 函数 |
| 项目服务器获取 | 根据项目获取对应服务器列表 | 函数 |
| 平台类型检测 | 检测新统一平台/标准版平台 | 函数 |
---
......@@ -144,11 +146,30 @@ function Invoke-RemoteCommand {
| 子任务 | 描述 | 输出 |
|--------|------|------|
| 服务器选择 | 预设服务器列表选择或手动输入 | 函数 |
| 项目选择 | 项目编号选择 | 函数 |
| 项目选择 | 显示项目列表,支持选择 | 函数 |
| 服务器选择 | 根据项目显示服务器列表,支持选择或手动输入 | 函数 |
| 系统类型选择 | 预定/运维集控/语音转录选择 | 函数 |
| 更新类型选择 | 前端/后端/全量选择 | 函数 |
**用户交互流程:**
```
1. 显示项目列表
2. 用户选择项目
3. 显示该项目对应的服务器列表
4. 用户选择服务器
5. 连接并检测平台类型
6. 选择系统类型
7. 选择更新类型
8. 执行更新
```
#### 任务 2.2:前端更新模块
| 子任务 | 描述 | 输出 |
......@@ -168,7 +189,7 @@ function Invoke-RemoteCommand {
| Jar 文件更新 | 替换 .jar 文件 | 函数 |
| 服务重启 | 执行 run.sh 脚本 | 函数 |
| 进程检测 | 验证服务启动成功 | 函数 |
| 数据库备份 | 预留函数接口 | 函数(空实现) |
| 数据库备份 | 预留函数接口 | 函数(空实现)|
#### 任务 2.4:回滚模块
......@@ -176,7 +197,7 @@ function Invoke-RemoteCommand {
|--------|------|------|
| 前端回滚 | 恢复前端备份文件 | 函数 |
| 后端回滚 | 恢复后端 Jar 文件 | 函数 |
| 数据库恢复 | 预留函数接口 | 函数(空实现) |
| 数据库恢复 | 预留函数接口 | 函数(空实现)|
| 全量回滚 | 前后端一起回滚 | 函数 |
---
......@@ -198,6 +219,8 @@ function Invoke-RemoteCommand {
| 测试项 | 测试内容 | 预期结果 |
|--------|----------|----------|
| 项目选择测试 | 测试项目列表显示和选择 | 正确选择项目 |
| 服务器选择测试 | 测试根据项目显示服务器 | 正确显示项目对应服务器 |
| SSH 连接测试 | 测试不同服务器连接 | 连接成功 |
| 平台检测测试 | 测试新统一平台/标准版平台 | 正确识别 |
| 前端更新测试 | 测试前端更新流程 | 更新成功 |
......@@ -216,16 +239,29 @@ function Invoke-RemoteCommand {
## 4. 详细功能设计
### 4.1 服务器选择流程
### 4.1 项目-服务器选择流程
```
开始
|
v
显示预设服务器列表
加载项目配置
|
v
显示项目列表
|
v
用户输入项目编号
|
+---> 输入 0 ---> 手动输入项目信息 (暂不支持)
|
+---> 输入 1-N ---> 获取该项目对应的服务器列表
|
v
显示该项目的服务器列表
|
v
用户输入编号
用户输入服务器编号
|
+---> 输入 0 ---> 手动输入服务器信息 (IP/User/Pass/Port)
|
......@@ -306,7 +342,7 @@ function Invoke-SSHCommand {
}
```
### 4.3 容器匹配逻辑
### 4.4 容器匹配逻辑
```powershell
function Find-Container {
......@@ -331,7 +367,7 @@ function Find-Container {
}
```
### 4.4 Jar 文件备份逻辑
### 4.5 Jar 文件备份逻辑
```powershell
function Backup-JarFiles {
......@@ -365,7 +401,7 @@ function Backup-JarFiles {
}
```
### 4.5 服务重启验证逻辑
### 4.6 服务重启验证逻辑
```powershell
function Test-ServiceStatus {
......@@ -400,38 +436,34 @@ function Test-ServiceStatus {
## 5. 配置文件格式
### 5.1 服务器配置 (servers.json)
```json
{
"servers": {
"1": {
"name": "中广核大亚湾-前端测试环境",
"ip": "10.126.4.79",
"port": 1122,
"ssh_username": "root",
"ssh_password": "Admin@123Admin@123"
},
"2": {
"name": "中广核大亚湾-后端测试环境",
"ip": "10.126.4.81",
"port": 1122,
"ssh_username": "appadmin",
"ssh_password": "CGNadm!@345CGNadm!@345",
"su_username": "root",
"su_password": "Admin@123Admin@123"
}
}
}
```
### 5.2 项目配置 (projects.json)
### 5.1 项目配置文件 (projects.json)
```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": {
"新统一平台": {
"预定系统": {
......@@ -496,6 +528,7 @@ function Test-ServiceStatus {
| 模块 | 测试用例 | 预期结果 |
|------|----------|----------|
| 日志模块 | 测试不同级别的日志输出 | 正常输出 |
| 项目配置加载 | 测试项目配置加载 | 正确解析项目-服务器关联 |
| SSH 连接(直接root) | 测试直接 root 连接 | 连接成功,命令正常执行 |
| SSH 连接(普通用户+su) | 测试普通用户连接后 su 切换 | 连接成功,命令以 root 权限执行 |
| SSH 连接(错误密码) | 测试错误密码连接 | 连接失败,提示错误 |
......@@ -507,6 +540,7 @@ function Test-ServiceStatus {
| 测试场景 | 测试步骤 | 预期结果 |
|----------|----------|----------|
| 项目选择 | 选择项目后显示对应服务器 | 正确显示项目服务器 |
| 前端更新-正常 | 完整前端更新流程 | 更新成功 |
| 前端更新-路径不存在 | 前端路径不存在 | 提示错误 |
| 后端更新-正常 | 完整后端更新流程 | 更新成功,服务正常 |
......@@ -542,12 +576,13 @@ function Test-ServiceStatus {
### 8.1 功能验收
- [ ] 支持项目-服务器关联配置
- [ ] 支持先选择项目,再选择项目对应的服务器
- [ ] 支持 SSH 连接管理(预设+手动输入)
- [ ] 支持直接 root 连接方式
- [ ] 支持普通用户 + su 切换连接方式
- [ ] su 切换后命令能正确以 root 权限执行
- [ ] 自动识别平台类型(新统一平台/标准版平台)
- [ ] 支持项目配置和路径映射
- [ ] 前端更新功能完整(备份、更新、清理)
- [ ] 后端更新功能完整(Jar 备份、更新、重启、验证)
- [ ] 全量更新功能完整(顺序执行、联合回滚)
......@@ -582,9 +617,39 @@ function Test-ServiceStatus {
---
## 10. 附录
## 10. 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| 1.0 | 2026-02-10 | 初始版本 | - |
| 2.0 | 2026-02-10 | 流程调整:先选择项目,再选择服务器 | - |
### 2.0 版本变更说明
**变更原因:**
- 实际使用中,用户更习惯先确定要更新的项目,再选择该项目对应的服务器
- 项目和服务器关联配置更符合业务逻辑
**主要变更:**
1. 配置结构调整
- 取消独立的 `servers.json`
- 将服务器信息整合到 `projects.json` 的每个项目下
- 每个项目包含自己的 `servers` 配置
2. 用户流程调整
- 原:选择服务器 → 选择项目 → ...
- 新:选择项目 → 选择服务器 → ...
3. 代码调整范围
- `Get-ProjectConfig` 函数:返回含服务器的项目配置
- `Select-Server` 函数:接收项目配置参数,显示项目对应的服务器列表
- `Main` 函数:调整执行顺序
---
## 11. 附录
### 10.1 更新包目录结构
### 11.1 更新包目录结构
```
update.zip
......@@ -601,7 +666,7 @@ update.zip
└── *.jar
```
### 10.2 服务器连接配置说明
### 11.2 服务器连接配置说明
#### 连接方式
......@@ -616,6 +681,7 @@ update.zip
```json
{
"name": "前端测试环境",
"type": "frontend",
"ip": "10.126.4.79",
"port": 1122,
"ssh_username": "root",
......@@ -627,6 +693,7 @@ update.zip
```json
{
"name": "后端测试环境",
"type": "backend",
"ip": "10.126.4.81",
"port": 1122,
"ssh_username": "appadmin",
......@@ -636,17 +703,7 @@ update.zip
}
```
#### su 切换执行命令格式
```bash
# su 切换后执行命令的格式
echo '[su_password]' | su - [su_username] -c '[command]'
# 示例
echo 'Admin@123' | su - root -c 'docker ps'
```
### 10.3 常用命令参考
### 11.3 常用命令参考
| 功能 | 命令 |
|------|------|
......@@ -662,3 +719,4 @@ echo 'Admin@123' | su - root -c 'docker ps'
*文档创建日期: 2026-02-10*
*文档状态: 待执行*
*文档版本: 2.0*
# 远程更新程序需求文档
## 背景
当前存在人工手动更新服务器端程序问题,需要创建此需求文档。
......@@ -13,35 +14,82 @@
## 需求步骤
### powershell脚本步骤大纲
1. 通过预设的服务器信息输入编号或是手动输入服务器信息建立ssh连接,并自动通过服务器上的目录结构判断平台类型为:1.新统一平台类型;2.标准版平台类型
2. 选择项目编号,每个项目编号都有独立的前后端服务路径映射表。
3. 输入编号选择系统类型:1.预定系统;2.运维集控系统;3.语音转录系统
4. 输入程序更新编号,通过编号确认更新为:1,前端更新;2.后端更新;3.前端和后端都更新。
5. 根据对应的程序更新编号,执行对应更新步骤;
1. 选择项目编号,每个项目编号都有独立的前后端服务路径映射表和对应的服务器列表
2. 根据选择的项目显示该项目对应的服务器列表,选择服务器编号或手动输入服务器信息
3. 建立ssh连接,自动通过服务器上的目录结构判断平台类型为:1.新统一平台类型;2.标准版平台类型
4. 输入编号选择系统类型:1.预定系统;2.运维集控系统;3.语音转录系统
5. 输入程序更新编号,通过编号确认更新为:1,前端更新;2.后端更新;3.前端和后端都更新。
6. 根据对应的程序更新编号,执行对应更新步骤
#### 步骤1:选择项目
```aiignore
$project_info = @{
项目A=@{
name="项目A描述";
servers=@{
"1"=@{
name="项目A-前端服务器";
ip=192.168.1.1;
port=22;
ssh_username="root";
ssh_password="<PASSWORD>";
type="frontend"; # 服务器类型标识
}
"2"=@{
name="项目A-后端服务器";
ip=192.168.1.2;
port=22;
ssh_username="appadmin";
ssh_password="<PASSWORD>";
su_username="root";
su_password="<PASSWORD>";
type="backend"; # 服务器类型标识
}
}
platforms=@{
# 平台配置...
}
}
}
#### 步骤1
# 显示项目列表
function Show-Projects {
Write-Host "可选择的项目:"
foreach ($key in $project_info.Keys) {
Write-Host " [$key] $($project_info[$key].name)"
}
Write-Host " [0] 手动输入项目信息"
}
```
#### 步骤2:选择服务器(基于项目)
```aiignore
$server_info = @{
服务器A=@{
name="服务器A描述";
ip=192.168.1.1;
port=22;
ssh_username="root"; # SSH连接账号
ssh_password="<PASSWORD>"; # SSH连接密码
su_username=""; # 为空表示直接root连接
su_password="";
function Get-ProjectServers {
param($project_key)
$project = $project_info[$project_key]
Write-Host "该项目可用的服务器:"
foreach ($key in $project.servers.Keys) {
$server = $project.servers[$key]
Write-Host " [$key] $($server.name) ($($server.ip):$($server.port))"
}
服务器B=@{
name="服务器B描述";
ip=192.168.1.2;
port=22;
ssh_username="appadmin"; # 先用普通用户连接
ssh_password="<PASSWORD>";
su_username="root"; # 再切换到root
su_password="<PASSWORD>";
Write-Host " [0] 手动输入服务器信息"
# 用户选择服务器
$server_key = Read-Host "请输入服务器编号"
if ($server_key -eq "0") {
# 手动输入服务器信息
return Get-ManualServerInfo
} else {
return $project.servers[$server_key]
}
}
```
#### 步骤3:建立SSH连接
```aiignore
# 通过ssh建立连接
function Connect-SSH($server_info) {
# 根据su_username是否为空决定连接方式
......@@ -74,46 +122,28 @@ function Get-PlatformType($server_info) {
elseif server_path 存在 /var/www/
server_type = "标准版平台类型"
}
```
#### 步骤2
```aiignore
$project_info = @{
项目A=@{
if system_type = "预定系统"
前端更新路径=/data/service/projectA/frontend;
后端更新路径=/data/service/projectA/backend;
elif system_type = "运维集控系统"
前端更新路径=/data/service/projectA/frontend;
后端更新路径=/data/service/projectA/backend;
elif system_type = "语音转录系统"
前端更新路径=/data/service/projectA/frontend;
后端更新路径=/data/service/projectA/backend;
}
}
```
#### 步骤3
#### 步骤4
```aiignore
function Get-ProjectInfo($project_info) {
function Get-SystemTypeInfo($project_info) {
# 根据选择的项目获取其平台配置
1:预定系统
2:运维集控系统
3:语音转录系统
}
```
#### 步骤4
#### 步骤5
```aiignore
function Get-UpdateType($update_type) {
1:前端更新
2:后端更新
3:全量更新
}
```
#### 步骤5
#### 步骤6
```aiignore
# 根据前面获取的平台类型、项目编号、程序更新编号,执行对应更新步骤
function Update-Program($server_info, $project_info, $update_type) {
......@@ -142,7 +172,96 @@ function Update-Program($server_info, $project_info, $update_type) {
| 对内后端名称 | ubains-inner-api |
| 对外后端名称 | ubains-exapi-api |
### 2. 服务器连接配置
### 2. 项目配置(包含服务器信息)
#### 2.1 配置文件结构
**配置文件位置**: `config/projects.json`
#### 2.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"]
}
}
}
}
```
### 3. 服务器连接配置
| 配置项 | 说明 |
|--------|------|
......@@ -153,6 +272,7 @@ function Update-Program($server_info, $project_info, $update_type) {
| ssh_password | 建立 SSH 连接的密码 |
| su_username | 切换 root 账号的名称(可选) |
| su_password | 切换 root 账号的密码(可选) |
| type | 服务器类型标识(frontend/backend)|
**连接说明:**
- 如果 `su_username` 为空或不配置,表示服务器可以直接通过 root 权限连接,无需切换
......@@ -165,6 +285,7 @@ function Update-Program($server_info, $project_info, $update_type) {
```json
{
"name": "前端测试环境",
"type": "frontend",
"ip": "10.126.4.79",
"port": 1122,
"ssh_username": "root",
......@@ -176,6 +297,7 @@ function Update-Program($server_info, $project_info, $update_type) {
```json
{
"name": "后端测试环境",
"type": "backend",
"ip": "10.126.4.81",
"port": 1122,
"ssh_username": "appadmin",
......@@ -185,7 +307,7 @@ function Update-Program($server_info, $project_info, $update_type) {
}
```
### 3. 数据库配置(预留功能)
### 4. 数据库配置(预留功能)
> 注意:由于不同项目使用的数据库类型可能不同(MySQL/达梦等),部署方式也存在差异(容器化/非容器化),数据库备份功能暂时不执行,仅预留函数位置供后续扩展。
......@@ -223,15 +345,7 @@ function Restore-Database {
}
```
### 3. 项目配置
#### 3.1 预设项目列表
| 项目编号 | 项目名称 | 说明 |
|----------|----------|------|
| 1 | 中广核大亚湾项目 | 示例项目 |
#### 3.2 项目路径映射表
### 5. 项目路径映射表
**中广核大亚湾项目路径配置:**
......@@ -244,9 +358,9 @@ function Restore-Database {
| 标准版平台 | 运维集控系统 | `/data/` | `/data/` | `/data/` | `/home/appadmin/` |
| 标准版平台 | 语音转录系统 | `/data/` | `/data/` | `/data/` | `/home/appadmin/` |
> 注:项目路径配置可根据实际情况在需求文档中补充
> 注:项目路径配置可根据实际情况在配置文件中补充
#### 3.3 项目数据库配置
### 6. 项目数据库配置
**中广核大亚湾项目数据库配置:**
......@@ -257,7 +371,7 @@ function Restore-Database {
| 数据库密码 | dNrprU&2S |
| 数据库名称 | ubains、devops |
### 4. 服务操作配置
### 7. 服务操作配置
| 服务类型 | 操作方式 | 验证方式 | 失败处理 |
|----------|----------|----------|----------|
......@@ -265,14 +379,14 @@ function Restore-Database {
| 对内后端 | 模糊匹配容器后进入容器,到指定目录执行 run.sh | 通过进程判断服务状态 | 回滚备份 |
| 对外后端 | 在宿主机指定目录执行 run.sh | 通过进程判断服务状态 | 回滚备份 |
#### 4.1 容器匹配规则
#### 7.1 容器匹配规则
**对内后端容器匹配:**
- 使用模糊匹配查找容器(如 `ujava*`
- 匹配优先级:精确匹配 > 模糊匹配
- 如果匹配到多个容器,需提示用户选择
#### 4.2 后端服务重启详细说明
#### 7.2 后端服务重启详细说明
**对内后端服务重启流程:**
1. 模糊匹配并进入容器
......@@ -286,7 +400,7 @@ function Restore-Database {
3. 执行目录下的 run.sh 脚本(无需传参,脚本自行处理停止和启动)
4. 验证进程是否存在
#### 4.3 后端 Jar 文件处理详细说明
#### 7.3 后端 Jar 文件处理详细说明
**备份规则:**
- 只备份 `.jar` 结尾的文件
......@@ -299,7 +413,7 @@ function Restore-Database {
- 配置文件(如 `config/``conf/` 目录)保持不变
- 对于容器内的服务,需要先从容器内复制 jar 文件到宿主机备份,然后再替换
### 5. 备份策略配置
### 8. 备份策略配置
| 配置项 | 说明 |
|--------|------|
......@@ -307,13 +421,13 @@ function Restore-Database {
| 保留策略 | 保留最近两个版本 |
| 存储位置 | 前后端各自路径下的 `backup` 目录 |
#### 5.1 备份文件保留规则
#### 8.1 备份文件保留规则
- 保留最近 2 个版本的备份文件
- 如果服务器上只有 1 个备份文件,则保留 1 个
- 超出保留数量的旧备份文件自动删除
#### 5.2 备份目录结构示例
#### 8.2 备份目录结构示例
```
前端路径/
......@@ -341,14 +455,17 @@ function Restore-Database {
```
1. 检查 update.zip 文件是否存在
2. 连接服务器,验证前端路径
3. 上传 update.zip 文件到服务器上传路径目录下(项目配置中的服务器上传路径)
4. 在服务器上解压缩 update.zip
5. 创建前端路径下的 backup 目录(如果不存在)
6. 将前端当前目录下的所有文件移动至 backup 目录下
7. 将 update 解压后的 ubains-web 目录下的所有文件移动到前端目录下
8. 清理旧备份(保留最近2个版本)
9. 完成(无需验证,无需重启服务)
2. 选择项目编号
3. 根据项目显示服务器列表,选择服务器或手动输入
4. 连接服务器,自动检测平台类型(新统一平台/标准版平台)
5. 验证前端路径
6. 上传 update.zip 文件到服务器上传路径目录下(项目配置中的服务器上传路径)
7. 在服务器上解压缩 update.zip
8. 创建前端路径下的 backup 目录(如果不存在)
9. 将前端当前目录下的所有文件移动至 backup 目录下
10. 将 update 解压后的 ubains-web 目录下的所有文件移动到前端目录下
11. 清理旧备份(保留最近2个版本)
12. 完成(无需验证,无需重启服务)
```
**注意事项:**
......@@ -359,27 +476,29 @@ function Restore-Database {
```
1. 检查 update.zip 文件是否存在
2. 连接服务器,验证后端路径
3. 上传 update.zip 文件到服务器上传路径目录下(项目配置中的服务器上传路径)
4. 在服务器上解压缩 update.zip
5. [预留] 执行数据库备份(暂不执行,仅预留函数位置)
- 调用 Backup-Database 函数(预留)
6. 备份当前后端 jar 文件到 backup 目录
2. 选择项目编号
3. 根据项目显示服务器列表,选择服务器或手动输入
4. 连接服务器,自动检测平台类型(新统一平台/标准版平台)
5. 验证后端路径
6. 上传 update.zip 文件到服务器上传路径目录下(项目配置中的服务器上传路径)
7. 在服务器上解压缩 update.zip
8. [预留] 执行数据库备份(暂不执行,仅预留函数位置)
9. 备份当前后端 jar 文件到 backup 目录
- 只备份 .jar 结尾的文件
- 不影响配置文件(config、conf 等)
- 对内后端:从容器内复制 jar 文件到宿主机 backup 目录
- 对外后端:直接复制 jar 文件到 backup 目录
7. 清理旧备份(保留最近2个版本)
8. 替换 jar 文件
- 对内后端:将 update 解压后的 ubains-inner-api 目录下的 jar 文件复制到容器内后端路径
- 对外后端:将 update 解压后的 ubains-exapi-api 目录下的 jar 文件复制到后端路径
- 配置文件保持不变
9. 执行 run.sh 脚本启动服务(无需传参)
- 对内后端:进入容器后执行
- 对外后端:在宿主机执行
10. 验证服务启动成功(进程检测)
11. 如果启动失败,执行回滚
12. 完成
10. 清理旧备份(保留最近2个版本)
11. 替换 jar 文件
- 对内后端:将 update 解压后的 ubains-inner-api 目录下的 jar 文件复制到容器内后端路径
- 对外后端:将 update 解压后的 ubains-exapi-api 目录下的 jar 文件复制到后端路径
- 配置文件保持不变
12. 执行 run.sh 脚本启动服务(无需传参)
- 对内后端:进入容器后执行
- 对外后端:在宿主机执行
13. 验证服务启动成功(进程检测)
14. 如果启动失败,执行回滚
15. 完成
```
**注意事项:**
......@@ -450,6 +569,8 @@ function Restore-Database {
### 严重错误(停止更新)
- update.zip 文件不存在
- 项目配置文件不存在或格式错误
- 服务器信息配置错误
- SSH 连接失败
- 目标路径不存在
- 上传文件到服务器失败
......@@ -511,38 +632,49 @@ docker cp [容器名称]:[容器内路径] [宿主机路径]
docker cp [宿主机路径] [容器名称]:[容器内路径]
```
### D. 项目配置模板
### D. 用户交互界面示例
```powershell
$ProjectConfig = @{
"项目编号" = @{
Name = "项目名称"
Platforms = @{
"新统一平台" = @{
"预定系统" = @{
FrontendPath = "/data/xxx"
InnerBackendPath = "/data/xxx"
OuterBackendPath = "/data/xxx"
UploadPath = "/home/appadmin/"
}
"运维集控系统" = @{ ... }
"语音转录系统" = @{ ... }
}
"标准版平台" = @{
"预定系统" = @{ ... }
"运维集控系统" = @{ ... }
"语音转录系统" = @{ ... }
}
}
Database = @{
# 数据库配置(预留功能)
Type = "MySQL" # 或 "DM"(达梦)
IsContainerized = $true
ContainerPattern = "umysql"
Username = "root"
Password = "dNrprU&2S"
Databases = @("ubains", "devops")
}
}
}
```
\ No newline at end of file
```
==================================================================
远程更新程序 (PowerShell 版本)
==================================================================
[INFO] 脚本版本: 1.0.0
[INFO] 可选择的项目:
[1] 中广核大亚湾项目
[0] 手动输入项目信息
请输入项目编号: 1
[INFO] 已选择: 中广核大亚湾项目
[INFO] 该项目可用的服务器:
[1] 中广核大亚湾-前端测试环境 (10.126.4.79:1122)
[2] 中广核大亚湾-后端测试环境 (10.126.4.81:1122)
[0] 手动输入服务器信息
请输入服务器编号: 1
[INFO] 已选择: 中广核大亚湾-前端测试环境
[INFO] 正在测试 SSH 连接...
[SUCCESS] SSH 连接测试通过
[INFO] 自动检测目标服务器平台类型...
[INFO] 检测结果: 新统一平台
[INFO] 请选择系统类型:
[1] 预定系统
[2] 运维集控系统
[3] 语音转录系统
请输入系统类型编号: 1
[INFO] 请选择更新类型:
[1] 前端更新
[2] 后端更新
[3] 全量更新(前端+后端)
请输入更新类型编号: _
```
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论