Rails使用caxlsx调用send_file导出Excel未下载直接在浏览器显示乱码
问题原因
- 视图层
link_to默认是普通页面跳转,未指定download属性,浏览器会默认尝试解析响应内容,而非触发下载 - 控制器中设置的MIME类型错误:
application/vnd.ms-excel是旧版.xls格式的MIME类型,.xlsx格式正确的MIME类型为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,类型识别错误会导致浏览器直接展示二进制流内容 - 固定的临时文件路径
#{Rails.root}/tmp/basic.xlsx存在并发冲突风险,多用户同时导出时文件内容会被覆盖,也可能引发响应异常 - 部分场景下Rails UJS的异步请求处理会干扰下载响应的识别
解决方案
1. 修改视图层链接配置
给link_to添加download属性,同时禁用异步请求处理,Rails 5.2版本修改为:
link_to '导出买家列表', export_customers_buyers_path, download: true, data: { remote: false }
2. 修正控制器导出逻辑
- 更换为正确的MIME类型,显式指定
disposition: 'attachment'强制下载 - 推荐直接返回流式内容,省略本地文件写入步骤,避免并发冲突,修改后控制器代码如下:
def export_customers @customers = @client.customers package = Axlsx::Package.new workbook = package.workbook field = [] field_new = [] @client.custom_field_hstore.each do |field_name, _is_reuire| field << field_name field_new << field_name end workbook.add_worksheet(name: 'Buyers List') do |sheet| sheet.add_row(['S.No'] + field.first(15)) @customers.each_with_index do |customer, index| row = [index + 1] row.concat field_new.first(15).map { |f| customer.customer_detail_hstore[f] } sheet.add_row row end end # 直接返回流,无需写入本地文件 send_data package.to_stream.read, filename: 'Buyers.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: 'attachment' end
如果确实需要保留本地文件存储逻辑,使用临时文件避免并发覆盖:
temp_file = Tempfile.new(['buyers_', '.xlsx'], Rails.root.join('tmp')) package.serialize(temp_file.path) send_file temp_file.path, filename: 'Buyers.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: 'attachment' temp_file.close temp_file.unlink # 请求处理完成后自动删除临时文件
3. 可选配置路由规则
如果项目路由配置了格式限制,给导出路由添加对xlsx格式的支持:
get 'export_customers', to: 'buyers#export_customers', defaults: { format: 'xlsx' }
内容的提问来源于stack exchange,提问作者JV Rasool
相关产品推荐
相关产品推荐

