React Native向DRF后端发送带附件FormData报Bad Request错误
React Native 向 DRF 提交含附件表单字段丢失问题排查
我在开发React Native项目,需要向DRF后端提交带图片/PDF附件的“申请助学金”表单。使用react-native-document-picker选择文件后,通过文件URI获取Blob构造FormData发送,但后端未接收到全部字段,触发Bad Request错误。后端Web端接收数据正常,问题出在前端。
选择文件代码
const selectFile = async () => { // Opening Document Picker to select one file try { const res = await DocumentPicker.pickSingle({ type: [DocumentPicker.types.allFiles], }); console.log('res simple', res); console.log('res : ' + JSON.stringify(res)); //fetch uri from the response const fileUri = res.uri; console.log('res : ' + fileUri); setSingleFile(res); // handleAcceptedFiles(res); } catch (err) { setSingleFile(null); if (DocumentPicker.isCancel(err)) { ToastAndroid.show('Canceled', ToastAndroid.SHORT); } else { alert('Unknown Error: ' + JSON.stringify(err)); throw err; } } };
获取Blob代码
useEffect(() => { if (singleFile != null) { getData(); } }, [singleFile]); const getData = async () => { console.log('inside getData'); // console.log("singleFile",singleFile) console.log('singlefile', singleFile.uri); await fetch(singleFile.uri) .then(response => { console.log('response', response.blob()); // response.blob() setBlob(response.blob()); }) .catch(err => { console.log(err); }); };
上传数据代码
const uploadData = async () => { console.log("uploading data"); if (blob != null) { const fileToUpload = singleFile; console.log('blob', blob); const data = new FormData(); // data.append('Attachment', fileToUpload); data.append('Attachment', blob); data.append('Address', address); data.append('CNIC', CNIC); data.append('Phone_Number', number); data.append('Status', 'Pending Response'); data.append('Description', description); data.append('Appointment_id', 'b082564a-67f0-4f3d-96d7-1a441e071ef4'); data.append('Patient_id', email); data.append('Amount', '4200'); console.log('data', data); // Alert('data', data); try{ let res= fetch(`${url}/Finance/requestFA`, { method: 'POST', headers: { // Accept: 'application/json', 'Content-Type': 'multipart/form-data', Authorization: `Bearer ${user.refresh_token}`, }, body: data, }) console.log('res in fecth ', res); } catch (error) { console.log('error in upload', error); } } };
前端日志
blob {"_h": 0, "_i": 2, "_j": [TypeError: Already read], "_k": null, "_o": 0} LOG data {"_parts": [["Attachment", [Promise]], ["Address", [Object]], ["CNIC", [Object]], ["Phone_Number", [Object]], ["Status", "Pending Response"], ["Description", [Object]], ["Appointment_id", "b082564a-67f0-4f3d-96d7-1a441e071ef4"], ["Patient_id", "testmobile@gmail.com"], ["Amount", "4200"]]} LOG res in fecth {"_h": 0, "_i": 0, "_j": null, "_k": null}
后端日志
<QueryDict: {'Status': ['Pending Response'], 'Appointment_id': ['b082564a-67f0-4f3d-96d7-1a441e071ef4'], 'Patient_id': ['testmobile@gmail.com'], 'Amount': ['4200']}> 'Description' Bad Request: /Finance/requestFA
问题根源与修复方案
核心问题
- Blob未正确解析:
response.blob()返回Promise,直接存入state导致FormData中混入未resolved的Promise,破坏表单结构,导致后续字段无法被DRF解析。 - 字段类型错误:前端日志显示
Address、CNIC等字段是[Object]类型,DRF无法解析对象类型的表单字段,必须转为字符串。 - Content-Type设置错误:手动设置
multipart/form-data会丢失自动生成的boundary分隔符,导致后端无法正确拆分表单数据。
修复步骤
1. 正确获取Blob
修改getData函数,等待Blob Promise完成后再存入state:
useEffect(() => { if (singleFile != null) { getData(); } }, [singleFile]); const getData = async () => { console.log('inside getData'); try { const response = await fetch(singleFile.uri); const blob = await response.blob(); setBlob(blob); } catch (err) { console.log(err); } };
2. 修正字段类型并调整请求头
确保所有非文件字段为字符串,并移除手动设置的Content-Type:
const uploadData = async () => { console.log("uploading data"); if (blob != null) { const data = new FormData(); // 添加文件名帮助后端识别文件类型 data.append('Attachment', blob, singleFile.name); // 转换对象类型字段为字符串 data.append('Address', typeof address === 'object' ? JSON.stringify(address) : address); data.append('CNIC', typeof CNIC === 'object' ? JSON.stringify(CNIC) : CNIC); data.append('Phone_Number', typeof number === 'object' ? JSON.stringify(number) : number); data.append('Status', 'Pending Response'); data.append('Description', typeof description === 'object' ? JSON.stringify(description) : description); data.append('Appointment_id', 'b082564a-67f0-4f3d-96d7-1a441e071ef4'); data.append('Patient_id', email); data.append('Amount', '4200'); try{ const res = await fetch(`${url}/Finance/requestFA`, { method: 'POST', headers: { Authorization: `Bearer ${user.refresh_token}`, // 移除手动Content-Type,让系统自动生成带boundary的头 }, body: data, }); // 解析响应查看详细结果 const responseData = await res.json(); console.log('res in fetch ', responseData); } catch (error) { console.log('error in upload', error); } } };
3. 简化方案:直接使用DocumentPicker返回的文件对象
无需转换Blob,直接用选中的文件对象构造FormData,更高效:
// 移除blob相关的useState和getData函数 const uploadData = async () => { console.log("uploading data"); if (singleFile != null) { const data = new FormData(); // 直接传入文件对象,自动处理URI、文件名和类型 data.append('Attachment', { uri: singleFile.uri, name: singleFile.name, type: singleFile.type || 'application/octet-stream' }); // 同样确保字段为字符串 data.append('Address', typeof address === 'object' ? JSON.stringify(address) : address); data.append('CNIC', typeof CNIC === 'object' ? JSON.stringify(CNIC) : CNIC); data.append('Phone_Number', typeof number === 'object' ? JSON.stringify(number) : number); data.append('Status', 'Pending Response'); data.append('Description', typeof description === 'object' ? JSON.stringify(description) : description); data.append('Appointment_id', 'b082564a-67f0-4f3d-96d7-1a441e071ef4'); data.append('Patient_id', email); data.append('Amount', '4200'); try{ const res = await fetch(`${url}/Finance/requestFA`, { method: 'POST', headers: { Authorization: `Bearer ${user.refresh_token}`, }, body: data, }); const responseData = await res.json(); console.log('res in fetch ', responseData); } catch (error) { console.log('error in upload', error); } } };
内容的提问来源于stack exchange,提问作者Alishbah riaz
相关产品推荐
相关产品推荐

