# ============================================================= # RVW V4.0 多租户 API 测试脚本(PowerShell) # 测试目标:验证 x-tenant-id 多租户隔离全链路 # 运行前提:仅需启动后端(npm run dev),无需启动前端 # 运行方式:在项目根目录执行:.\backend\scripts\test-data\02_test_rvw_v4_api.ps1 # ============================================================= $BASE_URL = "http://localhost:3001" $TENANT_SLUG = "jtim" $TEST_USER_PHONE = "13900139001" $TEST_USER_PASSWORD = "Test@1234" # 输出工具函数 function Print-Step($step, $msg) { Write-Host "`n[$step] $msg" -ForegroundColor Cyan } function Print-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green } function Print-FAIL($msg) { Write-Host " [FAIL] $msg" -ForegroundColor Red } function Print-INFO($msg) { Write-Host " [INFO] $msg" -ForegroundColor Yellow } # ===================================================================== # STEP 0:检查后端是否已启动 # ===================================================================== Print-Step "0" "检查后端服务可用性 $BASE_URL/health" try { $health = Invoke-RestMethod -Uri "$BASE_URL/health" -Method GET -ErrorAction Stop Print-OK "后端健康检查通过: $($health | ConvertTo-Json -Compress)" } catch { Print-FAIL "后端未启动或不可达!请先运行:cd backend && npm run dev" Print-INFO "错误:$($_.Exception.Message)" exit 1 } # ===================================================================== # STEP 1:登录获取 JWT Token # ===================================================================== Print-Step "1" "登录测试账号 (phone: $TEST_USER_PHONE)" $loginBody = @{ phone = $TEST_USER_PHONE password = $TEST_USER_PASSWORD } | ConvertTo-Json try { $loginResp = Invoke-RestMethod -Uri "$BASE_URL/api/v1/auth/login/password" ` -Method POST ` -ContentType "application/json" ` -Body $loginBody ` -ErrorAction Stop # 响应结构:data.tokens.accessToken if ($loginResp.success -and $loginResp.data.tokens.accessToken) { $TOKEN = $loginResp.data.tokens.accessToken $USER_ID = $loginResp.data.user.id Print-OK "登录成功!userId=$USER_ID tenantCode=$($loginResp.data.user.tenantCode)" Print-INFO "Token (前50字符): $($TOKEN.Substring(0, [Math]::Min(50, $TOKEN.Length)))..." } else { Print-FAIL "登录响应异常: $($loginResp | ConvertTo-Json)" exit 1 } } catch { Print-FAIL "登录失败:$($_.Exception.Message)" $errorBody = $_.ErrorDetails.Message if ($errorBody) { Print-INFO "响应体:$errorBody" } exit 1 } # ===================================================================== # STEP 2:不带 x-tenant-id,获取任务列表(主站单租户模式,应正常返回) # ===================================================================== Print-Step "2" "不带 x-tenant-id Header - 获取任务列表(主站模式)" try { $listNoTenant = Invoke-RestMethod -Uri "$BASE_URL/api/v1/rvw/tasks" ` -Method GET ` -Headers @{ "Authorization" = "Bearer $TOKEN" } ` -ErrorAction Stop $total1 = if ($listNoTenant.data.total) { $listNoTenant.data.total } else { 0 } Print-OK "主站模式任务列表获取成功,共 $total1 条" } catch { $status = $_.Exception.Response.StatusCode.value__ Print-FAIL "请求失败(HTTP $status):$($_.ErrorDetails.Message)" } # ===================================================================== # STEP 3:携带 x-tenant-id: jtim,获取任务列表(期刊租户模式) # ===================================================================== Print-Step "3" "携带 x-tenant-id: $TENANT_SLUG - 获取任务列表(期刊租户模式)" try { $listWithTenant = Invoke-RestMethod -Uri "$BASE_URL/api/v1/rvw/tasks" ` -Method GET ` -Headers @{ "Authorization" = "Bearer $TOKEN" "x-tenant-id" = $TENANT_SLUG } ` -ErrorAction Stop $total2 = if ($listWithTenant.data.total) { $listWithTenant.data.total } else { 0 } Print-OK "期刊租户模式任务列表获取成功,共 $total2 条" } catch { $status = $_.Exception.Response.StatusCode.value__ $body = $_.ErrorDetails.Message Print-FAIL "请求失败(HTTP $status):$body" if ($status -eq 401) { Print-INFO "401 说明 rvwTenantMiddleware 拦截成功(用户不在该租户,或中间件未注册)" } if ($status -eq 403) { Print-INFO "403 说明用户无 RVW 模块权限" } } # ===================================================================== # STEP 4:携带不存在的租户 slug,应返回 404 或 401 # ===================================================================== Print-Step "4" "携带无效 x-tenant-id: nonexistent-journal - 应被拦截" try { $listBadTenant = Invoke-RestMethod -Uri "$BASE_URL/api/v1/rvw/tasks" ` -Method GET ` -Headers @{ "Authorization" = "Bearer $TOKEN" "x-tenant-id" = "nonexistent-journal" } ` -ErrorAction Stop Print-FAIL "期望被拦截但未拦截!响应:$($listBadTenant | ConvertTo-Json -Compress)" } catch { $status = $_.Exception.Response.StatusCode.value__ if ($status -eq 404 -or $status -eq 401 -or $status -eq 403) { Print-OK "已正确拦截无效租户!HTTP $status(符合预期)" } else { Print-FAIL "拦截状态码异常:HTTP $status,期望 401/403/404" } } # ===================================================================== # STEP 5:用另一个用户 Token 访问 jtim 租户(非成员),应被拦截 # ===================================================================== Print-Step "5" "用其他用户(非 jtim 成员)访问 jtim 租户 - 应返回 401/403" Print-INFO "此步骤需手动提供另一个用户的 Token,跳过自动化测试" # ===================================================================== # STEP 6:查询 DB 验证本地数据一致性 # ===================================================================== Print-Step "6" "数据库验证 - 查询 jtim 租户配置" $sqlCheck = "SELECT t.code, rc.data_forensics_level, tm.role FROM platform_schema.tenants t JOIN platform_schema.tenant_rvw_configs rc ON rc.tenant_id = t.id JOIN platform_schema.tenant_members tm ON tm.tenant_id = t.id WHERE t.code = 'jtim';" $dbResult = docker exec -i ai-clinical-postgres psql -U postgres -d ai_clinical_research -c $sqlCheck 2>&1 Print-INFO "DB 查询结果:`n$dbResult" # ===================================================================== # 测试总结 # ===================================================================== Write-Host "`n" -NoNewline Write-Host "======================================================" -ForegroundColor Magenta Write-Host " RVW V4.0 多租户 API 测试完成" -ForegroundColor Magenta Write-Host "======================================================" -ForegroundColor Magenta Write-Host "" Write-Host "测试账号信息(用于前端手动验证):" -ForegroundColor White Write-Host " 手机号:$TEST_USER_PHONE" Write-Host " 密码: $TEST_USER_PASSWORD" Write-Host " 租户: $TENANT_SLUG → http://localhost:5173/$TENANT_SLUG" Write-Host "" Write-Host "全流程前端测试步骤:" -ForegroundColor White Write-Host " 1. 启动后端:cd backend && npm run dev" Write-Host " 2. 启动前端:cd frontend-v2 && npm run dev" Write-Host " 3. 浏览器访问:http://localhost:5173/$TENANT_SLUG" Write-Host " 4. 期望重定向到:http://localhost:5173/t/$TENANT_SLUG/login" Write-Host " 5. 用上方账号登录,登录后应进入期刊审稿页面" Write-Host ""