青核后端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

122 lines
6.5 KiB

[CmdletBinding()]
param()
# 仅做离线检查,不启动 Java、Maven 或 Docker,不连接数据库和云服务。
$ErrorActionPreference = 'Stop'
$launcher = Join-Path $PSScriptRoot 'Start-Local.ps1'
$tokens = $null
$parseErrors = $null
[System.Management.Automation.Language.Parser]::ParseFile($launcher, [ref]$tokens, [ref]$parseErrors) | Out-Null
if ($parseErrors.Count) { throw 'Start-Local.ps1 语法检查失败。' }
$fixtureDirectory = Join-Path ([IO.Path]::GetTempPath()) ('qinghe-deployment-check-' + [Guid]::NewGuid().ToString('N'))
[void](New-Item -ItemType Directory -Path $fixtureDirectory)
$fixtureFile = Join-Path $fixtureDirectory '.env'
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
$baseFixture = @'
DB_PASSWORD=test-db-value
MYSQL_ROOT_PASSWORD=test-root-value
REDIS_PASSWORD=test-redis-value
RABBITMQ_PASSWORD=test-rabbit-value
JWT_SECRET=test-only-secret-with-at-least-32-bytes-000
'@
$passedCases = 0
function Assert-ConfigurationCase {
param([string]$Name, [string]$Content, [bool]$ShouldPass, [string]$ExpectedMessage)
[IO.File]::WriteAllText($fixtureFile, $Content, $utf8Bom)
$didPass = $true
$failureMessage = ''
try { & $launcher -EnvFile $fixtureFile -CheckOnly } catch {
$didPass = $false
$failureMessage = $_.Exception.Message
}
if ($didPass -ne $ShouldPass) { throw "用例失败:$Name,结果不符合预期。" }
if (-not $ShouldPass -and -not $failureMessage.Contains($ExpectedMessage)) {
throw "用例失败:$Name,未得到预期的配置错误提示。"
}
Write-Host "PASS: $Name"
}
try {
Assert-ConfigurationCase '合法配置' $baseFixture $true ''
$passedCases++
Assert-ConfigurationCase '拒绝短 JWT secret' ($baseFixture.Replace('test-only-secret-with-at-least-32-bytes-000', 'short')) $false '至少需要 32 字节'
$passedCases++
Assert-ConfigurationCase '拒绝未修改的示例密码' ($baseFixture.Replace('test-db-value', 'replace-me-db-password')) $false 'DB_PASSWORD'
$passedCases++
Assert-ConfigurationCase '拒绝缺失密码' ($baseFixture.Replace('REDIS_PASSWORD=test-redis-value', 'REDIS_PASSWORD=')) $false 'REDIS_PASSWORD'
$passedCases++
Assert-ConfigurationCase '拒绝重复变量' ($baseFixture + "`nDB_PASSWORD=second-value") $false '重复配置名'
$passedCases++
Assert-ConfigurationCase '拒绝未知变量' ($baseFixture + "`nPATH=unknown") $false '未知配置名'
$passedCases++
Assert-ConfigurationCase '拒绝脚本表达式' ($baseFixture + "`nOSS_DOMAIN=" + '$(Get-Process)') $false '不能包含变量引用'
$passedCases++
Assert-ConfigurationCase '拒绝带引号的值' ($baseFixture + "`nOSS_DOMAIN=" + '"localhost"') $false '未加引号'
$passedCases++
$examplePath = Join-Path $PSScriptRoot '.env.example'
$exampleContent = [IO.File]::ReadAllText($examplePath)
Assert-ConfigurationCase '原始示例必须先填写' $exampleContent $false 'DB_PASSWORD'
$passedCases++
# 用局部 Java 替身检查启动参数及环境恢复,不启动 JVM 或访问任何服务。
if (Test-Path -LiteralPath (Join-Path $PSScriptRoot 'target/portable/campus-server-1.0.0.jar')) {
$javaObservation = @{ Arguments = @(); DatabasePassword = $null }
function java {
$javaObservation.Arguments = @($args)
$javaObservation.DatabasePassword = [Environment]::GetEnvironmentVariable('DB_PASSWORD', 'Process')
$global:LASTEXITCODE = 0
}
$previousPassword = [Environment]::GetEnvironmentVariable('DB_PASSWORD', 'Process')
try {
[Environment]::SetEnvironmentVariable('DB_PASSWORD', 'outer-test-sentinel', 'Process')
[IO.File]::WriteAllText($fixtureFile, $baseFixture, $utf8Bom)
& $launcher -EnvFile $fixtureFile -SkipBuild
if ($javaObservation.DatabasePassword -ne 'test-db-value') { throw '启动时未正确注入本地变量。' }
$locationArguments = @($javaObservation.Arguments | Where-Object { $_ -like '--spring.config.location=file:*application.example.yml' })
if ($locationArguments.Count -ne 1) { throw '未显式指定独立模板。' }
if ([Environment]::GetEnvironmentVariable('DB_PASSWORD', 'Process') -ne 'outer-test-sentinel') {
throw '退出后没有恢复原进程环境变量。'
}
Write-Host 'PASS: 独立配置参数、环境注入及退出恢复(Java 替身,无外部连接)'
$passedCases++
} finally {
if ($null -eq $previousPassword) {
Remove-Item -LiteralPath Env:DB_PASSWORD -ErrorAction SilentlyContinue
} else {
[Environment]::SetEnvironmentVariable('DB_PASSWORD', $previousPassword, 'Process')
}
Remove-Item -LiteralPath Function:\java
}
}
$jarPath = Join-Path $PSScriptRoot 'target/portable/campus-server-1.0.0.jar'
if (Test-Path -LiteralPath $jarPath -PathType Leaf) {
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [IO.Compression.ZipFile]::OpenRead($jarPath)
try {
$privateEntries = @($archive.Entries | Where-Object {
$_.FullName -match '^BOOT-INF/classes/(.*/)?(application[^/]*\.(yml|yaml|properties)|\.env[^/]*)$'
})
if ($privateEntries.Count) { throw '部署 JAR 混入应用配置或 .env 文件,禁止分发。' }
if (-not $archive.GetEntry('BOOT-INF/classes/com/campus/CampusApplication.class')) {
throw '部署 JAR 缺少应用主类。'
}
} finally { $archive.Dispose() }
Write-Host 'PASS: 部署 JAR 不含约定的私有应用配置,且存在主类'
$passedCases++
} else {
Write-Host 'SKIP: 未找到 portable JAR,打包内容尚未检查。'
}
Write-Host "离线检查通过:$passedCases 项。未连接外部服务。"
} finally {
# 只清理本脚本刚创建、路径已核对的临时夹;不触及用户环境文件。
$resolvedFixture = [IO.Path]::GetFullPath($fixtureDirectory)
$resolvedTemp = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
if (-not $resolvedFixture.StartsWith($resolvedTemp, [StringComparison]::OrdinalIgnoreCase) -or
-not ([IO.Path]::GetFileName($resolvedFixture)).StartsWith('qinghe-deployment-check-')) {
throw '临时检查目录超出预期路径,未清理。'
}
Remove-Item -LiteralPath $resolvedFixture -Recurse -Force
}