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

React async/await上传xlsx至Flask路由报错排查

问题定位

两个报错同源,都是前端改async/await请求写法时,混淆了原生fetch和你封装的api(一般是axios实例)的传参规则,导致FormData没有被正确作为文件上传请求发送到后端:

  • 当FormData被完全错误序列化为普通JSON对象时,后端request.files中不存在project字段,直接触发KeyError: 'project'返回400错误
  • 当FormData被部分序列化、文件二进制内容损坏时,后端就算拿到了文件字段,存下来的也是损坏的无效文件,pandas无法识别Excel格式,触发ValueError: Excel file format cannot be determined, you must specify an engine manually错误

你之前用原生fetch时写法是对的:fetch的第二个参数是请求配置对象,body字段放请求体。但axios.post的参数规则是post(url, 请求体数据, 请求配置),你现在把{body: formData}直接作为第二个参数传给api.post,等于把包裹了formData的普通JSON对象当成了请求体,完全不符合文件上传的格式要求,而且你也没设置对应的multipart/form-data请求头,进一步导致请求解析异常。

修复步骤

1. 修正前端请求代码

注意两点:一是axios的post请求第二个参数直接传FormData实例,不要套body字段;二是设置正确的请求头,让浏览器自动生成multipart格式需要的boundary分隔符,不要手动硬编码boundary值。

const onSubmit = async (ev) => {
  ev.preventDefault();
  const formData = new FormData();
  // 提前校验selectedFile必须是用户选择的原生File对象,不能是本地路径、base64等其他格式
  formData.append("project", selectedFile);
  
  try {
    // 注意参数顺序:地址、请求体、配置
    const res = await api.post("/projects", formData, {
      headers: {
        "Content-Type": "multipart/form-data"
      }
    });
    console.log("上传成功", res.data);
  } catch (err) {
    console.log("上传失败", err);
  }
};

2. 后端可选加固(减少定位成本)

  • 增加上传字段前置校验,避免直接硬取request.files["project"]触发不友好的KeyError
  • 给pandas读取Excel时显式指定解析引擎,避免自动识别失败
@projects.route('/projects', methods=['POST'])
@authenticate(token_auth)
def request_load_pickle_sim():
    # 先校验文件是否存在
    if "project" not in request.files:
        return jsonify({"msg": "未找到上传的项目文件"}), 400
    upload_file = request.files["project"]
    if not upload_file.filename.endswith((".xlsx", ".xls")):
        return jsonify({"msg": "仅支持上传xls/xlsx格式的Excel文件"}), 400
    
    fn = save_file(None, "project", "xlsx")
    print(fn)
    upload_file.save(fn)

    # 显式指定解析引擎,xlsx用openpyxl,xls用xlrd
    if fn.endswith(".xlsx"):
        result = process_raw_util_xl_file(fn, engine="openpyxl")
    else:
        result = process_raw_util_xl_file(fn, engine="xlrd")
    
    # 后续原有逻辑保持不变即可
    claims = result['claims']
    print(claims)
    utilities = result['utilities']
    weights = result['weights']
    maxdiff_scores = result['maxdiff_scores']
    data = jsonify(claims)
    print(data)
    mdp = MaxDiffProject()
    mdp.config = MutableDict({
        'claims': claims,
        'utilities': utilities,
        'weights': weights,
        'maxdiff_scores': maxdiff_scores
    })
    print(mdp.config)
    db.session.add(mdp)
    db.session.commit()
    mdp = MaxDiffProject().query.first()
    project_config = mdp.config
    claims = project_config['claims']
    print(claims)
    data = jsonify(claims)
    return data

对应修改process_raw_util_xl_file函数接收engine参数:

# 新增engine入参
def process_raw_util_xl_file(xl_fn, engine=None):
    # 传入指定引擎
    df = pd.read_excel(xl_fn, engine=engine)
    print(df)
    row = df.shape[0]
    index_col = [col for col in df if 'id' in col.lower()][0]
    print(index_col)
    df.set_index(index_col, inplace=True)

    if 'Weights' not in df:
        df['Weights'] = 1

    weights = df['Weights']
    df.drop('Weights', axis=1, inplace=True)
    sum = weights.values.sum()

    rlh_cols = [col for col in df if 'rlh' in col.lower()][:1]
    df = df.drop(rlh_cols, axis=1)

    max_diff_scores = (e ** df) / (1 + e ** df) * 100

    utils = df

    return {
        "utilities": utils,
        "claims": [col for col in utils],
        "maxdiff_scores": maxdiff_scores,
        "weights": weights
    }

改完前端传参逻辑后,两个偶发错误都会直接消失,本质就是改请求写法时没注意不同请求库的API差异,把fetch的配置格式直接套到了axios上。

内容的提问来源于stack exchange,提问作者E B

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 15:15:47