- 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: 路径与端口覆盖模板
122 lines
4.1 KiB
PowerShell
122 lines
4.1 KiB
PowerShell
<#
|
||
.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
|
||
}
|
||
}
|