<# .SYNOPSIS Edu start all application services script .DESCRIPTION Starts 10 application services + 1 frontend (teacher-portal) Each service runs in a separate terminal window for log visibility Automatically injects Python service env vars (CLICKHOUSE/KAFKA/OTEL) Prerequisite: infrastructure containers (MySQL/Redis/Kafka/ClickHouse etc) must be running .PARAMETER SkipInfraCheck Skip infrastructure health check (use when infra is known to be running) .EXAMPLE .\scripts\start-all.ps1 .\scripts\start-all.ps1 -SkipInfraCheck #> param( [switch]$SkipInfraCheck, [switch]$Force ) $ErrorActionPreference = "Stop" $ProjectRoot = Split-Path -Parent $PSScriptRoot Write-Host "========================================" -ForegroundColor Cyan Write-Host " Edu Start All Services" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host "" # ===== 1. Infrastructure health check ===== if (-not $SkipInfraCheck) { Write-Host "[1/6] Checking infrastructure health..." -ForegroundColor Yellow $infraServices = @( @{Name="MySQL"; Container="edu-mysql"}, @{Name="Redis"; Container="edu-redis"}, @{Name="Kafka"; Container="edu-kafka"}, @{Name="ClickHouse"; Container="edu-clickhouse"}, @{Name="Debezium"; Container="edu-debezium"}, @{Name="Jaeger"; Container="edu-jaeger"} ) $allHealthy = $true $prevEAP = $ErrorActionPreference $ErrorActionPreference = "Continue" foreach ($svc in $infraServices) { $running = docker inspect -f '{{.State.Running}}' $svc.Container 2>$null if ($running -ne "true") { Write-Host " [FAIL] $($svc.Name) not running" -ForegroundColor Red $allHealthy = $false continue } # 条件模板:Health 不存在时返回空字符串,不报错 $health = docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' $svc.Container 2>$null if ($health -eq "healthy") { Write-Host " [OK] $($svc.Name) ($($svc.Container))" -ForegroundColor Green } else { Write-Host " [WARN] $($svc.Name) running (no healthcheck)" -ForegroundColor Yellow } } $ErrorActionPreference = $prevEAP if (-not $allHealthy) { Write-Host "" Write-Host "Infrastructure not ready. Start it first:" -ForegroundColor Red Write-Host " docker compose -f infra/docker-compose.yml --profile p6 --profile observability up -d" -ForegroundColor White exit 1 } Write-Host "" } # ===== 2. Environment variables ===== Write-Host "[2/6] Preparing environment variables..." -ForegroundColor Yellow $env:DEV_MODE = "true" $env:OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318" $env:DATABASE_URL = "mysql://edu:changeme@localhost:3306/next_edu_cloud" $env:REDIS_URL = "redis://localhost:6379" $env:JWT_SECRET = "p1-dev-secret-change-in-production" $env:KAFKA_BROKERS = "localhost:9092" $pyEnv = @{ CLICKHOUSE_HOST = "localhost" CLICKHOUSE_PORT = "8123" CLICKHOUSE_USER = "default" CLICKHOUSE_PASSWORD = "clickhouse" CLICKHOUSE_DATABASE = "edu_analytics" KAFKA_BROKERS = "localhost:9092" OTEL_ENDPOINT = "http://localhost:4318" DEV_MODE = "true" } Write-Host " DEV_MODE=true (dev-token bypass)" -ForegroundColor Green Write-Host " OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318" -ForegroundColor Green Write-Host "" # ===== 3. Port conflict check ===== Write-Host "[3/6] Checking port conflicts..." -ForegroundColor Yellow $portMap = @{ 3001="classes"; 3002="iam"; 3003="teacher-bff"; 3004="core-edu" 3005="content"; 3006="data-ana"; 3007="msg"; 3008="ai" 8080="api-gateway"; 8081="push-gateway"; 3000="teacher-portal" } $conflicts = @() foreach ($port in $portMap.Keys | Sort-Object) { $conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue if ($conn) { $svcName = $portMap[$port] $procId = $conn[0].OwningProcess $procName = "" try { $procName = (Get-Process -Id $procId -ErrorAction Stop).ProcessName } catch {} Write-Host " [WARN] Port $port ($svcName) occupied by PID $procId ($procName)" -ForegroundColor Yellow $conflicts += [PSCustomObject]@{Port=$port; Service=$svcName; PID=$procId; Process=$procName} } } if ($conflicts.Count -gt 0) { Write-Host "" Write-Host " $($conflicts.Count) port(s) already in use." -ForegroundColor Yellow $shouldKill = $false if ($Force) { Write-Host " -Force specified, killing automatically..." -ForegroundColor White $shouldKill = $true } else { Write-Host " These services may already be running. Options:" -ForegroundColor White Write-Host " 1. Run .\scripts\stop-all.ps1 -KillByPort first, then re-run start-all" -ForegroundColor White Write-Host " 2. Re-run with -Force to auto-kill and continue" -ForegroundColor White Write-Host "" $answer = Read-Host " Kill existing processes and continue? (y/N)" if ($answer -eq "y" -or $answer -eq "Y") { $shouldKill = $true } } if ($shouldKill) { foreach ($c in $conflicts) { try { Stop-Process -Id $c.PID -Force -ErrorAction Stop Write-Host " [OK] Killed PID $($c.PID) ($($c.Process)) on port $($c.Port)" -ForegroundColor Green Start-Sleep -Milliseconds 500 } catch { Write-Host " [WARN] Cannot kill PID $($c.PID): $($_.Exception.Message)" -ForegroundColor Yellow } } Start-Sleep -Seconds 2 } else { Write-Host " Aborting. Please stop existing services first." -ForegroundColor Red exit 1 } } else { Write-Host " [OK] All app ports are free" -ForegroundColor Green } Write-Host "" # ===== 4. Build NestJS services (required for nest start --watch) ===== Write-Host "[4/6] Building NestJS services (first time required)..." -ForegroundColor Yellow $nestjsServices = @( "@edu/classes-service", "@edu/iam-service", "@edu/teacher-bff", "@edu/core-edu-service", "@edu/content-service", "@edu/msg-service" ) # Clean tsbuildinfo cache (incremental mode leftover causes tsc to skip emit) $nestjsDirs = @("classes", "iam", "teacher-bff", "core-edu", "content", "msg") foreach ($dir in $nestjsDirs) { $svcPath = Join-Path $ProjectRoot "services\$dir" if (Test-Path $svcPath) { Get-ChildItem -Path $svcPath -Filter "*.tsbuildinfo" -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue } } foreach ($svc in $nestjsServices) { Write-Host " Building $svc..." -ForegroundColor Gray -NoNewline $buildResult = pnpm --filter $svc build 2>&1 if ($LASTEXITCODE -eq 0) { Write-Host " [OK]" -ForegroundColor Green } else { Write-Host " [FAIL]" -ForegroundColor Red Write-Host " $buildResult" -ForegroundColor DarkGray } } Write-Host "" # ===== 5. Start application services ===== Write-Host "[5/6] Starting application services (11 windows)..." -ForegroundColor Yellow $services = @( @{Title="edu-app-classes"; Cmd="pnpm"; Args=@("--filter","@edu/classes-service","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-iam"; Cmd="pnpm"; Args=@("--filter","@edu/iam-service","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-teacher-bff"; Cmd="pnpm"; Args=@("--filter","@edu/teacher-bff","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-core-edu"; Cmd="pnpm"; Args=@("--filter","@edu/core-edu-service","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-content"; Cmd="pnpm"; Args=@("--filter","@edu/content-service","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-msg"; Cmd="pnpm"; Args=@("--filter","@edu/msg-service","dev"); Dir="$ProjectRoot"}, @{Title="edu-app-data-ana"; Cmd="uv"; Args=@("run","uvicorn","data_ana.main:app","--app-dir","src","--host","0.0.0.0","--port","3006","--reload"); Dir="$ProjectRoot\services\data-ana"; PyEnv=$true}, @{Title="edu-app-ai"; Cmd="uv"; Args=@("run","uvicorn","ai.main:app","--app-dir","src","--host","0.0.0.0","--port","3008","--reload"); Dir="$ProjectRoot\services\ai"; PyEnv=$true}, @{Title="edu-app-api-gateway"; Cmd="go"; Args=@("run","."); Dir="$ProjectRoot\services\api-gateway"; GoEnv=$true}, @{Title="edu-app-push-gateway"; Cmd="go"; Args=@("run","."); Dir="$ProjectRoot\services\push-gateway";GoEnv=$true}, @{Title="edu-app-teacher-portal";Cmd="pnpm";Args=@("--filter","teacher-portal","dev"); Dir="$ProjectRoot"} ) foreach ($svc in $services) { $cmdStr = "$($svc.Cmd) $($svc.Args -join ' ')" $psCmd = "Set-Location '$($svc.Dir)'; " if ($svc.PyEnv) { foreach ($kv in $pyEnv.GetEnumerator()) { $psCmd += "`$env:$($kv.Key)='$($kv.Value)'; " } } if ($svc.GoEnv) { $psCmd += "`$env:Path = 'C:\Program Files\Go\bin;' + `$env:Path; " } $psCmd += "$cmdStr; Write-Host ''; Write-Host 'Service stopped. Press any key to close...' -ForegroundColor Yellow; `$null = `$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')" Start-Process -FilePath "powershell" -ArgumentList "-NoExit","-Command",$psCmd -WindowStyle Normal | Out-Null Write-Host " [START] $($svc.Title)..." -ForegroundColor Green Start-Sleep -Milliseconds 500 } Write-Host "" Write-Host " All services started in new windows. Waiting 40s for init..." -ForegroundColor Yellow Start-Sleep -Seconds 40 # ===== 6. Health check ===== Write-Host "[6/6] Health check..." -ForegroundColor Yellow Write-Host "" $healthServices = @( @{Name="classes"; Url="http://localhost:3001/healthz"}, @{Name="iam"; Url="http://localhost:3002/healthz"}, @{Name="teacher-bff"; Url="http://localhost:3003/healthz"}, @{Name="core-edu"; Url="http://localhost:3004/healthz"}, @{Name="content"; Url="http://localhost:3005/healthz"}, @{Name="data-ana"; Url="http://localhost:3006/healthz"}, @{Name="msg"; Url="http://localhost:3007/healthz"}, @{Name="ai"; Url="http://localhost:3008/healthz"}, @{Name="api-gateway"; Url="http://localhost:8080/healthz"}, @{Name="push-gateway"; Url="http://localhost:8081/healthz"}, @{Name="teacher-portal";Url="http://localhost:3000/"} ) $okCount = 0 $failCount = 0 $failedServices = @() foreach ($svc in $healthServices) { $retries = 0 $maxRetries = 3 $success = $false $lastError = "" while ($retries -lt $maxRetries -and -not $success) { try { $null = Invoke-RestMethod -Uri $svc.Url -Method Get -TimeoutSec 5 -ErrorAction Stop Write-Host " [OK] $($svc.Name)" -ForegroundColor Green $success = $true $okCount++ } catch { $lastError = $_.Exception.Message $retries++ if ($retries -lt $maxRetries) { Start-Sleep -Seconds 5 } } } if (-not $success) { Write-Host " [FAIL] $($svc.Name) (after $maxRetries retries: $lastError)" -ForegroundColor Red $failCount++ $failedServices += $svc.Name } } Write-Host "" Write-Host "========================================" -ForegroundColor Cyan Write-Host " Started: [OK] $okCount ready / [FAIL] $failCount failed" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan if ($failCount -gt 0) { Write-Host "" Write-Host "Failed services: $($failedServices -join ', ')" -ForegroundColor Red Write-Host "" Write-Host "Troubleshooting:" -ForegroundColor Yellow Write-Host " 1. Check the service window for error output" -ForegroundColor White Write-Host " 2. Verify dependencies: pnpm install / uv sync / go mod tidy" -ForegroundColor White Write-Host " 3. Re-run health check: .\scripts\health-check.ps1" -ForegroundColor White Write-Host " 4. Stop and retry: .\scripts\stop-all.ps1 -KillByPort then .\scripts\start-all.ps1" -ForegroundColor White Write-Host "" exit 1 } Write-Host "" Write-Host "Next steps:" -ForegroundColor Yellow Write-Host " Health check: .\scripts\health-check.ps1" -ForegroundColor White Write-Host " CDC test: .\scripts\test-cdc.ps1" -ForegroundColor White Write-Host " Stop all: .\scripts\stop-all.ps1" -ForegroundColor White Write-Host ""