| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 |
- #Requires -Version 5.1
- <#
- .SYNOPSIS
- 独立备份页面:点选磁盘/文件夹,不手输路径。只监听 127.0.0.1。
- #>
- $ErrorActionPreference = "Stop"
- $RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path
- $HtmlFile = Join-Path $RootDir "backup-ui.html"
- $BackupScript = Join-Path $RootDir "backup-db.ps1"
- $Port = 18765
- $Url = $null
- $listener = $null
- . (Join-Path $RootDir "db-config.ps1")
- if (-not (Test-Path $HtmlFile)) { throw "找不到 backup-ui.html" }
- if (-not (Test-Path $BackupScript)) { throw "找不到 backup-db.ps1" }
- Add-Type -AssemblyName System.Windows.Forms
- if (-not ("MesFolderPicker" -as [type])) {
- Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing -TypeDefinition @"
- using System;
- using System.Runtime.InteropServices;
- using System.Threading;
- using System.Windows.Forms;
- public static class MesFolderPicker {
- [DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr hWnd);
- [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
- [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
- [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
- [DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
- [DllImport("user32.dll")] static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
- [DllImport("user32.dll")] static extern bool BringWindowToTop(IntPtr hWnd);
- public static string Pick(string description) {
- string selected = null;
- Thread t = new Thread(delegate() {
- Form form = new Form();
- form.Text = "MES Backup - Select Folder";
- form.TopMost = true;
- form.ShowInTaskbar = true;
- form.StartPosition = FormStartPosition.CenterScreen;
- form.Width = 420;
- form.Height = 90;
- form.FormBorderStyle = FormBorderStyle.FixedToolWindow;
- form.MinimizeBox = false;
- form.MaximizeBox = false;
- form.Show();
- form.WindowState = FormWindowState.Normal;
- form.Activate();
- ForceForeground(form.Handle);
- Application.DoEvents();
- FolderBrowserDialog dlg = new FolderBrowserDialog();
- dlg.Description = description;
- dlg.ShowNewFolderButton = true;
- dlg.RootFolder = Environment.SpecialFolder.MyComputer;
- DialogResult r = dlg.ShowDialog(form);
- if (r == DialogResult.OK) {
- selected = dlg.SelectedPath;
- }
- form.Close();
- form.Dispose();
- });
- t.SetApartmentState(ApartmentState.STA);
- t.Start();
- t.Join();
- return selected;
- }
- static void ForceForeground(IntPtr hwnd) {
- IntPtr fg = GetForegroundWindow();
- uint fgThread = GetWindowThreadProcessId(fg, IntPtr.Zero);
- uint thisThread = GetCurrentThreadId();
- if (fgThread != thisThread) {
- AttachThreadInput(thisThread, fgThread, true);
- }
- ShowWindow(hwnd, 9);
- BringWindowToTop(hwnd);
- SetForegroundWindow(hwnd);
- if (fgThread != thisThread) {
- AttachThreadInput(thisThread, fgThread, false);
- }
- }
- }
- "@
- }
- $State = [hashtable]::Synchronized(@{
- Running = $false
- Log = New-Object System.Collections.ArrayList
- Result = ""
- Error = ""
- Path = ""
- StartedAt = ""
- EndedAt = ""
- Proc = $null
- OutFile = ""
- DbBytes = 0
- })
- function Add-UiLog {
- param([string]$Text)
- if ([string]::IsNullOrWhiteSpace($Text)) { return }
- [void]$State.Log.Add($Text)
- if ($State.Log.Count -gt 800) {
- $State.Log.RemoveRange(0, 200)
- }
- }
- function Get-SafePath {
- param([string]$Path)
- if ([string]::IsNullOrWhiteSpace($Path)) { throw "请先选择保存位置" }
- $p = $Path.Trim().Trim('"')
- if ($p -notmatch '^[A-Za-z]:\\') { throw "只允许本机磁盘路径" }
- $full = [System.IO.Path]::GetFullPath($p)
- if ($full -match '^[A-Za-z]:\\?$') {
- return ($full.Substring(0, 1).ToUpper() + ":\")
- }
- return $full.TrimEnd("\")
- }
- $script:MysqlToolCache = @{}
- function Find-MysqlTool {
- param([string]$Name)
- if ($script:MysqlToolCache.ContainsKey($Name)) { return $script:MysqlToolCache[$Name] }
- $cmd = Get-Command $Name -ErrorAction SilentlyContinue
- if ($cmd -and $cmd.Source) {
- $script:MysqlToolCache[$Name] = $cmd.Source
- return $cmd.Source
- }
- $candidates = New-Object System.Collections.Generic.List[string]
- foreach ($root in @(
- "C:\Program Files\MySQL",
- "C:\Program Files (x86)\MySQL",
- "C:\mysql",
- "D:\MySQL",
- "E:\MySQL",
- "C:\Program Files\MariaDB"
- )) {
- if (-not (Test-Path $root)) { continue }
- Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue | ForEach-Object {
- $direct = Join-Path $_.FullName ("bin\{0}.exe" -f $Name)
- $candidates.Add($direct)
- Get-ChildItem -Path $_.FullName -Directory -ErrorAction SilentlyContinue | ForEach-Object {
- $candidates.Add((Join-Path $_.FullName ("bin\{0}.exe" -f $Name)))
- }
- }
- }
- foreach ($p in $candidates) {
- if (Test-Path -LiteralPath $p) {
- $script:MysqlToolCache[$Name] = $p
- return $p
- }
- }
- return $null
- }
- function Get-DbSizeBytes {
- try {
- $cfg = Read-MesDbConfig
- $mysql = Find-MysqlTool "mysql"
- if (-not $mysql) { return 0 }
- $cnf = Join-Path $env:TEMP ("mes_ui_size_{0}.cnf" -f $PID)
- Write-MesMysqlCnf -Config $cfg -CnfPath $cnf
- $sql = "SELECT IFNULL(SUM(data_length+index_length),0) FROM information_schema.tables WHERE table_schema='$($cfg.database)'"
- $out = & $mysql "--defaults-extra-file=$cnf" "--connect-timeout=3" -N -s -e $sql 2>$null
- Remove-Item $cnf -Force -ErrorAction SilentlyContinue
- $n = 0
- [void][int64]::TryParse(($out | Out-String).Trim(), [ref]$n)
- return $n
- } catch {
- return 0
- }
- }
- function Test-MesMysqlConn {
- param($Config)
- $cfg = ConvertTo-MesDbConfig -InputObject $Config
- $mysql = Find-MysqlTool "mysql"
- if (-not $mysql) { throw "找不到 mysql.exe,请安装 MySQL 客户端" }
- $cnf = Join-Path $env:TEMP ("mes_ui_test_{0}.cnf" -f $PID)
- Write-MesMysqlCnf -Config $cfg -CnfPath $cnf
- try {
- $out = & $mysql "--defaults-extra-file=$cnf" "--connect-timeout=5" -N -s -e "SELECT 1" 2>&1
- if ($LASTEXITCODE -ne 0) {
- throw ("连接失败: {0}" -f ($out | Out-String).Trim())
- }
- } finally {
- Remove-Item $cnf -Force -ErrorAction SilentlyContinue
- }
- return $cfg
- }
- function Get-DriveList {
- $list = New-Object System.Collections.Generic.List[object]
- foreach ($root in [System.IO.Directory]::GetLogicalDrives()) {
- if ($root -match '^[AB]:\\$') { continue }
- $letter = $root.Substring(0, 2).ToUpper()
- $list.Add([ordered]@{
- letter = $letter
- path = $root
- name = $letter
- type = "磁盘"
- removable = $false
- ready = $true
- sizeGB = 0
- freeGB = 0
- freeBytes = 0
- })
- }
- return $list
- }
- function Get-FolderList {
- param([string]$Path)
- $full = Get-SafePath $Path
- if (-not (Test-Path -LiteralPath $full)) { throw "路径不存在,请确认已插入磁盘" }
- $parent = [System.IO.Path]::GetDirectoryName($full)
- $folders = New-Object System.Collections.Generic.List[object]
- Get-ChildItem -LiteralPath $full -Directory -Force -ErrorAction SilentlyContinue |
- Select-Object -First 80 |
- ForEach-Object {
- $folders.Add([ordered]@{ name = $_.Name; path = $_.FullName })
- }
- $pathOut = $full
- if (-not $pathOut.EndsWith("\")) { $pathOut += "\" }
- return [ordered]@{
- path = $pathOut
- parent = $parent
- folders = $folders
- }
- }
- function Get-FreeBytes {
- param([string]$Path)
- try {
- $full = Get-SafePath $Path
- $root = [System.IO.Path]::GetPathRoot($full)
- $di = New-Object System.IO.DriveInfo ($root)
- if ($di.IsReady) { return [int64]$di.AvailableFreeSpace }
- } catch { }
- return 0
- }
- function Show-FolderPicker {
- return [MesFolderPicker]::Pick("选择备份文件保存位置(可选手动插入的 U 盘或移动硬盘)")
- }
- function Get-WrittenBackup {
- $info = [ordered]@{ bytes = 0; name = ""; doneBytes = 0 }
- $dir = $State.Path
- if (-not $dir -or -not (Test-Path -LiteralPath $dir)) { return $info }
- $files = @(Get-ChildItem -LiteralPath $dir -File -ErrorAction SilentlyContinue |
- Where-Object { $_.Name -like "mes_cloud_*.sql.gz*" } |
- Sort-Object LastWriteTime -Descending)
- if ($files.Count -gt 0) {
- $final = @($files | Where-Object { $_.Name -notlike "*.tmp" }) | Select-Object -First 1
- $pick = if ($final) { $final } else { $files[0] }
- $info.name = $pick.Name
- $info.bytes = [int64]$pick.Length
- if ($pick.Name -notlike "*.tmp") {
- $info.doneBytes = [int64]$pick.Length
- }
- }
- return $info
- }
- function Read-JsonBody {
- param($Request)
- $reader = New-Object System.IO.StreamReader($Request.InputStream, [System.Text.Encoding]::UTF8)
- try {
- $raw = $reader.ReadToEnd()
- } finally {
- $reader.Dispose()
- }
- if ([string]::IsNullOrWhiteSpace($raw)) { return $null }
- return $raw | ConvertFrom-Json
- }
- function Send-Bytes {
- param($Response, [byte[]]$Bytes, [string]$ContentType, [int]$Status = 200)
- $Response.StatusCode = $Status
- $Response.ContentType = $ContentType
- $Response.Headers["Cache-Control"] = "no-store"
- $Response.ContentLength64 = $Bytes.Length
- $Response.OutputStream.Write($Bytes, 0, $Bytes.Length)
- $Response.OutputStream.Close()
- }
- function Send-Json {
- param($Response, $Object, [int]$Status = 200)
- $json = $Object | ConvertTo-Json -Depth 8 -Compress
- $bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
- Send-Bytes -Response $Response -Bytes $bytes -ContentType "application/json; charset=utf-8" -Status $Status
- }
- function Send-Html {
- param($Response)
- $bytes = [System.IO.File]::ReadAllBytes($HtmlFile)
- Send-Bytes -Response $Response -Bytes $bytes -ContentType "text/html; charset=utf-8"
- }
- function Start-BackupJob {
- param([string]$TargetDir, [int]$KeepDays)
- if ($State.Running) { throw "已有备份正在进行" }
- $dir = Get-SafePath $TargetDir
- $free = Get-FreeBytes -Path $dir
- $dbBytes = Get-DbSizeBytes
- $State.DbBytes = $dbBytes
- # gzip 后通常小于库文件,但 SQL 文本可能接近库大小;预留 1.2 倍库容量
- if ($dbBytes -gt 0 -and $free -gt 0 -and $free -lt [int64]($dbBytes * 1.2)) {
- $needGB = [math]::Round($dbBytes * 1.2 / 1GB, 2)
- $freeGB = [math]::Round($free / 1GB, 2)
- throw ("目标盘剩余空间不足:约需 {0} GB,当前剩余 {1} GB。请换更大的 U 盘/硬盘。" -f $needGB, $freeGB)
- }
- if (-not (Test-Path -LiteralPath $dir)) {
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
- }
- $outFile = Join-Path $env:TEMP ("mes_backup_ui_{0}.log" -f (Get-Date -Format "yyyyMMddHHmmss"))
- $errFile = $outFile + ".err"
- if (Test-Path $outFile) { Remove-Item $outFile -Force }
- if (Test-Path $errFile) { Remove-Item $errFile -Force }
- $arg = '-NoProfile -ExecutionPolicy Bypass -File "{0}" -BackupDir "{1}" -KeepDaysParam {2}' -f $BackupScript, $dir, $KeepDays
- $proc = Start-Process -FilePath "powershell.exe" -ArgumentList $arg -PassThru -WindowStyle Hidden `
- -RedirectStandardOutput $outFile -RedirectStandardError $errFile
- $State.Running = $true
- $State.Result = ""
- $State.Error = ""
- $State.Path = $dir
- $State.StartedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
- $State.EndedAt = ""
- $State.Proc = $proc
- $State.OutFile = $outFile
- $State.Log.Clear()
- Add-UiLog ("开始备份到: {0}" -f $dir)
- if ($dbBytes -gt 0) {
- Add-UiLog ("库数据约 {0} GB,采用边导出边压缩,请耐心等待,勿拔盘" -f ([math]::Round($dbBytes / 1GB, 2)))
- }
- }
- function Read-LogFiles {
- $lines = New-Object System.Collections.ArrayList
- foreach ($f in @($State.OutFile, ($State.OutFile + ".err"))) {
- if (-not $f -or -not (Test-Path $f)) { continue }
- $text = $null
- foreach ($encName in @("UTF8", "Default")) {
- try {
- $enc = [System.Text.Encoding]::$encName
- $text = [System.IO.File]::ReadAllText($f, $enc)
- if ($text -and $text.IndexOf([char]0xFFFD) -lt 0) { break }
- } catch { }
- }
- if ($text) {
- foreach ($line in ($text -split "`r?`n")) {
- if ($line.Trim() -ne "") { [void]$lines.Add($line) }
- }
- }
- }
- return $lines
- }
- function Test-BackupFileOk {
- $dir = $State.Path
- if (-not $dir -or -not (Test-Path -LiteralPath $dir)) { return $false }
- $started = $null
- try {
- if ($State.StartedAt) { $started = [datetime]::Parse($State.StartedAt) }
- } catch { }
- $gz = Get-ChildItem -LiteralPath $dir -File -Filter "mes_cloud_*.sql.gz" -ErrorAction SilentlyContinue |
- Sort-Object LastWriteTime -Descending |
- Select-Object -First 1
- if ($null -eq $gz -or $gz.Length -le 100) { return $false }
- if ($started -and $gz.LastWriteTime -lt $started.AddMinutes(-2)) { return $false }
- return $true
- }
- function Update-BackupState {
- if (-not $State.Running) { return }
- $proc = $State.Proc
- $lines = Read-LogFiles
- if ($lines.Count -gt 0) {
- $State.Log.Clear()
- foreach ($line in $lines) { [void]$State.Log.Add($line) }
- }
- if ($null -eq $proc) { return }
- try { $proc.Refresh() } catch { }
- try { $exited = $proc.HasExited } catch { $exited = $true }
- if (-not $exited) { return }
- try { $proc.WaitForExit(1000) } catch { }
- $code = $null
- try { $code = $proc.ExitCode } catch { $code = $null }
- $State.Running = $false
- $State.EndedAt = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
- $State.Proc = $null
- $fileOk = Test-BackupFileOk
- # Start-Process 有时读不到 ExitCode(界面显示「退出码」后面空白);有新生成的 .gz 即视为成功
- $success = ($code -eq 0) -or $fileOk
- if ($success) {
- $State.Result = "ok"
- $State.Error = ""
- Add-UiLog "备份完成"
- } else {
- $State.Result = "error"
- $codeText = if ($null -eq $code) { "未知" } else { [string]$code }
- $State.Error = "备份失败,退出码 $codeText"
- Add-UiLog $State.Error
- }
- }
- $listener = $null
- $started = $false
- $lastError = $null
- foreach ($tryPort in 18765..18785) {
- $tryUrl = "http://127.0.0.1:{0}/" -f $tryPort
- $tryListener = New-Object System.Net.HttpListener
- try {
- $tryListener.Prefixes.Add($tryUrl)
- $tryListener.Start()
- $listener = $tryListener
- $Port = $tryPort
- $Url = $tryUrl
- $started = $true
- break
- } catch {
- $lastError = $_.Exception.Message
- try { $tryListener.Close() } catch { }
- }
- }
- if (-not $started) {
- throw ("无法启动本机页面,18765-18785 端口都被占用。请先关掉之前打开的备份窗口后再试。详情: {0}" -f $lastError)
- }
- Write-Host ""
- Write-Host ("数据库备份页面已启动(仅本机): {0}" -f $Url)
- if ($Port -ne 18765) {
- Write-Host ("原端口 18765 已被占用,已自动改用 {0}。请先关掉旧的备份窗口,避免开两个。" -f $Port)
- }
- Write-Host "请在网页点「浏览文件夹」选择 U 盘/硬盘目录,不要手输路径。"
- Write-Host "关闭本窗口即停止页面。"
- Write-Host ""
- Start-Process $Url
- try {
- while ($listener.IsListening) {
- $ctx = $null
- try {
- $ctx = $listener.GetContext()
- $req = $ctx.Request
- $res = $ctx.Response
- $path = $req.Url.AbsolutePath.TrimEnd("/")
- if ([string]::IsNullOrEmpty($path)) { $path = "/" }
- if ($req.HttpMethod -eq "OPTIONS") {
- $res.StatusCode = 204
- $res.AddHeader("Access-Control-Allow-Origin", "*")
- $res.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
- $res.AddHeader("Access-Control-Allow-Headers", "Content-Type")
- $res.ContentLength64 = 0
- $res.Close()
- continue
- }
- if ($req.HttpMethod -eq "GET" -and ($path -eq "/" -or $path -eq "/index.html")) {
- Send-Html -Response $res
- continue
- }
- if ($req.HttpMethod -eq "GET" -and $path -eq "/api/drives") {
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- drives = @(Get-DriveList)
- })
- continue
- }
- if ($req.HttpMethod -eq "GET" -and $path -eq "/api/dbsize") {
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- dbBytes = [int64](Get-DbSizeBytes)
- })
- continue
- }
- if ($req.HttpMethod -eq "GET" -and $path -eq "/api/folders") {
- $folderPath = ""
- $query = $req.Url.Query.TrimStart("?")
- if ($query) {
- foreach ($pair in $query.Split([char]38)) {
- $kv = $pair.Split("=", 2)
- if ($kv.Count -ge 2 -and [uri]::UnescapeDataString($kv[0]) -eq "path") {
- $folderPath = [uri]::UnescapeDataString(($kv[1] -replace "\+", " "))
- }
- }
- }
- Send-Json -Response $res -Object ([ordered]@{ ok = $true; data = (Get-FolderList -Path $folderPath) })
- continue
- }
- if ($req.HttpMethod -eq "POST" -and $path -eq "/api/browse") {
- $picked = Show-FolderPicker
- if ([string]::IsNullOrWhiteSpace($picked)) {
- Send-Json -Response $res -Object ([ordered]@{ ok = $true; cancelled = $true })
- } else {
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- cancelled = $false
- path = $picked
- freeBytes = [int64](Get-FreeBytes -Path $picked)
- })
- }
- continue
- }
- if ($req.HttpMethod -eq "GET" -and $path -eq "/api/status") {
- Update-BackupState
- $written = Get-WrittenBackup
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- running = [bool]$State.Running
- result = $State.Result
- error = $State.Error
- path = $State.Path
- startedAt = $State.StartedAt
- endedAt = $State.EndedAt
- log = @($State.Log)
- dbBytes = [int64]$State.DbBytes
- writtenBytes = [int64]$written.bytes
- fileName = $written.name
- })
- continue
- }
- if ($req.HttpMethod -eq "GET" -and $path -eq "/api/config") {
- try {
- $cfg = Read-MesDbConfig
- } catch {
- $cfg = Get-DefaultMesDbConfig
- }
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- engine = "mysql"
- host = [string]$cfg.host
- port = [int]$cfg.port
- database = [string]$cfg.database
- user = [string]$cfg.user
- password = [string]$cfg.password
- })
- continue
- }
- if ($req.HttpMethod -eq "POST" -and $path -eq "/api/config") {
- $body = Read-JsonBody -Request $req
- $cfg = Save-MesDbConfig -Config $body
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- message = "已保存 MySQL 配置"
- host = [string]$cfg.host
- port = [int]$cfg.port
- database = [string]$cfg.database
- user = [string]$cfg.user
- })
- continue
- }
- if (($req.HttpMethod -eq "GET" -or $req.HttpMethod -eq "POST") -and $path -eq "/api/test") {
- $body = $null
- if ($req.HttpMethod -eq "POST") {
- $body = Read-JsonBody -Request $req
- }
- if ($null -eq $body) {
- $q = $req.Url.Query.TrimStart("?")
- $map = @{}
- if ($q) {
- foreach ($pair in $q.Split([char]38)) {
- $kv = $pair.Split("=", 2)
- if ($kv.Count -ge 2) {
- $map[[uri]::UnescapeDataString($kv[0])] = [uri]::UnescapeDataString(($kv[1] -replace "\+", " "))
- }
- }
- }
- $body = [pscustomobject]@{
- engine = "mysql"
- host = $map["host"]
- port = $map["port"]
- database = $map["database"]
- user = $map["user"]
- password = $map["password"]
- }
- }
- $cfg = Test-MesMysqlConn -Config $body
- Send-Json -Response $res -Object ([ordered]@{
- ok = $true
- message = ("MySQL 连接成功: {0}:{1} / {2}" -f $cfg.host, $cfg.port, $cfg.database)
- })
- continue
- }
- if ($req.HttpMethod -eq "POST" -and $path -eq "/api/backup") {
- $body = Read-JsonBody -Request $req
- if ($body -and $body.host) {
- Save-MesDbConfig -Config $body | Out-Null
- }
- $keep = 0
- if ($body -and $null -ne $body.keepDays) { $keep = [int]$body.keepDays }
- Start-BackupJob -TargetDir $body.path -KeepDays $keep
- Send-Json -Response $res -Object ([ordered]@{ ok = $true; message = "已开始备份" })
- continue
- }
- Send-Json -Response $res -Object ([ordered]@{ ok = $false; message = "未找到接口" }) -Status 404
- } catch {
- if ($null -ne $ctx) {
- try {
- Send-Json -Response $ctx.Response -Object ([ordered]@{ ok = $false; message = $_.Exception.Message }) -Status 500
- } catch { }
- }
- }
- }
- } finally {
- if ($listener) {
- try { $listener.Stop() } catch { }
- try { $listener.Close() } catch { }
- }
- }
|