feat: 闭环观察台角色、补齐 Web.config 路径覆盖、修 S1 竞态
- loop.ps1 新增 console 角色(:5090),传入 ABP / iXserver 日志目录与库连接 - overrides.psd1 补 WebchatPath、OffLineFormPath、AppConfigManager_AppConfigJsonFile(新增 config/ixserver/config.json) - loop.psd1 新增 Ports.Console、QuotaName - S1 等第三方回复记录改为轮询,失败时打印最新的记录 - README:观察台、iXserver 主日志位置 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
parent
ad136a9d53
commit
ff0e390238
|
|
@ -10,6 +10,7 @@
|
|||
| 第三方 chatbot 客户端 | 5056 | `iXserver\Tools\ThirdPartyChatbotTestClient` |
|
||||
| IG 后端模拟 | 5070 | `iXserver\Tools\IgMockServer` |
|
||||
| SCRM 客服系统模拟 | 5080 | `iXserver\Tools\ScrmMockServer` |
|
||||
| 闭环观察台(只读,泳道时序图) | 5090 | `iXserver\Tools\LoopConsole` |
|
||||
|
||||
所有端口只绑本机。iXserver 的旧接口和 WebSocket **没有鉴权**,绝不能对外开放 8081 / 33346。
|
||||
|
||||
|
|
@ -51,12 +52,14 @@
|
|||
.\scenarios\s8-human-chain.ps1 # SCRM 开工 → 新 IG 用户 → 人工工单推送 → 指派 / 回复 → IG 收到 → 结束 → 下班
|
||||
```
|
||||
|
||||
跑的时候开着观察台 <http://127.0.0.1:5090>,每一跳和状态变化会实时画成泳道时序图;`?ticket=<工单号>` 只看一张。
|
||||
|
||||
S8 结束时会把服务号切回机器人、在线人数归零,所以 S1 / S8 先后跑互不影响;要停在人工状态看页面加 `-KeepShift`。
|
||||
|
||||
从 Mac 看几个页面用 SSH 本地端口转发:
|
||||
|
||||
```bash
|
||||
ssh -N -L 5056:127.0.0.1:5056 -L 5070:127.0.0.1:5070 -L 5080:127.0.0.1:5080 FG-W11-ICON-DEV
|
||||
ssh -N -L 5056:127.0.0.1:5056 -L 5070:127.0.0.1:5070 -L 5080:127.0.0.1:5080 -L 5090:127.0.0.1:5090 FG-W11-ICON-DEV
|
||||
```
|
||||
|
||||
## 几个容易踩的点
|
||||
|
|
@ -87,6 +90,8 @@ ssh -N -L 5056:127.0.0.1:5056 -L 5070:127.0.0.1:5070 -L 5080:127.0.0.1:5080 FG-W
|
|||
`appsettings.Loop.json` 把所有连接串都盖成本机 iCONAbpFX。
|
||||
- PowerShell 5.1 的两个坑已经在 `loop.ps1` 里绕开:原生命令往 stderr 写一行就抛异常(统一走 `Invoke-Native`);
|
||||
`Get-Content` 默认按 GBK 读无 BOM 的 UTF-8(拷配置用 `Copy-Item`,脚本文件一律带 BOM)。
|
||||
- **iXserver 的主日志在 `run\ixserver\Log\{Debug,Information,Warning,Error}\<日期>\`**(注入的 ILogger,内部步骤很全,观察台就读它)。
|
||||
`run\data\API_Log` 是静态 `Logger` 写的另一份,几乎是空的,别看它下结论。排查先看 `Log\Error`。
|
||||
- `deployed.json` 记录每次部署时两个源码仓库的 commit 与未提交文件。闭环结果离开「当时部署的是哪个版本」没法解读,
|
||||
跑场景的记录里引用它。注意 winbox 上 iXserver 仓库的 HEAD 不随 `sync.sh` 前进(只推文件),
|
||||
`commit` 字段可能停在很久以前,以 `uncommitted` 清单和 Mac 上的提交为准。
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -33,6 +33,11 @@
|
|||
'FileFolder' = '{RunData}\API_FileFolder\'
|
||||
'DbTimeLogLocation' = '{RunData}\API_Log_DB\'
|
||||
'AutoChatJsonSetting_JsonStoreDir' = '{RunData}\TicketJsonDir'
|
||||
# sys_language 启动时把生成的 JS 写进 WebchatPath;写死的 D:\ 不存在会记一条「设备未就绪」
|
||||
'WebchatPath' = '{RunData}\Webchat'
|
||||
'OffLineFormPath' = '{RunData}\pages\'
|
||||
# 仓库里没有 config.json,读不到就记 Error 并退回默认值;指到 LoopEnv 里那份(空对象 = 全用默认值)
|
||||
'AppConfigManager_AppConfigJsonFile' = '{LoopRoot}\config\ixserver\config.json'
|
||||
|
||||
# ---- 自身地址:上传文件的访问地址要能从本机打开
|
||||
'ApiUrl' = 'http://localhost:{IxserverPort}/'
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
Chatbot = 5056
|
||||
IgMock = 5070
|
||||
ScrmMock = 5080
|
||||
Console = 5090 # 闭环观察台:只读汇总各角色,画泳道时序图
|
||||
}
|
||||
|
||||
# 闭环专用的假凭据:与 staging / 生产无关,可以进 git
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
IgPageId = '568932443471849'
|
||||
CompanyCode = 'SM_HKTB_IG'
|
||||
AdminCode = 'HKTB'
|
||||
QuotaName = 'HKTB' # 额度池名,与管理员 code 同名只是碰巧
|
||||
|
||||
# 第三方客户端收到投递后自动回复:echo / 任意固定文本;留空则只能在页面上手动回
|
||||
ChatbotAutoReply = 'echo'
|
||||
|
|
|
|||
25
loop.ps1
25
loop.ps1
|
|
@ -54,7 +54,7 @@ $Dirs = @{
|
|||
}
|
||||
foreach ($d in $Dirs.Values) { if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d | Out-Null } }
|
||||
|
||||
$AllRoles = @('abp', 'ixserver', 'chatbot', 'igmock', 'scrm')
|
||||
$AllRoles = @('abp', 'ixserver', 'chatbot', 'igmock', 'scrm', 'console')
|
||||
$Roles = if ($Only) { $AllRoles | Where-Object { $Only -contains $_ } } else { $AllRoles }
|
||||
|
||||
# 健康检查要连本机 ABP 的 dev 证书;只影响本 PowerShell 进程
|
||||
|
|
@ -70,6 +70,7 @@ function Write-Warn2([string]$text) { Write-Host " $text" -ForegroundColor Ye
|
|||
function Expand-Template([string]$text) {
|
||||
$map = @{
|
||||
'{RunData}' = $Dirs.Data
|
||||
'{LoopRoot}' = $Root
|
||||
'{IxserverPort}' = $Cfg.Ports.Ixserver
|
||||
'{WebSocketPort}' = $Cfg.Ports.WebSocket
|
||||
'{AbpPort}' = $Cfg.Ports.Abp
|
||||
|
|
@ -230,11 +231,27 @@ function Get-RoleSpec([string]$role) {
|
|||
}
|
||||
'scrm' {
|
||||
$script = "$tools\ScrmMockServer\server.js"
|
||||
if (-not (Test-Path $script)) { return $null } # P7 之前还没有
|
||||
if (-not (Test-Path $script)) { return $null } # iXserver 仓库还没同步到带这个工具的版本
|
||||
$cmd = "`"$(Get-NodeExe)`" `"$script`" --port $($p.ScrmMock) --ixserver http://localhost:$($p.Ixserver)" +
|
||||
" --ws ws://localhost:$($p.WebSocket) --admin-code $($Cfg.AdminCode) --company-code $($Cfg.CompanyCode) --service-code $($Cfg.ServiceCode)"
|
||||
" --ws ws://localhost:$($p.WebSocket) --admin-code $($Cfg.AdminCode) --company-code $($Cfg.CompanyCode) --service-code $($Cfg.ServiceCode) --quota-name $($Cfg.QuotaName)"
|
||||
return @{ Port = $p.ScrmMock; WorkDir = $Root; Command = $cmd; Health = { Test-HttpAlive "http://127.0.0.1:$($Cfg.Ports.ScrmMock)/_api/state" }; Timeout = 30 }
|
||||
}
|
||||
'console' {
|
||||
$script = "$tools\LoopConsole\server.js"
|
||||
if (-not (Test-Path $script)) { return $null }
|
||||
$s = $Cfg.Sql
|
||||
$cmd = "`"$(Get-NodeExe)`" `"$script`" --port $($p.Console)" +
|
||||
" --ig http://127.0.0.1:$($p.IgMock) --chatbot http://127.0.0.1:$($p.Chatbot) --scrm http://127.0.0.1:$($p.ScrmMock)" +
|
||||
" --ixserver http://localhost:$($p.Ixserver) --abp https://localhost:$($p.Abp)" +
|
||||
" --abp-log-dir `"$(Join-Path $Dirs.Abp 'Logs\Information')`" --ix-log-dir `"$(Join-Path $Dirs.Ixserver 'Log')`" --deployed `"$(Join-Path $Root 'deployed.json')`"" +
|
||||
" --sql-server `"$($s.Server)`" --sql-user $($s.User) --db $($s.IxserverDb)" +
|
||||
" --company-code $($Cfg.CompanyCode) --service-code $($Cfg.ServiceCode) --quota-name $($Cfg.QuotaName)"
|
||||
return @{
|
||||
Port = $p.Console; WorkDir = $Root; Command = $cmd
|
||||
Env = @{ LOOP_SQL_PASSWORD = $s.Password }
|
||||
Health = { Test-HttpAlive "http://127.0.0.1:$($Cfg.Ports.Console)/_api/state" }; Timeout = 30
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +293,7 @@ function Set-WebConfigOverrides {
|
|||
|
||||
$xml.Save($path)
|
||||
|
||||
foreach ($sub in 'API_Log', 'API_TempFolder', 'API_FileFolder', 'API_Log_DB', 'TicketJsonDir', 'aspnet-temp') {
|
||||
foreach ($sub in 'API_Log', 'API_TempFolder', 'API_FileFolder', 'API_Log_DB', 'TicketJsonDir', 'aspnet-temp', 'Webchat\Scripts', 'pages') {
|
||||
$d = Join-Path $Dirs.Data $sub
|
||||
if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d | Out-Null }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ function Fail([string]$why) {
|
|||
Write-Host '--- IG 模拟记录'
|
||||
(Invoke-RestMethod "$ig/_api/state").events | ForEach-Object { $_ | ConvertTo-Json -Depth 8 -Compress } | Out-Host
|
||||
Write-Host '--- chatbot 记录(最近 6 条)'
|
||||
(Invoke-RestMethod "$cb/_api/state").events | Select-Object -Last 6 | ForEach-Object { $_ | ConvertTo-Json -Depth 8 -Compress } | Out-Host
|
||||
# 记录是新的在前
|
||||
(Invoke-RestMethod "$cb/_api/state").events | Select-Object -First 6 | ForEach-Object { $_ | ConvertTo-Json -Depth 8 -Compress } | Out-Host
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +37,12 @@ if (-not $hit) { Fail "$TimeoutSec 秒内 IG 用户没有收到「$expected」"
|
|||
if (@($hit.issues).Count) { Fail "出站请求有问题:$($hit.issues -join ';')" }
|
||||
if (-not $hit.tokenPresent) { Fail '出站请求没带 access_token' }
|
||||
|
||||
$reply = @((Invoke-RestMethod "$cb/_api/state").events | Where-Object { $_.kind -eq 'reply' -and $_.message -eq $expected }) | Select-Object -First 1
|
||||
# 也要等:iXserver 在 send-txt 应答之前就把消息发到 IG 了,客户端拿到应答才记 reply,会晚上百毫秒
|
||||
$reply = $null
|
||||
while (-not $reply -and (Get-Date) -lt $deadline.AddSeconds(5)) {
|
||||
$reply = @((Invoke-RestMethod "$cb/_api/state").events | Where-Object { $_.kind -eq 'reply' -and $_.message -eq $expected }) | Select-Object -First 1
|
||||
if (-not $reply) { Start-Sleep -Milliseconds 200 }
|
||||
}
|
||||
if (-not $reply) { Fail 'chatbot 侧没有对应的回复记录' }
|
||||
if ($reply.response.status -ne 201) { Fail "send-txt 返回 $($reply.response.status)" }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue