[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$BackupFileName, [Parameter(Mandatory = $true)] [string]$DatabaseName, [Parameter(Mandatory = $true)] [string]$DataLogicalName, [Parameter(Mandatory = $true)] [string]$LogLogicalName, [string]$ContainerName = "ys-tplus-sqlserver-dev", [string]$BackupRoot = "D:\work\app\tplus-sqlserver\backup", [string]$DataRoot = "D:\work\app\tplus-sqlserver\data", [string]$LogRoot = "D:\work\app\tplus-sqlserver\log", [Parameter(Mandatory = $true)] [SecureString]$SaPassword ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" . (Join-Path $PSScriptRoot "container_sql.ps1") if ($DatabaseName -notmatch '^[A-Za-z_][A-Za-z0-9_]{0,127}$') { throw "恢复数据库名只能使用字母、数字和下划线。" } if ([IO.Path]::GetFileName($BackupFileName) -ne $BackupFileName) { throw "备份文件名不能包含目录。" } foreach ($path in @($BackupRoot, $DataRoot, $LogRoot)) { if (-not [IO.Path]::GetFullPath($path).StartsWith("D:\", [StringComparison]::OrdinalIgnoreCase)) { throw "恢复目录必须位于 D 盘。" } } $mounts = docker inspect $ContainerName --format '{{json .Mounts}}' | ConvertFrom-Json if ($LASTEXITCODE -ne 0) { throw "无法读取 SQL Server 容器挂载。" } foreach ($pair in @( @{ Target="/var/opt/mssql/data"; Source=$DataRoot }, @{ Target="/var/opt/mssql/log"; Source=$LogRoot }, @{ Target="/var/opt/mssql/backup"; Source=$BackupRoot } )) { $mount = @($mounts | Where-Object { $_.Destination -eq $pair.Target }) if ($mount.Count -ne 1 -or $mount[0].Type -ne "bind") { throw "目标目录未明确绑定到宿主机 D 盘:$($pair.Target)" } if ([IO.Path]::GetFullPath($mount[0].Source) -ine [IO.Path]::GetFullPath($pair.Source)) { throw "实际挂载与恢复目录配置不一致:$($pair.Target)" } } foreach ($path in @($BackupRoot, $DataRoot, $LogRoot)) { if (-not (Test-Path -LiteralPath $path)) { New-Item -ItemType Directory -Path $path -Force | Out-Null } } $backupPath = Join-Path $BackupRoot $BackupFileName if (-not (Test-Path -LiteralPath $backupPath)) { throw "找不到备份文件:$backupPath" } $containerBackupPath = "/var/opt/mssql/backup/$BackupFileName" $dataPath = "/var/opt/mssql/data/$DatabaseName.mdf" $logPath = "/var/opt/mssql/log/$DatabaseName`_log.ldf" $safeDatabaseIdentifier = $DatabaseName.Replace("]", "]]" ) $safeDatabaseLiteral = $DatabaseName.Replace("'", "''") $safeDataLogicalName = $DataLogicalName.Replace("'", "''") $safeLogLogicalName = $LogLogicalName.Replace("'", "''") $safeBackupPath = $containerBackupPath.Replace("'", "''") $sql = @" RESTORE VERIFYONLY FROM DISK = N'$safeBackupPath'; IF DB_ID(N'$safeDatabaseLiteral') IS NOT NULL BEGIN THROW 51000, 'Target database exists; refusing to overwrite.', 1; END; RESTORE DATABASE [$safeDatabaseIdentifier] FROM DISK = N'$safeBackupPath' WITH MOVE N'$safeDataLogicalName' TO N'$dataPath', MOVE N'$safeLogLogicalName' TO N'$logPath', RECOVERY, STATS = 5; ALTER DATABASE [$safeDatabaseIdentifier] SET READ_ONLY; DBCC CHECKDB (N'$safeDatabaseLiteral') WITH NO_INFOMSGS; "@ Write-Host "正在校验并恢复备份(密码不会输出)……" Invoke-TPlusContainerSql -ContainerName $ContainerName -Sql $sql -SaPassword $SaPassword Write-Host "恢复完成:$DatabaseName;数据库文件位于 D 盘绑定目录。"