Rails 7 Turbo Stream POST请求中send_data无法触发文件下载
问题描述
上传文件修改后返回,响应能获取到blob但浏览器不触发下载。添加data-turbo="false"后功能正常,但希望保留使用Turbo Stream。
原因分析
Turbo 默认通过 XMLHttpRequest 处理表单提交(当请求类型为 TURBO_STREAM 时),而浏览器不会为 XHR 请求自动触发文件下载行为——哪怕响应头包含Content-Disposition: attachment,也只会返回 blob 数据,不会弹出下载框。
解决方案
通过 Turbo Stream 将文件数据传递到前端,用 JavaScript 手动触发下载。以下是具体实现步骤:
步骤1:修改控制器代码
不再直接用send_data返回文件,而是生成文件的 Base64 编码,通过 Turbo Stream 渲染一段触发下载的 JS 代码:
class HomeController < ApplicationController require 'rubyXL/convenience_methods' require 'base64' # 引入Base64模块 def index end def generate workbook = RubyXL::Parser.parse(params[:file]) worksheet = workbook[0] worksheet = workbook.add_worksheet('testSheet') # 生成文件数据并转Base64 file_data = workbook.stream.read file_base64 = Base64.encode64(file_data) respond_to do |format| format.turbo_stream do render turbo_stream: turbo_stream.update('download-trigger', <<~HTML) <script> // 将Base64转回Blob const blob = new Blob([Uint8Array.from(atob("#{file_base64}"), c => c.charCodeAt(0))], { type: 'application/xlsx' }); // 创建临时URL触发下载 const url = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = url; downloadLink.download = "FileName.xlsx"; document.body.appendChild(downloadLink); downloadLink.click(); // 清理资源 document.body.removeChild(downloadLink); URL.revokeObjectURL(url); </script> HTML end end end end
步骤2:更新视图
在表单下方添加一个隐藏的容器,用于接收 Turbo Stream 推送的 JS 代码:
<%= form_with(url: generate_path) do |form| %> <div style="margin: 10px"> <%= form.file_field :file %> </div> <div style="margin: 10px"> <%= form.submit "Generate", style: "background: red; cursor: pointer" %> </div> <% end %> <!-- 用于接收Turbo Stream的容器 --> <div id="download-trigger"></div>
原理说明
- 控制器将生成的 Excel 文件转为 Base64 编码,嵌入到 Turbo Stream 的 HTML 响应中;
- Turbo 接收到响应后,会更新页面上的
download-trigger容器,执行里面的 JavaScript; - JS 代码将 Base64 转回 Blob,创建临时下载链接并触发点击,从而让浏览器弹出下载框。
内容的提问来源于stack exchange,提问作者Bouaik Lhoussaine
相关产品推荐
相关产品推荐

