如何通过Django Rest Framework向React前端传输多个PDF文件?
问题描述
我正尝试从Django Rest Framework中获取通过React前端上传的文件。现有CourseDocument模型定义如下:
class CourseDocument(models.Model): title = models.CharField(max_length=30, default='') document = models.FileField(upload_to='courses') course = models.ForeignKey(to='Course', on_delete=models.SET_NULL, null=True)
对应的DRF端点代码如下:
@action(methods=['GET', 'POST'], detail=True, url_path='documents', permission_classes=EMPLOYEE_PERMISSION) def course_documents(self, request, *args, **kwargs): if request.method == 'GET': qs = CourseDocument.objects.filter(course_id=kwargs['pk']) fs = FileSystemStorage() filename = qs.first().document.path if fs.exists(filename): with fs.open(filename) as pdf: response = HttpResponse(pdf, content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="mypdf.pdf"' return response else: print("NOT FOUND") if request.method == 'POST': serializer = CourseDocumentSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(data=serializer.data, status=200) else: return Response(data=serializer.errors, status=400) return Response(status=200)
目前我通过FileSystemStorage在HTTP响应中以application/pdf类型返回文件,但仅能处理单个PDF文件。出于安全考虑,我不想通过链接传递PDF,请问如何实现向前端传输多个PDF文件?
解决方案
方法1:将多个PDF打包为ZIP文件返回
这是最实用的方案,前端收到ZIP后可解压处理,既满足安全需求,也能一次性传输多个文件。
修改GET请求的处理逻辑:
import zipfile from io import BytesIO @action(methods=['GET', 'POST'], detail=True, url_path='documents', permission_classes=EMPLOYEE_PERMISSION) def course_documents(self, request, *args, **kwargs): if request.method == 'GET': qs = CourseDocument.objects.filter(course_id=kwargs['pk']) if not qs.exists(): return Response({"detail": "没有找到相关文档"}, status=404) # 创建内存中的ZIP文件,避免写入磁盘 zip_buffer = BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for doc in qs: # 提取文件原始名称,避免带路径 original_filename = doc.document.name.split('/')[-1] # 将文件添加到ZIP包中 zip_file.write(doc.document.path, arcname=original_filename) # 重置缓冲区指针到起始位置 zip_buffer.seek(0) # 构造ZIP响应返回给前端 response = HttpResponse(zip_buffer, content_type='application/zip') response['Content-Disposition'] = f'attachment; filename="course_documents.zip"' return response # POST请求逻辑保持不变 if request.method == 'POST': serializer = CourseDocumentSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(data=serializer.data, status=200) else: return Response(data=serializer.errors, status=400) return Response(status=200)
React前端处理示例
使用jszip库解压ZIP包并处理内部PDF:
import JSZip from 'jszip'; async function fetchCourseDocuments(courseId) { const response = await fetch(`/api/courses/${courseId}/documents/`, { method: 'GET', credentials: 'include' // 需认证时添加 }); const zipBlob = await response.blob(); const zip = await JSZip.loadAsync(zipBlob); // 遍历ZIP中的所有PDF文件 for (const filename in zip.files) { if (filename.endsWith('.pdf')) { const pdfBlob = await zip.files[filename].async('blob'); // 生成临时URL用于预览或下载 const pdfUrl = URL.createObjectURL(pdfBlob); // 后续可将URL绑定到预览组件或下载按钮 console.log(`处理PDF文件: ${filename}`, pdfUrl); // 注意:使用完后调用URL.revokeObjectURL(pdfUrl)释放内存 } } }
方法2:返回多个PDF的Base64编码列表
如果不需要ZIP打包,可将每个PDF转换为Base64字符串后以列表形式返回。此方案适合文件数量少、体积小的场景,避免响应体过大。
修改GET请求逻辑:
import base64 @action(methods=['GET', 'POST'], detail=True, url_path='documents', permission_classes=EMPLOYEE_PERMISSION) def course_documents(self, request, *args, **kwargs): if request.method == 'GET': qs = CourseDocument.objects.filter(course_id=kwargs['pk']) if not qs.exists(): return Response({"detail": "没有找到相关文档"}, status=404) document_list = [] for doc in qs: with open(doc.document.path, 'rb') as pdf_file: # 将PDF文件编码为Base64字符串 pdf_base64 = base64.b64encode(pdf_file.read()).decode('utf-8') document_list.append({ 'title': doc.title, 'filename': doc.document.name.split('/')[-1], 'pdf_base64': pdf_base64 }) return Response(document_list, status=200) # POST请求逻辑保持不变 if request.method == 'POST': serializer = CourseDocumentSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(data=serializer.data, status=200) else: return Response(data=serializer.errors, status=400) return Response(status=200)
React前端处理示例
将Base64字符串转换为Blob后处理:
// 工具函数:Base64转Blob function base64ToBlob(base64Data, contentType = 'application/pdf') { const byteCharacters = atob(base64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512); const byteNumbers = Array.from(slice, char => char.charCodeAt(0)); byteArrays.push(new Uint8Array(byteNumbers)); } return new Blob(byteArrays, { type: contentType }); } async function fetchCourseDocuments(courseId) { const response = await fetch(`/api/courses/${courseId}/documents/`, { method: 'GET', credentials: 'include' }); const documents = await response.json(); documents.forEach(doc => { const pdfBlob = base64ToBlob(doc.pdf_base64); const pdfUrl = URL.createObjectURL(pdfBlob); // 处理预览或下载逻辑 console.log(doc.title, pdfUrl); // 使用后记得释放临时URL // URL.revokeObjectURL(pdfUrl); }); }
注意事项
- 优先选择ZIP方案:尤其是文件数量多或体积大时,Base64会让响应体积增加约30%,容易导致性能问题。
- 内存占用:ZIP打包在内存中进行,超大文件可考虑流式ZIP实现,但复杂度较高。
- 前端内存管理:使用
URL.createObjectURL后,务必在不需要时调用URL.revokeObjectURL释放内存,避免内存泄漏。
内容的提问来源于stack exchange,提问作者pav0x
相关产品推荐
相关产品推荐

