Expo React Native上传PDF至DRF路径错误求助
Expo React Native 上传PDF至DRF API路径错误排查
问题概述
开发Expo React Native应用时,将PDF文件上传到Django Rest Framework搭建的API后,文件未保存到指定的media/pdfs/目录,而是直接存入media根目录,导致访问时返回404错误。浏览器端上传一切正常,但Expo应用上传出现路径异常。
后端代码
models.py
# models.py from django.db import models class PDFFile(models.Model): name = models.CharField(max_length=200, blank=True) pdf_file = models.FileField(upload_to="pdfs/", blank=True) def __str__(self): return str(self.name)
serializers.py
# serializers.py from rest_framework import serializers from .models import PDFFile class PDFFileModelSerializer(serializers.ModelSerializer): class Meta: model = PDFFile fields = ["id", "name", "pdf_file"]
views.py
# views.py from .models import PDFFile from .serializers import PDFFileModelSerializer from rest_framework import viewsets, status from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.response import Response class PDFFileViewSet(viewsets.ModelViewSet): queryset = PDFFile.objects.all() serializer_class = PDFFileModelSerializer parser_classes = (MultiPartParser, FormParser) def create(self, request, *args, **kwargs): name = request.data["name"] pdf_file = request.data["pdf_file"] print("PDF FILE: ", pdf_file) PDFFile.objects.create(name=name, pdf_file=pdf_file) return Response("PDF create with success!", status=status.HTTP_200_OK)
应用urls.py
# urls.py (app) from rest_framework import routers from .views import PDFFileViewSet from django.urls import path, include router = routers.DefaultRouter() router.register("pdfs", PDFFileViewSet) urlpatterns = [ path("", include(router.urls)) ]
项目urls.py
# urls.py (project) from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('api/', include("pdf_uploader.urls")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
settings.py
# settings.py import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ) } DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 STATIC_URL = '/static/' MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] STATIC_ROOT = BASE_DIR / 'staticfiles'
Expo React Native 组件代码
import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView, Alert } from 'react-native' import React, { useState } from 'react' import * as DocumentPicker from 'expo-document-picker' import { PDF_BASE_URL } from '../data/api' const PdfPicker = () => { const [pdfFile, setPdfFile] = useState(null) const [name, setName] = useState("") const [uploading, setUploading] = useState(false) const pickerPdf = async () => { let result = await DocumentPicker.getDocumentAsync({ type: 'application/pdf', }) const fileName = result.uri.split('/').pop() // extract the file name from the URI const source = { uri: fileName, name: result.name } console.log("source", source) setPdfFile(source.uri) setName(source.name) } const uploadPdfFile = async () => { setUploading(true) const uploadPdf = pdfFile const pdfName = name const data = new FormData() data.append("name", pdfName) data.append("pdf_file", uploadPdf) const response = await fetch(PDF_BASE_URL + "pdfs/", { method: "POST", headers: { "Content-Type": "multipart/form-data", }, body: data }) console.log("RESULT: ", JSON.stringify(response)) try { await JSON.stringify(response) } catch (e) { console.log("Catch error:", e) } setUploading(false) Alert.alert( "Pdf file uploaded..!!" ) setName("") setPdfFile(null) } return ( <SafeAreaView style={styles.container} > <View> <Text style={{color: "white", marginBottom: 30}}>{name}</Text> </View> <TouchableOpacity style={styles.selectButton} onPress={pickerPdf} > <Text style={styles.buttonText}>Pick a Pdf file</Text> </TouchableOpacity> <View style={styles.pdfContainer}> <TouchableOpacity style={styles.uploadButton} onPress={uploadPdfFile}> <Text style={styles.buttonText}>Upload Pdf file</Text> </TouchableOpacity> </View> </SafeAreaView> ) } export default PdfPicker const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', backgroundColor: '#000', justifyContent: 'center', }, selectButton: { borderRadius: 5, width: 150, height: 50, backgroundColor: 'blue', alignItems: 'center', justifyContent: 'center', }, buttonText: { color: 'white', fontSize: 18, fontWeight: 'bold' }, pdfContainer: { marginTop: 30, marginBottom: 50, alignItems: 'center', }, uploadButton: { borderRadius: 5, width: 150, height: 50, backgroundColor: 'green', alignItems: 'center', justifyContent: 'center', } })
日志与返回结果
Expo 日志
LOG RESULT: {"type":"default","status":200,"ok":true,"statusText":"","headers":{"map":{"allow":"GET, POST, HEAD, OPTIONS","content-length":"26","content-type":"application/json","cross-origin-opener-policy":"same-origin","date":"Thu, 06 Apr 2023 14:19:30 GMT","referrer-policy":"same-origin","server":"railway","vary":"Accept, Origin, Cookie","x-content-type-options":"nosniff","x-frame-options":"DENY"}},"url":"{my.server}/api/pdfs/","bodyUsed":false,"_bodyInit":{"_data":{"size":26,"offset":0,"blobId":"837A79CD-3B4F-4D1C-A15D-74BE46D9CD26","type":"application/json","name":"pdfs.json","__collector":{}}},"_bodyBlob":{"_data":{"size":26,"offset":0,"blobId":"837A79CD-3B4F-4D1C-A15D-74BE46D9CD26","type":"application/json","name":"pdfs.json","__collector":{}}}}
浏览器上传返回结果
{ "id": 5, "name": "livro", "pdf_file": "{my.server}/media/pdfs/Stephen.King.-.A.Balsa_WF9W8Kc.pdf" }
Expo 上传返回结果
{ "id": 4, "name": "CERTIDAO-IANDERSONPIZETTEUENDERSONPABLOIII.pdf", "pdf_file": "{my.server}/media/C2699748-A1CF-49F5-A3C7-6B527AD3FF44.pdf" },
后端日志
Not Found: /media/C2699748-A1CF-49F5-A3C7-6B527AD3FF44.pdf
问题核心
浏览器上传的文件正常保存到media/pdfs/目录,但Expo上传的文件直接存入media根目录,导致访问时返回404。已查阅官方文档并尝试相关工具,仍未解决。
解决方案
问题根源
- Expo端提取文件时错误地将URI的最后一段作为
uri值,而非完整的文件本地URI; - 构建FormData时未传入符合要求的文件对象,仅传入了字符串文件名,导致DRF无法正确识别文件类型并应用
upload_to规则。
修改后的Expo代码
const pickerPdf = async () => { let result = await DocumentPicker.getDocumentAsync({ type: 'application/pdf', copyToCacheDirectory: true // 确保文件可被访问 }) if (!result.canceled) { // 保留完整的文件URI,提取原始文件名 setPdfFile(result.assets[0].uri) setName(result.assets[0].name) } } const uploadPdfFile = async () => { setUploading(true) const data = new FormData() // 正确构建FormData文件项,包含uri、文件名和MIME类型 data.append("name", name) data.append("pdf_file", { uri: pdfFile, name: name, type: 'application/pdf' }) try { const response = await fetch(PDF_BASE_URL + "pdfs/", { method: "POST", // 不要手动设置Content-Type,fetch会自动添加正确的boundary分隔符 headers: {}, body: data }) const responseData = await response.json() console.log("RESULT: ", responseData) Alert.alert("PDF文件上传成功!") } catch (e) { console.log("上传错误:", e) Alert.alert("上传失败,请重试") } finally { setUploading(false) setName("") setPdfFile(null) } }
后端代码优化(可选)
建议改用序列化器处理创建逻辑,避免直接操作模型,保证验证和逻辑一致性:
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) return Response(serializer.data, status=status.HTTP_201_CREATED)
修改后,Expo上传的文件会正确保存到media/pdfs/目录,解决404访问错误。
内容的提问来源于stack exchange,提问作者Pablo III
相关产品推荐
相关产品推荐

