ops: add production observability and recovery
This commit is contained in:
95
start-backend.ps1
Normal file
95
start-backend.ps1
Normal file
@@ -0,0 +1,95 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Restart
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProjectRoot = $PSScriptRoot
|
||||
$BackendDir = Join-Path $ProjectRoot "backend"
|
||||
$EnvFile = Join-Path $ProjectRoot ".env"
|
||||
|
||||
function Get-MatchingProcesses {
|
||||
param([string]$Pattern)
|
||||
|
||||
return @(Get-CimInstance Win32_Process | Where-Object {
|
||||
$_.CommandLine -and $_.CommandLine -match $Pattern
|
||||
})
|
||||
}
|
||||
|
||||
function Stop-MatchingProcesses {
|
||||
param([string]$Name, [string]$Pattern)
|
||||
|
||||
$processes = Get-MatchingProcesses -Pattern $Pattern
|
||||
if (-not $processes) {
|
||||
return
|
||||
}
|
||||
$hasStaleEnvironment = @($processes | Where-Object {
|
||||
$_.CreationDate -lt $script:EnvLastWriteTime
|
||||
}).Count -gt 0
|
||||
if (-not $Restart -and -not $hasStaleEnvironment) {
|
||||
Write-Host "[skip] $Name 已运行" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
foreach ($process in $processes) {
|
||||
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$reason = if ($Restart) { "显式重启" } else { ".env 晚于进程启动时间" }
|
||||
Write-Host "[restart] 已停止旧 $Name 进程($reason)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Start-PythonProcess {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Pattern,
|
||||
[string]$Command
|
||||
)
|
||||
|
||||
if (Get-MatchingProcesses -Pattern $Pattern) {
|
||||
return
|
||||
}
|
||||
$escapedDirectory = $BackendDir.Replace("'", "''")
|
||||
$windowCommand = "Set-Location -LiteralPath '$escapedDirectory'; `$Host.UI.RawUI.WindowTitle = '$Name'; $Command"
|
||||
Start-Process powershell.exe -ArgumentList @("-NoExit", "-Command", $windowCommand)
|
||||
Write-Host "[start] $Name" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $EnvFile)) {
|
||||
throw "未找到 $EnvFile,请先复制 .env.example 并完成配置。"
|
||||
}
|
||||
if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
|
||||
throw "未找到 python,请先安装 Python 3.12 并加入 PATH。"
|
||||
}
|
||||
$script:EnvLastWriteTime = (Get-Item -LiteralPath $EnvFile).LastWriteTime
|
||||
|
||||
Push-Location $ProjectRoot
|
||||
try {
|
||||
python -c "from cryptography.fernet import Fernet; from dotenv import dotenv_values; key=dotenv_values('.env').get('AI_CREDENTIAL_ENCRYPTION_KEY',''); Fernet(key.encode()); print('[ok] AI credential encryption key is valid')"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw ".env 中的 AI_CREDENTIAL_ENCRYPTION_KEY 缺失或不是有效 Fernet 密钥。"
|
||||
}
|
||||
|
||||
Stop-MatchingProcesses -Name "Django API" -Pattern "manage\.py runserver 0\.0\.0\.0:8000"
|
||||
Stop-MatchingProcesses -Name "Celery Worker" -Pattern "celery.*-A config\.celery worker"
|
||||
Stop-MatchingProcesses -Name "Celery Beat" -Pattern "celery.*-A config\.celery beat"
|
||||
|
||||
Start-PythonProcess -Name "AI Ticket - Django API" `
|
||||
-Pattern "manage\.py runserver 0\.0\.0\.0:8000" `
|
||||
-Command "python manage.py runserver 0.0.0.0:8000"
|
||||
Start-PythonProcess -Name "AI Ticket - Celery Worker" `
|
||||
-Pattern "celery.*-A config\.celery worker" `
|
||||
-Command "python -m celery -A config.celery worker -l info -P solo"
|
||||
Start-PythonProcess -Name "AI Ticket - Celery Beat" `
|
||||
-Pattern "celery.*-A config\.celery beat" `
|
||||
-Command "python -m celery -A config.celery beat -l info"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Python 后端程序已启动:" -ForegroundColor Cyan
|
||||
Write-Host " Django API http://localhost:8000/api/docs"
|
||||
Write-Host " Celery Worker 已启动"
|
||||
Write-Host " Celery Beat 已启动"
|
||||
Write-Host ""
|
||||
Write-Host "修改 .env 后可执行:.\start-backend.ps1 -Restart" -ForegroundColor Yellow
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user