Files
company-ai-platform/scripts/start_runtime.ps1
JiuContinent eb8267ed18 ```
feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能

- 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避
- 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐
- 增加运行组件心跳检测和readiness就绪检查机制
- 实现app_ticket事件的安全轮换和验证处理
- 添加生产环境运行编排和fail-closed安全机制
- 支持webhook快速确认和长连接独立进程处理
- 完善个人数据擦除时的待处理事件清理功能
```
2026-07-27 17:14:37 +08:00

179 lines
5.6 KiB
PowerShell

param(
[int]$Port = 8010,
[int]$ReadyTimeoutSeconds = 60
)
$ErrorActionPreference = "Stop"
$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$runtimeRoot = Join-Path $projectRoot ".runtime"
$logRoot = Join-Path $projectRoot "logs\runtime"
New-Item -ItemType Directory -Force -Path $runtimeRoot, $logRoot | Out-Null
function Get-OwnedRuntimeProcess {
param([string]$PidFile)
if (-not (Test-Path -LiteralPath $PidFile)) {
return $null
}
$parts = (Get-Content -LiteralPath $PidFile -Raw).Trim().Split("|")
if ($parts.Count -ne 2) {
return $null
}
$processId = 0
$startTicks = 0L
if (
-not [int]::TryParse($parts[0], [ref]$processId) -or
-not [long]::TryParse($parts[1], [ref]$startTicks)
) {
return $null
}
$process = Get-Process -Id $processId -ErrorAction SilentlyContinue
if (
$null -eq $process -or
$process.StartTime.ToUniversalTime().Ticks -ne $startTicks
) {
return $null
}
return $process
}
$pidFiles = @{
Api = Join-Path $runtimeRoot "api.pid"
FeishuEvents = Join-Path $runtimeRoot "feishu-events.pid"
}
foreach ($entry in $pidFiles.GetEnumerator()) {
if ($null -ne (Get-OwnedRuntimeProcess -PidFile $entry.Value)) {
throw "$($entry.Key) is already running. Stop it before starting another instance."
}
if (Test-Path -LiteralPath $entry.Value) {
Remove-Item -LiteralPath $entry.Value -Force
}
}
$pythonOutput = & conda run -n company-ai-platform python -c "import sys; print(sys.executable)"
if ($LASTEXITCODE -ne 0) {
throw "Unable to locate Python in the company-ai-platform Conda environment."
}
$pythonExe = (
$pythonOutput |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Last 1
)
if (-not [string]::IsNullOrWhiteSpace($pythonExe)) {
$pythonExe = $pythonExe.Trim()
}
if (
[string]::IsNullOrWhiteSpace($pythonExe) -or
-not (Test-Path -LiteralPath $pythonExe)
) {
$condaEnvironmentOutput = & conda env list --json
if ($LASTEXITCODE -ne 0) {
throw "Unable to list Conda environments."
}
$condaEnvironmentList = (
($condaEnvironmentOutput -join [Environment]::NewLine) |
ConvertFrom-Json
)
$matchingEnvironmentRoots = @(
$condaEnvironmentList.envs |
Where-Object {
(Split-Path -Leaf $_) -eq "company-ai-platform"
}
)
if ($matchingEnvironmentRoots.Count -ne 1) {
throw "Unable to resolve a unique company-ai-platform Conda environment."
}
$pythonExe = Join-Path $matchingEnvironmentRoots[0] "python.exe"
}
if (-not (Test-Path -LiteralPath $pythonExe -PathType Leaf)) {
throw "The company-ai-platform Python executable was not found."
}
# Process environment variables take precedence over .env.
$env:SCHEDULER_ENABLED = "true"
$env:TASK_QUEUE_ENABLED = "false"
$env:FEISHU_EVENT_TRANSPORT = "long_connection"
$preflightOutput = & $pythonExe -m app.tools.runtime_preflight
if ($LASTEXITCODE -ne 0) {
throw "Runtime preflight failed. Review the configuration and migrate the platform database."
}
Write-Host ($preflightOutput | Select-Object -Last 1)
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
function Start-HiddenRuntimeProcess {
param(
[string]$Name,
[string[]]$Arguments,
[string]$PidFile
)
$stdoutPath = Join-Path $logRoot "$Name-$timestamp.stdout.log"
$stderrPath = Join-Path $logRoot "$Name-$timestamp.stderr.log"
$process = Start-Process `
-FilePath $pythonExe `
-ArgumentList $Arguments `
-WorkingDirectory $projectRoot `
-WindowStyle Hidden `
-RedirectStandardOutput $stdoutPath `
-RedirectStandardError $stderrPath `
-PassThru
$identity = "$($process.Id)|$($process.StartTime.ToUniversalTime().Ticks)"
Set-Content -LiteralPath $PidFile -Value $identity -NoNewline
return @{
Process = $process
Stdout = $stdoutPath
Stderr = $stderrPath
}
}
$started = @()
try {
$api = Start-HiddenRuntimeProcess `
-Name "api" `
-Arguments @(
"-m", "uvicorn", "app.main:app",
"--host", "127.0.0.1",
"--port", "$Port"
) `
-PidFile $pidFiles.Api
$started += $api
$feishuEvents = Start-HiddenRuntimeProcess `
-Name "feishu-events" `
-Arguments @("-m", "app.modules.feishu.long_connection") `
-PidFile $pidFiles.FeishuEvents
$started += $feishuEvents
$deadline = (Get-Date).AddSeconds($ReadyTimeoutSeconds)
$readyUrl = "http://127.0.0.1:$Port/api/v1/health/ready"
while ((Get-Date) -lt $deadline) {
foreach ($item in $started) {
if ($item.Process.HasExited) {
throw "A runtime process exited before readiness succeeded."
}
}
try {
$response = Invoke-WebRequest -Uri $readyUrl -TimeoutSec 3 -UseBasicParsing
if ($response.StatusCode -eq 200) {
Write-Host "Runtime is ready at http://127.0.0.1:$Port"
Write-Host "Logs: $logRoot"
return
}
} catch {
# Readiness can be unavailable briefly while both processes initialize.
}
Start-Sleep -Seconds 1
}
throw "Runtime did not become ready within $ReadyTimeoutSeconds seconds."
} catch {
foreach ($item in $started) {
if (-not $item.Process.HasExited) {
Stop-Process -Id $item.Process.Id -Force -ErrorAction SilentlyContinue
}
}
Remove-Item -LiteralPath $pidFiles.Api, $pidFiles.FeishuEvents `
-Force -ErrorAction SilentlyContinue
throw
}