ops: add production observability and recovery

This commit is contained in:
hailin
2026-07-27 19:06:34 +08:00
parent e69368a2f3
commit b02d18d3b9
33 changed files with 8832 additions and 1 deletions

95
start-backend.ps1 Normal file
View 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
}