- Introduced `pending_trade` handling in the chat API to manage trade requests more effectively. - Updated the `submit_trade_api` to allow advisors to access customer trades based on assigned roles. - Added new methods in `GatewayRepository` for managing core holdings during trade subscriptions and redemptions. - Implemented context-aware trade dialogue management in the customer service layer to improve user experience during multi-turn interactions. - Enhanced the tool service to support trade actions and suitability checks, ensuring accurate processing of user requests. This update significantly improves the trade interaction flow, providing a more robust and user-friendly experience for customers engaging in trading activities.
309 lines
12 KiB
PowerShell
309 lines
12 KiB
PowerShell
#Requires -Version 5.1
|
||
<#
|
||
.SYNOPSIS
|
||
重启本机 JinRong 前后端开发服务(前端 Vite · 后端 Docker 或本机 uvicorn)。
|
||
.EXAMPLE
|
||
.\scripts\dev\restart-dev.ps1
|
||
.\scripts\dev\restart-dev.ps1 -Backend Local
|
||
.\scripts\dev\restart-dev.ps1 -DockerContainer stu-system-app
|
||
.NOTES
|
||
后端 Auto:若 Docker 有 jinrong* 容器映射 8000 → docker restart;否则 taskkill /T 清掉所有 app.main:app uvicorn + 8000 监听,再开单实例 uvicorn。
|
||
前端:taskkill /T 清 vite/node(5173–5175)后在新 PowerShell 窗口执行 npm run dev。
|
||
Redis 不在此脚本内;需要时先 .\scripts\dev\start-redis.ps1
|
||
#>
|
||
param(
|
||
[ValidateSet("Auto", "Docker", "Local")]
|
||
[string]$Backend = "Auto",
|
||
[string]$DockerContainer = "",
|
||
[switch]$SkipBackend,
|
||
[switch]$SkipFrontend
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
$Root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||
$Web = Join-Path $Root "web"
|
||
Set-Location $Root
|
||
|
||
function Invoke-DockerText {
|
||
param([Parameter(ValueFromRemainingArguments)][string[]]$Args)
|
||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { return $null }
|
||
$prevEa = $ErrorActionPreference
|
||
$ErrorActionPreference = "SilentlyContinue"
|
||
try {
|
||
$lines = & docker @Args 2>&1 | Where-Object { $_ -is [string] }
|
||
if ($LASTEXITCODE -ne 0) { return $null }
|
||
$line = $lines | Select-Object -First 1
|
||
if (-not $line) { return $null }
|
||
return "$line".Trim()
|
||
} catch {
|
||
return $null
|
||
} finally {
|
||
$ErrorActionPreference = $prevEa
|
||
}
|
||
}
|
||
|
||
function Invoke-DockerOk {
|
||
param([Parameter(ValueFromRemainingArguments)][string[]]$Args)
|
||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { return $false }
|
||
$prevEa = $ErrorActionPreference
|
||
$ErrorActionPreference = "SilentlyContinue"
|
||
try {
|
||
& docker @Args 2>&1 | Out-Null
|
||
return ($LASTEXITCODE -eq 0)
|
||
} catch {
|
||
return $false
|
||
} finally {
|
||
$ErrorActionPreference = $prevEa
|
||
}
|
||
}
|
||
|
||
function Get-ListenerPids([int[]]$Ports) {
|
||
$pids = @()
|
||
foreach ($port in $Ports) {
|
||
Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue |
|
||
ForEach-Object { $pids += $_.OwningProcess }
|
||
}
|
||
$pids | Select-Object -Unique
|
||
}
|
||
|
||
function Stop-ProcessTree([int]$ProcId) {
|
||
if (-not $ProcId -or $ProcId -le 0) { return }
|
||
# /T 结束子进程(uvicorn --reload 会起父子两套 python,只杀 Listen PID 常会叠尸)
|
||
$null = Start-Process -FilePath "taskkill.exe" -ArgumentList @("/PID", $ProcId, "/T", "/F") -Wait -WindowStyle Hidden -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
function Stop-PortListeners([int[]]$Ports, [switch]$WithTree) {
|
||
foreach ($procId in (Get-ListenerPids $Ports)) {
|
||
if ($procId -and $procId -gt 0) {
|
||
if ($WithTree) { Stop-ProcessTree $procId } else { Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue }
|
||
}
|
||
}
|
||
}
|
||
|
||
function Test-IsJinRongUvicornCommandLine([string]$CommandLine) {
|
||
if (-not $CommandLine) { return $false }
|
||
if ($CommandLine -notmatch '(?i)uvicorn') { return $false }
|
||
if ($CommandLine -notmatch 'app\.main:app') { return $false }
|
||
if ($CommandLine -match '(?i)--port\s+8000') { return $true }
|
||
if ($CommandLine -match ':8000') { return $true }
|
||
if ($CommandLine -match [regex]::Escape($Root)) { return $true }
|
||
return $false
|
||
}
|
||
|
||
function Get-JinRongUvicornPids {
|
||
$pids = @()
|
||
foreach ($exe in @("python.exe", "python3.exe", "pythonw.exe")) {
|
||
Get-CimInstance Win32_Process -Filter "Name='$exe'" -ErrorAction SilentlyContinue |
|
||
Where-Object { Test-IsJinRongUvicornCommandLine $_.CommandLine } |
|
||
ForEach-Object { $pids += $_.ProcessId }
|
||
}
|
||
$pids | Select-Object -Unique
|
||
}
|
||
|
||
function Stop-AllBackendOnPort8000 {
|
||
Write-Host "清理 8000(Docker 外来容器 + 本机 uvicorn 进程树)..." -ForegroundColor Cyan
|
||
Stop-ForeignPort8000Container
|
||
for ($round = 1; $round -le 4; $round++) {
|
||
$batch = @()
|
||
$batch += Get-JinRongUvicornPids
|
||
$batch += Get-ListenerPids @(8000)
|
||
$batch = $batch | Where-Object { $_ -and $_ -gt 0 } | Select-Object -Unique
|
||
if ($batch.Count -eq 0) { break }
|
||
foreach ($procId in $batch) {
|
||
Write-Host " [$round] taskkill /T PID $procId" -ForegroundColor DarkGray
|
||
Stop-ProcessTree $procId
|
||
}
|
||
Start-Sleep -Milliseconds 700
|
||
}
|
||
Stop-PortListeners @(8000) -WithTree
|
||
Start-Sleep -Milliseconds 400
|
||
}
|
||
|
||
function Test-RedisDevReady {
|
||
try {
|
||
$ready = Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/ready" -TimeoutSec 4 -ErrorAction Stop
|
||
if ($ready.checks.redis -eq $true) { return $true }
|
||
} catch {
|
||
# 后端尚未起来时走端口探测
|
||
}
|
||
$listen6380 = Get-NetTCPConnection -LocalPort 6380 -State Listen -ErrorAction SilentlyContinue
|
||
return [bool]$listen6380
|
||
}
|
||
|
||
function Write-RedisDevReminder {
|
||
Write-Host ""
|
||
Write-Host "[WARN] Redis 未就绪(推荐 Docker jinrong-redis @ 127.0.0.1:6380)。" -ForegroundColor Yellow
|
||
Write-Host " 影响:客户助手会话窗口、交易多轮抽槽、问数 D-06 缓存、JWT 吊销列表等(fail-open 时可能静默降级)。" -ForegroundColor Yellow
|
||
Write-Host " 不影响:交易中心直连 simulate、持仓/流水/看板(走 MySQL Core)。" -ForegroundColor DarkGray
|
||
Write-Host " 请先打开 Docker Desktop,再执行: .\scripts\dev\start-redis.ps1" -ForegroundColor Yellow
|
||
Write-Host " 自检: http://127.0.0.1:8000/api/ready (checks.redis 应为 true)" -ForegroundColor DarkGray
|
||
}
|
||
|
||
function Stop-AllViteDev {
|
||
$webEsc = [regex]::Escape($Web)
|
||
Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
||
Where-Object {
|
||
$cmd = $_.CommandLine
|
||
$cmd -and ($cmd -match '(?i)\bvite\b') -and ($cmd -match $webEsc -or $cmd -match 'npm run dev')
|
||
} |
|
||
ForEach-Object {
|
||
Write-Host " taskkill vite PID $($_.ProcessId)" -ForegroundColor DarkGray
|
||
Stop-ProcessTree $_.ProcessId
|
||
}
|
||
Stop-PortListeners @(5173, 5174, 5175) -WithTree
|
||
Start-Sleep -Milliseconds 400
|
||
}
|
||
|
||
function Wait-Port8000Clear([int]$TimeoutSec = 15) {
|
||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||
while ((Get-Date) -lt $deadline) {
|
||
$left = @(Get-ListenerPids @(8000))
|
||
if ($left.Count -eq 0) { return $true }
|
||
Start-Sleep -Seconds 1
|
||
}
|
||
return $false
|
||
}
|
||
|
||
function Test-BackendHasNavHistoryRoute {
|
||
try {
|
||
$paths = (Invoke-RestMethod -Uri "http://127.0.0.1:8000/openapi.json" -TimeoutSec 10).paths
|
||
return [bool]$paths.'/api/products/{product_id}/nav/history'
|
||
} catch {
|
||
return $false
|
||
}
|
||
}
|
||
|
||
function Get-DockerBackendName {
|
||
if ($DockerContainer -ne "") {
|
||
return $DockerContainer
|
||
}
|
||
$name = Invoke-DockerText ps --filter "publish=8000" --format "{{.Names}}"
|
||
if (-not $name) { return $null }
|
||
# 仅把 JinRong 自有容器当后端;其它项目(如 stu-system-app)占 8000 时走本机 uvicorn
|
||
if ($name -match '^jinrong') { return $name }
|
||
return $null
|
||
}
|
||
|
||
function Stop-ForeignPort8000Container {
|
||
$name = Invoke-DockerText ps --filter "publish=8000" --format "{{.Names}}"
|
||
if (-not $name) { return }
|
||
if ($name -match '^jinrong') { return }
|
||
Write-Host "[WARN] 容器 '$name' 占用 8000 且非 JinRong 后端,将 stop 以便加载本仓库 uvicorn(行情 nav/history 等路由)" -ForegroundColor Yellow
|
||
Invoke-DockerOk stop $name | Out-Null
|
||
}
|
||
|
||
function Test-HttpOk([string]$Url, [int]$TimeoutSec = 12) {
|
||
try {
|
||
$null = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec $TimeoutSec
|
||
return $true
|
||
} catch {
|
||
return $false
|
||
}
|
||
}
|
||
|
||
function Test-HealthOk {
|
||
try {
|
||
$r = Invoke-RestMethod -Uri "http://127.0.0.1:8000/health" -TimeoutSec 15
|
||
return ($r.status -eq "ok")
|
||
} catch {
|
||
return $false
|
||
}
|
||
}
|
||
|
||
# --- 后端 -----------------------------------------------------------------
|
||
if (-not $SkipBackend) {
|
||
$useDocker = $false
|
||
$container = Get-DockerBackendName
|
||
|
||
switch ($Backend) {
|
||
"Docker" {
|
||
if (-not $container) {
|
||
Write-Error "未找到映射 8000 的 Docker 容器;请 -DockerContainer 指定或改用 -Backend Local"
|
||
exit 1
|
||
}
|
||
$useDocker = $true
|
||
}
|
||
"Local" { $useDocker = $false }
|
||
default {
|
||
$useDocker = [bool]$container
|
||
}
|
||
}
|
||
|
||
if ($useDocker) {
|
||
Write-Host "重启后端 Docker: $container (8000) ..." -ForegroundColor Cyan
|
||
if (-not (Invoke-DockerOk restart $container)) {
|
||
Write-Error "docker restart $container 失败(Docker Desktop 是否已启动?)"
|
||
exit 1
|
||
}
|
||
$deadline = (Get-Date).AddSeconds(45)
|
||
while ((Get-Date) -lt $deadline) {
|
||
if (Test-HealthOk) { break }
|
||
Start-Sleep -Seconds 2
|
||
}
|
||
if (-not (Test-HealthOk)) {
|
||
Write-Host "[WARN] /health 暂未就绪,请稍后 docker logs $container" -ForegroundColor Yellow
|
||
} else {
|
||
Write-Host "后端就绪: http://127.0.0.1:8000/health" -ForegroundColor Green
|
||
}
|
||
} else {
|
||
Stop-AllBackendOnPort8000
|
||
$foreign = Invoke-DockerText ps --filter "publish=8000" --format "{{.Names}}"
|
||
if ($foreign -and ($foreign -notmatch '^jinrong')) {
|
||
Write-Host "[WARN] 8000 仍被 Docker 容器 '$foreign' 占用;脚本已尝试 stop,若仍存在请手动 docker stop 或启动 Docker Desktop" -ForegroundColor Yellow
|
||
}
|
||
if (-not (Wait-Port8000Clear)) {
|
||
Write-Host "[WARN] 8000 仍被占用。将列出监听进程 — 请关掉所有「uvicorn」PowerShell 窗口后重跑本脚本。" -ForegroundColor Yellow
|
||
netstat -ano | findstr ":8000"
|
||
exit 1
|
||
}
|
||
Write-Host "重启本机 uvicorn (8000) ..." -ForegroundColor Cyan
|
||
Start-Sleep -Seconds 1
|
||
$uvicornCmd = "Set-Location '$Root'; `$env:PYTHONPATH='$Root'; python -m uvicorn app.main:app --reload --host 127.0.0.1 --port 8000"
|
||
Start-Process powershell -ArgumentList @("-NoExit", "-Command", $uvicornCmd)
|
||
$routeDeadline = (Get-Date).AddSeconds(30)
|
||
$routesOk = $false
|
||
while ((Get-Date) -lt $routeDeadline) {
|
||
if ((Test-HealthOk) -and (Test-BackendHasNavHistoryRoute)) {
|
||
$routesOk = $true
|
||
break
|
||
}
|
||
Start-Sleep -Seconds 2
|
||
}
|
||
if ($routesOk) {
|
||
Write-Host "后端就绪: http://127.0.0.1:8000/health · nav/history 已挂载" -ForegroundColor Green
|
||
} elseif (Test-HealthOk) {
|
||
Write-Host "[WARN] /health 有响应但 OpenAPI 无 nav/history — 仍在跑旧代码,请关掉所有 uvicorn 窗口后重跑本脚本" -ForegroundColor Yellow
|
||
} else {
|
||
Write-Host "[WARN] uvicorn 窗口已打开,/health 尚未响应(可能仍在启动)" -ForegroundColor Yellow
|
||
}
|
||
}
|
||
}
|
||
|
||
# --- 前端 -----------------------------------------------------------------
|
||
if (-not $SkipFrontend) {
|
||
if (-not (Test-Path (Join-Path $Web "package.json"))) {
|
||
Write-Error "未找到 web/package.json"
|
||
exit 1
|
||
}
|
||
Write-Host "重启前端 Vite (5173) ..." -ForegroundColor Cyan
|
||
Stop-AllViteDev
|
||
Start-Sleep -Seconds 1
|
||
$viteCmd = "Set-Location '$Web'; npm run dev"
|
||
Start-Process powershell -ArgumentList @("-NoExit", "-Command", $viteCmd)
|
||
Start-Sleep -Seconds 4
|
||
if (Test-HttpOk "http://localhost:5173/") {
|
||
Write-Host "前端就绪: http://localhost:5173/" -ForegroundColor Green
|
||
} else {
|
||
Write-Host "[WARN] Vite 窗口已打开,5173 暂未响应(可能落在 5174,请看新窗口日志)" -ForegroundColor Yellow
|
||
}
|
||
}
|
||
|
||
if (-not (Test-RedisDevReady)) {
|
||
Write-RedisDevReminder
|
||
} else {
|
||
Write-Host "Redis 自检: 6380 / api/ready checks.redis = OK" -ForegroundColor Green
|
||
}
|
||
|
||
Write-Host "`n完成。后端 Swagger: http://127.0.0.1:8000/docs" -ForegroundColor DarkGray
|
||
Write-Host "提示: 本脚本不启动 Redis;需要时先 .\scripts\dev\start-redis.ps1(见脚本头部 NOTES)" -ForegroundColor DarkGray
|