1. 这不是“配环境”,是交付一个能跑在生产服务器上的完整系统
Vue + ASP.NET Web API 前后端分离项目发布部署,这八个字背后藏着的不是“点几下鼠标就能上线”的幻觉,而是一整套工程闭环:前端构建产物如何与后端服务协同、静态资源路径怎么不被404吞掉、跨域问题在生产环境里根本不存在——因为它压根不该出现在生产环境里、token怎么从登录态安全流转到每次请求头、IIS或Kestrel怎么扛住真实用户并发、甚至一个favicon.ico 404都可能暴露你的目录结构。我做过27个上线项目,其中19个卡在部署环节,不是代码写错了,而是对“发布”二字的理解停留在本地 npm run build 和 dotnet publish 的机械执行上。真正决定项目能否稳定运行的,恰恰是那几十行配置、几个路径参数、一次反向代理的映射规则,以及你有没有在凌晨三点盯着 IIS 日志查500错误时,突然意识到 web.config 里 的 customHeaders 写错了顺序。
这个过程适合三类人:刚用 Vue CLI 搭完登录页、正准备把项目交给客户验收的前端同学;接手了前端打包产物、却不知道该往 IIS 哪个文件夹扔的 .NET 开发者;还有那些被“前后端分离”概念绕晕、以为只要接口通了就万事大吉的项目经理。它不讲 Vue 的响应式原理,也不深挖 ASP.NET Core 的中间件管道,只聚焦一件事:让 build 出来的 dist 文件夹和 publish 出来的 dll 文件,在 Windows Server 或 Linux 服务器上,像在你本机 localhost:8080 + localhost:5000 那样稳稳当当地跑起来,并且扛得住真实流量。接下来所有内容,全部来自我踩过的坑、改过的配置、重装过三次的 IIS、以及客户服务器上那段被反复注释又取消注释的 nginx location 块。
2. 整体设计思路:为什么必须拆成“前端静态托管”+“后端API服务”两层
2.1 前后端分离的本质不是技术选型,而是职责边界
很多人误以为“用了 Vue 和 Web API 就叫前后端分离”,其实这只是表象。真正的分离,是把“谁负责呈现页面”和“谁负责提供数据”彻底解耦。Vue 应用编译后是一堆 HTML、CSS、JS 和图片,它们不需要服务器执行任何逻辑,只需要被 HTTP 服务器原样返回给浏览器;而 ASP.NET Web API 是一个独立的进程,监听某个端口(比如 5000),只做一件事:接收 HTTP 请求、校验 token、查数据库、返回 JSON。这两者之间,不应该存在任何代码级依赖,也不应该共享同一个进程或应用池。我见过最典型的反模式,是把 Vue 的 dist 文件夹直接塞进 ASP.NET MVC 的 Views 目录,再用 Controller 返回一个空 View,靠 script 标签加载 index.js —— 这看起来像分离,实则把前端构建产物变成了后端项目的“资源文件”,一旦要升级 Vue 版本或更换构建工具,就得重新编译整个 .NET 解决方案,完全违背了分离的初衷。
所以部署的第一步,就是明确物理隔离:前端走 Nginx/IIS 静态文件托管,后端走 Kestrel/IIS 反向代理或独立进程。这种设计带来三个硬性好处:
- 发布解耦:前端团队可以独立发布新版本,只需替换 dist 文件夹内容,不影响后端服务进程;后端团队升级框架或修复接口,前端无需重新构建。
- 性能优化:静态资源由擅长处理高并发小文件的 Web 服务器(如 Nginx)直接返回,不经过 .NET 运行时,首屏加载速度提升 30%~60%;API 请求则由 Kestrel 这种高性能 HTTP 服务器专注处理。
- 安全加固:前端资源可配置强缓存(Cache-Control: public, max-age=31536000),减少重复请求;后端 API 则可通过 IIS 的 IP 限制、请求过滤等模块做第一道防线,两者策略互不干扰。
2.2 为什么不能直接用 Vue DevServer 代理到后端?生产环境没有 devServer
Vue CLI 的 vue.config.js 里写个 devServer.proxy,本地开发时确实方便。但这是开发阶段的“模拟”,本质是 webpack-dev-server 启动了一个中间层,把 /api/ 开头的请求转发给 http://localhost:5000。这个 proxy 在 build 之后就彻底消失,不会打包进任何产物里。很多新手把本地能跑通的代码直接扔到服务器,发现所有接口 404,第一反应是“后端没启动”,其实是前端页面里的 fetch('/api/login') 这个路径,在生产环境里根本没人监听——因为浏览器直接向当前域名发起请求,而你的 Nginx 或 IIS 根本没配置这条路由的转发规则。
正确的做法是:前端代码里所有 API 请求,必须使用绝对路径或相对路径,且该路径需与生产环境的反向代理规则严格匹配。例如,约定所有 API 路径以 /api/ 开头,那么 Nginx 配置就必须把 /api/ 开头的请求,无条件转发到 http://127.0.0.1:5000;同时,前端 axios 的 baseURL 就设为 '/api/',而不是 'http://localhost:5000/api/'。这样,无论前端部署在 www.example.com 还是 app.example.com,只要 Nginx 规则一致,请求就能精准抵达后端。
2.3 ASP.NET Web API 的宿主选择:Kestrel vs IIS,不是二选一,而是组合拳
ASP.NET Core 官方文档说 Kestrel 是跨平台、高性能的 Web 服务器,IIS 是 Windows 上的传统 Web 服务器。但实际部署中,Kestrel 绝不单独暴露在公网。原因很现实:Kestrel 缺少企业级功能,比如请求压缩、SSL 卸载、IP 白名单、URL 重写、负载均衡支持。它就像一辆顶级超跑,引擎强悍,但没有空调、没有安全气囊、不能上高速——必须套在一个更稳健的“外壳”里。
所以标准架构是:Kestrel 作为应用内嵌服务器,监听 localhost:5000(或任意内部端口),只接受来自本机的请求;IIS(或 Nginx)作为反向代理,监听 80/443 端口,接收所有外部请求,再根据规则把 /api/ 转发给 Kestrel。IIS 在这里不是“宿主”,而是“网关”。这种组合既保留了 Kestrel 的高性能,又利用了 IIS 成熟的管理界面、日志系统和安全模块。我在客户现场遇到过一次事故:某同事图省事,把 Kestrel 直接绑定到 0.0.0.0:5000 并开放防火墙端口,结果第二天就被扫描器打爆了内存,因为 Kestrel 默认不启用请求体大小限制,恶意构造的超大 POST 请求直接拖垮进程。而换成 IIS 反向代理后,我们能在 IIS 层面设置 requestLimits,瞬间解决问题。
3. 核心细节解析:从构建到上线的每一步关键操作
3.1 Vue 项目构建前的必改项:public/index.html 与 router 的 base 配置
Vue CLI 构建时,默认把所有静态资源路径写成相对路径(如 ./js/app.js)。如果前端部署在域名根路径(https://example.com/),这没问题;但如果部署在子路径(https://example.com/admin/),就会出现 JS/CSS 加载 404。根源在于,浏览器解析 ./js/app.js 时,会以当前页面 URL 为基准,即 https://example.com/admin/ → https://example.com/admin/js/app.js。而实际文件在 https://example.com/admin/dist/js/app.js。
解决方案分两步:
第一步,修改 vue.config.js 中的 publicPath:
// vue.config.js module.exports = { // 如果部署在根路径,设为 '/' // 如果部署在子路径,比如 https://example.com/myapp/,则设为 '/myapp/' publicPath: process.env.NODE_ENV === 'production' ? '/admin/' : '/', outputDir: 'dist', assetsDir: 'static' }这个 publicPath 会注入到 index.html 的<script>和<link>标签中,确保资源路径正确。
第二步,如果用了 Vue Router 的 history 模式(即 URL 不带 #),必须配置 router 的 base:
// router/index.js const router = new VueRouter({ mode: 'history', base: process.env.NODE_ENV === 'production' ? '/admin/' : '/', routes: [...] })base 值必须与 publicPath 完全一致。否则,用户直接访问 https://example.com/admin/user/1 时,Nginx 会把请求转发给前端静态服务器,但前端 router 找不到匹配的路由,显示空白页。这是因为 history 模式下,router 初始化时会读取当前 URL 的 pathname(/admin/user/1),然后去 routes 里匹配,如果 base 设为 '/',它会尝试匹配 /user/1,自然失败。
提示:如果你不确定部署路径,可以在构建后手动编辑 dist/index.html,把
<script src="/js/app.js">改成<script src="/admin/js/app.js">,但这只是临时救急,长期维护必须靠配置驱动。
3.2 ASP.NET Web API 的跨域(CORS)配置:开发期开,生产期关
CORS 是开发阶段的“便利贴”,不是生产环境的“安全门”。本地开发时,Vue 运行在 http://localhost:8080,Web API 在 http://localhost:5000,浏览器同源策略会拦截请求,所以我们在 Startup.cs 里加:
// Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() // 允许所有来源 .AllowAnyMethod() .AllowAnyHeader(); }); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseCors("AllowAll"); // 启用 CORS }这段代码在开发时救命,但在生产环境必须删除或禁用。原因有二:一是 AllowAnyOrigin() 会返回 Access-Control-Allow-Origin: *,但若响应头包含 credentials(如 cookie 或 Authorization header),浏览器会拒绝该响应;二是它完全放弃了来源控制,任何网站都能调用你的 API,等于把数据库钥匙挂在门口。
生产环境的正确做法是:在反向代理层(Nginx/IIS)统一处理跨域,后端代码里彻底移除 CORS 配置。因为前端和后端在生产环境共享同一个域名(如 https://example.com),浏览器认为它们同源,根本不会触发跨域检查。所有 /api/ 请求,都是从 https://example.com 发起,被 Nginx 拦截并转发给后端,响应再原路返回,全程无跨域。我曾帮一个金融客户排查过 API 响应慢的问题,最后发现是后端启用了 CORS,每次请求都多了一次 Preflight(OPTIONS)预检,而预检响应里又包含了大量不必要的 headers,白白消耗了 200ms。关掉 CORS 后,接口平均耗时下降 15%。
3.3 Token 处理的两种落地方式:localStorage vs HttpOnly Cookie
Vue 前端登录后,token 怎么存、怎么发,直接影响安全性。常见方案有两种:
方案一:存 localStorage,每次请求手动添加 Authorization header
// 登录成功后 localStorage.setItem('token', response.data.token) // axios 请求拦截器 axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })优点:简单直接,JWT token 可以轻松解析 payload 查看用户信息;缺点:XSS 攻击可窃取 token,因为 localStorage 可被 JavaScript 读取。
方案二:后端 Set-Cookie 返回 HttpOnly Cookie,前端无需操作
// 登录成功后 var cookieOptions = new CookieOptions { HttpOnly = true, // JS 无法读取 Secure = true, // 仅 HTTPS 传输 SameSite = SameSiteMode.Strict, Expires = DateTime.UtcNow.AddDays(7) }; Response.Cookies.Append("auth_token", token, cookieOptions);前端完全不用管 token,浏览器自动在每次请求中携带 Cookie。后端用 [Authorize] 特性自动验证。优点:防 XSS,token 不在前端内存中;缺点:需要处理 CSRF(可用 SameSite=Strict 缓解),且 JWT 优势(无状态、自包含)被弱化,因为 token 存在服务端 session 或 Redis 中。
我的建议是:内部管理系统用方案二,面向公众的网站用方案一 + 前端加密存储(如用 crypto-js AES 加密后再存 localStorage)。前者更重安全,后者更重灵活性。无论哪种,都必须在后端 API 的响应头中,明确设置 Access-Control-Allow-Credentials: true,并在前端 axios 配置 withCredentials: true,否则 Cookie 不会发送。
3.4 IIS 部署 ASP.NET Web API 的五个致命细节
IIS 部署不是把 publish 文件夹复制过去就完事。以下是我在 Windows Server 2016/2019 上踩过的五个必改点:
应用池 .NET CLR 版本必须设为“无托管代码”
ASP.NET Core 应用是独立进程(dotnet.exe),不依赖 IIS 的 .NET 运行时。如果应用池设为 .NET CLR v4.0,IIS 会试图用 w3wp.exe 加载你的 dll,导致 502.5 错误。正确设置:应用池 → 高级设置 → .NET CLR 版本 → 无托管代码。应用池“启用 32 位应用程序”必须与你的 SDK 匹配
如果你用 x64 SDK publish,应用池必须禁用 32 位;反之亦然。错误会导致“找不到 dll”或“BadImageFormatException”。检查方法:命令行运行dotnet --list-runtimes,看输出的是 Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] 还是 [C:\Program Files (x86)\dotnet\shared...]。web.config 必须存在,且内容正确
publish 时会自动生成 web.config,但有时会被覆盖。核心内容如下:<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\YourApi.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess"> <environmentVariables> <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" /> </environmentVariables> </aspNetCore> </system.webServer> </configuration>关键点:
processPath="dotnet"(不是 yourapp.exe),hostingModel="inprocess"(推荐,性能更好),stdoutLogEnabled="true"(开启日志,排错必备)。站点绑定必须包含 HTTPS,且 SSL 证书已绑定
即使前端用 HTTP,API 也必须走 HTTPS。否则浏览器会阻止混合内容(HTTP 页面加载 HTTPS 资源可行,但 HTTPS 页面加载 HTTP API 会被拦截)。在 IIS 站点 → 绑定 → 添加 → 类型选 https,端口 443,SSL 证书选已安装的证书。Windows 防火墙必须放行端口
Kestrel 默认监听 localhost:5000,但 IIS 反向代理需要访问这个端口。防火墙默认阻止本地回环访问。解决:高级安全 Windows 防火墙 → 入站规则 → 新建规则 → 端口 → TCP 5000 → 允许连接 → 作用域设为“本地回环”。
注意:IIS 日志默认在 C:\inetpub\logs\LogFiles\W3SVC1,但 stdout 日志在你 publish 文件夹下的 logs 目录。500 错误时,先看 stdout_log.txt,里面会有详细的异常堆栈,比 IIS 日志有用十倍。
4. 实操过程:从零开始部署一个可运行的 demo
4.1 准备工作:环境清单与权限确认
在动手前,必须确认以下五项,缺一不可:
- 服务器操作系统:Windows Server 2016 或更高版本(IIS 部署);或 Ubuntu 20.04(Nginx + systemd 部署)。本文以 Windows 为例。
- 已安装软件:
- IIS(角色服务里勾选“Web 服务器(IIS)”、“.NET Extensibility 4.8”、“HTTP 响应标头”)
- ASP.NET Core Runtime 6.0(下载地址:https://dotnet.microsoft.com/download/dotnet/6.0,选 Runtime,不是 SDK)
- URL Rewrite Module(IIS 反向代理必需,下载地址:https://www.iis.net/downloads/microsoft/url-rewrite)
- 文件权限:publish 文件夹需赋予 IIS_IUSRS 用户“读取 & 执行”权限;logs 文件夹需额外赋予“写入”权限,否则 stdout 日志无法生成。
- 域名与证书:已申请好域名(如 api.example.com),并安装好 SSL 证书到服务器证书存储区。
- 网络权限:服务器 80/443 端口已开放(云服务器需检查安全组),5000 端口仅限本机访问(防火墙已按 3.4 节配置)。
我建议新建一个专用 Windows 用户(如 deployer),将其加入 IIS_IUSRS 组,并用此用户登录远程桌面操作,避免用 Administrator 账户引发权限混乱。
4.2 Vue 前端部署:Nginx 静态托管实战
虽然标题是 ASP.NET,但前端部署同样关键。我们用 Nginx(轻量、稳定、配置直观)托管 Vue 构建产物:
下载 Nginx for Windows(https://nginx.org/en/download.html),解压到 C:\nginx。
修改 conf/nginx.conf:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # 配置前端站点 server { listen 80; server_name www.example.com; # 根路径指向 dist 文件夹 location / { root C:/deploy/frontend/dist; index index.html; try_files $uri $uri/ /index.html; # history 模式必备 } # API 请求全部转发给后端 location /api/ { proxy_pass http://127.0.0.1:5000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # 配置 HTTPS 站点(可选,但强烈推荐) server { listen 443 ssl; server_name www.example.com; ssl_certificate C:/certs/example.com.crt; ssl_certificate_key C:/certs/example.com.key; location / { root C:/deploy/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass https://127.0.0.1:5001/; # 后端也走 HTTPS # ... 其他 proxy_set_header 同上 } } }关键点解释:
try_files $uri $uri/ /index.html:确保 history 模式下,用户刷新 /user/1 页面时,Nginx 不返回 404,而是返回 index.html,由 Vue Router 处理路由。proxy_pass http://127.0.0.1:5000/:末尾的/至关重要!它表示“去掉 /api/ 前缀再转发”。例如,请求 /api/login,会被转发为 http://127.0.0.1:5000/login。如果写成proxy_pass http://127.0.0.1:5000;(无斜杠),则转发为 http://127.0.0.1:5000/api/login,后端控制器收不到。
启动 Nginx:双击 nginx.exe,或命令行
start nginx。检查是否运行:任务管理器 → 服务 → nginx.exe 进程是否存在。测试前端:浏览器访问 http://www.example.com,应看到 Vue 页面;F12 查看 Network,确认 js/css 加载正常,且无 404。
4.3 ASP.NET Web API 部署:IIS 反向代理全流程
现在部署后端:
在 Visual Studio 中,右键 Web API 项目 → 发布 → 选择“文件夹” → 目标位置设为 C:\deploy\backend\publish。
确保发布设置正确:
- 配置:Release
- 目标框架:net6.0
- 部署模式:框架依赖(Framework-dependent)
- 目标运行时:win-x64(与服务器 CPU 架构一致)
- 勾选“删除目标文件夹中的现有文件”
复制 publish 文件夹全部内容到 C:\deploy\backend\。
打开 IIS 管理器 → 左侧“连接”窗格 → 右键“网站” → “添加网站”:
- 网站名称:MyApi
- 物理路径:C:\deploy\backend\
- 绑定:类型 https,IP 地址全部未分配,端口 443,主机名 api.example.com,SSL 证书选已安装的证书
配置反向代理(URL Rewrite):
- 选中刚创建的 MyApi 网站 → 双击“URL 重写”
- 点击右侧“添加规则” → 选择“空白规则”
- 名称:Proxy to Kestrel
- 匹配 URL:请求的 URL → 使用正则表达式 → 模式:^(.*)$
- 条件:无
- 操作:重写 → 重写 URL:http://127.0.0.1:5000/{R:1} → 附加查询字符串:勾选 → 停止处理后续规则:勾选
- 点击“应用”
启动网站:右键 MyApi → “启动”。检查应用池是否也处于“正在运行”状态。
测试 API:浏览器访问 https://api.example.com/health(假设你有一个 HealthController),应返回 {"status":"Healthy"};用 Postman 调用 https://api.example.com/api/login,应返回 token。
实操心得:IIS 的 URL Rewrite 规则,比 Nginx 的 location 更容易出错。常见错误是“重写 URL”里写了 http://localhost:5000/{R:1},但 localhost 在 IIS 进程里解析失败,必须用 127.0.0.1。另外,规则必须放在“网站”级别,不能放在“应用程序”级别,否则不生效。
4.4 前后端联调与最终验证:三步确认法
部署完成后,必须进行三步验证,缺一不可:
第一步:前端独立验证
打开浏览器开发者工具 → Network 标签 → 刷新页面。观察:
- 所有 JS/CSS/图片请求状态码为 200,Size 列显示文件大小(非 0);
- 没有 404 请求,特别是 favicon.ico、manifest.json;
- 页面渲染正常,Vue Devtools 显示组件树。
第二步:API 独立验证
用 curl 或 Postman 直接请求后端:
curl -X POST "https://api.example.com/api/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'预期返回 200 和 token。如果返回 500,立刻查看 C:\deploy\backend\logs\stdout_*.log,找第一行异常信息。
第三步:全链路验证
在 Vue 页面上点击登录按钮。F12 查看 Network:
- 登录请求 URL 是 https://www.example.com/api/login(注意是 www 域名,不是 api);
- 请求 Method 是 POST,Status 是 200;
- Response Headers 里有 Set-Cookie(如果用了 Cookie 方案)或直接返回 token 字段;
- 后续请求(如获取用户信息)的 Request Headers 里有 Authorization: Bearer xxxxx。
如果第三步失败,90% 的原因是前端 baseURL 设错了,或者 Nginx 的 location /api/ 规则没生效。此时不要猜,直接在 Nginx 日志(logs/access.log)里搜索 “api/login”,看是否有记录;再检查 IIS 的 Failed Request Tracing,开启后能捕获完整的请求生命周期。
5. 常见问题与排查技巧实录:那些凌晨三点的救火记录
5.1 问题速查表:高频故障与一键定位
| 现象 | 可能原因 | 快速定位方法 | 解决方案 |
|---|---|---|---|
前端页面白屏,Console 报错Failed to load resource: net::ERR_CONNECTION_REFUSED | Nginx 未运行,或 80 端口被占用 | 命令行netstat -ano | findstr :80,看 PID 对应哪个进程 | 重启 Nginx,或taskkill /PID {PID} /F杀掉占用进程 |
| API 请求返回 502 Bad Gateway | IIS 反向代理未连通 Kestrel,或 Kestrel 进程崩溃 | 查看 IIS 应用池状态;检查 C:\deploy\backend\logs\stdout_*.log 最后一行 | 重启应用池;确认 publish 文件夹里有 YourApi.dll 和 web.config |
| 登录成功,但后续请求 401 Unauthorized | token 未正确添加到请求头,或后端 JWT 验证失败 | F12 → Network → 点击一个 401 请求 → Headers → 看 Request Headers 里是否有 Authorization | 前端检查 axios 拦截器;后端检查 Startup.cs 的 AddJwtBearer 配置,Issuer 和 Audience 是否与 token 里的一致 |
| 页面样式错乱,字体图标显示为方块 | publicPath 配置错误,导致 CSS 中的字体路径 404 | F12 → Network → Filter 输入.woff,看是否 404 | 修改 vue.config.js 的 publicPath,确保与部署路径一致;构建后检查 dist/css/app.css 里的 url() 路径 |
| HTTPS 页面加载 HTTP API 被浏览器拦截 | 前端 baseURL 写了 http://,或 Nginx proxy_pass 用了 http:// | F12 → Console,看是否有Mixed Content警告 | 前端 baseURL 改为/api/;Nginx proxy_pass 改为https://127.0.0.1:5001/,后端 Kestrel 启用 HTTPS |
5.2 我踩过的三个典型坑及独家修复技巧
坑一:IIS 应用池“闲置超时”导致 API 首次请求巨慢
现象:用户第一次访问页面,登录要等 10 秒以上,后续请求秒开。
原因:IIS 应用池默认“闲置超时”为 20 分钟,超时后进程被回收。下次请求来时,IIS 要重新加载 .NET 运行时、初始化 Kestrel,耗时很长。
修复技巧:应用池 → 高级设置 → “闲置超时(分钟)” 改为 0(永不超时);同时,“启动模式”改为“始终运行”,“预加载启用”设为 True。这样应用池启动时就加载应用,永远不休眠。
坑二:Vue Router history 模式在 IE11 下白屏
现象:Chrome 正常,IE11 打开首页白屏,Console 报错Object doesn't support property or method 'assign'。
原因:Vue Router 4.x 默认使用 Object.assign,IE11 不支持。
修复技巧:在 Vue 项目根目录新建 vue.config.js,添加 babel polyfill:
module.exports = { transpileDependencies: ['vue-router'], configureWebpack: { resolve: { fallback: { crypto: false, stream: false, os: false, util: false } } } }并在 main.js 顶部引入:
import '@babel/polyfill'然后npm install --save-dev @babel/polyfill。这是兼容 IE11 的最小代价方案。
坑三:Linux 服务器上 Nginx 代理 ASP.NET Core,返回 502 且日志无记录
现象:Ubuntu 上部署,Nginx 日志只有 502,Kestrel 日志为空。
原因:Linux 的 SELinux 或 AppArmor 限制了 Nginx 访问 127.0.0.1:5000。
修复技巧(Ubuntu):
# 检查 AppArmor 状态 sudo aa-status # 临时禁用(测试用) sudo systemctl stop apparmor # 永久禁用(生产环境慎用) sudo systemctl disable apparmor # 或者,给 Nginx 添加网络访问权限 sudo nano /etc/apparmor.d/usr.sbin.nginx # 在文件末尾添加: # network inet stream, # network inet6 stream, sudo systemctl reload apparmor5.3 生产环境必须开启的三项监控
部署不是终点,而是运维的起点。这三个监控点,能帮你提前 80% 的故障:
- Nginx 访问日志分析:每天用 awk 统计 4xx/5xx 状态码占比。如果 /api/login 的 400 错误突增,可能是前端传参格式错误;如果 /api/data 的 429 突增,说明需要加限流。
- IIS stdout 日志轮转:在 web.config 的 aspNetCore 节点里,添加
stdoutLogEnabled="true"和stdoutLogFile=".\logs\stdout",并用 PowerShell 脚本每日压缩旧日志:Get-ChildItem "C:\deploy\backend\logs\stdout_*" | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-7)} | ForEach-Object { Compress-Archive $_.FullName "$($_.DirectoryName)\$($_.BaseName).zip"; Remove-Item $_.FullName } - 前端 Sentry 错误监控:在 Vue 项目里集成 Sentry(https://docs.sentry.io/platforms/javascript/guides/vue/),捕获 JS 运行时错误、Promise reject、Vue 异常。它能告诉你,用户在哪个页面、什么机型、什么网络环境下遇到了什么错误,比客服电话反馈快十倍。
最后再分享一个小技巧:每次发布新版本,都在 dist 文件夹里生成一个 version.json 文件,内容为:
{ "version": "1.2.3", "buildTime": "2023-10-15T14:22:33Z", "commit": "a1b2c3d" }然后在 Vue 的 mounted 钩子中 fetch 这个文件,把版本号显示在页面 footer。这样,客户说“功能不对”,你一眼就能确认他用的是不是最新版,省去无数沟通成本。