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
相关产品推荐
相关产品推荐

