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: 路径与端口覆盖模板
This commit is contained in:
toom1996
2026-10-22 10:37:59 +08:00
commit e366ee7498
10 changed files with 1269 additions and 0 deletions

121
vision/vision.ps1 Normal file
View File

@ -0,0 +1,121 @@
<#
.SYNOPSIS
CLIP 向量服务(fashion-siglip)的独立起停脚本。
.DESCRIPTION
该服务把图片编码成归一化向量,供后台「疑似同款」提示使用。
- 只监听 127.0.0.1,不对外暴露
- --workers 必须为 1(模型常驻进程内,多 worker 会重复加载模型)
- 后端需要 VISION_ENABLED=true 才会调用它;服务不可用时后端自动降级,不影响入库
.EXAMPLE
.\vision\vision.ps1 start
.\vision\vision.ps1 status
.\vision\vision.ps1 logs
.\vision\vision.ps1 stop
#>
param(
[Parameter(Position = 0)]
[ValidateSet('start', 'stop', 'restart', 'status', 'logs')]
[string]$Action = 'start',
[switch]$Force
)
$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
$port = [int](Get-Setting $envMap 'VISION_PORT' '8900')
$healthUrl = "http://127.0.0.1:$port/health"
function Start-CLIP {
if (Test-PortInUse $port) {
$owner = Get-PortOwnerPid $port
$saved = Get-ServicePid 'vision'
if ($saved -gt 0 -and $saved -eq $owner) {
Write-Note "视觉服务已在运行(PID $owner)"
return $true
}
if (-not $Force) {
Write-Fail "端口 $port 被 PID $owner 占用"
Write-Note '加 -Force 强制释放:.\vision\vision.ps1 start -Force'
return $false
}
Stop-PidTree $owner | Out-Null
}
if (-not $svc.Vision.IsValid) {
Write-Fail "视觉服务目录无效:$($svc.Vision.Path)"
return $false
}
$uvicorn = Join-Path $svc.Vision.Path '.venv\Scripts\uvicorn.exe'
if (-not (Test-Path $uvicorn)) {
Write-Fail "找不到 $uvicorn"
Write-Note "在 $($svc.Vision.Path) 建虚拟环境:python -m venv .venv && .venv\Scripts\pip install -r requirements.txt"
return $false
}
Write-Step "启动视觉服务(端口 $port,首次加载模型较慢)"
Start-LoggedProcess -Name 'vision' -FilePath $uvicorn `
-ArgumentList @('app:app', '--host', '127.0.0.1', '--port', "$port", '--workers', '1') `
-WorkingDirectory $svc.Vision.Path | Out-Null
if (Wait-HttpOk -Url $healthUrl -TimeoutSec 180) {
Write-Ok '视觉服务就绪'
Show-Health
return $true
}
Write-Fail '视觉服务 180 秒内未就绪'
Show-LogTail -Name 'vision' -Lines 25
return $false
}
function Show-Health {
try {
$r = Invoke-WebRequest -Uri $healthUrl -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
Write-Note ("health: " + $r.Content.Trim())
} catch { }
}
Write-Host ''
Write-Host ' app-cli / CLIP 视觉服务' -ForegroundColor White
Write-Host ' ----------------------------------------' -ForegroundColor DarkGray
switch ($Action) {
'start' {
if (-not (Start-CLIP)) { exit 1 }
}
'stop' {
if (Stop-NamedService 'vision') { Write-Ok '视觉服务已停止' }
else { Write-Note '视觉服务未在本脚本登记(可能没启动过)' }
if (Test-PortInUse $port) {
$owner = Get-PortOwnerPid $port
Write-Warn "端口 $port 仍被 PID $owner 占用"
if ($Force) { Stop-PidTree $owner | Out-Null; Write-Ok "已终止 PID $owner" }
else { Write-Note '加 -Force 强制终止:.\vision\vision.ps1 stop -Force' }
}
}
'restart' {
Stop-NamedService 'vision' | Out-Null
Start-Sleep -Milliseconds 500
if (-not (Start-CLIP)) { exit 1 }
}
'status' {
$st = Get-ServiceStatus -Name 'vision' -Port $port -HealthUrl $healthUrl
if ($st.Healthy) {
Write-Ok "运行中(PID $($st.OwnerPid),端口 $port)"
Show-Health
} elseif ($st.Listening) {
Write-Warn "端口 $port 已监听但 /health 未通过(模型可能仍在加载)"
Show-LogTail -Name 'vision' -Lines 10
} else {
Write-Note '未运行'
}
}
'logs' {
Show-LogTail -Name 'vision' -Lines 40
}
}