[CmdletBinding()] param( [string]$EnvFile = '.env', [switch]$SkipBuild, [switch]$CheckOnly ) $ErrorActionPreference = 'Stop' # 仅载入此脚本允许的配置名,不把 .env 当成可执行脚本。 $examplePath = Join-Path $PSScriptRoot '.env.example' $allowedNames = @{} foreach ($line in Get-Content -LiteralPath $examplePath -Encoding UTF8) { if ($line -match '^([A-Z][A-Z0-9_]*)=') { $allowedNames[$Matches[1]] = $true } } $envPath = if ([IO.Path]::IsPathRooted($EnvFile)) { $EnvFile } else { Join-Path $PSScriptRoot $EnvFile } if (-not (Test-Path -LiteralPath $envPath -PathType Leaf)) { throw '缺少本地 .env;请从 .env.example 复制到 .env 并填写,不要覆盖现有文件。' } $settings = @{} foreach ($line in Get-Content -LiteralPath $envPath -Encoding UTF8) { if ([string]::IsNullOrWhiteSpace($line) -or $line.TrimStart().StartsWith('#')) { continue } if ($line -notmatch '^([A-Z][A-Z0-9_]*)=(.*)$') { throw '环境文件格式错误:请使用 KEY=value,不使用 export 或引号。' } $settingName = $Matches[1] $settingValue = $Matches[2] if (-not $allowedNames.ContainsKey($settingName)) { throw "环境文件包含未知配置名:$settingName" } if ($settings.ContainsKey($settingName)) { throw "环境文件包含重复配置名:$settingName" } if ($settingValue -match '^["'']' -or $settingValue -match '\s#' -or $settingValue.Contains('$')) { throw "配置 $settingName 必须使用未加引号的单行原文,不能包含变量引用或行尾注释。" } $settings[$settingName] = $settingValue } foreach ($requiredName in @('DB_PASSWORD','MYSQL_ROOT_PASSWORD','REDIS_PASSWORD','RABBITMQ_PASSWORD','JWT_SECRET')) { if (-not $settings.ContainsKey($requiredName) -or [string]::IsNullOrWhiteSpace($settings[$requiredName]) -or $settings[$requiredName].StartsWith('replace-me-')) { throw "请为 $requiredName 设置自己的非示例值。" } } if ([Text.Encoding]::UTF8.GetByteCount($settings['JWT_SECRET']) -lt 32) { throw 'JWT_SECRET 至少需要 32 字节。' } if ($CheckOnly) { Write-Host '本地配置格式与必填项检查通过(未连接任何服务、未构建或启动)。' return } $savedEnvironment = @{} foreach ($settingName in $allowedNames.Keys) { $savedEnvironment[$settingName] = [Environment]::GetEnvironmentVariable($settingName, 'Process') } Push-Location $PSScriptRoot try { # 清除继承的同名云凭据,只接受该 .env 中填写的值;空项回落到模板的占位值。 foreach ($settingName in $allowedNames.Keys) { $settingValue = if ($settings.ContainsKey($settingName) -and $settings[$settingName] -ne '') { $settings[$settingName] } else { $null } if ($null -eq $settingValue) { Remove-Item -LiteralPath "Env:$settingName" -ErrorAction SilentlyContinue } else { [Environment]::SetEnvironmentVariable($settingName, $settingValue, 'Process') } } if (-not $SkipBuild) { & mvn -Pportable clean verify if ($LASTEXITCODE -ne 0) { throw '构建或测试失败,未启动后端。' } } $jarPath = Join-Path $PSScriptRoot 'target/portable/campus-server-1.0.0.jar' if (-not (Test-Path -LiteralPath $jarPath -PathType Leaf)) { throw '缺少 portable JAR,请先执行 mvn -Pportable clean verify。' } $configPath = (Join-Path $PSScriptRoot 'src/main/resources/application.example.yml').Replace('\', '/') & java -Duser.timezone=GMT+08 -jar $jarPath "--spring.config.location=file:$configPath" if ($LASTEXITCODE -ne 0) { throw '后端退出异常,请检查启动日志。' } } finally { foreach ($settingName in $allowedNames.Keys) { if ($null -eq $savedEnvironment[$settingName]) { Remove-Item -LiteralPath "Env:$settingName" -ErrorAction SilentlyContinue } else { [Environment]::SetEnvironmentVariable($settingName, $savedEnvironment[$settingName], 'Process') } } Pop-Location }