Files
baiyue-firstleg/scripts/export-center-test-env-acceptance.ps1
T

172 lines
7.2 KiB
PowerShell
Raw Normal View History

2026-08-04 09:05:28 +08:00
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidatePattern('^https?://')]
[string]$GatewayBaseUrl,
[string[]]$AccessTokens = @(),
[ValidateRange(1, 4)]
[int]$TaskCount = 1,
[string[]]$TaskIds = @(),
[ValidateRange(1, 7200)]
[int]$TimeoutSeconds = 3600,
[ValidateRange(1, 60)]
[int]$PollSeconds = 2,
[ValidateRange(0, 200000)]
[int]$MinimumRows = 0,
[string]$Keyword = '',
[ValidateSet('', 'PROFILE_REQUIRED', 'PENDING', 'ACTIVE', 'DISABLED', 'REJECTED')]
[string]$Status = '',
[string]$OutputDirectory = '',
[switch]$CreateOnly,
[switch]$SkipDownload
)
$ErrorActionPreference = 'Stop'
$baseUrl = $GatewayBaseUrl.TrimEnd('/')
$apiPath = "$baseUrl/api/platform/v1/files/export-tasks"
if ($AccessTokens.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($env:ADMIN_ACCESS_TOKEN)) {
$AccessTokens = @($env:ADMIN_ACCESS_TOKEN)
}
if ($AccessTokens.Count -eq 0) {
throw '至少提供一个 -AccessTokens,或通过 ADMIN_ACCESS_TOKEN 环境变量注入。'
}
if ($TaskIds.Count -eq 0 -and $AccessTokens.Count -lt $TaskCount) {
throw "创建 $TaskCount 个并发任务需要至少 $TaskCount 个不同用户的 Access Token,避免 60 秒重复提交合并。"
}
if ($TaskIds.Count -gt 0 -and $AccessTokens.Count -lt $TaskIds.Count) {
throw '恢复验收时每个 TaskId 都需要对应申请人的 Access Token。'
}
function Get-Headers([string]$Token) {
return @{ Authorization = "Bearer $Token" }
}
function Invoke-ApiJson([string]$Method, [string]$Uri, [string]$Token, [object]$Body = $null) {
$arguments = @{
Method = $Method
Uri = $Uri
Headers = Get-Headers $Token
TimeoutSec = 60
}
if ($null -ne $Body) {
$arguments.ContentType = 'application/json'
$arguments.Body = $Body | ConvertTo-Json -Depth 10 -Compress
}
$response = Invoke-RestMethod @arguments
if ($null -eq $response -or [string]$response.code -ne '200') {
$requestId = if ($response.requestId) { " requestId=$($response.requestId)" } else { '' }
$traceId = if ($response.traceId) { " traceId=$($response.traceId)" } else { '' }
throw "API 失败 method=$Method uri=$Uri code=$($response.code) message=$($response.message)$requestId$traceId"
}
return $response.data
}
function New-ExportTask([string]$Token, [int]$Index) {
$parameters = [ordered]@{}
if (-not [string]::IsNullOrWhiteSpace($Keyword)) { $parameters.keyword = $Keyword.Trim() }
if (-not [string]::IsNullOrWhiteSpace($Status)) { $parameters.status = $Status }
$payload = @{
exportType = 'CUSTOMER_LIST'
format = 'XLSX'
taskName = "客户列表验收-$Index"
parameters = $parameters
}
return Invoke-ApiJson POST $apiPath $Token $payload
}
function Wait-ExportTask([string]$TaskId, [string]$Token) {
$deadline = [DateTimeOffset]::UtcNow.AddSeconds($TimeoutSeconds)
$lastStatus = ''
while ([DateTimeOffset]::UtcNow -lt $deadline) {
$task = Invoke-ApiJson GET "$apiPath/$TaskId" $Token
if ($task.status -ne $lastStatus) {
Write-Host "taskId=$TaskId status=$($task.status) processed=$($task.processedRows) total=$($task.totalRows)"
$lastStatus = $task.status
}
if ($task.status -in @('SUCCEEDED', 'FAILED', 'EXPIRED')) { return $task }
Start-Sleep -Seconds $PollSeconds
}
throw "等待导出任务超时 taskId=$TaskId timeoutSeconds=$TimeoutSeconds"
}
function Save-And-VerifyXlsx([object]$Task, [string]$Token, [string]$Directory) {
$target = Join-Path $Directory "$($Task.id).xlsx"
$response = Invoke-WebRequest -UseBasicParsing -Uri "$apiPath/$($Task.id)/download" `
-Headers (Get-Headers $Token) -TimeoutSec 600 -OutFile $target -PassThru
$contentType = [string]$response.Headers['Content-Type']
if ($contentType.ToLowerInvariant().Contains('json')) {
$payload = Get-Content -Raw -LiteralPath $target | ConvertFrom-Json
throw "下载返回业务错误 taskId=$($Task.id) code=$($payload.code) message=$($payload.message) requestId=$($payload.requestId) traceId=$($payload.traceId)"
}
$stream = [IO.File]::OpenRead($target)
try {
$signature = New-Object byte[] 4
if ($stream.Read($signature, 0, 4) -ne 4 -or $signature[0] -ne 0x50 -or $signature[1] -ne 0x4B `
-or $signature[2] -ne 0x03 -or $signature[3] -ne 0x04) {
throw "下载结果不是有效 XLSX/ZIP taskId=$($Task.id) contentType=$contentType"
}
} finally {
$stream.Dispose()
}
return [pscustomobject]@{ Path = $target; Bytes = (Get-Item -LiteralPath $target).Length; ContentType = $contentType }
}
$createdTasks = [System.Collections.Generic.List[object]]::new()
if ($TaskIds.Count -eq 0) {
for ($index = 0; $index -lt $TaskCount; $index++) {
$task = New-ExportTask $AccessTokens[$index] ($index + 1)
$createdTasks.Add([pscustomobject]@{ Task = $task; Token = $AccessTokens[$index] })
Write-Host "created taskId=$($task.id) owner=$($task.requestedBy) status=$($task.status)"
}
$uniqueIds = @($createdTasks | ForEach-Object { [string]$_.Task.id } | Sort-Object -Unique)
if ($uniqueIds.Count -ne $createdTasks.Count) {
throw '多个创建请求被重复提交规则合并;容量验收需使用不同申请用户,或等待 60 秒后重试。'
}
} else {
for ($index = 0; $index -lt $TaskIds.Count; $index++) {
$createdTasks.Add([pscustomobject]@{ Task = [pscustomobject]@{ id = $TaskIds[$index] }; Token = $AccessTokens[$index] })
}
}
if ($CreateOnly) {
$createdTasks | ForEach-Object { [pscustomobject]@{ taskId = [string]$_.Task.id } } | ConvertTo-Json
exit 0
}
$temporaryOutput = [string]::IsNullOrWhiteSpace($OutputDirectory)
if ($temporaryOutput) {
$OutputDirectory = Join-Path ([IO.Path]::GetTempPath()) "baiyue-export-acceptance-$([Guid]::NewGuid())"
}
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
$results = [System.Collections.Generic.List[object]]::new()
try {
foreach ($entry in $createdTasks) {
$task = Wait-ExportTask ([string]$entry.Task.id) $entry.Token
if ($task.status -ne 'SUCCEEDED') {
throw "导出任务未成功 taskId=$($task.id) status=$($task.status) failure=$($task.failureMessage)"
}
if ([long]$task.processedRows -lt $MinimumRows) {
throw "导出行数未达到门槛 taskId=$($task.id) processed=$($task.processedRows) minimum=$MinimumRows"
}
$artifact = $null
if (-not $SkipDownload) { $artifact = Save-And-VerifyXlsx $task $entry.Token $OutputDirectory }
$results.Add([pscustomobject]@{
taskId = [string]$task.id
status = $task.status
processedRows = [long]$task.processedRows
sizeBytes = [long]$task.sizeBytes
downloadedBytes = if ($artifact) { [long]$artifact.Bytes } else { 0 }
file = if ($artifact) { $artifact.Path } else { $null }
})
}
$results | ConvertTo-Json -Depth 5
Write-Host "EXPORT_CENTER_ACCEPTANCE_PASSED=$($results.Count)"
} finally {
if ($temporaryOutput -and (Test-Path -LiteralPath $OutputDirectory)) {
Remove-Item -LiteralPath $OutputDirectory -Recurse -Force
}
}