From 2e3f68472edee9f9647a133e51c4c51198d62163 Mon Sep 17 00:00:00 2001 From: fanhongcai Date: Mon, 24 Aug 2026 00:53:41 +0800 Subject: [PATCH] =?UTF-8?q?deploy:=20=E4=BF=AE=E6=AD=A3FTP=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E7=9B=AE=E6=A0=87=E4=B8=BA/wwwroot=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E4=B8=8A=E4=BC=A0=E5=81=A5=E5=A3=AE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RemoteRoot 固定为 /wwwroot (IIS 站点物理根), 网页必须存放于 wwwroot 才可访问 - ftp_upload.ps1: 每文件独立连接、上传后校验大小、3次重试、日志记录 - 新增 ftp_fast.ps1 (快速覆盖上传) 与 ftp_sync.ps1 (仅补传缺失/大小不符文件) - 上传前删除服务商默认欢迎页 default.html, 避免优先于 index.html --- deploy/ftp_fast.ps1 | 31 +++++++++++++++ deploy/ftp_sync.ps1 | 70 ++++++++++++++++++++++++++++++++++ deploy/ftp_upload.ps1 | 88 +++++++++++++++++++++++++------------------ deploy/publish.ps1 | 13 ++++++- 4 files changed, 164 insertions(+), 38 deletions(-) create mode 100644 deploy/ftp_fast.ps1 create mode 100644 deploy/ftp_sync.ps1 diff --git a/deploy/ftp_fast.ps1 b/deploy/ftp_fast.ps1 new file mode 100644 index 0000000..add9622 --- /dev/null +++ b/deploy/ftp_fast.ps1 @@ -0,0 +1,31 @@ +# Minimal direct upload (no size check) - fast overwrite upload +$FtpHost="116.198.221.125"; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts" +$Stage = Join-Path $env:TEMP "wcy_publish_staging" +$Log = "D:\CodeBuddy\Pros\WenChuanyi\deploy\ftp_fast.log" +Set-Content -Path $Log -Value "start $(Get-Date -Format HH:mm:ss)" -Encoding UTF8 +function L([string]$m){ Add-Content -Path $Log -Value $m -Encoding UTF8 } +function NewR($p,$m){ $r=[System.Net.FtpWebRequest]::Create("ftp://${FtpHost}/$p"); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=120000; $r } +function Mk($p){ $r=NewR $p ([System.Net.WebRequestMethods+Ftp]::MakeDirectory); try{$null=$r.GetResponse()}catch{} } +function Put($lp,$rp){ + for($i=1;$i -le 3;$i++){ + try{ + $r=NewR $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) + $b=[IO.File]::ReadAllBytes($lp) + $r.ContentLength=$b.Length + $s=$r.GetRequestStream(); $s.Write($b,0,$b.Length); $s.Close() + $x=$r.GetResponse(); $x.Close() + L "OK $rp"; return + }catch{ L "RETRY($i) $rp : $($_.Exception.Message)"; Start-Sleep 3 } + } + L "FAIL $rp" +} +function Up($ld,$rd){ + foreach($d in Get-ChildItem $ld -Directory){ + # skip Kerberos native dir - rejected by server, not needed on Windows + if($d.FullName -match "win-x64[\\/]native$"){ L "SKIPDIR $($d.FullName)"; continue } + $c="$rd/$($d.Name)"; Mk $c; Up $d.FullName $c + } + foreach($f in Get-ChildItem $ld -File){ Put $f.FullName "$rd/$($f.Name)" } +} +Up $Stage "/wwwroot" +L "DONE $(Get-Date -Format HH:mm:ss)" diff --git a/deploy/ftp_sync.ps1 b/deploy/ftp_sync.ps1 new file mode 100644 index 0000000..d7624f0 --- /dev/null +++ b/deploy/ftp_sync.ps1 @@ -0,0 +1,70 @@ +# Sync-only upload: upload files that are missing or size-mismatched on FTP +$ErrorActionPreference = "Continue" +$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts" +$RemoteRoot="/wwwroot" +$Stage = Join-Path $env:TEMP "wcy_publish_staging" +$Log = "D:\CodeBuddy\Pros\WenChuanyi\deploy\ftp_sync.log" +$fail = $false +Set-Content -Path $Log -Value "=== sync start $(Get-Date -Format 'HH:mm:ss') ===" -Encoding UTF8 + +function L([string]$m){ Add-Content -Path $Log -Value $m -Encoding UTF8 } +function New-FtpReq([string]$path,[string]$method,[int]$timeoutMs=90000){ + $uri="ftp://${FtpHost}:${FtpPort}/"+$path.TrimStart('/') + $r=[System.Net.FtpWebRequest]::Create($uri) + $r.Method=$method + $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass) + $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false + $r.Timeout=$timeoutMs + return $r +} +function Get-FtpSize([string]$p){ + for($i=0;$i -lt 3;$i++){ + try{ + $r=New-FtpReq $p ([System.Net.WebRequestMethods+Ftp]::GetFileSize) 30000 + $resp=$r.GetResponse(); $s=$resp.ContentLength; $resp.Close() + return $s + }catch{ Start-Sleep -Seconds 2 } + } + return -1 +} +function UpFile([string]$lp,[string]$rp){ + $localSize=(Get-Item $lp).Length + $remoteSize=Get-FtpSize $rp + if($remoteSize -eq $localSize){ L "SKIP $rp"; return } + L "UPLOAD $rp (remote=$remoteSize local=$localSize)" + for($i=1;$i -le 3;$i++){ + try{ + $r=New-FtpReq $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 180000 + $b=[System.IO.File]::ReadAllBytes($lp) + $r.ContentLength=$b.Length + $s=$r.GetRequestStream() + try{ $s.Write($b,0,$b.Length) } finally { $s.Close() } + $resp=$r.GetResponse(); try{ $resp.Close() }catch{} + Start-Sleep -Seconds 1 + $after=Get-FtpSize $rp + if($after -eq $localSize){ L "OK $rp"; return } + L "VERIFYFAIL $rp (got $after)"; continue + }catch{ + L "RETRY($i) $rp : $($_.Exception.Message)" + Start-Sleep -Seconds 5 + } + } + L "FAIL $rp" + $script:fail=$true +} +function UpDir([string]$ld,[string]$rd){ + foreach($d in Get-ChildItem $ld -Directory){ + $c="$rd/$($d.Name)" + $mr=New-FtpReq $c ([System.Net.WebRequestMethods+Ftp]::MakeDirectory) 30000 + try{ $null=$mr.GetResponse() }catch{} + UpDir $d.FullName $c + } + foreach($f in Get-ChildItem $ld -File){ + UpFile $f.FullName "$rd/$($f.Name)" + } +} + +if(-not (Test-Path $Stage)){ L "STAGING_MISSING"; exit 1 } +UpDir $Stage $RemoteRoot +if($fail){ L "RESULT=PARTIAL_FAIL" } else { L "RESULT=OK" } +L "=== sync end $(Get-Date -Format 'HH:mm:ss') ===" diff --git a/deploy/ftp_upload.ps1 b/deploy/ftp_upload.ps1 index 2b1dc28..7382f28 100644 --- a/deploy/ftp_upload.ps1 +++ b/deploy/ftp_upload.ps1 @@ -1,59 +1,73 @@ -# FTP 上传脚本 (UTF-8 BOM) - 健壮版 -$FtpHost="116.198.221.125" -$FtpPort=21 -$FtpUser="wenchuanyi" -$FtpPass="r7P^f*v7rFts" -$RemoteRoot="/" +# Robust FTP upload: per-file connection, size-check skip, retry +$ErrorActionPreference = "Continue" +[System.Net.ServicePointManager]::DefaultConnectionLimit = 64 +$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts" +$RemoteRoot="/wwwroot" $Stage = Join-Path $env:TEMP "wcy_publish_staging" +$Log = "D:\CodeBuddy\Pros\WenChuanyi\deploy\ftp_upload.log" +$fail = $false -function New-FtpReq([string]$path,[string]$method){ +function L([string]$m){ Add-Content -Path $Log -Value $m -Encoding UTF8 } +function New-FtpReq([string]$path,[string]$method,[int]$timeoutMs=120000){ $uri="ftp://${FtpHost}:${FtpPort}/"+$path.TrimStart('/') $r=[System.Net.FtpWebRequest]::Create($uri) $r.Method=$method $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass) - $r.UsePassive=$true - $r.UseBinary=$true - $r.KeepAlive=$false - $r.Timeout=120000 + $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false + $r.Timeout=$timeoutMs return $r } -function MkDir([string]$p){ - $r=New-FtpReq $p ([System.Net.WebRequestMethods+Ftp]::MakeDirectory) - try{ $null=$r.GetResponse() }catch{} -} -function Exist([string]$p){ - $r=New-FtpReq $p ([System.Net.WebRequestMethods+Ftp]::GetDateTimestamp) - try{ $null=$r.GetResponse(); return $true }catch{ return $false } +function Get-FtpSize([string]$p){ + $r=New-FtpReq $p ([System.Net.WebRequestMethods+Ftp]::GetFileSize) 30000 + try{ $resp=$r.GetResponse(); $s=$resp.ContentLength; $resp.Close(); return $s }catch{ return -1 } } function UpFile([string]$lp,[string]$rp){ - $r=New-FtpReq $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) - $b=[System.IO.File]::ReadAllBytes($lp) - $r.ContentLength=$b.Length - $s=$r.GetRequestStream() - try{ $s.Write($b,0,$b.Length) }finally{ $s.Close() } - $resp=$r.GetResponse() - try{ $resp.Close() }catch{} + $localSize=(Get-Item $lp).Length + $remoteSize=Get-FtpSize $rp + if($remoteSize -eq $localSize){ L "SKIP $rp (size ok)"; return $true } + for($i=1;$i -le 3;$i++){ + try{ + $r=New-FtpReq $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 240000 + $b=[System.IO.File]::ReadAllBytes($lp) + $r.ContentLength=$b.Length + $s=$r.GetRequestStream() + try{ $s.Write($b,0,$b.Length) } finally { $s.Close() } + $resp=$r.GetResponse() + try{ $resp.Close() }catch{} + $after=Get-FtpSize $rp + if($after -eq $localSize){ L "UP $rp"; return $true } + else{ L "VERIFYFAIL $rp (got $after want $localSize)"; continue } + }catch{ + L "RETRY($i) $rp : $($_.Exception.Message)" + Start-Sleep -Seconds 3 + } + } + L "FAIL $rp" + $script:fail=$true + return $false } function UpDir([string]$ld,[string]$rd){ foreach($d in Get-ChildItem $ld -Directory){ $c="$rd/$($d.Name)" - if(-not (Exist $c)){ MkDir $c } + $mr=New-FtpReq $c ([System.Net.WebRequestMethods+Ftp]::MakeDirectory) 30000 + try{ $null=$mr.GetResponse() }catch{} UpDir $d.FullName $c } foreach($f in Get-ChildItem $ld -File){ $rf="$rd/$($f.Name)" - try{ - if(-not (Exist $rf)){ UpFile $f.FullName $rf; Write-Host "UP $rf" } - else { Write-Host "SKIP(exists) $rf" } - }catch{ - Write-Host "RETRY $rf" - try{ UpFile $f.FullName $rf; Write-Host "OK $rf" }catch{ Write-Host "FAIL $rf : $_" } - } + [void](UpFile $f.FullName $rf) } } -if(-not (Test-Path $Stage)){ Write-Host "STAGING_MISSING"; exit 1 } -if(-not (Exist $RemoteRoot)){ MkDir $RemoteRoot } +Set-Content -Path $Log -Value "=== upload start $(Get-Date -Format 'HH:mm:ss') ===" -Encoding UTF8 +if(-not (Test-Path $Stage)){ L "STAGING_MISSING"; exit 1 } + +# delete welcome page first +$dr=New-FtpReq "/wwwroot/default.html" ([System.Net.WebRequestMethods+Ftp]::DeleteFile) 30000 +try{ $null=$dr.GetResponse(); L "DELETED default.html" }catch{ L "default.html not present" } + +$mr=New-FtpReq $RemoteRoot ([System.Net.WebRequestMethods+Ftp]::MakeDirectory) 30000 +try{ $null=$mr.GetResponse() }catch{} UpDir $Stage $RemoteRoot -Write-Host "FTP_UPLOAD_DONE" -Remove-Item $Stage -Recurse -Force +if($fail){ L "RESULT=PARTIAL_FAIL" } else { L "RESULT=OK" } +L "=== upload end ===" diff --git a/deploy/publish.ps1 b/deploy/publish.ps1 index 700b47e..817dbd6 100644 --- a/deploy/publish.ps1 +++ b/deploy/publish.ps1 @@ -4,6 +4,9 @@ # 2. npm run build 前端 (Vite, dist) # 3. FTP 上传合并产物至 IIS 站点根目录 # +# 重要: 官方提示 —— 网页必需存放在 wwwroot 目录下才能被访问! +# 因此 RemoteRoot 固定为 /wwwroot (FTP 上的目录, 即 IIS 站点物理根) +# # 用法: # powershell -ExecutionPolicy Bypass -File deploy/publish.ps1 # @@ -14,7 +17,7 @@ param( [int] $FtpPort = 21, [string]$FtpUser = "wenchuanyi", [string]$FtpPass = "r7P^f*v7rFts", - [string]$RemoteRoot = "/", # FTP 远端目标目录 (IIS 站点根目录) + [string]$RemoteRoot = "/wwwroot", # FTP 远端目标目录 = IIS 站点根目录 (勿改!) [switch]$SkipBuild # 跳过构建, 仅上传已有产物 ) @@ -106,6 +109,14 @@ function Send-FtpDirectory([string]$localDir, [string]$remoteDir) { } # ---------------- 上传 ---------------- +# 删除服务商默认欢迎页 default.html, 避免 IIS 默认文档优先于 index.html +function Remove-FtpFile([string]$remotePath) { + $req = New-FtpRequest $remotePath ([System.Net.WebRequestMethods+Ftp]::DeleteFile) + try { $null = $req.GetResponse(); Write-Host " DEL $remotePath" -ForegroundColor DarkGray } + catch { } +} +Remove-FtpFile "$RemoteRoot/default.html" + New-FtpDirectory $RemoteRoot Send-FtpDirectory $StagingDir $RemoteRoot