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
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
|
|
}
|
|
|