| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- [CmdletBinding()]
- param()
- $ErrorActionPreference = "Stop"
- $ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
- $StatePath = Join-Path $ProjectRoot ".runtime\processes.json"
- function Get-ListeningProcessId {
- param([int]$Port)
- $connection = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue |
- Select-Object -First 1
- if ($null -eq $connection) {
- return $null
- }
- return [int]$connection.OwningProcess
- }
- function Get-ProcessCommandLine {
- param([int]$ProcessId)
- $process = Get-CimInstance Win32_Process -Filter "ProcessId = $ProcessId" -ErrorAction SilentlyContinue
- return [string]$process.CommandLine
- }
- function Stop-ProcessTree {
- param([int]$RootProcessId)
- if ($RootProcessId -le 0 -or -not (Get-Process -Id $RootProcessId -ErrorAction SilentlyContinue)) {
- return
- }
- # 递归收集并从最深层开始停止,确保 node/python 的子进程不会残留。
- $pending = [System.Collections.Generic.List[int]]::new()
- $pending.Add($RootProcessId)
- $allProcessIds = [System.Collections.Generic.List[int]]::new()
- while ($pending.Count -gt 0) {
- $currentProcessId = $pending[0]
- $pending.RemoveAt(0)
- $allProcessIds.Add($currentProcessId)
- Get-CimInstance Win32_Process -Filter "ParentProcessId = $currentProcessId" -ErrorAction SilentlyContinue |
- ForEach-Object { $pending.Add([int]$_.ProcessId) }
- }
- $orderedProcessIds = @($allProcessIds)
- [array]::Reverse($orderedProcessIds)
- foreach ($processId in $orderedProcessIds) {
- Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue
- }
- }
- $processIds = [System.Collections.Generic.HashSet[int]]::new()
- if (Test-Path -LiteralPath $StatePath -PathType Leaf) {
- try {
- $state = Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json
- foreach ($entry in @($state.backend, $state.frontend)) {
- if ($null -eq $entry) {
- continue
- }
- foreach ($value in @($entry.listener_pid, $entry.launcher_pid)) {
- if ($null -ne $value -and [int]$value -gt 0) {
- [void]$processIds.Add([int]$value)
- }
- }
- }
- } catch {
- Write-Warning "运行状态文件无法读取,将使用端口和命令行进行兜底检查。"
- }
- }
- # 状态文件丢失时,只接管命令行明确属于本项目的默认端口进程。
- foreach ($port in @(8000, 5173)) {
- $listenerPid = Get-ListeningProcessId -Port $port
- if ($null -eq $listenerPid) {
- continue
- }
- $commandLine = Get-ProcessCommandLine -ProcessId $listenerPid
- $isBackend = $port -eq 8000 -and $commandLine -like "*uvicorn*app.main:app*"
- $isFrontend = $port -eq 5173 -and $commandLine -like "*vite*"
- if ($isBackend -or $isFrontend) {
- [void]$processIds.Add($listenerPid)
- }
- }
- if ($processIds.Count -eq 0) {
- Remove-Item -LiteralPath $StatePath -Force -ErrorAction SilentlyContinue
- Write-Host "项目当前未运行。" -ForegroundColor Yellow
- exit 0
- }
- foreach ($processId in $processIds) {
- Stop-ProcessTree -RootProcessId $processId
- }
- Remove-Item -LiteralPath $StatePath -Force -ErrorAction SilentlyContinue
- Write-Host "项目已关闭。" -ForegroundColor Green
|