- 文件上传验证: ModelUploader组件增强、上传API错误处理 - GLTF解析器: 增强节点提取和元数据 - 板材编辑: PanelEditor客户端验证 + panels API服务端验证 - 装配步骤编辑: 修复未使用代码,添加预览按钮 - 装配动画播放: AssemblyPlayer步骤导航边界检查优化 - 分享链接: 完整CRUD API(创建、获取、删除),含验证和过期检查 跳过可选测试任务以加快MVP开发
316 lines
8.9 KiB
PowerShell
316 lines
8.9 KiB
PowerShell
# ============================================
|
||
# 数据库备份脚本 (PowerShell)
|
||
# ============================================
|
||
# 功能:备份 PostgreSQL 数据库
|
||
# 使用:.\backup-db.ps1 [-BackupDir <目录>]
|
||
# ============================================
|
||
|
||
param(
|
||
[string]$BackupDir = ".\backups\database"
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
# 配置
|
||
$Timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||
$BackupFile = "db-backup-$Timestamp.sql.gz"
|
||
$LogFile = "backup-db-$Timestamp.log"
|
||
|
||
# 日志函数
|
||
function Write-Log {
|
||
param([string]$Message, [string]$Level = "INFO")
|
||
|
||
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||
$LogMessage = "[$TimeStamp] $($Level): $Message"
|
||
|
||
switch ($Level) {
|
||
"ERROR" { Write-Host $LogMessage -ForegroundColor Red }
|
||
"WARN" { Write-Host $LogMessage -ForegroundColor Yellow }
|
||
"SUCCESS" { Write-Host $LogMessage -ForegroundColor Green }
|
||
default { Write-Host $LogMessage }
|
||
}
|
||
|
||
Add-Content -Path "$BackupDir\$LogFile" -Value $LogMessage
|
||
}
|
||
|
||
# 检查依赖
|
||
function Test-Dependencies {
|
||
Write-Log "检查依赖..."
|
||
|
||
# 检查 pg_dump
|
||
if (-not (Get-Command pg_dump -ErrorAction SilentlyContinue)) {
|
||
Write-Log "pg_dump 未安装,请安装 PostgreSQL 客户端工具" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
# 检查 7-Zip 或 gzip
|
||
$global:CompressTool = $null
|
||
if (Get-Command 7z -ErrorAction SilentlyContinue) {
|
||
$global:CompressTool = "7z"
|
||
} elseif (Get-Command gzip -ErrorAction SilentlyContinue) {
|
||
$global:CompressTool = "gzip"
|
||
} else {
|
||
Write-Log "未找到压缩工具(7z 或 gzip)" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
Write-Log "✅ 依赖检查通过" "SUCCESS"
|
||
}
|
||
|
||
# 加载环境变量
|
||
function Load-EnvFile {
|
||
if (Test-Path .env) {
|
||
Get-Content .env | ForEach-Object {
|
||
if ($_ -match '^([^#][^=]+)=(.*)$') {
|
||
$name = $matches[1].Trim()
|
||
$value = $matches[2].Trim()
|
||
[Environment]::SetEnvironmentVariable($name, $value, "Process")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# 验证数据库连接
|
||
function Test-DatabaseConnection {
|
||
Write-Log "验证数据库连接..."
|
||
|
||
$DatabaseUrl = $env:DATABASE_URL
|
||
if (-not $DatabaseUrl) {
|
||
Write-Log "DATABASE_URL 环境变量未设置" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
# 解析数据库连接字符串
|
||
if ($DatabaseUrl -match 'postgresql://([^:]+):([^@]+)@([^:]+):([^/]+)/(.+)') {
|
||
$global:DbUser = $matches[1]
|
||
$global:DbPass = $matches[2]
|
||
$global:DbHost = $matches[3]
|
||
$global:DbPort = $matches[4]
|
||
$global:DbName = $matches[5]
|
||
} else {
|
||
Write-Log "DATABASE_URL 格式无效" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
# 测试连接
|
||
$env:PGPASSWORD = $global:DbPass
|
||
$testQuery = "SELECT 1"
|
||
$result = & psql -h $global:DbHost -p $global:DbPort -U $global:DbUser -d $global:DbName -c $testQuery 2>&1
|
||
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Log "无法连接到数据库" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
Write-Log "✅ 数据库连接成功" "SUCCESS"
|
||
}
|
||
|
||
# 创建备份目录
|
||
function New-BackupDirectory {
|
||
Write-Log "创建备份目录: $BackupDir"
|
||
|
||
if (-not (Test-Path $BackupDir)) {
|
||
New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null
|
||
}
|
||
|
||
if (-not (Test-Path $BackupDir)) {
|
||
Write-Log "无法创建备份目录" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
Write-Log "✅ 备份目录已创建" "SUCCESS"
|
||
}
|
||
|
||
# 检查磁盘空间
|
||
function Test-DiskSpace {
|
||
Write-Log "检查磁盘空间..."
|
||
|
||
$drive = (Get-Item $BackupDir).PSDrive
|
||
$freeSpace = [math]::Round($drive.Free / 1MB, 2)
|
||
|
||
# 获取数据库大小
|
||
$env:PGPASSWORD = $global:DbPass
|
||
$sizeQuery = "SELECT pg_database_size('$($global:DbName)') / 1024 / 1024"
|
||
$dbSize = & psql -h $global:DbHost -p $global:DbPort -U $global:DbUser -d $global:DbName -t -c $sizeQuery 2>&1
|
||
$dbSize = [math]::Round([double]$dbSize, 0)
|
||
|
||
$requiredSpace = $dbSize * 2
|
||
|
||
Write-Log "数据库大小: $dbSize MB"
|
||
Write-Log "可用空间: $freeSpace MB"
|
||
Write-Log "需要空间: $requiredSpace MB"
|
||
|
||
if ($freeSpace -lt $requiredSpace) {
|
||
Write-Log "磁盘空间不足" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
Write-Log "✅ 磁盘空间充足" "SUCCESS"
|
||
}
|
||
|
||
# 执行备份
|
||
function Start-Backup {
|
||
Write-Log "开始备份数据库..."
|
||
Write-Log "备份文件: $BackupFile"
|
||
|
||
$startTime = Get-Date
|
||
|
||
# 设置密码环境变量
|
||
$env:PGPASSWORD = $global:DbPass
|
||
|
||
# 执行备份
|
||
$backupPath = Join-Path $BackupDir $BackupFile
|
||
$tempSqlFile = Join-Path $BackupDir "temp-$Timestamp.sql"
|
||
|
||
try {
|
||
# 导出数据库
|
||
& pg_dump -h $global:DbHost -p $global:DbPort -U $global:DbUser -d $global:DbName `
|
||
--format=plain `
|
||
--no-owner `
|
||
--no-acl `
|
||
--clean `
|
||
--if-exists `
|
||
-f $tempSqlFile
|
||
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "pg_dump 执行失败"
|
||
}
|
||
|
||
# 压缩文件
|
||
if ($global:CompressTool -eq "7z") {
|
||
& 7z a -tgzip $backupPath $tempSqlFile | Out-Null
|
||
} else {
|
||
& gzip -c $tempSqlFile > $backupPath
|
||
}
|
||
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "压缩失败"
|
||
}
|
||
|
||
# 删除临时文件
|
||
Remove-Item $tempSqlFile -Force
|
||
|
||
$endTime = Get-Date
|
||
$duration = ($endTime - $startTime).TotalSeconds
|
||
|
||
Write-Log "✅ 备份完成,耗时: $([math]::Round($duration, 2)) 秒" "SUCCESS"
|
||
}
|
||
catch {
|
||
Write-Log "备份失败: $_" "ERROR"
|
||
if (Test-Path $tempSqlFile) {
|
||
Remove-Item $tempSqlFile -Force
|
||
}
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
# 验证备份文件
|
||
function Test-BackupFile {
|
||
Write-Log "验证备份文件..."
|
||
|
||
$backupPath = Join-Path $BackupDir $BackupFile
|
||
|
||
if (-not (Test-Path $backupPath)) {
|
||
Write-Log "备份文件不存在" "ERROR"
|
||
exit 1
|
||
}
|
||
|
||
# 检查文件大小
|
||
$fileSize = (Get-Item $backupPath).Length
|
||
$fileSizeMB = [math]::Round($fileSize / 1MB, 2)
|
||
Write-Log "备份文件大小: $fileSizeMB MB"
|
||
|
||
# 生成 checksum
|
||
$hash = Get-FileHash -Path $backupPath -Algorithm SHA256
|
||
$checksum = $hash.Hash
|
||
$checksum | Out-File -FilePath "$backupPath.sha256" -Encoding ASCII
|
||
|
||
Write-Log "✅ 备份文件完整性验证通过" "SUCCESS"
|
||
Write-Log "✅ Checksum: $checksum" "SUCCESS"
|
||
}
|
||
|
||
# 清理旧备份
|
||
function Remove-OldBackups {
|
||
Write-Log "清理旧备份..."
|
||
|
||
$keepCount = 7
|
||
|
||
# 获取所有备份文件
|
||
$backupFiles = Get-ChildItem -Path $BackupDir -Filter "db-backup-*.sql.gz" |
|
||
Sort-Object LastWriteTime -Descending
|
||
|
||
$backupCount = $backupFiles.Count
|
||
|
||
if ($backupCount -gt $keepCount) {
|
||
$deleteCount = $backupCount - $keepCount
|
||
Write-Log "发现 $backupCount 个备份,删除最旧的 $deleteCount 个"
|
||
|
||
$backupFiles | Select-Object -Skip $keepCount | ForEach-Object {
|
||
Write-Log "删除: $($_.Name)"
|
||
Remove-Item $_.FullName -Force
|
||
$checksumFile = "$($_.FullName).sha256"
|
||
if (Test-Path $checksumFile) {
|
||
Remove-Item $checksumFile -Force
|
||
}
|
||
}
|
||
|
||
Write-Log "✅ 清理完成" "SUCCESS"
|
||
} else {
|
||
Write-Log "当前有 $backupCount 个备份,无需清理"
|
||
}
|
||
}
|
||
|
||
# 生成备份摘要
|
||
function New-BackupSummary {
|
||
Write-Log "生成备份摘要..."
|
||
|
||
$backupPath = Join-Path $BackupDir $BackupFile
|
||
$fileSize = (Get-Item $backupPath).Length
|
||
$checksum = Get-Content "$backupPath.sha256"
|
||
|
||
$summary = @{
|
||
timestamp = (Get-Date -Format "o")
|
||
backup_file = $BackupFile
|
||
database = $global:DbName
|
||
host = $global:DbHost
|
||
size = $fileSize
|
||
checksum = $checksum
|
||
status = "success"
|
||
}
|
||
|
||
$summaryPath = Join-Path $BackupDir "backup-summary-$Timestamp.json"
|
||
$summary | ConvertTo-Json | Out-File -FilePath $summaryPath -Encoding UTF8
|
||
|
||
Write-Log "✅ 备份摘要已生成" "SUCCESS"
|
||
}
|
||
|
||
# 主函数
|
||
function Main {
|
||
Write-Host "========================================"
|
||
Write-Host " 数据库备份脚本"
|
||
Write-Host "========================================"
|
||
Write-Host ""
|
||
|
||
Load-EnvFile
|
||
Test-Dependencies
|
||
Test-DatabaseConnection
|
||
New-BackupDirectory
|
||
Test-DiskSpace
|
||
Start-Backup
|
||
Test-BackupFile
|
||
Remove-OldBackups
|
||
New-BackupSummary
|
||
|
||
Write-Host ""
|
||
Write-Host "========================================"
|
||
Write-Log "🎉 备份成功完成!" "SUCCESS"
|
||
Write-Host "========================================"
|
||
Write-Host ""
|
||
Write-Host "备份文件: $BackupDir\$BackupFile"
|
||
Write-Host "日志文件: $BackupDir\$LogFile"
|
||
Write-Host ""
|
||
}
|
||
|
||
# 执行主函数
|
||
Main
|