#Requires -Version 5.1 param( [switch] $NonInteractive, [ValidateSet('Sit', 'Uat', 'Prod')] [string] $Environment = 'Sit', [string] $GitUrl = 'http://8.152.195.41:3000/dujian/h5.git', [string] $Branch = 'master', [string] $SshUser = 'root', # 留空则使用当前环境在 $Environments 中的默认远程目录 [string] $RemotePath = '', # 非空时覆盖环境默认 IP(便于 Jenkins / 命令行临时指定) [string] $TargetHost = '' ) <# .SYNOPSIS 从 Git 将 master(或指定分支)部署到目标服务器:先清空远程目录再覆盖上传。 .DESCRIPTION 无参数启动:图形界面。加 -NonInteractive 时供 Jenkins 等无人值守调用。 默认仓库:http://8.152.195.41:3000/dujian/h5.git 默认:Sit → 120.26.186.130:/docker/middleware/nginx/html/h5/; Uat → 39.106.135.88:/alien_uat/nginx/html/h5/; Prod → 39.106.135.88:/alien_produ/nginx/html/h5/ #> $ErrorActionPreference = 'Stop' $Environments = @( @{ Key = 'Sit' Label = 'Sit(测试环境)' Server = '120.26.186.130' RemotePath = '/docker/middleware/nginx/html/h5/' }, @{ Key = 'Uat' Label = 'Uat(预生产环境)' Server = '39.106.135.88' RemotePath = '/alien_uat/nginx/html/h5/' }, @{ Key = 'Prod' Label = 'Prod(生产环境)' Server = '39.106.135.88' RemotePath = '/alien_produ/nginx/html/h5/' } ) function Test-GitAvailable { $g = Get-Command git -ErrorAction SilentlyContinue if (-not $g) { throw "未找到 git。请先安装 Git for Windows 并确保 git 在 PATH 中。" } } function Test-SshScpAvailable { $ssh = Get-Command ssh -ErrorAction SilentlyContinue $scp = Get-Command scp -ErrorAction SilentlyContinue if (-not $ssh -or -not $scp) { throw "未找到 ssh 或 scp。请在 Windows「可选功能」中安装「OpenSSH 客户端」。" } } function Invoke-GitCloneMaster { param( [string] $RepoUrl, [string] $Branch, [string] $CloneInto ) if (Test-Path -LiteralPath $CloneInto) { Remove-Item -LiteralPath $CloneInto -Recurse -Force } New-Item -ItemType Directory -Path (Split-Path -LiteralPath $CloneInto -Parent) -Force | Out-Null $cloneArgs = @('clone', '--depth', '1', '--branch', $Branch, $RepoUrl, $CloneInto) & git @cloneArgs if ($LASTEXITCODE -ne 0) { throw "git clone 失败,退出码:$LASTEXITCODE" } } function Remove-LocalGitMetadata { param([string] $RepoRoot) $gitDir = Join-Path $RepoRoot '.git' if (Test-Path -LiteralPath $gitDir) { Remove-Item -LiteralPath $gitDir -Recurse -Force } } function Invoke-RemoteClearDirectory { param( [string] $User, [string] $TargetHost, [string] $RemotePath ) $r = $RemotePath.Trim().TrimEnd('/') if ($r.IndexOf([char]39) -ge 0 -or $r.Contains('$') -or $r.Contains('`')) { throw '远程目录路径暂不支持包含单引号、反引号或美元符号。' } $remoteShell = "mkdir -p '$r' && find '$r' -mindepth 1 -delete" & ssh @('-o', 'StrictHostKeyChecking=accept-new', "${User}@${TargetHost}", $remoteShell) if ($LASTEXITCODE -ne 0) { throw "远程清空目录失败(ssh),退出码:$LASTEXITCODE" } } function Invoke-DeployScp { param( [string] $StagePath, [string] $User, [string] $TargetHost, [string] $RemotePath ) $dest = ('{0}@{1}:{2}' -f $User, $TargetHost, ($RemotePath.TrimEnd('/') + '/')) $items = @(Get-ChildItem -LiteralPath $StagePath -Force) if ($items.Count -eq 0) { throw '克隆后的目录为空,无法上传。' } $scpArgs = @('-r', '-o', 'StrictHostKeyChecking=accept-new') + ($items | ForEach-Object { $_.FullName }) + @($dest) & scp @scpArgs if ($LASTEXITCODE -ne 0) { throw "scp 失败,退出码:$LASTEXITCODE" } } function Invoke-H5DeployFromGit { param( [Parameter(Mandatory)] [ValidateSet('Sit', 'Uat', 'Prod')] [string] $Environment, [string] $RepoUrl, [string] $Branch, [string] $User, [string] $RemotePath, [string] $TargetHostOverride ) $envItem = @($Environments | Where-Object { $_.Key -eq $Environment })[0] if (-not $envItem) { throw "未知环境:$Environment" } $hostAddr = if ($TargetHostOverride) { $TargetHostOverride.Trim() } else { $envItem.Server } if (-not $hostAddr) { throw '目标主机不能为空。' } $resolvedRemote = if ($RemotePath -and $RemotePath.Trim()) { $RemotePath.Trim() } else { $envItem.RemotePath } if (-not $resolvedRemote) { throw '远程目录不能为空。' } Test-GitAvailable Test-SshScpAvailable $base = Join-Path ([System.IO.Path]::GetTempPath()) ('h5-deploy-' + [Guid]::NewGuid().ToString('N')) $cloneInto = Join-Path $base 'h5' try { Write-Host "[$($envItem.Key)] 克隆 $Branch …" Invoke-GitCloneMaster -RepoUrl $RepoUrl -Branch $Branch -CloneInto $cloneInto Write-Host '移除 .git(仅上传站点文件)…' Remove-LocalGitMetadata -RepoRoot $cloneInto Write-Host "清空远程目录 $hostAddr`:$resolvedRemote …" Invoke-RemoteClearDirectory -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote Write-Host "上传到 $hostAddr …" Invoke-DeployScp -StagePath $cloneInto -User $User -TargetHost $hostAddr -RemotePath $resolvedRemote Write-Host "完成:$($envItem.Label) → ${User}@${hostAddr}:${resolvedRemote}" } finally { if (Test-Path -LiteralPath $base) { Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue } } } if ($NonInteractive) { try { Invoke-H5DeployFromGit -Environment $Environment -RepoUrl $GitUrl -Branch $Branch ` -User $SshUser -RemotePath $RemotePath -TargetHostOverride $TargetHost exit 0 } catch { Write-Error $_ exit 1 } } # ---------- 以下为图形界面 ---------- Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $DefaultGitUrl = $GitUrl $DefaultBranch = $Branch $form = New-Object System.Windows.Forms.Form $form.Text = '从 Git 部署 H5 到服务器' $form.Size = New-Object System.Drawing.Size(520, 400) $form.StartPosition = 'CenterScreen' $form.FormBorderStyle = 'FixedDialog' $form.MaximizeBox = $false $y = 18 $dy = 36 $lblEnv = New-Object System.Windows.Forms.Label $lblEnv.Location = New-Object System.Drawing.Point(20, $y) $lblEnv.Size = New-Object System.Drawing.Size(120, 22) $lblEnv.Text = '环境:' $combo = New-Object System.Windows.Forms.ComboBox $combo.Location = New-Object System.Drawing.Point(140, ($y - 2)) $combo.Size = New-Object System.Drawing.Size(340, 24) $combo.DropDownStyle = 'DropDownList' foreach ($envDef in $Environments) { [void] $combo.Items.Add($envDef.Label) } $idxGui = 0 for ($i = 0; $i -lt $Environments.Count; $i++) { if ($Environments[$i].Key -eq $Environment) { $idxGui = $i; break } } $combo.SelectedIndex = $idxGui $y += $dy $lblGit = New-Object System.Windows.Forms.Label $lblGit.Location = New-Object System.Drawing.Point(20, $y) $lblGit.Size = New-Object System.Drawing.Size(120, 22) $lblGit.Text = '仓库 URL:' $txtGit = New-Object System.Windows.Forms.TextBox $txtGit.Location = New-Object System.Drawing.Point(140, ($y - 2)) $txtGit.Size = New-Object System.Drawing.Size(340, 22) $txtGit.Text = $DefaultGitUrl $y += $dy $lblBranch = New-Object System.Windows.Forms.Label $lblBranch.Location = New-Object System.Drawing.Point(20, $y) $lblBranch.Size = New-Object System.Drawing.Size(120, 22) $lblBranch.Text = '分支:' $txtBranch = New-Object System.Windows.Forms.TextBox $txtBranch.Location = New-Object System.Drawing.Point(140, ($y - 2)) $txtBranch.Size = New-Object System.Drawing.Size(120, 22) $txtBranch.Text = $DefaultBranch $y += $dy $lblHost = New-Object System.Windows.Forms.Label $lblHost.Location = New-Object System.Drawing.Point(20, $y) $lblHost.Size = New-Object System.Drawing.Size(120, 22) $lblHost.Text = '目标主机:' $txtHost = New-Object System.Windows.Forms.TextBox $txtHost.Location = New-Object System.Drawing.Point(140, ($y - 2)) $txtHost.Size = New-Object System.Drawing.Size(340, 22) $txtHost.ReadOnly = $true $y += $dy $lblUser = New-Object System.Windows.Forms.Label $lblUser.Location = New-Object System.Drawing.Point(20, $y) $lblUser.Size = New-Object System.Drawing.Size(120, 22) $lblUser.Text = 'SSH 用户:' $txtUser = New-Object System.Windows.Forms.TextBox $txtUser.Location = New-Object System.Drawing.Point(140, ($y - 2)) $txtUser.Size = New-Object System.Drawing.Size(340, 22) $txtUser.Text = $SshUser $y += $dy $lblPath = New-Object System.Windows.Forms.Label $lblPath.Location = New-Object System.Drawing.Point(20, $y) $lblPath.Size = New-Object System.Drawing.Size(120, 22) $lblPath.Text = '远程目录:' $txtPath = New-Object System.Windows.Forms.TextBox $txtPath.Location = New-Object System.Drawing.Point(140, ($y - 2)) $txtPath.Size = New-Object System.Drawing.Size(340, 22) $sel0 = $Environments[$idxGui] $txtHost.Text = $sel0.Server $txtPath.Text = if ($RemotePath.Trim()) { $RemotePath.Trim() } else { $sel0.RemotePath } $y += 44 $btnOk = New-Object System.Windows.Forms.Button $btnOk.Location = New-Object System.Drawing.Point(160, $y) $btnOk.Size = New-Object System.Drawing.Size(120, 32) $btnOk.Text = '开始部署' $btnCancel = New-Object System.Windows.Forms.Button $btnCancel.Location = New-Object System.Drawing.Point(300, $y) $btnCancel.Size = New-Object System.Drawing.Size(120, 32) $btnCancel.Text = '取消' $y += 48 $status = New-Object System.Windows.Forms.Label $status.Location = New-Object System.Drawing.Point(20, $y) $status.Size = New-Object System.Drawing.Size(470, 72) $status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。" function Update-FromEnvironmentSelection { $idx = $combo.SelectedIndex if ($idx -lt 0) { return } $sel = $Environments[$idx] $txtHost.Text = $sel.Server $txtPath.Text = $sel.RemotePath } $combo.Add_SelectedIndexChanged({ Update-FromEnvironmentSelection }) $form.Controls.AddRange(@( $lblEnv, $combo, $lblGit, $txtGit, $lblBranch, $txtBranch, $lblHost, $txtHost, $lblUser, $txtUser, $lblPath, $txtPath, $btnOk, $btnCancel, $status )) $btnCancel.Add_Click({ $form.DialogResult = 'Cancel'; $form.Close() }) $btnOk.Add_Click({ try { $idx = $combo.SelectedIndex if ($idx -lt 0) { throw '请选择环境。' } $envItem = $Environments[$idx] $hostAddr = $txtHost.Text.Trim() $user = $txtUser.Text.Trim() $remotePath = $txtPath.Text.Trim() $repoUrl = $txtGit.Text.Trim() $branch = $txtBranch.Text.Trim() if (-not $repoUrl) { throw '仓库 URL 不能为空。' } if (-not $branch) { throw '分支不能为空。' } $btnOk.Enabled = $false $btnCancel.Enabled = $false Invoke-H5DeployFromGit -Environment $envItem.Key -RepoUrl $repoUrl -Branch $branch ` -User $user -RemotePath $remotePath -TargetHostOverride '' [System.Windows.Forms.MessageBox]::Show( ("部署完成。`n环境:{0}`n仓库:{1} ({2})`n目标:{3}@{4}:{5}" -f ` $envItem.Label, $repoUrl, $branch, $user, $hostAddr, $remotePath), '成功', 'OK', 'Information' ) | Out-Null $form.DialogResult = 'OK' $form.Close() } catch { [System.Windows.Forms.MessageBox]::Show( $_.Exception.Message, '错误', 'OK', 'Error' ) | Out-Null $btnOk.Enabled = $true $btnCancel.Enabled = $true $status.Text = "将克隆:$DefaultGitUrl ($DefaultBranch)`n上传前会删除远程目录内已有文件后再覆盖。" } }) [void] $form.ShowDialog()