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

feat(services): 添加安卓设备自检功能

- 实现安卓设备连接状态检测,支持通过adb connect命令连接指定IP和端口的设备
- 添加日志文件收集功能,通过adb pull命令导出应用的files和cache目录日志
- 实现自动断开连接机制,在日志收集完成后自动执行adb disconnect命令
- 集成到服务自检报告中,将安卓设备检测结果显示在整体自检报告中
- 添加adb工具路径解析和依赖检查,优先使用脚本目录下的adb.exe
- 支持设备未授权或离线状态的错误处理和用户提示
- 实现可选目录导出逻辑,当cache目录不存在时自动跳过该目录导出操作
上级 a048a53a
...@@ -2238,6 +2238,272 @@ function Test-ContainerPorts { ...@@ -2238,6 +2238,272 @@ function Test-ContainerPorts {
return $results return $results
} }
# ================================
# Android 设备自检(需求 15)
# ================================
$AndroidLogRemoteDir = "/sdcard/Android/data/com.ubains.local.gviewer/files/"
$AndroidDefaultPort = 5555
# 优先使用脚本目录下的 adb.exe
$script:ADB_PATH = $null
function Resolve-AdbPath {
# 1) 脚本目录
$localAdb = Join-Path $SCRIPT_DIR "adb.exe"
if (Test-Path $localAdb) { return $localAdb }
# 2) 系统 PATH
try {
$cmd = Get-Command adb -ErrorAction Stop
return $cmd.Source
} catch {
return $null
}
}
function Test-AdbAvailable {
$script:ADB_PATH = Resolve-AdbPath
if ([string]::IsNullOrWhiteSpace($script:ADB_PATH)) { return $false }
# 如果是本地 adb.exe,检查 DLL 是否齐全(避免“只放 adb.exe”运行不了)
$adbDir = Split-Path $script:ADB_PATH -Parent
$dll1 = Join-Path $adbDir "AdbWinApi.dll"
$dll2 = Join-Path $adbDir "AdbWinUsbApi.dll"
if ((Split-Path $script:ADB_PATH -Leaf) -ieq "adb.exe") {
if (-not (Test-Path $dll1) -or -not (Test-Path $dll2)) {
Write-Log -Level "WARN" -Message "[Android] 找到 adb.exe 但缺少依赖 DLL:AdbWinApi.dll/AdbWinUsbApi.dll。请将它们与 adb.exe 放在同一目录"
return $false
}
}
return $true
}
function Invoke-Adb {
param(
[Parameter(Mandatory=$true)] [string[]] $Args
)
if (-not $script:ADB_PATH) { $script:ADB_PATH = Resolve-AdbPath }
$adbExe = if ($script:ADB_PATH) { $script:ADB_PATH } else { "adb" }
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $adbExe
$psi.Arguments = ($Args -join " ")
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $psi
[void]$p.Start()
$out = $p.StandardOutput.ReadToEnd()
$err = $p.StandardError.ReadToEnd()
$p.WaitForExit()
return @{
ExitCode = $p.ExitCode
Out = $out
Err = $err
CmdLine = ("{0} {1}" -f $adbExe, ($Args -join ' '))
}
}
function Test-AndroidDeviceHealth {
param(
[Parameter(Mandatory=$true)] [string] $ScriptDir
)
Write-Host ""
Write-Log -Level "INFO" -Message "========== 安卓设备自检 (PRD 15) =========="
$results = @()
# 0) 依赖检查:adb
if (-not (Test-AdbAvailable)) {
Write-Log -Level "WARN" -Message "[Android] 未检测到 adb.exe(请安装 Android Platform Tools 或将 adb 加入 PATH),跳过安卓自检"
$results += @{
Check = "Android自检"
Status = "跳过"
Details = "adb.exe 未找到"
Success = $false
}
return $results
}
# 1) 输入设备 IP/端口
$deviceIp = Read-Host "请输入安卓设备IP(留空则跳过安卓自检)"
if ([string]::IsNullOrWhiteSpace($deviceIp)) {
Write-Log -Level "INFO" -Message "[Android] 未输入设备IP,跳过安卓自检"
$results += @{
Check = "Android自检"
Status = "跳过"
Details = "未输入设备IP"
Success = $true
}
return $results
}
$portInput = Read-Host ("请输入安卓设备端口 [默认 {0}]" -f $AndroidDefaultPort)
$port = $AndroidDefaultPort
if (-not [string]::IsNullOrWhiteSpace($portInput)) {
try { $port = [int]$portInput } catch { $port = $AndroidDefaultPort }
}
$target = ("{0}:{1}" -f $deviceIp.Trim(), $port)
Write-Log -Level "INFO" -Message ("[Android] 目标设备: {0}" -f $target)
$connected = $false
try {
# 2) 连接设备:adb connect
$conn = Invoke-Adb -Args @("connect", $target)
$connOut = (($conn.Out + "`n" + $conn.Err) -replace "`r","").Trim()
Write-Log -Level "INFO" -Message ("[Android] 执行: {0}" -f $conn.CmdLine)
if ($connOut) { Write-Log -Level "INFO" -Message ("[Android] 输出: {0}" -f ($connOut -replace '\s+',' ')) }
# 判定连接成功(兼容多种输出)
if ($connOut -match "(?i)connected to" -or $connOut -match "(?i)already connected to") {
$connected = $true
}
# adb devices 复核
if ($connected) {
$dev = Invoke-Adb -Args @("devices")
$devTxt = (($dev.Out + "`n" + $dev.Err) -replace "`r","")
if ($devTxt -match [regex]::Escape($target) + "\s+device") {
$connected = $true
} elseif ($devTxt -match [regex]::Escape($target) + "\s+(unauthorized|offline)") {
$connected = $false
Write-Log -Level "ERROR" -Message "[Android] 设备处于 unauthorized/offline,请在设备上确认调试授权或检查网络"
} else {
$connected = $false
}
}
if (-not $connected) {
Write-Log -Level "ERROR" -Message "[Android] adb 连接失败,终止后续安卓日志导出"
$results += @{
Check = "Android连接"
Status = "失败"
Details = "adb connect 失败或设备未授权/离线"
Success = $false
}
return $results
}
Write-Log -Level "SUCCESS" -Message "[Android] 设备连接成功"
$results += @{
Check = "Android连接"
Status = "成功"
Details = "adb connect 成功: " + $target
Success = $true
}
# 3) 日志拉取:files + cache -> logs/android/<ip_port_ts>/
$androidLocalDir = Join-Path $ScriptDir "logs\android"
if (-not (Test-Path $androidLocalDir)) {
New-Item -ItemType Directory -Path $androidLocalDir -Force | Out-Null
}
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$safeTarget = (($target -replace '[^\w\.\-:]', '_') -replace ':','_')
$pullRoot = Join-Path $androidLocalDir ("{0}_{1}" -f $safeTarget, $ts)
New-Item -ItemType Directory -Path $pullRoot -Force | Out-Null
function Pull-AndroidDir {
param(
[Parameter(Mandatory=$true)][string] $RemoteDir,
[Parameter(Mandatory=$true)][string] $LocalSubDir,
[Parameter(Mandatory=$true)][string] $CheckName,
[Parameter(Mandatory=$false)][switch] $SkipIfMissing # 新增:目录不存在则跳过
)
# 先检查远端目录是否存在(针对 cache:不存在则跳过)
if ($SkipIfMissing) {
$remoteDirNoSlash = $RemoteDir.TrimEnd('/')
$check = Invoke-Adb -Args @("-s", $target, "shell", "test -d `"$remoteDirNoSlash`" && echo __EXISTS__ || echo __MISSING__")
$checkOut = (($check.Out + "`n" + $check.Err) -replace "`r","").Trim()
if ($checkOut -match "__MISSING__") {
Write-Log -Level "WARN" -Message ("[Android] 远端目录不存在,跳过导出:{0}" -f $RemoteDir)
$results += @{
Check = $CheckName
Status = "跳过"
Details = "远端目录不存在: " + $RemoteDir
Success = $true
}
return
}
}
$localPath = Join-Path $pullRoot $LocalSubDir
New-Item -ItemType Directory -Path $localPath -Force | Out-Null
Write-Log -Level "INFO" -Message ("[Android] 开始拉取: {0} -> {1}" -f $RemoteDir, $localPath)
$pull = Invoke-Adb -Args @("-s", $target, "pull", $RemoteDir, $localPath)
$pullOut = (($pull.Out + "`n" + $pull.Err) -replace "`r","").Trim()
Write-Log -Level "INFO" -Message ("[Android] 执行: {0}" -f $pull.CmdLine)
if ($pullOut) { Write-Log -Level "INFO" -Message ("[Android] 输出: {0}" -f (($pullOut -split "`n" | Select-Object -First 5) -join " | ")) }
$ok = $false
$fileCount = 0
if ($pull.ExitCode -eq 0 -and (Test-Path $localPath)) {
$fileCount = (Get-ChildItem -Path $localPath -Recurse -File -ErrorAction SilentlyContinue | Measure-Object).Count
if ($fileCount -gt 0) { $ok = $true }
$lvl = if ($ok) { "SUCCESS" } else { "WARN" }
Write-Log -Level $lvl -Message ("[Android] 拉取完成:{0} 文件数={1},目录={2}" -f $LocalSubDir, $fileCount, $localPath)
} else {
Write-Log -Level "ERROR" -Message ("[Android] adb pull 失败:{0}" -f $RemoteDir)
}
$statusText = if ($ok) { "成功" } else { "失败" }
$detailText = if ($ok) { "已导出到: $localPath (files=$fileCount)" } else { "导出失败/目录不存在/权限不足: $RemoteDir" }
$results += @{
Check = $CheckName
Status = $statusText
Details = $detailText
Success = $ok
}
}
# 3.1 pull files(必须导出:不存在就算失败)
Pull-AndroidDir -RemoteDir "/sdcard/Android/data/com.ubains.local.gviewer/files/" -LocalSubDir "files" -CheckName "Android日志导出(files)"
# 3.2 pull cache(可选:不存在就跳过)
Pull-AndroidDir -RemoteDir "/sdcard/Android/data/com.ubains.local.gviewer/cache/" -LocalSubDir "cache" -CheckName "Android日志导出(cache)" -SkipIfMissing
}
finally {
# 4) 断开连接(新增)
# PRD 要求:收集完成后自动断开。即使中途异常也尽量断开,避免残留连接。
$discOk = $false
try {
$disc = Invoke-Adb -Args @("disconnect", $target)
$discOut = (($disc.Out + "`n" + $disc.Err) -replace "`r","").Trim()
Write-Log -Level "INFO" -Message ("[Android] 执行: {0}" -f $disc.CmdLine)
if ($discOut) { Write-Log -Level "INFO" -Message ("[Android] 输出: {0}" -f ($discOut -replace '\s+',' ')) }
if ($discOut -match "(?i)disconnected" -or $disc.ExitCode -eq 0) {
$discOk = $true
}
} catch {
$discOk = $false
}
$discStatus = if ($discOk) { "成功" } else { "失败" }
$discDetails = if ($discOk) { "adb disconnect 成功: $target" } else { "adb disconnect 失败(可能未连接/adb 异常): $target" }
$results += @{
Check = "Android断开连接"
Status = $discStatus
Details = $discDetails
Success = $discOk
}
}
return $results
}
# ================================ # ================================
# 生成检测报告 # 生成检测报告
# ================================ # ================================
...@@ -2255,7 +2521,8 @@ function Show-HealthReport { ...@@ -2255,7 +2521,8 @@ function Show-HealthReport {
[hashtable]$LogExportResults, [hashtable]$LogExportResults,
[hashtable]$NTPResults, [hashtable]$NTPResults,
[hashtable]$FilePermResults, [hashtable]$FilePermResults,
[array]$ContainerInfo [array]$ContainerInfo,
[array]$AndroidResults
) )
if (-not $SCRIPT_DIR -or [string]::IsNullOrWhiteSpace($SCRIPT_DIR)) { $SCRIPT_DIR = (Get-Location).Path } if (-not $SCRIPT_DIR -or [string]::IsNullOrWhiteSpace($SCRIPT_DIR)) { $SCRIPT_DIR = (Get-Location).Path }
...@@ -2552,6 +2819,26 @@ function Show-HealthReport { ...@@ -2552,6 +2819,26 @@ function Show-HealthReport {
} }
$md += "" $md += ""
# 安卓设备自检结果
if ($AndroidResults -and $AndroidResults.Count -gt 0) {
Write-Host "【安卓设备自检】" -ForegroundColor Yellow
$md += "## 安卓设备自检"
foreach ($r in $AndroidResults) {
$ok = $false
if ($r -is [hashtable]) { $ok = [bool]$r.Success } else { $ok = [bool]$r.Success }
$icon = if ($ok) { "✅" } else { "❌" }
$name = if ($r -is [hashtable]) { $r.Check } else { $r.Check }
$st = if ($r -is [hashtable]) { $r.Status } else { $r.Status }
$dt = if ($r -is [hashtable]) { $r.Details } else { $r.Details }
$line = "- {0} {1}: {2}" -f $icon, $name, $st
if ($dt) { $line += ",$dt" }
$md += $line
Write-Host " $line"
}
$md += ""
Write-Host ""
}
# 总结 # 总结
Write-Host "==================================================================" -ForegroundColor Cyan Write-Host "==================================================================" -ForegroundColor Cyan
Write-Host "【检测总结】" -ForegroundColor Yellow Write-Host "【检测总结】" -ForegroundColor Yellow
...@@ -3499,6 +3786,9 @@ function Main { ...@@ -3499,6 +3786,9 @@ function Main {
Write-Log -Level "INFO" -Message "跳过日志导出" Write-Log -Level "INFO" -Message "跳过日志导出"
} }
# 安卓设备自检(按 PRD 15:手动输入设备IP,连接+拉取日志)
$androidResults = Test-AndroidDeviceHealth -ScriptDir $SCRIPT_DIR
# 生成检测报告 # 生成检测报告
Show-HealthReport -Server $server -PlatformType $platformType -SystemInfo $systemInfo ` Show-HealthReport -Server $server -PlatformType $platformType -SystemInfo $systemInfo `
-UjavaContainerResults $ujavaContainerResults ` -UjavaContainerResults $ujavaContainerResults `
...@@ -3510,7 +3800,8 @@ function Main { ...@@ -3510,7 +3800,8 @@ function Main {
-LogExportResults $logExportResults ` -LogExportResults $logExportResults `
-NTPResults $ntpResults ` -NTPResults $ntpResults `
-FilePermResults $filePermResults ` -FilePermResults $filePermResults `
-ContainerInfo $containerInfo -ContainerInfo $containerInfo `
-AndroidResults $androidResults
} }
# 执行主函数 # 执行主函数
......
...@@ -453,7 +453,32 @@ ...@@ -453,7 +453,32 @@
- 对于高风险操作(如 Redis data 目录清理),自检场景采用非交互模式(--non-interactive --yes), - 对于高风险操作(如 Redis data 目录清理),自检场景采用非交互模式(--non-interactive --yes),
手工运维场景则可直接在服务器上交互式执行 issue_handler.sh,由运维人员确认后再进行删除或修改操作。 手工运维场景则可直接在服务器上交互式执行 issue_handler.sh,由运维人员确认后再进行删除或修改操作。
##### 15、服务自检报告输出(✅ 已实现): ##### 15、安卓设备的自检(待实现):
功能描述:
针对连接到服务器的安卓设备,执行一系列自检操作以确保设备状态正常。
主要检测项包括:
- 设备连接状态
- 日志文件收集
具体要求:
1)设备连接状态检测:
- 设备IP由手动输入,端口默认为5555.连接指令为:adb connect <device_ip>:<port>
- 使用 adb 命令连接设备,并检查连接状态。
- 若设备未连接或连接异常,记录错误信息并终止后续检测。
2)日志文件收集:
- 通过指令导出日志文件目录,并使用 adb 命令导出日志文件。
- adb pull /sdcard/Android/data/com.ubains.local.gviewer/files/ 当前脚本所在目录下的 logs/android/ 子目录。
- adb pull /sdcard/Android/data/com.ubains.local.gviewer/cache/ 当前脚本所在目录下的 logs/android/ 子目录。如果没有cache目录就记录打印后跳过这个目录导出操作。
3)断开连接:
- 收集完成后需要自动断开连接,adb disconnect <device_ip>:<port>
报告输出:
- 将安卓设备自检结果整合到整体自检报告中,明确标识各检测项的状态。
- 对于异常项,提供简要说明和建议处理措施。
##### 16、服务自检报告输出(✅ 已实现):
功能描述: 功能描述:
将本次服务自检过程中所有检测项的执行步骤、检测结果、异常说明及修复情况统一输出为报告文件, 将本次服务自检过程中所有检测项的执行步骤、检测结果、异常说明及修复情况统一输出为报告文件,
既包含完整日志记录,也生成便于阅读的 Markdown 自检报告。 既包含完整日志记录,也生成便于阅读的 Markdown 自检报告。
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论