docs: 更新需求文档至v0.5并归档开发进度;修复大文件上传与二维码海报
- 需求文档升级到 v0.5:补充二维码海报保存文件名默认格式、大文件上传三层限制(IIS+ASP.NET+前端)部署约束、新增第11章开发进度状态归档(已上线功能/已修复缺陷/已知限制/后续优化) - 后端 FileController.Upload 增加 [RequestFormLimits(MultipartBodyLengthLimit=220_200_960)],修复>128MB multipart 上传被拒 - 前端 UploadView 二维码海报:修正 drawImage 缩放错位、顶部logo/标题垂直居中、分区标题间距、大小与有效期分行显示;保存文件名改为 文传易取件码-取件码(原文件名)-流水号.png - 新增部署脚本:apply_backend.ps1 / force_dll.ps1(app_offline 解锁DLL)/ ftp_fe.ps1 / ftp_diag.ps1 / ftp_apply.ps1 / _diag/check_dll.ps1 (部署手册 IIS部署与FTP发布.md 仅含编码/BOM 差异,未纳入本次提交)
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Download remote DLL + search for the new attribute metadata string
|
||||
$ErrorActionPreference = "Continue"
|
||||
$h="116.198.221.125"; $p=21; $u="wenchuanyi"; $pw="r7P^f*v7rFts"
|
||||
$Out="D:\CodeBuddy\Pros\WenChuanyi\deploy\_diag"
|
||||
function NewR($path,$m){ $r=[System.Net.FtpWebRequest]::Create("ftp://${h}:${p}/"+$path.TrimStart('/')); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($u,$pw); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=60000; return $r }
|
||||
# download remote dll
|
||||
try{
|
||||
$r=NewR "/wwwroot/WenChuanyi.Api.dll" ([System.Net.WebRequestMethods+Ftp]::DownloadFile)
|
||||
$resp=$r.GetResponse(); $fs=[IO.File]::Create("$Out\server-WenChuanyi.Api.dll")
|
||||
$s=$resp.GetResponseStream(); $b=New-Object byte[] 65536
|
||||
while(($n=$s.Read($b,0,65536)) -gt 0){ $fs.Write($b,0,$n) }
|
||||
$fs.Close(); $s.Close(); $resp.Close()
|
||||
$remote=Get-Item "$Out\server-WenChuanyi.Api.dll"
|
||||
$local=Get-Item "D:\CodeBuddy\Pros\WenChuanyi\backend\WenChuanyi.Api\bin\Release\net8.0\publish\WenChuanyi.Api.dll"
|
||||
Write-Host "remote dll len=$($remote.Length) mtime=$($remote.LastWriteTime)"
|
||||
Write-Host "local dll len=$($local.Length) mtime=$($local.LastWriteTime)"
|
||||
$bytes=[IO.File]::ReadAllBytes($remote.FullName)
|
||||
$ascii=[Text.Encoding]::ASCII.GetString($bytes)
|
||||
Write-Host "remote has 'RequestFormLimits': $($ascii.Contains('RequestFormLimitsAttribute'))"
|
||||
$lb=[IO.File]::ReadAllBytes($local.FullName)
|
||||
$lascii=[Text.Encoding]::ASCII.GetString($lb)
|
||||
Write-Host "local has 'RequestFormLimits': $($lascii.Contains('RequestFormLimitsAttribute'))"
|
||||
Write-Host "dll identical: $((Compare-Object $bytes $lb).Count -eq 0)"
|
||||
}catch{ Write-Host "ERR $($_.Exception.Message)" }
|
||||
@@ -0,0 +1,78 @@
|
||||
# Apply new backend build via app_offline.htm (unlock inprocess DLLs) + full sync
|
||||
$ErrorActionPreference = "Continue"
|
||||
$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts"
|
||||
$Root="D:\CodeBuddy\Pros\WenChuanyi"
|
||||
$PublishDir = "$Root\backend\WenChuanyi.Api\bin\Release\net8.0\publish"
|
||||
$DistDir = "$Root\frontend\dist"
|
||||
$Stage = Join-Path $env:TEMP "wcy_publish_staging"
|
||||
$RemoteRoot="/wwwroot"
|
||||
$Log = "$Root\deploy\apply.log"
|
||||
$fail=$false
|
||||
Set-Content -Path $Log -Value "=== apply start $(Get-Date -Format 'yyyy-MM-dd 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 4;$i++){
|
||||
try{
|
||||
$r=New-FtpReq $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 300000
|
||||
$b=[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)"
|
||||
}catch{ L "RETRY($i) $rp : $($_.Exception.Message)" }
|
||||
Start-Sleep -Seconds 4
|
||||
}
|
||||
L "FAIL $rp"; $script:fail=$true
|
||||
}
|
||||
function DelF([string]$rp){
|
||||
try{ $r=New-FtpReq $rp ([System.Net.WebRequestMethods+Ftp]::DeleteFile)
|
||||
$resp=$r.GetResponse(); try{$resp.Close()}catch{}; L "DEL $rp" }catch{ L "DEL skip $rp" }
|
||||
Start-Sleep -Milliseconds 800
|
||||
}
|
||||
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)" }
|
||||
}
|
||||
# 1. stage
|
||||
if(Test-Path $Stage){ Remove-Item $Stage -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $Stage | Out-Null
|
||||
Copy-Item "$DistDir\*" $Stage -Recurse -Force
|
||||
Copy-Item "$PublishDir\*" $Stage -Recurse -Force
|
||||
L "stage files: $((Get-ChildItem $Stage -File -Recurse).Count)"
|
||||
# 2. app_offline to stop app & unlock DLLs
|
||||
Set-Content -Path "$Stage\app_offline.htm" -Value "<html><body>maintenance</body></html>" -Encoding UTF8
|
||||
UpFile "$Stage\app_offline.htm" "$RemoteRoot/app_offline.htm"
|
||||
L "wait 6s for app shutdown..."
|
||||
Start-Sleep -Seconds 6
|
||||
# 3. full sync
|
||||
UpDir $Stage $RemoteRoot
|
||||
# 4. remove app_offline -> app auto-restart
|
||||
DelF "$RemoteRoot/app_offline.htm"
|
||||
Remove-Item $Stage -Recurse -Force
|
||||
if($fail){ L "RESULT=PARTIAL_FAIL" } else { L "RESULT=OK" }
|
||||
L "=== apply end $(Get-Date -Format 'HH:mm:ss') ==="
|
||||
@@ -0,0 +1,24 @@
|
||||
# Force-overwrite WenChuanyi.Api.dll via app_offline (dll size identical -> size-check skipped it)
|
||||
$ErrorActionPreference = "Continue"
|
||||
$h="116.198.221.125"; $p=21; $u="wenchuanyi"; $pw="r7P^f*v7rFts"
|
||||
$LocalDll="D:\CodeBuddy\Pros\WenChuanyi\backend\WenChuanyi.Api\bin\Release\net8.0\publish\WenChuanyi.Api.dll"
|
||||
$Log="D:\CodeBuddy\Pros\WenChuanyi\deploy\force_dll.log"
|
||||
Set-Content $Log "=== force dll start $(Get-Date -Format 'HH:mm:ss') ===" -Encoding UTF8
|
||||
function L([string]$m){ Add-Content $Log $m -Encoding UTF8; Write-Host $m }
|
||||
function NewR([string]$path,[string]$m,[int]$t=90000){ $r=[System.Net.FtpWebRequest]::Create("ftp://${h}:${p}/"+$path.TrimStart('/')); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($u,$pw); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=$t; return $r }
|
||||
function UpRaw([string]$lp,[string]$rp){
|
||||
$b=[IO.File]::ReadAllBytes($lp); $r=NewR $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 300000
|
||||
$r.ContentLength=$b.Length; $s=$r.GetRequestStream(); try{ $s.Write($b,0,$b.Length) } finally { $s.Close() }
|
||||
$resp=$r.GetResponse(); try{ $resp.Close() }catch{}; L "UP $rp len=$($b.Length)"
|
||||
}
|
||||
# 1. stop app
|
||||
Set-Content "$env:TEMP\app_offline.htm" "maintenance" -Encoding UTF8
|
||||
try{ UpRaw "$env:TEMP\app_offline.htm" "/wwwroot/app_offline.htm" }catch{ L "offline up err: $($_.Exception.Message)" }
|
||||
L "wait 7s..."; Start-Sleep -Seconds 7
|
||||
# 2. force dll
|
||||
try{ UpRaw $LocalDll "/wwwroot/WenChuanyi.Api.dll"; L "DLL FORCE-UP OK" }catch{ L "DLL FAIL: $($_.Exception.Message)" }
|
||||
Start-Sleep -Seconds 1
|
||||
# 3. remove app_offline -> restart
|
||||
try{ $r=NewR "/wwwroot/app_offline.htm" ([System.Net.WebRequestMethods+Ftp]::DeleteFile); $resp=$r.GetResponse(); try{$resp.Close()}catch{}; L "app_offline removed (restarting)" }catch{ L "offline del err: $($_.Exception.Message)" }
|
||||
Remove-Item "$env:TEMP\app_offline.htm" -ErrorAction SilentlyContinue
|
||||
L "=== force dll end ==="
|
||||
@@ -0,0 +1,55 @@
|
||||
# Apply: new appsettings.json (localhost) + restore web.config (stdout off, triggers restart) + nested assets
|
||||
$ErrorActionPreference = "Continue"
|
||||
$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts"
|
||||
$Local = "D:\CodeBuddy\Pros\WenChuanyi\backend\WenChuanyi.Api\bin\Release\net8.0\publish"
|
||||
$Dist = "D:\CodeBuddy\Pros\WenChuanyi\frontend\dist"
|
||||
function NewR($p,$m,$to=90000){ $u="ftp://${FtpHost}:${FtpPort}/"+$p.TrimStart('/'); $r=[System.Net.FtpWebRequest]::Create($u); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=$to; $r }
|
||||
function Mk($p){
|
||||
try{ $r=NewR $p ([System.Net.WebRequestMethods+Ftp]::MakeDirectory); $resp=$r.GetResponse(); try{$resp.Close()}catch{} }catch{}
|
||||
Start-Sleep -Milliseconds 300
|
||||
}
|
||||
function GetSizeF($p){
|
||||
try{ $r=NewR $p ([System.Net.WebRequestMethods+Ftp]::GetFileSize) 30000; $resp=$r.GetResponse(); $s=$resp.ContentLength; $resp.Close(); return $s }
|
||||
catch{ return -1 }
|
||||
}
|
||||
function UpF($lp,$rp){
|
||||
$want=(Get-Item $lp).Length
|
||||
for($i=1;$i -le 4;$i++){
|
||||
try{
|
||||
$r=NewR $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 300000
|
||||
$b=[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 -Milliseconds 300
|
||||
$after=GetSizeF $rp
|
||||
if($after -eq $want){ Write-Host "UP OK $rp"; return $true }
|
||||
Write-Host "SIZE MISMATCH $rp got=$after want=$want"
|
||||
}catch{ Write-Host "RETRY($i) $rp : $($_.Exception.Message)" }
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
Write-Host "FAIL $rp"; return $false
|
||||
}
|
||||
# 1. appsettings.json (localhost db)
|
||||
UpF "$Local\appsettings.json" "/wwwroot/appsettings.json"
|
||||
# 2. web.config: stdout off (production) - also triggers app pool restart
|
||||
$wc = @"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<location path="." inheritInChildApplications="false">
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
|
||||
</handlers>
|
||||
<aspNetCore processPath="dotnet" arguments=".\WenChuanyi.Api.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
|
||||
</system.webServer>
|
||||
</location>
|
||||
</configuration>
|
||||
"@
|
||||
$tmp = Join-Path $env:TEMP "wcy_web_prod.config"
|
||||
[IO.File]::WriteAllText($tmp, $wc, [Text.Encoding]::UTF8)
|
||||
UpF $tmp "/wwwroot/web.config"
|
||||
# 3. nested wwwroot assets (frontend static files served by ASP.NET Core WebRoot)
|
||||
Mk "/wwwroot/wwwroot/assets"
|
||||
UpF "$Dist\assets\index-Bwz67ZeI.js" "/wwwroot/wwwroot/assets/index-Bwz67ZeI.js"
|
||||
UpF "$Dist\assets\index-c9wa7v4K.css" "/wwwroot/wwwroot/assets/index-c9wa7v4K.css"
|
||||
Write-Host "APPLY DONE"
|
||||
@@ -0,0 +1,29 @@
|
||||
# Download web.config + list logs dir for upload-failure diagnosis
|
||||
$ErrorActionPreference = "Continue"
|
||||
$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts"
|
||||
$Out = "D:\CodeBuddy\Pros\WenChuanyi\deploy\_diag"
|
||||
New-Item -ItemType Directory -Force -Path $Out | Out-Null
|
||||
function NewR($p,$m,$to=60000){ $u="ftp://${FtpHost}:${FtpPort}/"+$p.TrimStart('/'); $r=[System.Net.FtpWebRequest]::Create($u); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=$to; $r }
|
||||
function Down($rp,$lp){
|
||||
try{
|
||||
$r=NewR $rp ([System.Net.WebRequestMethods+Ftp]::DownloadFile)
|
||||
$resp=$r.GetResponse(); $fs=[IO.File]::Create($lp)
|
||||
$s=$resp.GetResponseStream(); $b=New-Object byte[] 65536
|
||||
while(($n=$s.Read($b,0,65536)) -gt 0){ $fs.Write($b,0,$n) }
|
||||
$fs.Close(); $s.Close(); $resp.Close()
|
||||
Write-Host "DOWN OK $rp -> $lp"
|
||||
}catch{ Write-Host "DOWN FAIL $rp : $($_.Exception.Message)" }
|
||||
}
|
||||
function ListF($p){
|
||||
try{
|
||||
$r=NewR $p ([System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails)
|
||||
$resp=$r.GetResponse(); $sr=New-Object IO.StreamReader($resp.GetResponseStream(),[Text.Encoding]::UTF8)
|
||||
$txt=$sr.ReadToEnd(); $sr.Close(); $resp.Close()
|
||||
return $txt
|
||||
}catch{ return "ERR $($_.Exception.Message)" }
|
||||
}
|
||||
Down "/wwwroot/web.config" "$Out\server-web.config"
|
||||
Write-Host "=== logs dir ==="
|
||||
(ListF "/wwwroot/logs/") | ForEach-Object { Write-Host $_ }
|
||||
Write-Host "=== wwwroot dir ==="
|
||||
(ListF "/wwwroot/") | ForEach-Object { Write-Host $_ }
|
||||
@@ -0,0 +1,41 @@
|
||||
# Upload frontend build (nested wwwroot) - overwrite index.html, upload new assets, delete old ones
|
||||
$ErrorActionPreference = "Continue"
|
||||
$FtpHost="116.198.221.125"; $FtpPort=21; $FtpUser="wenchuanyi"; $FtpPass="r7P^f*v7rFts"
|
||||
$Dist = "D:\CodeBuddy\Pros\WenChuanyi\frontend\dist"
|
||||
function NewR($p,$m,$to=90000){ $u="ftp://${FtpHost}:${FtpPort}/"+$p.TrimStart('/'); $r=[System.Net.FtpWebRequest]::Create($u); $r.Method=$m; $r.Credentials=New-Object System.Net.NetworkCredential($FtpUser,$FtpPass); $r.UsePassive=$true; $r.UseBinary=$true; $r.KeepAlive=$false; $r.Timeout=$to; $r }
|
||||
function GetSizeF($p){
|
||||
try{ $r=NewR $p ([System.Net.WebRequestMethods+Ftp]::GetFileSize) 30000; $resp=$r.GetResponse(); $s=$resp.ContentLength; $resp.Close(); return $s }
|
||||
catch{ return -1 }
|
||||
}
|
||||
function DelF($p){
|
||||
try{ $r=NewR $p ([System.Net.WebRequestMethods+Ftp]::DeleteFile); $resp=$r.GetResponse(); try{$resp.Close()}catch{}; Write-Host "DEL $p" }
|
||||
catch{ Write-Host "DEL skip $p (not exist or locked)" }
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
function UpF($lp,$rp){
|
||||
$want=(Get-Item $lp).Length
|
||||
DelF $rp
|
||||
Start-Sleep -Seconds 1
|
||||
for($i=1;$i -le 4;$i++){
|
||||
try{
|
||||
$r=NewR $rp ([System.Net.WebRequestMethods+Ftp]::UploadFile) 300000
|
||||
$b=[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 -Milliseconds 500
|
||||
$after=GetSizeF $rp
|
||||
if($after -eq $want){ Write-Host "UP OK $rp ($after bytes)"; return $true }
|
||||
Write-Host "SIZE MISMATCH $rp got=$after want=$want"
|
||||
}catch{ Write-Host "RETRY($i) $rp : $($_.Exception.Message)" }
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
Write-Host "FAIL $rp"; return $false
|
||||
}
|
||||
# upload new dist
|
||||
$ok=$true
|
||||
$ok=(UpF "$Dist\index.html" "/wwwroot/wwwroot/index.html") -and $ok
|
||||
$ok=(UpF "$Dist\assets\index-DmgEpwV6.js" "/wwwroot/wwwroot/assets/index-DmgEpwV6.js") -and $ok
|
||||
$ok=(UpF "$Dist\assets\index-BjMMI5BR.css" "/wwwroot/wwwroot/assets/index-BjMMI5BR.css") -and $ok
|
||||
# remove stale old-hash assets
|
||||
DelF "/wwwroot/wwwroot/assets/index-C2wmSVTP.js"
|
||||
if($ok){ Write-Host "FE DEPLOY OK" } else { Write-Host "FE DEPLOY PARTIAL" }
|
||||
Reference in New Issue
Block a user