You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于Django REST Framework的电子书阅读器:无损传输PDF数据方案问询

嘿,我之前刚好做过类似的移动端电子书阅读器项目,用DRF传输带图片的PDF其实核心就是别搞乱二进制数据的传输流程,下面给你一步步讲怎么实现,避开那些容易踩的坑:

核心思路:直接传输二进制流,避免文本编码干扰

PDF本身是二进制格式文件,一旦被当成文本进行编码/解码操作,里面的图片、字体等非文本内容肯定会损坏。所以我们要做的就是让服务器直接把PDF的二进制原数据传给客户端,全程不碰文本编码。

1. 先确保用户上传的PDF存储没问题

首先得保证用户上传的PDF在服务器端存储时是完整的,用Django的FileField就能搞定,它默认会以二进制模式处理文件:

from django.db import models

class UploadedPDF(models.Model):
    title = models.CharField(max_length=200)
    # 上传的PDF会存在media/pdfs目录下,确保这个目录有读写权限
    pdf_file = models.FileField(upload_to='pdfs/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

只要你没手动修改文件的存储编码逻辑,上传的PDF里的图片就不会丢失。

2. 编写DRF视图返回PDF二进制数据

别用DRF默认的JSON响应!要用专门的文件响应类,这里有两种靠谱的实现方式:

方式一:用DRF的FileResponse(推荐)

这是DRF专门为文件传输设计的响应类,处理二进制数据很省心:

from rest_framework.views import APIView
from rest_framework.response import FileResponse
from .models import UploadedPDF

class PDFDetailView(APIView):
    def get(self, request, pk):
        try:
            pdf = UploadedPDF.objects.get(pk=pk)
        except UploadedPDF.DoesNotExist:
            return Response({"error": "PDF not found"}, status=404)
        
        # 一定要用二进制只读模式打开文件
        pdf_file = open(pdf.pdf_file.path, 'rb')
        # 设置正确的Content-Type,告诉客户端这是PDF文件
        response = FileResponse(pdf_file, content_type='application/pdf')
        # 可选:设置Content-Disposition,inline表示直接在客户端打开,attachment表示触发下载
        response['Content-Disposition'] = f'inline; filename="{pdf.title}.pdf"'
        return response

方式二:用Django原生HttpResponse

如果你更习惯原生写法,也可以这么实现,效果完全一致:

from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import UploadedPDF

class PDFDetailView(APIView):
    def get(self, request, pk):
        try:
            pdf = UploadedPDF.objects.get(pk=pk)
        except UploadedPDF.DoesNotExist:
            return Response({"error": "PDF not found"}, status=404)
        
        # 用with语句自动关闭文件,更安全
        with open(pdf.pdf_file.path, 'rb') as f:
            pdf_data = f.read()
        
        response = HttpResponse(pdf_data, content_type='application/pdf')
        response['Content-Disposition'] = f'inline; filename="{pdf.title}.pdf"'
        return response

3. 避开那些容易踩的坑

  • 绝对别用JSONResponse返回PDF:JSON会把二进制数据转成字符串,直接导致PDF损坏,打开时会提示“文件已损坏”。
  • 检查自定义存储后端:如果你用了云存储(比如S3)或者自定义存储类,要确保后端支持二进制文件的完整读写,别在存储环节就把编码搞乱了。
  • 测试时先直接验证PDF:别只看APP里的显示,先在浏览器里访问接口,下载PDF后打开看看图片是否正常,能快速定位是服务器还是客户端的问题。
  • 大PDF优化(可选):如果是几百兆的大PDF,直接返回整个文件会占用大量内存,用StreamingHttpResponse分块传输更友好:
from django.http import StreamingHttpResponse
import os
from rest_framework.views import APIView
from .models import UploadedPDF

class PDFStreamView(APIView):
    def get(self, request, pk):
        try:
            pdf = UploadedPDF.objects.get(pk=pk)
        except UploadedPDF.DoesNotExist:
            return Response({"error": "PDF not found"}, status=404)
        
        file_path = pdf.pdf_file.path
        chunk_size = 8192  # 每次传8KB,可根据需求调整
        
        def file_iterator():
            with open(file_path, 'rb') as f:
                while chunk := f.read(chunk_size):
                    yield chunk
        
        response = StreamingHttpResponse(file_iterator(), content_type='application/pdf')
        response['Content-Disposition'] = f'inline; filename="{pdf.title}.pdf"'
        response['Content-Length'] = os.path.getsize(file_path)
        return response

4. 客户端接收的小提示

客户端拿到响应后,要把二进制数据直接保存成PDF文件,别做额外的字符串解码操作。比如iOS用Data类型直接写入文件,Android用InputStream读取后写入本地,这样就能保证PDF里的图片完整无损。

内容的提问来源于stack exchange,提问作者alex

火山引擎 最新活动