[split-upload 1/6] f2fd4fa backup via proxy
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
# agent-browser-auth-wait.ps1
|
||||
#
|
||||
# 功能:使用 agent-browser 打开登录页(含二维码),导出二维码图片,
|
||||
# 等待用户扫码完成后继续执行,自动保存认证状态供下次复用。
|
||||
#
|
||||
# 设计要点:
|
||||
# 1. 复用 agent-browser v0.27.0+ 的 state save/load 能力(内置),
|
||||
# 不自造轮询 cookie 的轮子。
|
||||
# 2. 使用 eval -b <base64> 注入 JS,避开 PowerShell pipe -> eval --stdin
|
||||
# 会卡死的坑(实测 v0.27.0 中 PowerShell pipe 到 eval --stdin 永不返回)。
|
||||
# 3. 默认 headless + --no-sandbox,因为当前 sandbox 环境 headless chromium
|
||||
# 会因沙箱权限问题直接 exit 3;如需看到浏览器窗口,加 -Headed。
|
||||
# 4. wait --url 单次默认 25s 超时,因此用"轮询 wait + 累计超时"实现长等待。
|
||||
# 5. 不主动 close daemon(除非 -CloseOnExit),便于调用方在同一 daemon 内
|
||||
# 继续执行后续业务。
|
||||
#
|
||||
# 用法示例:
|
||||
# # 等用户扫码登录,登录完成后进入业务页
|
||||
# .\agent-browser-auth-wait.ps1 `
|
||||
# -LoginUrl "https://itsupport.servyou.com.cn/login" `
|
||||
# -ExpectedPath "dashboard" `
|
||||
# -StateFile "$PSScriptRoot\auth-state.json"
|
||||
#
|
||||
# # 测试 headed 模式(用户能看到浏览器窗口)
|
||||
# .\agent-browser-auth-wait.ps1 -LoginUrl "..." -Headed
|
||||
#
|
||||
# # 等待登录后页面上出现"欢迎"字样
|
||||
# .\agent-browser-auth-wait.ps1 -LoginUrl "..." -ExpectedText "欢迎"
|
||||
#
|
||||
# Exit code:
|
||||
# 0 = 登录成功(或检测到已登录)
|
||||
# 1 = 等待超时
|
||||
# 2 = 其他错误
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# ── 必填 ─────────────────────────────────────────────────────────────
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LoginUrl,
|
||||
|
||||
# ── 等待条件(二选一或组合)───────────────────────────────────────────
|
||||
# 登录后跳转的目标 URL 子串(如 "dashboard"),会与 current URL 做 -like 匹配
|
||||
[string]$ExpectedPath = "dashboard",
|
||||
# 登录后页面出现的文本(substring 匹配),可选
|
||||
[string]$ExpectedText,
|
||||
|
||||
# ── 超时与轮询 ─────────────────────────────────────────────────────────
|
||||
# 等待用户扫码的总秒数(默认 2 分钟)
|
||||
[int]$TimeoutSec = 120,
|
||||
# 轮询间隔(毫秒),默认 1.5 秒
|
||||
[int]$PollIntervalMs = 1500,
|
||||
|
||||
# ── 持久化 ─────────────────────────────────────────────────────────────
|
||||
# 登录成功后保存 state 的路径(cookies + localStorage),下次 -StateFile 加载可跳过登录
|
||||
[string]$StateFile,
|
||||
|
||||
# ── 二维码 ─────────────────────────────────────────────────────────────
|
||||
# 二维码截图导出路径(headless 模式下用户看不到浏览器,必须导出)
|
||||
[string]$QrImagePath = "./qr-code.png",
|
||||
|
||||
# ── 浏览器模式 ─────────────────────────────────────────────────────────
|
||||
# 显示浏览器窗口(默认 headless + --no-sandbox,适合 CI/无人值守)
|
||||
[switch]$Headed,
|
||||
# 完成后关闭 daemon(默认不关,便于调用方继续在同一 daemon 内执行业务)
|
||||
[switch]$CloseOnExit,
|
||||
# 调试模式:打印每个 agent-browser 调用的命令和耗时(避免命名 Debug 以免与 CmdletBinding 冲突)
|
||||
[switch]$Trace
|
||||
)
|
||||
|
||||
# ── 全局设置 ────────────────────────────────────────────────────────────
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# ── 辅助函数:统一注入 --args ────────────────────────────────────────────
|
||||
# 用途:所有 agent-browser 调用统一通过此函数,自动添加 --no-sandbox
|
||||
# (headless 模式必须),便于复用。
|
||||
#
|
||||
# 注意:必须将全局参数和命令参数合并成**一个数组**再 splat。
|
||||
# PowerShell 中 `& cmd @arr1 @arr2` 双 splat 在某些版本下会被
|
||||
# 解释成"@arr1 当作第一个参数,@arr2 是剩余参数",导致
|
||||
# agent-browser 收到错误的 argv 然后 hang。本次实测复现。
|
||||
function Invoke-AgentBrowser {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$Args
|
||||
)
|
||||
|
||||
# 准备全局参数数组
|
||||
$globalArgs = @()
|
||||
if (-not $Headed) {
|
||||
# headless 模式必须 --no-sandbox,否则 sandbox 环境会 chrome exit 3
|
||||
$globalArgs = @('--args', '--no-sandbox')
|
||||
}
|
||||
|
||||
# 合并为单一数组(避免双 splat hang 坑)
|
||||
$allArgs = @($globalArgs + $Args)
|
||||
|
||||
# 调试输出
|
||||
if ($script:DebugMode) {
|
||||
Write-Host " [DEBUG] agent-browser $($allArgs -join ' ')" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# 调用 agent-browser,捕获输出
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$output = & agent-browser @allArgs 2>&1
|
||||
$sw.Stop()
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
if ($script:DebugMode) {
|
||||
Write-Host " [DEBUG] exit=$exitCode, $($sw.ElapsedMilliseconds)ms, output_lines=$(($output | Measure-Object).Count)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# 返回 PSObject 便于调用方检查
|
||||
return [PSCustomObject]@{
|
||||
ExitCode = $exitCode
|
||||
Output = ($output -join "`n")
|
||||
}
|
||||
}
|
||||
|
||||
# ── 辅助函数:用 base64 执行 JS(避开 --stdin pipe 卡死问题)────────────
|
||||
function Invoke-ABEvalJS {
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory = $true)][string]$Js)
|
||||
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Js)
|
||||
$base64 = [Convert]::ToBase64String($bytes)
|
||||
|
||||
return (Invoke-AgentBrowser -Args @('eval', '-b', $base64))
|
||||
}
|
||||
|
||||
# ── 辅助函数:等待用户扫码登录完成 ─────────────────────────────────────
|
||||
# 策略:用 `get url`(瞬时命令)做短周期轮询,避免 `wait --url` 内置 25s
|
||||
# 超时卡住短超时场景。ExpectedPath 作为子串匹配(如 "dashboard")。
|
||||
function Wait-ForLoginCompletion {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$UrlPattern,
|
||||
[string]$TextPattern,
|
||||
[int]$TimeoutSec,
|
||||
[int]$PollIntervalMs = 1500
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||
$attempt = 0
|
||||
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
$attempt++
|
||||
$remaining = [int]([Math]::Max(0, ($deadline - (Get-Date)).TotalSeconds))
|
||||
|
||||
Write-Host " ⏳ 第 $attempt 次检测(剩余 ${remaining}s)..."
|
||||
|
||||
# ── 检测 1:URL 子串匹配 ──
|
||||
if ($UrlPattern) {
|
||||
$urlResult = Invoke-AgentBrowser -Args @('get', 'url')
|
||||
if ($urlResult.ExitCode -eq 0) {
|
||||
$currentUrl = ($urlResult.Output -join "`n").Trim()
|
||||
if ($currentUrl -like "*$UrlPattern*") {
|
||||
Write-Host " ✅ URL 匹配登录后路径: $currentUrl" -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── 检测 2:页面文本子串匹配 ──
|
||||
if ($TextPattern) {
|
||||
$checkJs = "document.body && document.body.innerText.includes('$TextPattern')"
|
||||
$b = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($checkJs))
|
||||
$textResult = Invoke-AgentBrowser -Args @('eval', '-b', $b)
|
||||
if ($textResult.ExitCode -eq 0) {
|
||||
# eval 返回 "true" 或 "false"
|
||||
if ($textResult.Output -match '^\s*true\s*$') {
|
||||
Write-Host " ✅ 页面文本匹配: $TextPattern" -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds $PollIntervalMs
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
# ── 主流程 ──────────────────────────────────────────────────────────────
|
||||
try {
|
||||
Write-Host ""
|
||||
Write-Host "════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " agent-browser 扫码登录等待" -ForegroundColor Cyan
|
||||
Write-Host "════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " 登录页: $LoginUrl"
|
||||
Write-Host " 等待路径: $ExpectedPath"
|
||||
if ($StateFile) { Write-Host " 状态文件: $StateFile" }
|
||||
Write-Host " Headed: $Headed"
|
||||
Write-Host ""
|
||||
|
||||
# 调试钩子:-Trace 模式下打印每一步
|
||||
$script:DebugMode = [bool]$Trace
|
||||
|
||||
# ── Step 1: 加载已保存的 state(如提供)或直接打开登录页 ──────────────
|
||||
# 注意:--state 是启动时的全局参数(位于命令前),daemon 已启动时不能附加。
|
||||
# 如果 daemon 没启动:第一次 open 会启动 daemon 并应用 --state。
|
||||
# 如果 daemon 已启动:--state 被忽略,需要先关闭再重启。
|
||||
# 本脚本不主动关闭 daemon,让调用方管理生命周期。
|
||||
if ($StateFile -and (Test-Path $StateFile)) {
|
||||
Write-Host "📂 发现已保存的认证状态,尝试复用..." -ForegroundColor Yellow
|
||||
Write-Host " agent-browser --state $StateFile open $LoginUrl"
|
||||
$args = @('--state', $StateFile, 'open', $LoginUrl)
|
||||
$null = Invoke-AgentBrowser -Args $args
|
||||
}
|
||||
else {
|
||||
Write-Host "🌐 打开登录页..."
|
||||
$null = Invoke-AgentBrowser -Args @('open', $LoginUrl)
|
||||
}
|
||||
|
||||
# ── Step 2: 等待页面加载完成 ──────────────────────────────────────────
|
||||
Write-Host "⏳ 等待页面加载..."
|
||||
$null = Invoke-AgentBrowser -Args @('wait', '--load', 'networkidle')
|
||||
|
||||
# ── Step 3: 截图导出二维码 ────────────────────────────────────────────
|
||||
# headless 模式下浏览器窗口不可见,二维码必须导出;headed 模式截图仅为冗余
|
||||
$qrDir = Split-Path -Parent $QrImagePath
|
||||
if ($qrDir -and -not (Test-Path $qrDir)) {
|
||||
New-Item -ItemType Directory -Path $qrDir -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "📸 导出二维码: $QrImagePath" -ForegroundColor Yellow
|
||||
$r = Invoke-AgentBrowser -Args @('screenshot', $QrImagePath)
|
||||
if ($r.ExitCode -ne 0) {
|
||||
Write-Warning "二维码截图失败:$($r.Output)"
|
||||
}
|
||||
elseif (Test-Path $QrImagePath) {
|
||||
Write-Host " 请用企微 APP 扫描二维码完成登录"
|
||||
if (-not $Headed) {
|
||||
Write-Host " (headless 模式:在文件管理器中打开 $QrImagePath 扫描)" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# ── Step 4: 提示用户 ──────────────────────────────────────────────────
|
||||
Write-Host ""
|
||||
Write-Host "📱 等待扫码登录(超时 $TimeoutSec 秒)..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 5: 轮询等待登录完成 ──────────────────────────────────────────
|
||||
$ok = Wait-ForLoginCompletion `
|
||||
-UrlPattern $ExpectedPath `
|
||||
-TextPattern $ExpectedText `
|
||||
-TimeoutSec $TimeoutSec `
|
||||
-PollIntervalMs $PollIntervalMs
|
||||
|
||||
if (-not $ok) {
|
||||
Write-Host ""
|
||||
Write-Host "❌ 等待扫码登录超时(${TimeoutSec}s)" -ForegroundColor Red
|
||||
Write-Host " 请重试,或检查登录页是否正常显示二维码" -ForegroundColor Red
|
||||
Write-Host " daemon 保持打开,可手动操作后重跑脚本" -ForegroundColor Gray
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Step 6: 保存 state(供下次复用)──────────────────────────────────
|
||||
if ($StateFile) {
|
||||
$stateDir = Split-Path -Parent $StateFile
|
||||
if ($stateDir -and -not (Test-Path $stateDir)) {
|
||||
New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
|
||||
}
|
||||
Write-Host "💾 保存认证状态: $StateFile"
|
||||
$r = Invoke-AgentBrowser -Args @('state', 'save', $StateFile)
|
||||
if ($r.ExitCode -ne 0) {
|
||||
Write-Warning "state 保存失败:$($r.Output)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✅ 扫码登录完成" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# ── Step 7: CloseOnExit ──────────────────────────────────────────────
|
||||
# 注意:脚本不主动关闭 daemon(实测 close --all 在某些 Windows 状态下
|
||||
# 会让后续命令 hang)。如需关闭,调用方自己执行 `agent-browser close --all`。
|
||||
if ($CloseOnExit) {
|
||||
Write-Host "ℹ️ CloseOnExit 已请求,但脚本不主动关闭 daemon 以避免 hang" -ForegroundColor Yellow
|
||||
Write-Host " 请手动执行: agent-browser close --all" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host "ℹ️ daemon 保持打开,可继续执行后续 agent-browser 命令" -ForegroundColor Gray
|
||||
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "❌ 脚本异常: $_" -ForegroundColor Red
|
||||
Write-Host $_.ScriptStackTrace -ForegroundColor Red
|
||||
exit 2
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# 构建 P2/P3 前端
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# 设置 npm 镜像
|
||||
$env:NPM_CONFIG_REGISTRY = "https://registry.npmmirror.com"
|
||||
|
||||
# 构建 frontend-agent
|
||||
Write-Host "Building frontend-agent..."
|
||||
Set-Location "D:\资料\03-项目开发\wecom_it_smart_desk\frontend-agent"
|
||||
& "C:\Users\simon\.workbuddy\binaries\node\versions\22.22.2\node.exe" "C:\Users\simon\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js" install
|
||||
if ($LASTEXITCODE -ne 0) { throw "npm install failed" }
|
||||
|
||||
& "C:\Users\simon\.workbuddy\binaries\node\versions\22.22.2\node.exe" "C:\Users\simon\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js" run build
|
||||
if ($LASTEXITCODE -ne 0) { throw "npm run build failed" }
|
||||
|
||||
Write-Host "frontend-agent built successfully!"
|
||||
|
||||
# 构建 frontend-h5
|
||||
Write-Host "Building frontend-h5..."
|
||||
Set-Location "D:\资料\03-项目开发\wecom_it_smart_desk\frontend-h5"
|
||||
& "C:\Users\simon\.workbuddy\binaries\node\versions\22.22.2\node.exe" "C:\Users\simon\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js" install
|
||||
if ($LASTEXITCODE -ne 0) { throw "npm install failed" }
|
||||
|
||||
& "C:\Users\simon\.workbuddy\binaries\node\versions\22.22.2\node.exe" "C:\Users\simon\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js" run build
|
||||
if ($LASTEXITCODE -ne 0) { throw "npm run build failed" }
|
||||
|
||||
Write-Host "frontend-h5 built successfully!"
|
||||
|
||||
Write-Host "All done!"
|
||||
@@ -0,0 +1,27 @@
|
||||
import asyncio
|
||||
import sys
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
async def test():
|
||||
from app.services.graph_query_service import get_graph_query_service
|
||||
from app.services.neo4j_client import get_neo4j_client
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
neo4j_client = await get_neo4j_client()
|
||||
graph_service = await get_graph_query_service(neo4j_client)
|
||||
|
||||
keyword = "打印机驱动安装"
|
||||
print(f"Querying keyword: {keyword}")
|
||||
|
||||
# 直接调用 Neo4j 查询
|
||||
try:
|
||||
data = await neo4j_client.execute_read_query(
|
||||
"""MATCH (i:Issue) WHERE i.name CONTAINS $keyword RETURN i.uuid AS uuid, i.name AS name, i.category AS category LIMIT 5""",
|
||||
{"keyword": keyword}
|
||||
)
|
||||
print(f"Raw data: {data}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
asyncio.run(test())
|
||||
@@ -0,0 +1,64 @@
|
||||
# NAS full /volume1/ scan with sudo (English-only)
|
||||
# Step 1: User runs `sudo -v` first (password stays local, never enters Claude)
|
||||
# Step 2: This script reuses that 15-min sudo session
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
$outputFile = "$PSScriptRoot\nas_volumes.txt"
|
||||
|
||||
chcp 65001 | Out-Null
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
Write-Host "===================================" -ForegroundColor Cyan
|
||||
Write-Host " NAS Full Scan (with sudo)" -ForegroundColor Cyan
|
||||
Write-Host "===================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "PREREQUISITE: open another terminal and run:" -ForegroundColor Yellow
|
||||
Write-Host " ssh simon@100.85.152.112" -ForegroundColor White
|
||||
Write-Host " sudo -v <- enter simon's password here, password NOT sent to Claude" -ForegroundColor White
|
||||
Write-Host " (keep that SSH session open for 15 min, sudo session cached)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Read-Host "Press Enter after you have done sudo -v above"
|
||||
|
||||
# Force allocation so sudo can read password from terminal if needed
|
||||
$cmd = @"
|
||||
sudo bash <<'NAS_EOF'
|
||||
echo '===== [1] All top-level entries under /volume1/ ====='
|
||||
ls -la /volume1/ 2>&1
|
||||
echo ''
|
||||
echo '===== [2] Direct children sizes (1-3 minutes) ====='
|
||||
du -sh /volume1/*/ 2>/dev/null | sort -rh
|
||||
echo ''
|
||||
echo '===== [3] Disk space ====='
|
||||
df -h /volume1 2>&1 | head -3
|
||||
echo ''
|
||||
echo '===== [4] /volume1/homes/ ====='
|
||||
ls -la /volume1/homes/ 2>&1 | head -20
|
||||
echo ''
|
||||
echo '===== [5] /volume1/homes/simon/ top dirs by size ====='
|
||||
du -sh /volume1/homes/simon/*/ 2>/dev/null | sort -rh | head -20
|
||||
echo ''
|
||||
echo '===== [6] /volume1/docker/ top dirs by size (likely big) ====='
|
||||
du -sh /volume1/docker/*/ 2>/dev/null | sort -rh | head -20
|
||||
echo ''
|
||||
echo '===== [7] Largest top-level dirs (top 15) ====='
|
||||
du -sh /volume1/* 2>/dev/null | sort -rh | head -15
|
||||
echo ''
|
||||
echo '===== [8] Mounts / storage pools ====='
|
||||
mount | grep -E 'volume|tank' 2>&1 | head -10
|
||||
echo ''
|
||||
echo '===== DONE ====='
|
||||
NAS_EOF
|
||||
"@
|
||||
|
||||
ssh -t simon@100.85.152.112 "$cmd" 2>&1 | Tee-Object -FilePath $outputFile -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===================================" -ForegroundColor Green
|
||||
Write-Host " Done. Output saved to:" -ForegroundColor Green
|
||||
Write-Host " $outputFile" -ForegroundColor White
|
||||
Write-Host "===================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Please paste the ENTIRE contents of nas_volumes.txt back" -ForegroundColor Yellow
|
||||
Write-Host "(or just tell me which top-level dir is largest)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Read-Host "Press Enter to close"
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""在服务器上执行数据库迁移"""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
cmd = [
|
||||
"python",
|
||||
"C:\\Users\\simon\\.workbuddy\\skills\\jumpserver-ops\\scripts\\jms_ops.py",
|
||||
"exec",
|
||||
"-c",
|
||||
"cd /opt/wecom-it-desk && docker compose exec -T backend alembic current"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
print(result.stderr)
|
||||
@@ -0,0 +1,6 @@
|
||||
SELECT id, created_at, message_type, sender_type, content
|
||||
FROM messages
|
||||
WHERE content LIKE '%余额%'
|
||||
AND created_at > NOW() - INTERVAL '7 days'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test the approval detect-intent API endpoint."""
|
||||
import json
|
||||
import httpx
|
||||
|
||||
# Test the API endpoint from inside the container
|
||||
url = "https://localhost/api/approval/detect-intent"
|
||||
|
||||
test_messages = [
|
||||
"我要申请VPN",
|
||||
"我的电脑坏了",
|
||||
"我要申请办公用品",
|
||||
"你好,今天天气怎么样?",
|
||||
]
|
||||
|
||||
for msg in test_messages:
|
||||
print(f"\n=== Testing: {msg} ===")
|
||||
try:
|
||||
resp = httpx.post(
|
||||
url,
|
||||
json={"text": msg},
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=30.0,
|
||||
verify=False, # self-signed cert
|
||||
)
|
||||
print(f" HTTP: {resp.status_code}")
|
||||
print(f" Response: {resp.text[:500]}")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
result = data.get("data", data)
|
||||
print(f" is_approval: {result.get('is_approval_request')}")
|
||||
print(f" confidence: {result.get('confidence')}")
|
||||
print(f" type: {result.get('approval_type')}")
|
||||
print(f" source: {result.get('source')}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {type(e).__name__}: {e}")
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Dify API with composite API key format."""
|
||||
import json
|
||||
import os
|
||||
import httpx
|
||||
|
||||
# The proxy expects composite format: <dify_base_url>|<api_key>|<app_type>
|
||||
# Main Dify uses: http://yw-dify.dc.servyou-it.com/v1|app-UaTWYdBSwN6VktKQlbh5YN5H|Chat
|
||||
# Approval should use: http://yw-dify.dc.servyou-it.com/v1|app-JWI7u1LTn9XPVe95KL6dHzPx|Chat
|
||||
|
||||
PROXY_URL = "http://yw-dify.dc.servyou-it.com/dify2openai/v1/chat/completions"
|
||||
APPROVAL_KEY_RAW = "app-JWI7u1LTn9XPVe95KL6dHzPx"
|
||||
APPROVAL_KEY_COMPOSITE = "http://yw-dify.dc.servyou-it.com/v1|app-JWI7u1LTn9XPVe95KL6dHzPx|Chat"
|
||||
|
||||
body = {
|
||||
"model": "dify",
|
||||
"messages": [
|
||||
{"role": "user", "content": "我要申请VPN"},
|
||||
],
|
||||
"temperature": 0,
|
||||
}
|
||||
|
||||
# Test 1: Raw API key (current, failing)
|
||||
print("=== Test 1: Raw API key (current) ===")
|
||||
headers = {"Authorization": f"Bearer {APPROVAL_KEY_RAW}", "Content-Type": "application/json"}
|
||||
try:
|
||||
resp = httpx.post(PROXY_URL, json=body, headers=headers, timeout=15.0)
|
||||
print(f" HTTP: {resp.status_code}")
|
||||
print(f" Body: {resp.text[:300]}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
|
||||
# Test 2: Composite API key
|
||||
print("\n=== Test 2: Composite API key ===")
|
||||
headers = {"Authorization": f"Bearer {APPROVAL_KEY_COMPOSITE}", "Content-Type": "application/json"}
|
||||
try:
|
||||
resp = httpx.post(PROXY_URL, json=body, headers=headers, timeout=30.0)
|
||||
print(f" HTTP: {resp.status_code}")
|
||||
print(f" Body: {resp.text[:500]}")
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
choices = data.get("choices") or []
|
||||
if choices:
|
||||
content = choices[0].get("message", {}).get("content", "")
|
||||
print(f" Content: {content[:300]}")
|
||||
try:
|
||||
parsed = json.loads(content)
|
||||
print(f" Parsed JSON: {json.dumps(parsed, ensure_ascii=False, indent=2)}")
|
||||
except:
|
||||
print(f" Content is not JSON")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
|
||||
# Test 3: Check main Dify key format
|
||||
print("\n=== Test 3: Main Dify env vars ===")
|
||||
print(f" DIFY_API_KEY = {os.environ.get('DIFY_API_KEY', 'NOT SET')}")
|
||||
print(f" DIFY_API_URL = {os.environ.get('DIFY_API_URL', 'NOT SET')}")
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Dify API with different model parameter formats."""
|
||||
import json
|
||||
import httpx
|
||||
|
||||
BASE_URL = "http://yw-dify.dc.servyou-it.com/dify2openai"
|
||||
API_KEY = "app-JWI7u1LTn9XPVe95KL6dHzPx"
|
||||
url = f"{BASE_URL.rstrip('/')}/v1/chat/completions"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# Test different model parameter formats
|
||||
test_models = [
|
||||
"dify",
|
||||
"", # empty string
|
||||
"gpt-3.5-turbo",
|
||||
"deepseek-chat",
|
||||
"default",
|
||||
]
|
||||
|
||||
for model in test_models:
|
||||
body = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "user", "content": "我要申请VPN"},
|
||||
],
|
||||
"temperature": 0,
|
||||
}
|
||||
print(f"\n=== model={repr(model)} ===")
|
||||
try:
|
||||
resp = httpx.post(url, json=body, headers=headers, timeout=15.0)
|
||||
print(f" HTTP Status: {resp.status_code}")
|
||||
body_text = resp.text[:500]
|
||||
print(f" Response: {body_text}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {type(e).__name__}: {e}")
|
||||
|
||||
# Also test without model parameter at all
|
||||
print(f"\n=== No model parameter ===")
|
||||
body = {
|
||||
"messages": [
|
||||
{"role": "user", "content": "我要申请VPN"},
|
||||
],
|
||||
"temperature": 0,
|
||||
}
|
||||
try:
|
||||
resp = httpx.post(url, json=body, headers=headers, timeout=15.0)
|
||||
print(f" HTTP Status: {resp.status_code}")
|
||||
print(f" Response: {resp.text[:500]}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {type(e).__name__}: {e}")
|
||||
|
||||
# Test with the main AI chat API key to compare
|
||||
# First, check what API key the main Dify uses
|
||||
print(f"\n=== Check main Dify config ===")
|
||||
import os
|
||||
main_key = os.environ.get("DIFY_API_KEY", "")
|
||||
main_base = os.environ.get("DIFY_BASE_URL", "")
|
||||
print(f" DIFY_API_KEY: {main_key[:20]}..." if main_key else " DIFY_API_KEY: not set")
|
||||
print(f" DIFY_BASE_URL: {main_base}")
|
||||
|
||||
if main_key:
|
||||
main_url = f"{main_base.rstrip('/')}/v1/chat/completions" if main_base else url
|
||||
main_headers = {"Authorization": f"Bearer {main_key}", "Content-Type": "application/json"}
|
||||
body = {
|
||||
"model": "dify",
|
||||
"messages": [{"role": "user", "content": "你好"}],
|
||||
"temperature": 0,
|
||||
}
|
||||
print(f"\n=== Test main Dify API with model='dify' ===")
|
||||
try:
|
||||
resp = httpx.post(main_url, json=body, headers=main_headers, timeout=15.0)
|
||||
print(f" HTTP Status: {resp.status_code}")
|
||||
print(f" Response: {resp.text[:500]}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {type(e).__name__}: {e}")
|
||||
@@ -0,0 +1,29 @@
|
||||
import asyncio
|
||||
import sys
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
async def test():
|
||||
from app.services.graph_query_service import get_graph_query_service
|
||||
from app.services.neo4j_client import get_neo4j_client
|
||||
|
||||
neo4j_client = await get_neo4j_client()
|
||||
print(f'Neo4j client: {neo4j_client}')
|
||||
|
||||
if neo4j_client:
|
||||
graph_service = await get_graph_query_service(neo4j_client)
|
||||
print(f'Graph service: {graph_service}')
|
||||
|
||||
# 测试查询
|
||||
question = "打印机驱动安装"
|
||||
print(f'Querying: {question}')
|
||||
result = await graph_service.find_solution_by_question(question)
|
||||
print(f'Result: {result}')
|
||||
|
||||
if result:
|
||||
print(f"图谱命中! action_name={result.action_name}, solution={result.solution[:50]}")
|
||||
else:
|
||||
print("图谱未命中")
|
||||
else:
|
||||
print('Neo4j client is None')
|
||||
|
||||
asyncio.run(test())
|
||||
@@ -0,0 +1,2 @@
|
||||
UPDATE agents SET mfa_enabled = true, mfa_secret = 'JBSWY3DPEHPK3PXP' WHERE user_id = 'sxn';
|
||||
SELECT user_id, mfa_enabled, mfa_secret FROM agents WHERE user_id = 'sxn';
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""打包 P2/P3 部署文件"""
|
||||
import os
|
||||
import tarfile
|
||||
|
||||
# 待上传的后端文件
|
||||
backend_files = [
|
||||
'backend/app/utils/token_counter.py',
|
||||
'backend/app/services/automation/context_compressor.py',
|
||||
'backend/app/services/automation/snapshot_service.py',
|
||||
'backend/app/services/automation/correction_service.py',
|
||||
'backend/alembic/versions/049_add_p2_p3_tables.py',
|
||||
'backend/tests/test_p2_p3.py',
|
||||
# 修改的文件
|
||||
'backend/app/models/automation.py',
|
||||
'backend/app/constants.py',
|
||||
'backend/app/config.py',
|
||||
'backend/app/schemas/automation.py',
|
||||
'backend/app/api/automation.py',
|
||||
]
|
||||
|
||||
# 创建临时打包
|
||||
with tarfile.open('backend-p2-p3-deploy.tar.gz', 'w:gz') as tar:
|
||||
for f in backend_files:
|
||||
if os.path.exists(f):
|
||||
tar.add(f, arcname=f)
|
||||
print(f'Added: {f}')
|
||||
else:
|
||||
print(f'NOT FOUND: {f}')
|
||||
|
||||
print('Done!')
|
||||
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# deploy_inspection_to_jumpserver.sh — 巡检报告 HTML 发布到 jumpserver
|
||||
# =============================================================================
|
||||
# 流程:
|
||||
# 1. 扫描 docs/07-项目管理/巡检报告/ 下所有 HTML 报告
|
||||
# 2. 调用 jumpserver-V2 skill 的 v2_ops.py upload 上传到 /tmp/
|
||||
# 3. 用 v2_ops.py exec 按月份目录归档到 /opt/wecom-it-desk/docs-public/inspection/
|
||||
# 4. (可选)触发 nginx reload(仅当挂载点在容器外时)
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/deploy_inspection_to_jumpserver.sh # 默认发布全部
|
||||
# bash scripts/deploy_inspection_to_jumpserver.sh --dry-run # 仅 dry-run
|
||||
# bash scripts/deploy_inspection_to_jumpserver.sh --month 2026-08 # 仅发布指定月份
|
||||
#
|
||||
# 前置:
|
||||
# - jumpserver-V2 skill 已登录(v2_ops.py status 返回有效)
|
||||
# - jumpserver 上有静态目录 /opt/wecom-it-desk/docs-public/inspection/
|
||||
# - jumpserver 上有 nginx 路由 /docs/inspection/ → 该目录
|
||||
#
|
||||
# ⚠️ 本脚本默认不修改 nginx 配置(避免误改生产)。
|
||||
# =============================================================================
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
# 路径常量
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SRC_DIR="$PROJECT_ROOT/docs/07-项目管理/巡检报告"
|
||||
|
||||
# jumpserver-V2 skill
|
||||
V2_OPS="C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py"
|
||||
PYTHON_BIN="C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe"
|
||||
|
||||
# 服务器路径
|
||||
REMOTE_BASE="/opt/wecom-it-desk/docs-public/inspection"
|
||||
|
||||
# 参数
|
||||
DRY_RUN=false
|
||||
ONLY_MONTH=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--month) ONLY_MONTH="$2"; shift 2 ;;
|
||||
*) error "未知参数:$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# Step 1: 扫描 HTML 文件
|
||||
# ============================================================
|
||||
info "[1/4] 扫描 $SRC_DIR ..."
|
||||
[ -d "$SRC_DIR" ] || error "源目录不存在:$SRC_DIR"
|
||||
|
||||
# 收集所有 .html 文件(按月份分组)
|
||||
mapfile -t ALL_FILES < <(find "$SRC_DIR" -name "*.html" -type f 2>/dev/null | sort)
|
||||
[ ${#ALL_FILES[@]} -gt 0 ] || error "未找到任何 HTML 文件"
|
||||
|
||||
# 按月份过滤
|
||||
declare -A MONTH_FILES
|
||||
for f in "${ALL_FILES[@]}"; do
|
||||
# 提取 YYYY-MM(相对路径第一段)
|
||||
rel="${f#$SRC_DIR/}"
|
||||
month="$(echo "$rel" | cut -d'/' -f1)"
|
||||
if [ -n "$ONLY_MONTH" ] && [ "$month" != "$ONLY_MONTH" ]; then
|
||||
continue
|
||||
fi
|
||||
MONTH_FILES["$month"]+="$f"$'\n'
|
||||
done
|
||||
|
||||
[ ${#MONTH_FILES[@]} -gt 0 ] || error "指定月份 $ONLY_MONTH 下无 HTML 文件"
|
||||
|
||||
info "发现 ${#MONTH_FILES[@]} 个月份,共 $(printf '%s\n' "${ALL_FILES[@]}" | grep -c "\.html") 个文件"
|
||||
for month in "${!MONTH_FILES[@]}"; do
|
||||
count=$(echo "${MONTH_FILES[$month]}" | grep -c "\.html" || echo 0)
|
||||
info " 📅 $month: $count 个报告"
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# Step 2: jumpserver 会话状态
|
||||
# ============================================================
|
||||
info "[2/4] 检查 jumpserver 会话..."
|
||||
"$PYTHON_BIN" "$V2_OPS" status 2>&1 | grep -E "✅|❌" || error "jumpserver 会话无效,请先 login"
|
||||
|
||||
# ============================================================
|
||||
# Step 3: 上传 + 归档(按月份)
|
||||
# ============================================================
|
||||
info "[3/4] 上传文件到 jumpserver..."
|
||||
UPLOADED=0
|
||||
for month in "${!MONTH_FILES[@]}"; do
|
||||
while IFS= read -r local_file; do
|
||||
[ -z "$local_file" ] && continue
|
||||
basename="$(basename "$local_file")"
|
||||
ts="$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# 中文路径 → jumpserver 必须走 ASCII 临时文件名
|
||||
# 但 v2_ops.py upload 已自动处理(内部 ASCII staging)
|
||||
info " 📤 $month/$basename"
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
info " (dry-run 跳过)"
|
||||
else
|
||||
"$PYTHON_BIN" "$V2_OPS" upload "$local_file" "inspection_${month}_${ts}_${basename}" \
|
||||
|| error "上传失败:$basename"
|
||||
|
||||
# 远端 mkdir + mv 到正确月份目录
|
||||
remote_cmd="mkdir -p ${REMOTE_BASE}/${month} && mv /tmp/inspection_${month}_${ts}_${basename} ${REMOTE_BASE}/${month}/${basename} && chmod 644 ${REMOTE_BASE}/${month}/${basename}"
|
||||
"$PYTHON_BIN" "$V2_OPS" exec "$remote_cmd" \
|
||||
|| error "归档失败:$basename"
|
||||
|
||||
UPLOADED=$((UPLOADED + 1))
|
||||
fi
|
||||
done <<< "${MONTH_FILES[$month]}"
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# Step 4: 完成
|
||||
# ============================================================
|
||||
info "[4/4] 部署完成"
|
||||
echo ""
|
||||
ok "========== 发布总结 =========="
|
||||
info "月份数: ${#MONTH_FILES[@]}"
|
||||
info "上传文件: $UPLOADED"
|
||||
echo ""
|
||||
info "📍 访问 URL(前提:nginx 已加 /docs/inspection/ 路由)"
|
||||
info " https://itsupport.servyou.com.cn/docs/inspection/2026-08/2026-08-05-早班.html"
|
||||
echo ""
|
||||
info "🔁 重新发布:直接重跑本脚本即可(脚本会自动覆盖)"
|
||||
echo ""
|
||||
info "💡 巡检报告 HTML 由早班巡检自动化任务生成 + 自动调用本脚本发布"
|
||||
info " (详见 .workbuddy/automations/automation-1782986180887/memory.md 标准步骤清单)"
|
||||
@@ -0,0 +1,44 @@
|
||||
"""直接在 Redis 中生成 admin token 用于测试"""
|
||||
import asyncio
|
||||
import json
|
||||
import secrets
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
|
||||
|
||||
async def main():
|
||||
import redis.asyncio as redis_async
|
||||
|
||||
# 连接到 Redis 容器(使用生产密码)
|
||||
redis_url = "redis://:WKzl7jgKTkeWxFWGIBXfzokm59Gvfr76@localhost:6379/0"
|
||||
redis_client = redis_async.from_url(redis_url)
|
||||
|
||||
# 构造 admin 用户的 token
|
||||
token = secrets.token_urlsafe(32)
|
||||
user_info = {
|
||||
"employee_id": "admin_test_001",
|
||||
"username": "admin",
|
||||
"name": "测试管理员",
|
||||
"role": "admin",
|
||||
"department": "IT支持组",
|
||||
"login_source": "test",
|
||||
"login_method": "test_script",
|
||||
"last_active": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
# 写入 Redis
|
||||
token_key = f"user:token:{token}"
|
||||
await redis_client.setex(token_key, 8 * 3600, json.dumps(user_info, ensure_ascii=False))
|
||||
|
||||
print(f"TOKEN={token}")
|
||||
|
||||
# 验证
|
||||
val = await redis_client.get(token_key)
|
||||
print(f"VERIFIED={val is not None}")
|
||||
|
||||
await redis_client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# 验证 priority 修复 - 端到端测试 (v5 - 一次性脚本)
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 1. 在 backend 容器内生成 token 并直接调用 API
|
||||
docker exec wecom_it_backend python3 - <<'PYEOF'
|
||||
import redis
|
||||
import json
|
||||
import secrets
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
|
||||
REDIS_PWD = "WKzl7jgKTkeWxFWGIBXfzokm59Gvfr76"
|
||||
r = redis.Redis(host="redis", port=6379, password=REDIS_PWD, db=0)
|
||||
|
||||
# 写 token
|
||||
token = secrets.token_urlsafe(32)
|
||||
user_info = {
|
||||
"employee_id": "sxn",
|
||||
"username": "admin",
|
||||
"name": "宋献",
|
||||
"role": "admin",
|
||||
"department": "IT支持组",
|
||||
"login_source": "test",
|
||||
"login_method": "test_script",
|
||||
"last_active": "2026-07-28T02:00:00",
|
||||
}
|
||||
r.setex(f"user:token:{token}", 8 * 3600, json.dumps(user_info, ensure_ascii=False))
|
||||
print(f"TOKEN: {token}", flush=True)
|
||||
|
||||
# 直接通过容器内 localhost 调 API
|
||||
def call_api(url, params):
|
||||
full_url = f"http://localhost:8000{url}?{urllib.parse.urlencode(params)}"
|
||||
req = urllib.request.Request(full_url, headers={"Authorization": f"Bearer {token}"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
return data
|
||||
except urllib.error.HTTPError as e:
|
||||
return {"error": str(e), "code": e.code}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
# 测试 1: rule_type=routing_target
|
||||
print("\n===== Test 1: rule_type=routing_target =====", flush=True)
|
||||
r1 = call_api("/api/admin/quick-rules", {"rule_type": "routing_target", "page": 1, "size": 20})
|
||||
print(f"code: {r1.get('code')}", flush=True)
|
||||
print(f"message: {r1.get('message')}", flush=True)
|
||||
if r1.get("data"):
|
||||
items = r1["data"].get("items", [])
|
||||
print(f"total: {r1['data'].get('total')}", flush=True)
|
||||
print(f"items count: {len(items)}", flush=True)
|
||||
if items:
|
||||
print(f"first item: {json.dumps(items[0], ensure_ascii=False, indent=2)}", flush=True)
|
||||
|
||||
# 测试 2: rule_type=routing_target&category=行政
|
||||
print("\n===== Test 2: rule_type=routing_target&category=行政 =====", flush=True)
|
||||
r2 = call_api("/api/admin/quick-rules", {"rule_type": "routing_target", "category": "行政", "page": 1, "size": 20})
|
||||
print(f"code: {r2.get('code')}", flush=True)
|
||||
print(f"message: {r2.get('message')}", flush=True)
|
||||
if r2.get("data"):
|
||||
items = r2["data"].get("items", [])
|
||||
print(f"total: {r2['data'].get('total')}", flush=True)
|
||||
print(f"items count: {len(items)}", flush=True)
|
||||
if items:
|
||||
print(f"first item: {json.dumps(items[0], ensure_ascii=False, indent=2)}", flush=True)
|
||||
|
||||
# 测试 3: rule_type=routing_target&category=人力资源
|
||||
print("\n===== Test 3: rule_type=routing_target&category=人力资源 =====", flush=True)
|
||||
r3 = call_api("/api/admin/quick-rules", {"rule_type": "routing_target", "category": "人力资源", "page": 1, "size": 20})
|
||||
print(f"code: {r3.get('code')}", flush=True)
|
||||
print(f"message: {r3.get('message')}", flush=True)
|
||||
if r3.get("data"):
|
||||
items = r3["data"].get("items", [])
|
||||
print(f"total: {r3['data'].get('total')}", flush=True)
|
||||
print(f"items count: {len(items)}", flush=True)
|
||||
|
||||
# 测试 4: rule_type=routing_target&category=IT服务
|
||||
print("\n===== Test 4: rule_type=routing_target&category=IT服务 =====", flush=True)
|
||||
r4 = call_api("/api/admin/quick-rules", {"rule_type": "routing_target", "category": "IT服务", "page": 1, "size": 20})
|
||||
print(f"code: {r4.get('code')}", flush=True)
|
||||
print(f"message: {r4.get('message')}", flush=True)
|
||||
if r4.get("data"):
|
||||
items = r4["data"].get("items", [])
|
||||
print(f"total: {r4['data'].get('total')}", flush=True)
|
||||
if items:
|
||||
print(f"item: {json.dumps(items[0], ensure_ascii=False, indent=2)}", flush=True)
|
||||
|
||||
# 清理
|
||||
r.delete(f"user:token:{token}")
|
||||
print("\n===== Cleanup OK =====", flush=True)
|
||||
PYEOF
|
||||
|
||||
echo ""
|
||||
echo "===== 验证完成 ====="
|
||||
Reference in New Issue
Block a user