Files
app-cli/dev/doctor.ps1
toom1996 e366ee7498 chore: 初始化 app-cli,本地开发环境脚本
- dev/dev.ps1: 一键启动后端(编译后运行)+前端(4321),校验 CORS 与 astro dev 单实例锁
- dev/stop.ps1: 按 PID 登记停止,-Force 清残留
- dev/status.ps1: 端口/PID/健康检查一览
- dev/doctor.ps1: 工具链/路径/依赖/数据库/端口/CORS 体检
- vision/vision.ps1: CLIP 向量服务起停
- lib/common.ps1: 路径探测、端口、进程登记、HTTP 探测
- config/local.env.example: 路径与端口覆盖模板
2026-10-22 10:37:59 +08:00

191 lines
8.2 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# dev/doctor.ps1 — 本地环境体检
#
# 只读检查,不改任何东西。把「启动后才发现」的问题提前暴露:
# 工具链版本 / 仓库路径 / 配置文件 / 依赖安装 / 数据库连通 / 端口占用 / CORS 白名单
#
# 退出码:0 = 可以开工;1 = 有必须先处理的问题。
$ErrorActionPreference = 'Stop'
$CliRoot = Split-Path -Parent $PSScriptRoot
. (Join-Path $CliRoot 'lib\common.ps1')
$envMap = Import-LocalEnv (Join-Path $CliRoot 'config\local.env')
$svc = Resolve-Services -EnvMap $envMap -Root $CliRoot
$backendPort = [int](Get-Setting $envMap 'BACKEND_PORT' '8090')
$frontendPort = [int](Get-Setting $envMap 'FRONTEND_PORT' '4321')
$visionPort = [int](Get-Setting $envMap 'VISION_PORT' '8900')
$script:Fail = 0
$script:Warn = 0
# Check:$Value 只在通过时显示(版本号、路径等);$Hint 只在未通过时显示(怎么修)。
function Check {
param(
[string]$Name,
[bool]$Ok,
[string]$Value = '',
[string]$Hint = '',
[bool]$Fatal = $true
)
if ($Ok) {
if ($Value -ne '') { Write-Ok "$Name($Value)" } else { Write-Ok $Name }
return
}
if ($Fatal) {
Write-Fail $Name
$script:Fail++
} else {
Write-Warn $Name
$script:Warn++
}
if ($Hint -ne '') { Write-Note " $Hint" }
}
Write-Host ''
Write-Host ' app-cli / 本地环境体检' -ForegroundColor White
Write-Host ' ----------------------------------------' -ForegroundColor DarkGray
# ---------------------------------------------------------------- 1. 工具链
Write-Step '工具链'
$goCmd = Get-Command go -ErrorAction SilentlyContinue
if ($goCmd) {
$goVer = (& go version) -replace 'go version ', '' -replace ' windows.*', ''
Check -Name 'Go' -Ok $true -Value $goVer
} else {
Check -Name 'Go' -Ok $false -Hint '未安装,后端无法编译'
}
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
if ($nodeCmd) {
$nodeVer = (& node -v).Trim()
$major = 0
if ($nodeVer -match 'v(\d+)') { $major = [int]$Matches[1] }
# 前端 package.json engines 要求 >=22.12,只做提示不拦
Check -Name 'Node' -Ok ($major -ge 22) -Value $nodeVer -Hint "当前 $nodeVer,前端要求 >=22.12" -Fatal $false
} else {
Check -Name 'Node' -Ok $false -Hint '未安装,前端无法启动'
}
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
if ($pythonCmd) {
$pyVer = (& python --version 2>&1).ToString().Trim()
Check -Name 'Python' -Ok $true -Value $pyVer
} else {
Check -Name 'Python' -Ok $false -Hint '未安装(只有跑视觉服务才需要)' -Fatal $false
}
# ---------------------------------------------------------------- 2. 仓库路径
Write-Step '仓库路径'
Check -Name "backend $($svc.Backend.Path)" -Ok $svc.Backend.IsValid -Hint "缺标记文件 $($svc.Backend.Marker)"
Check -Name "frontend $($svc.Frontend.Path)" -Ok $svc.Frontend.IsValid -Hint "缺标记文件 $($svc.Frontend.Marker)"
Check -Name "vision $($svc.Vision.Path)" -Ok $svc.Vision.IsValid -Hint "缺标记文件 $($svc.Vision.Marker),仅影响视觉服务" -Fatal $false
# ---------------------------------------------------------------- 3. 配置与依赖
Write-Step '配置与依赖'
if ($svc.Backend.IsValid) {
$cfgYml = Join-Path $svc.Backend.Path 'configs\config.yml'
$cfgLocal = Join-Path $svc.Backend.Path 'configs\config.local.yml'
Check -Name 'backend configs/config.yml' -Ok (Test-Path $cfgYml) -Hint '唯一配置源,缺失则全走默认值'
Check -Name 'backend configs/config.local.yml' -Ok (Test-Path $cfgLocal) -Hint '本机密钥(DB 密码 / JWT / S4),缺失会在启动时交互提示' -Fatal $false
}
if ($svc.Frontend.IsValid) {
$nm = Join-Path $svc.Frontend.Path 'node_modules'
$astro = Join-Path $svc.Frontend.Path 'node_modules\astro\bin\astro.mjs'
Check -Name 'frontend node_modules' -Ok (Test-Path $nm) -Hint '缺失时 dev.ps1 会自动 npm ci' -Fatal $false
Check -Name 'frontend astro CLI' -Ok (Test-Path $astro) -Hint "缺 $astro(依赖损坏,删掉 node_modules 重装)"
# 前端 dev 端口的两处来源必须一致,否则手动 npm run dev 会起在别的端口 → CORS 拦截
$astroCfg = Join-Path $svc.Frontend.Path 'astro.config.mjs'
if (Test-Path $astroCfg) {
$raw = Get-Content $astroCfg -Raw
if ($raw -match 'port:\s*(\d+)') {
$cfgPort = [int]$Matches[1]
Check -Name '前端端口一致性' -Ok ($cfgPort -eq $frontendPort) -Value "$cfgPort" `
-Hint "astro.config.mjs 的 server.port=$cfgPort,而 FRONTEND_PORT=$frontendPort;dev.ps1 会用 --port 覆盖,但你手动 npm run dev 会起在 $cfgPort 并被 CORS 拦截" `
-Fatal $false
}
}
}
if ($svc.Vision.IsValid) {
$uvicorn = Join-Path $svc.Vision.Path '.venv\Scripts\uvicorn.exe'
Check -Name 'vision .venv/uvicorn' -Ok (Test-Path $uvicorn) -Hint '缺失则无法启动 CLIP 服务(python -m venv .venv 后装依赖)' -Fatal $false
}
# ---------------------------------------------------------------- 4. 数据库连通
Write-Step '数据库'
if ($svc.Backend.IsValid) {
$cfgYml = Join-Path $svc.Backend.Path 'configs\config.yml'
$cfgLocal = Join-Path $svc.Backend.Path 'configs\config.local.yml'
$dbHost = Get-YamlScalar -Path $cfgYml -Section 'database' -Key 'host'
$dbPortRaw = Get-YamlScalar -Path $cfgYml -Section 'database' -Key 'port'
$dbName = Get-YamlScalar -Path $cfgYml -Section 'database' -Key 'name'
# 本机覆盖优先
$localHost = Get-YamlScalar -Path $cfgLocal -Section 'database' -Key 'host'
if ($localHost -ne '') { $dbHost = $localHost }
$localPort = Get-YamlScalar -Path $cfgLocal -Section 'database' -Key 'port'
if ($localPort -ne '') { $dbPortRaw = $localPort }
$dbPort = 5432
if ($dbPortRaw -ne '') { $dbPort = [int]$dbPortRaw }
if ($dbHost -eq '') {
Check -Name '数据库配置' -Ok $false -Hint '配置里没读到 database.host' -Fatal $false
} else {
$reachable = Test-TcpConnect -ComputerName $dbHost -Port $dbPort -TimeoutMs 4000
Check -Name "PostgreSQL $dbHost`:$dbPort/$dbName" -Ok $reachable `
-Hint 'TCP 连不上:后端启动会直接退出(查网络 / 密码 / 远端是否放行本机 IP)'
}
}
# ---------------------------------------------------------------- 5. 端口
Write-Step '端口'
$ports = @(
@{ Port = $backendPort; Name = 'backend 公开' },
@{ Port = 8091; Name = 'backend SSG' },
@{ Port = 8092; Name = 'backend 后台' },
@{ Port = $frontendPort; Name = 'frontend dev' },
@{ Port = $visionPort; Name = 'vision' }
)
foreach ($p in $ports) {
if (Test-PortInUse $p.Port) {
$owner = Get-PortOwnerPid $p.Port
Write-Note ($p.Port.ToString() + ' ' + $p.Name + ' 已被 PID ' + $owner + ' 占用(若已在跑属正常)')
} else {
Write-Note ($p.Port.ToString() + ' ' + $p.Name + ' 空闲')
}
}
# ---------------------------------------------------------------- 6. 运行时自检
Write-Step '运行时自检'
if (Test-PortInUse $backendPort) {
$healthUrl = "http://127.0.0.1:$backendPort/api/health"
Check -Name '后端 /api/health' -Ok (Wait-HttpOk -Url $healthUrl -TimeoutSec 3) -Value $healthUrl -Fatal $false
$origin = "http://localhost:$frontendPort"
$acao = Get-CorsOrigin -Url "http://127.0.0.1:$backendPort/api/v1/public/runway-looks?page=1" -Origin $origin
if ($acao -eq $origin) {
Check -Name "CORS 放行 $origin" -Ok $true -Value $acao
} else {
Check -Name "CORS 放行 $origin" -Ok $false -Fatal $false `
-Hint "后端回显 '$acao';不在白名单时浏览器会拦掉接口,页面显示「无数据」。改 backend/configs/config.local.yml → cors.allow_origins"
}
} else {
Write-Note '后端未运行,跳过健康与 CORS 检查'
}
# ---------------------------------------------------------------- 汇总
Write-Host ''
if ($script:Fail -gt 0) {
Write-Fail ("体检未通过:$($script:Fail) 项必处理,$($script:Warn) 项提醒")
exit 1
}
if ($script:Warn -gt 0) {
Write-Warn ("可以开工,但有 $($script:Warn) 项提醒")
exit 0
}
Write-Ok '体检全部通过'