```
feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能 - 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避 - 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐 - 增加运行组件心跳检测和readiness就绪检查机制 - 实现app_ticket事件的安全轮换和验证处理 - 添加生产环境运行编排和fail-closed安全机制 - 支持webhook快速确认和长连接独立进程处理 - 完善个人数据擦除时的待处理事件清理功能 ```
This commit is contained in:
178
scripts/start_runtime.ps1
Normal file
178
scripts/start_runtime.ps1
Normal file
@@ -0,0 +1,178 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user