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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:50:27