From a30da88d047922c1a8bc9b6ece58038c34abee2b Mon Sep 17 00:00:00 2001 From: fanhongcai Date: Mon, 24 Aug 2026 02:04:40 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=83=A8=E7=BD=B2=E6=89=8B=E5=86=8C?= =?UTF-8?q?=E6=8C=89=E7=BA=BF=E4=B8=8A=E5=AE=9E=E6=88=98=E6=8E=92=E9=94=99?= =?UTF-8?q?=E6=A0=A1=E6=AD=A3=EF=BC=88=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= =?UTF-8?q?/=E6=95=85=E9=9A=9C=E6=8E=92=E6=9F=A5=E9=80=9F=E6=9F=A5/500.30?= =?UTF-8?q?=E6=8E=92=E6=9F=A5=E6=B5=81=E7=A8=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增第0章目录结构认知:IIS 站点根=ContentRoot,前端产物需传嵌套 wwwroot/wwwroot,后端产物传 /wwwroot/ - 新增故障排查速查:500.30(publish漏传DLL/外来runtime文件/数据库连不上)、404静态资源、502 outofprocess、FTP 550(文件被IIS锁定)、FTP限流、配置不生效 - 新增500.30标准排查流程(开stdout/比对文件清单/检查目录结构) - 更新发布方式(FTP 端口21、密码同数据库密码)与部署架构说明,按 2026-08-24 线上实战经验校正 --- deploy/IIS部署与FTP发布.md | 211 +++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 103 deletions(-) diff --git a/deploy/IIS部署与FTP发布.md b/deploy/IIS部署与FTP发布.md index 2dec030..93185b1 100644 --- a/deploy/IIS部署与FTP发布.md +++ b/deploy/IIS部署与FTP发布.md @@ -2,161 +2,166 @@ > 目标环境:Windows Server + IIS 10 + .NET 8 Hosting Bundle > 站点地址:`https://wenchuanyi.bbitcn.net` -> 发布方式:FTP(`ftp://116.198.221.125`,默认端口 21,用户 `wenchuanyi`) +> 发布方式:FTP(`ftp://116.198.221.125`,端口 21,用户 `wenchuanyi`,密码同数据库密码) +> **本文档已按 2026-08-24 线上实战排错结果校正,照此执行可避免再踩坑。** -> **当前状态(2026-08-23)**:本地全流程已通过冒烟测试—— -> 上传(共享/私密/标签,含 200MB 拦截)→ 二维码与取件码 → 取件查询/下载(计数)→ 标签列表 → 管理列表(含上传 IP)/删除,均已验证;数据库表已建、OSS 真实凭据已写入 `appsettings.json`(该文件不入库)。服务器上首次发布后,建议用手机微信扫码访问正式站再做一轮真机验证(含微信聊天记录选文件)。 +--- + +## 0. 目录结构(最重要的认知,务必先看) + +IIS 站点物理根 = **ContentRoot**(应用工作目录),也是后端产物所在目录; +ASP.NET Core 的静态文件 WebRoot = **ContentRoot\wwwroot(嵌套)**。 + +实测服务器目录映射(FTP 的 `/wwwroot` ↔ 磁盘 `D:\wwwroot\wenchuanyi\wwwroot`): + +``` +D:\wwwroot\wenchuanyi\wwwroot\ ← FTP /wwwroot (ContentRoot,后端产物 + web.config) + ├── web.config ← inprocess + dotnet(见第 3 节) + ├── WenChuanyi.Api.dll / .exe / deps.json / runtimeconfig.json + ├── appsettings.json ← 数据库 localhost / OSS / 上传限制 + ├── *.dll(MySql.Data / BouncyCastle / ZstdSharp …) ← 缺一不可! + └── wwwroot\ ← FTP /wwwroot/wwwroot (嵌套 WebRoot,前端产物!) + ├── index.html + └── assets\index-*.js / *.css +``` + +> **规则:后端产物传 `/wwwroot/`,前端产物传 `/wwwroot/wwwroot/`(嵌套)。** +> 前端产物若只放站点根(`/wwwroot/`),页面能 200(IIS 默认文档/fallback)但 `/assets/*.js` 必然 404。 --- ## 1. 部署架构 ``` -浏览器 - │ https://wenchuanyi.bbitcn.net +浏览器 → https://wenchuanyi.bbitcn.net ▼ -IIS 站点(物理路径 = 站点根目录) - ├── index.html / assets/* ← 前端 dist 构建产物 - ├── web.config ← 由 dotnet publish 自动生成(ANCM InProcess) - ├── WenChuanyi.Api.dll ← 后端发布输出 - └── appsettings.json ← 数据库连接串 / OSS 凭据 / 上传限制 +IIS + ANCM(AspNetCoreModuleV2,InProcess) + ├── /assets/*、/ → ASP.NET Core UseDefaultFiles+UseStaticFiles(只读嵌套 WebRoot) + ├── /#/pickup 等 SPA → MapFallbackToFile("index.html") 兜底(hash 路由) + └── /api、/api/open → 后端控制器 +数据库:MySQL 同机 localhost:3306(库/用户 wenchuanyi) +存储:阿里云 OSS(凭据在 appsettings.json,文件不入库) ``` -- 前端静态资源与后端发布输出**放在同一 IIS 站点物理路径**(默认文件夹或 `wwwroot`)。 -- 后端 `UseStaticFiles` 提供前端静态文件,`MapFallbackToFile("index.html")` 兜底 SPA 路由; - `/api` 与 `/api/open` 由 ASP.NET Core Module(ANCM)直接处理。 -- 站点根目录布局见上;后端发布产物全部文件与前端 `dist/` 内容合并进同一目录。 - --- ## 2. 服务器一次性准备(首次部署前) -1. **安装 .NET 8 Hosting Bundle** - - 下载:https://dotnet.microsoft.com/download/dotnet/8.0(选择 **Hosting Bundle**) - - 安装后 IIS 中会出现 **ASP.NET Core Module v2**。 -2. **创建 IIS 站点** - - IIS → 右键「网站」→ 添加网站; - - 站点名称:`wenchuanyi`; - - 物理路径:`D:\wenchuanyi`(或任意磁盘,站点根目录); - - 端口 80(HTTP)→ 后续绑定 443 + 证书,域名 `wenchuanyi.bbitcn.net`; - - 应用程序池:.NET CLR 版本选「**无托管代码**」(InProcess 托管由 ANCM 处理)。 -3. **HTTPS 证书**:为 `wenchuanyi.bbitcn.net` 绑定 SSL 证书(企业已有证书或申请免费证书)。 -4. **防火墙**:放行 80 / 443(及测试期 5280)。 - -> 排障:若站点 502.5 / 500.30,临时开启 web.config 中的 `stdoutLogEnabled="true"` 查看 `stdoutLog` 输出。 +1. **安装 .NET 8 Hosting Bundle**(dotnet.microsoft.com/download/dotnet/8.0)。 +2. **创建 IIS 站点**:物理路径指向 `D:\wwwroot\wenchuanyi\wwwroot`(后端产物目录); + 应用程序池 .NET CLR 版本选「**无托管代码**」(InProcess 由 ANCM 托管)。 +3. **HTTPS 证书**:为 `wenchuanyi.bbitcn.net` 绑定 SSL 证书。 +4. **防火墙**:放行 80 / 443(测试期 5280)。 --- -## 3. 构建发布包(开发机执行) +## 3. web.config(不要乱改) -### 3.1 后端 +`dotnet publish` 自动生成的**默认配置**就是正确配置,保持原样: + +```xml + +``` + +- ✅ **必须 `inprocess + dotnet`**(Framework-dependent 标准)。 +- ❌ 不要改成 `outofprocess + exe`——实测会导致 502/runtime 探测失败。 +- ✅ `stdoutLogEnabled` 平时为 false;排障时才临时开 true,**查完必须改回 false**(改 web.config 会触发应用重启)。 + +--- + +## 4. 构建发布包(开发机) ```powershell +# 后端 cd backend/WenChuanyi.Api -dotnet publish -c Release -``` +dotnet publish -c Release # 输出 bin/Release/net8.0/publish/ -- 输出目录:`bin/Release/net8.0/publish/` -- Framework-dependent 模式,web.config 自动生成(`hostingModel="inprocess"`)。 -- 发布前确认 `appsettings.json` 已配置真实凭据(见第 4 节)。 - -### 3.2 前端 - -```powershell +# 前端 cd frontend -npm install # 首次 -npm run build +npm run build # 输出 dist/(index.html + assets/) ``` -- 输出目录:`frontend/dist/`(index.html + assets/*)。 -- 构建目标 ES2018,兼容微信 X5 内核。 +`publish/` 目录**所有文件**都要上传(约 24 个文件 / 12.4MB),框架依赖模式**不含** coreclr.dll 等 runtime 文件。 --- -## 4. 敏感配置(appsettings.json) +## 5. 敏感配置(appsettings.json,不入库) -`backend/WenChuanyi.Api/appsettings.json` 中需填写真实值,**该文件已在 .gitignore 中,不会入库**: - -| 配置节 | 键 | 说明 | +| 配置节 | 键 | 值 | | --- | --- | --- | -| `ConnectionStrings:MySql` | Password | 数据库密码(`r7P^f*v7rFts`) | -| `Oss` | AccessKeyId | 阿里云 OSS AK | -| `Oss` | AccessKeySecret | 阿里云 OSS SK | -| `OpenApi` | UploadRoot | 公开 API 可读取的服务端目录白名单 | -| `App` | BaseUrl | 站点地址,用于生成取件页链接(默认 `https://wenchuanyi.bbitcn.net`) | +| `ConnectionStrings:MySql` | Data Source | **`localhost`**(MySQL 与站点同机,勿用公网 IP 回环) | +| `ConnectionStrings:MySql` | Password | `r7P^f*v7rFts` | +| `Oss` | AccessKeyId / Secret | 阿里云 OSS 凭据 | +| `App` | BaseUrl | `https://wenchuanyi.bbitcn.net` | -- 连接串密码含 `^`、`*`,不含 `;` / `=`,无需特殊转义,JSON 原样写入即可。 -- 模板参考:`appsettings.example.json`(占位符 `YOUR_DB_PASSWORD` / `YOUR_ACCESS_KEY_ID` / `YOUR_ACCESS_KEY_SECRET`)。 +改连接串后**必须重启应用**(上传 web.config 或回收应用池)才生效——FreeSql 连接字符串启动时固定。 --- -## 5. FTP 发布 +## 6. FTP 发布 -### 5.1 手动发布 - -用任意 FTP 客户端(FileZilla / WinSCP 等,**被动模式**)登录: - -- 主机:`116.198.221.125` -- 端口:`21` -- 用户:`wenchuanyi` -- 密码:同数据库密码 - -上传清单(目标 = IIS 站点根目录,如 `D:\wenchuanyi`): +### 6.1 上传清单(位置错了就会出上面的 404/500) ``` -前端 dist/ 全部文件 → 站点根目录 -后端 bin/Release/net8.0/publish/* → 站点根目录(覆盖) +后端 publish/ 全部文件(web.config、appsettings.json、*.dll、*.exe、runtimes/) → FTP /wwwroot/ +前端 dist/index.html + assets/ 全部 → FTP /wwwroot/wwwroot/ ``` -> 注意:web.config、appsettings.json 等必须位于站点根目录,勿放入子文件夹。 +### 6.2 脚本 -### 5.2 一键脚本发布 +- `deploy/ftp_upload.ps1`:完整发布(后端 publish + 前端 build + FTP 递归上传)。 +- `deploy/ftp_fast.ps1`:快速覆盖上传当前产物。 +- `deploy/ftp_sync.ps1`:只补传缺失/大小不符的文件(推荐增量修复)。 +- `deploy/ftp_apply.ps1`:定向更新(appsettings / web.config / 嵌套 assets)。 -已提供 `deploy/publish.ps1`,一条命令完成「后端 publish + 前端 build + FTP 上传」: +### 6.3 发布后生效 + +上传 **web.config** 或**回收应用程序池**使新版本生效: +```powershell +C:\Windows\System32\inetsrv\appcmd recycle apppool /apppool.name:wenchuanyi +``` +> 无法远程回收时:重传一次 web.config(内容不变也行)即可触发 ANCM 重启。 + +--- + +## 7. 发布后验证清单(照做) ```powershell -powershell -ExecutionPolicy Bypass -File deploy/publish.ps1 +# 1. 首页 +curl.exe -s -o NUL -w "%{http_code}\n" https://wenchuanyi.bbitcn.net/ +# → 200 +# 2. JS / CSS 静态资源(最关键!之前 404 就出在这) +curl.exe -s -o NUL -w "%{http_code} %{size_download}\n" https://wenchuanyi.bbitcn.net/assets/index-Bwz67ZeI.js +# → 200 1558667 +# 3. API(返回业务校验/JSON 而非 404 即后端存活) +curl.exe -s https://wenchuanyi.bbitcn.net/api/files/by-tag/__probe__ +# → {"message":"标签无效"} HTTP 400(正常业务响应) +# 4. 全流程:上传 → 取件码 → 下载;管理列表可见 ``` -### 5.3 发布后生效 - -- FTP 上传完成后,**回收应用程序池**使新版本生效: - - IIS 管理器 → 应用程序池 → `wenchuanyi` → 右键「回收」; - - 或命令行:`C:\Windows\System32\inetsrv\appcmd recycle apppool /apppool.name:wenchuanyi` -- 浏览器验证 `https://wenchuanyi.bbitcn.net`: - 1. 首页正常显示; - 2. `/#/pickup`、`/#/admin` 路由可访问(hash 路由不受 IIS 影响); - 3. 上传一个测试文件 → 得到取件码 → 取件页下载成功 → 管理码列表可见。 - --- -## 6. 验证清单 +## 8. 故障排查速查(本次实战总结) -| 项 | 验证方式 | 预期 | +| 现象 | 原因 | 处理 | | --- | --- | --- | -| 首页静态资源 | 访问 `/` | 页面正常渲染,无 404 | -| SPA 路由 | 访问 `/#/pickup` | 取件页正常(hash 路由不受 IIS 影响) | -| API | `GET /api/open/download/000000` | 返回 JSON(凭证不存在提示),非 404 | -| 上传下载 | 上传 → 取件 → 下载 | 全流程通过,文件名还原 | -| 在线预览 | 文本/PDF/图片/音视频 | 预览正常 | -| 清理任务 | 查看日志 | 每 30 分钟扫描,无异常报错 | +| **500.30** app failed to start | ① publish 文件**漏传**(本次漏 `MySql.Data.dll`、`K4os.Compression.LZ4.dll`,FreeSql.MySql 运行必需);② 站点根混入外来 runtime 文件(coreclr/hostfxr/hostpolicy/aspnetcorev2_inprocess 等);③ 数据库连不上(启动时 `SyncStructure` 抛错) | 对比本地 `publish/` 与服务器文件清单;删掉外来 runtime 文件;开 stdout 看具体异常 | +| **404 静态资源**(`/assets/*.js`) | 前端产物放错目录——只放站点根没放**嵌套 `wwwroot/wwwroot`** | 前端产物必须传 `/wwwroot/wwwroot/` | +| **502 / outofprocess 探测失败** | web.config 被改成 `outofprocess + exe` | 恢复 `inprocess + dotnet` | +| **FTP 上传 550** | 目标文件已存在且被 IIS 进程锁定;或服务器限流 | **先 DeleteFile 再上传**,或临时名上传 + Rename;串行 + 重试 | +| **FTP 连接频繁中断** | 服务器对高频连接限流 | 所有 FTP 脚本已串行 + 每次间隔 + 校验大小重试 | +| **改配置不生效** | appsettings/代码变了但没重启应用 | 重传 web.config 或回收应用池 | + +### 排查 500.30 的标准流程 + +1. **开 stdout**:web.config 改 `stdoutLogEnabled="true"` → 等待重启 → 下载 `logs/stdout_*.log` 看异常 → 查完改回 false。 +2. **比对文件**:本地 `publish/` 文件清单(名字+大小)vs FTP `/wwwroot/` 清单,找出缺失项。 +3. **看目录结构**:确认前后端产物分别在 `/wwwroot/` 与 `/wwwroot/wwwroot/`,站点根无多余 runtime 文件。 --- -## 7. 常见问题 - -| 现象 | 原因与处理 | -| --- | --- | -| 502.5 / 500.30 | Hosting Bundle 未装或版本不匹配;或 appsettings.json 语法错误。检查事件查看器与 stdoutLog。 | -| 403.14 | 站点根目录无默认文档且 ANCM 未接管——确认 web.config 在站点根目录。 | -| 404 静态资源 | 前端产物未上传到站点根目录,或未启用 `UseStaticFiles`。 | -| API 连接 MySQL 失败 | 服务器 3306 未放行 / 白名单未加服务器出口 IP。 | -| 上传 OSS 失败 | OSS AK/SK 未填或权限不足;确认 Bucket `bbit-f8-web` 有 Put/Get/Delete 权限。 | -| 修改不生效 | 上传后未回收应用程序池。 | - ---- - -## 8. 安全提示 +## 9. 安全提示 - `appsettings.json` 含数据库密码与 OSS 凭据,**不要上传到代码仓库 / 不要外传**(已在 .gitignore)。 - FTP 凭据仅运维持有,建议定期轮换。