You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask-Restful使用AJAX POST方法下载Excel报方法不允许问题

问题现象

基于Python Flask-Restful框架开发接口,搭配AJAX实现Excel文件下载时遇到两个互斥问题:

  • 待传递的JSON数据体积较大,选择POST方法提交请求,接口返回Excel文件时触发method not allowed(方法不允许)错误
  • 改用GET方法发起请求,可以正常返回Excel文件,但服务端无法接收提交的JSON数据

原实现代码如下:

原服务端POST接口代码

def post(self):
    
        output = io.BytesIO()
        workbook = xlsxwriter.Workbook(output)
        worksheet = workbook.add_worksheet()
        filename = 'excel'+".xls"
        worksheet.merge_range('A2:E2', 'WORK!!')
        workbook.close()
        response = make_response(output.getvalue())
        response.headers['Content-Type'] = "application/x-xlsx"
        response.headers["Cache-Control"] = "no-cache"
        response.headers["Content-Disposition"] = "attachment; filename="+filename
        return response

原前端AJAX请求代码

$.ajax({
  url: '/downloadFile',
  type: 'POST',
  data: JSON.stringify({
    data1: data1,
    data2: data2
  }),
  dataType: "JSON",
  contentType: "application/json; charset=utf-8",
  traditional: true,
  success: function (response) {
    alert('ok')
    window.location = this.url;}})
错误根因
  • 前端AJAX配置和逻辑错误
    • dataType: "JSON"配置强制要求jQuery按JSON格式解析服务端响应,但接口实际返回的是二进制Excel文件流,解析阶段就会出错
    • success回调里的window.location = this.url会重新向接口地址发起GET请求,这个请求既没带上之前POST提交的JSON参数,又因为接口只实现了POST方法,直接触发405方法不允许错误
    • 基础认知误区:AJAX请求拿到的响应存储在JS内存中,不会自动触发浏览器的文件下载行为,直接跳转页面的写法完全绕开了POST传参逻辑
  • 后端实现细节不规范
    • 响应头的Content-Type值application/x-xlsx是非标准类型,标准xlsx格式MIME值应为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • 文件名未做编码处理,遇到中文文件名会出现乱码
    • 跨域场景下未暴露Content-Disposition响应头,前端无法读取下载文件名
修复方案

不要用页面跳转的方式实现POST请求后的文件下载,直接在AJAX拿到二进制流后,转成Blob对象手动触发浏览器下载即可,同时修正后端响应头配置。

修正后的前端代码

$.ajax({
  url: '/downloadFile',
  type: 'POST',
  data: JSON.stringify({
    data1: data1,
    data2: data2
  }),
  // 告诉jQuery不要解析响应,直接返回二进制Blob对象
  xhrFields: {
    responseType: 'blob'
  },
  contentType: "application/json; charset=utf-8",
  success: function (response) {
    // 从响应头提取文件名
    let disposition = this.getResponseHeader('Content-Disposition');
    let filename = decodeURI(disposition.split('filename=')[1]);
    // 创建Blob对象,匹配Excel文件MIME类型
    let blob = new Blob([response], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    // 创建临时下载链接触发点击
    let downloadUrl = window.URL.createObjectURL(blob);
    let a = document.createElement('a');
    a.href = downloadUrl;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    // 释放临时URL占用的内存
    window.URL.revokeObjectURL(downloadUrl);
    document.body.removeChild(a);
  }
})

修正后的后端接口代码

from flask import make_response
import io
import xlsxwriter
from urllib.parse import quote

def post(self):
    output = io.BytesIO()
    workbook = xlsxwriter.Workbook(output)
    worksheet = workbook.add_worksheet()
    filename = '导出数据.xlsx'
    worksheet.merge_range('A2:E2', 'WORK!!')
    workbook.close()
    # 将流指针移到起始位置
    output.seek(0)
    response = make_response(output.getvalue())
    # 设置标准MIME类型
    response.headers['Content-Type'] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    response.headers["Cache-Control"] = "no-cache"
    # 文件名做url编码,避免中文乱码
    response.headers["Content-Disposition"] = f"attachment; filename={quote(filename)}"
    # 跨域场景下暴露响应头,供前端读取文件名
    response.headers['Access-Control-Expose-Headers'] = 'Content-Disposition'
    return response

注意:不要用GET方式传递大体积JSON,GET参数长度受浏览器、服务端配置限制,很容易出现参数截断问题。如果使用Flask-CORS做跨域处理,需要手动在CORS配置中添加expose_headers=['Content-Disposition']规则,否则前端依然无法读取到响应头里的文件名。

内容的提问来源于stack exchange,提问作者GRACIELA FAUSTEN NOVINDRI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:01:25