‍ 写在开头点赞 收藏 学会 为什么会触发浏览器预览而不是下载当我们尝试在前端实现文件下载时经常会遇到浏览器直接打开文件如 PDF、图片进行预览而不是弹出下载框的情况。这通常是由以下两个核心原因导致的HTML5 download 属性的同源限制核心原因 根据 W3C 规范 标签的 download 属性 仅对同源 URL 生效 。如果你的文件地址比如 OSS 链接、第三方存储或跨域的后端 API与当前前端页面的域名、端口不同源浏览器就会忽略 download 属性将其视为普通的页面跳转。对于浏览器原生支持的格式就会直接打开预览。HTTP 响应头未强制指定下载 当请求跨域文件时浏览器是否下载取决于服务器返回的 HTTP 响应头。如果服务器返回的 Content-Disposition 的值是 inline 内联展示或者干脆没有设置该请求头只有 Content-Type 浏览器就会尝试直接渲染该文件。️ 常规解决方案方案一后端处理 最推荐 最优雅只需要后端在返回该文件的 HTTP 响应头Response Headers中显式指定 Content-Disposition 为 attachment 即可。Content-Disposition: attachment;filenameyour_file_name.pdf 提示 一旦有了这个响应头无论前端是不是跨域也无论前端有没有写 download 属性浏览器接收到响应后都会 强制触发文件下载 。方案二前端处理适用于后端无法修改的情况如果后端不方便修改响应头前端可以通过 fetch 或 axios 将文件数据请求下来转成 Blob 对象然后生成本地的同源 URL blob:http://… 。这样标签的 download 属性就能完美生效了。async handleDownload(url, fileName){if(!url)return;const baseUrlprocess.env.VUE_APP_BASE_API||;letfullUrlurl;// 补全完整 URLif(!url.startsWith(http://)!url.startsWith(https://)){fullUrl[baseUrl, url].join(/).replace(/(?!:)\//g,/);}try{this.$message.info(正在获取文件请稍候...);// 使用 fetch 获取文件流 const responseawait fetch(fullUrl);if(!response.ok)throw new Error(网络请求失败 );// 转换为 blob 数据 const blobawait response.blob();// 创建本地的 blob URL同源地址download 属性必定 生效 const objectUrlwindow.URL.createObjectURL(blob);const adocument.createElement(a);a.hrefobjectUrl;a.downloadfileName||下载文件;a.style.displaynone;document.body.appendChild(a);a.click();// 释放内存并移除 DOM document.body.removeChild(a);window.URL.revokeObjectURL(objectUrl);}catch(error){console.error(下载失败:, error);// 降级方案如果 fetch 失败例如目标服务器未开启 CORS 跨域回退到直接打开新窗口 window.open(fullUrl,_blank);}}☁️ 针对第三方 OSS 存储的跨域问题如果文件存放在第三方 OSS 上直接使用前端 fetch 处理会引发跨域报错吗是的 如果文件存放在第三方 OSS如阿里云 OSS、腾讯云 COS、七牛云等上直接在前端使用 fetch 去请求文件流 大概率会触发 CORS跨域资源共享错误 。除非 OSS 服务端明确返回了允许跨域的响应头 Access-Control-Allow-Origin 否则浏览器会拦截这个请求。既然文件在 OSS 上这里提供 3 种主流且优雅的解决方案 按推荐程度从高到低排列方案一在 OSS 链接后拼接参数强制下载 最推荐零跨域、零后端代码绝大多数主流 OSS 服务商阿里云、腾讯云、AWS 等都支持通过 在 URL 后面拼接参数 的方式来动态覆盖 HTTP 响应头中的 Content-Disposition 。这意味着你不需要后端改代码也不需要在前端做复杂的 Blob 转换。阿里云示例 在 URL 后追加 ?response-content-dispositionattachment指定文件名 ?response-content-dispositionattachment;filename编码后的文件名.pdf方案二配置 OSS 的 CORS 规则配合前端 Blob 方案如果你依然想用前端 fetch 转 Blob 的方式需要登录 OSS 控制台配置 跨域设置CORS 来源 (Origins) 填入前端项目的域名开发环境可填 * 。允许 Methods 勾选 GET 。允许 Headers 填入 * 。 ⚠️ 缺点 大文件下载时前端会先将文件整个吃进内存Blob如果文件过大如几百MB的视频可能会导致浏览器内存溢出崩溃。方案三后端做一层下载代理❌ 最不推荐如果 OSS 无法修改 CORS且不支持 URL 参数覆盖响应头只能让后端写一个下载接口由后端去下载 OSS 文件再流式返回给前端并附带 Content-Disposition: attachment 。⚠️ 缺点 极其浪费业务服务器的公网带宽和内存把原本 OSS 的流量压力转移到了自己的服务器上。 最终实践采用【URL拼接参数】方案综合考虑针对 OSS 存储的文件我们采用 方案一拼接参数处理即可完全无需后端修改也不会有内存溢出的风险。以下是最终优化后的前端实现代码/** * 处理附件下载 * 采用拼接 response-content-disposition 参数的方式 强制 OSS 响应下载头避免浏览器跨域预览 * * param{string}url 附件地址 * param{string}fileName 附件名称 */ handleDownload(url, fileName){if(!url)return;const baseUrlprocess.env.VUE_APP_BASE_API||;letfullUrlurl;// 补全非 http(s)开头的相对路径if(!url.startsWith(http://)!url.startsWith(https://)){fullUrl[baseUrl, url].join(/).replace(/(?!:)\//g,/);}// 构建强制下载的参数对文件名进行 URI 编码处理避免乱 码 const safeFileNameencodeURIComponent(fileName||下载文件);const dispositionattachment;filename${safeFileName};// 判断原 url 是否已经带了问号防止破坏 OSS 原有的预 签名参数 const separatorfullUrl.includes(?)?:?;const downloadUrl${fullUrl}${separator}response-content-disposition${disposition};// 动态创建 a 标签触发下载 const adocument.createElement(a);a.hrefdownloadUrl;a.downloadfileName||下载文件;// 备用 download 属性 a.style.displaynone;a.target_blank;// 兼容部分浏览器的安全策略 document.body.appendChild(a);a.click();document.body.removeChild(a);如果对您有所帮助欢迎您点个关注我会定时更新技术文档大家一起讨论学习一起进步。