Django REST Framework经Postman上传大CSV/ZIP文件损坏问题求助
问题环境
- 系统环境:Windows
- 技术栈:Django 4.0.6(Python 3.8)、djangorestframework 3.13.1
- 测试环境:本地localhost环境,使用最新版Postman搭配Postman Agent测试接口
- 接口需求:单次请求同时提交3个CSV文件、1个ZIP文件,附带2个文本字段参数;接口采用JWT鉴权,当前鉴权逻辑运行正常
异常现象
测试时仅体积最小的CSV文件可正常上传保存,大体积CSV、ZIP文件上传后均出现损坏:
- 大体积CSV文件后半部分出现乱码,乱码字符形如:Û™eÞqHÂÔpŠl°‹<û yjϪ›kÃx›Ûr-¥x¡S¼à2SÕkÛår'mÒÕµd5ÿ¶Vê0@1 ̦Záë1§ŠIÇaÎ
“’ÏÛ€t»vRoT"·‡Qf„¾´é-Oa)]ЧK‹5C¤sWB0),3 Zž—2¸Ñóo«jŸH“ - 上传的ZIP文件无法正常解压打开
已检索大量相关技术资料,未找到适配当前场景的解决方案,需要可行的修复建议。
相关代码与配置
模型定义代码
class FileUploader(models.Model): id_file = models.AutoField('id_file', primary_key=True) Campaign_Name = models.CharField('Campaign_Name', max_length=255) ViewpointSurvey = models.FileField('ViewpointSurvey',upload_to=path_and_rename_base,max_length=255,blank=False,null=False,db_column='ViewpointSurvey', name='ViewpointSurvey') ProjectSurvey = models.FileField('ProjectSurvey', upload_to=path_and_rename_base, max_length=255, blank=False, null=False, db_column='ProjectSurvey', name='ProjectSurvey') Trajectories = models.FileField('Trajectories', upload_to=path_and_rename_base, max_length=255, blank=False, null=False, db_column='Trajectories', name='Trajectories') screenshots = models.FileField('screenshots', upload_to=path_and_rename_base, max_length=255, blank=False, null=False, db_column='screenshots', name='screenshots') timestamp=models.DateTimeField('timestamp',auto_now_add=True,db_column='timestamp', name='timestamp') id_project=models.CharField('id_project', max_length=255) class Meta: db_table = "file_uploader" verbose_name_plural = 'file_uploader' verbose_name = "file_uploader"
视图定义代码
class CSVUploadAPI(GenericAPIView): parser_classes = [MultiPartParser] serializer_class = UploadSerializer def put(self, request): data_collect = request.data serializer = UploadSerializer(data=data_collect) if serializer.is_valid(): serializer.save() return Response('Done') else: return Response(UploadSerializer.errors,status=status.HTTP_400_BAD_REQUEST)
序列化器定义代码
class UploadSerializer(serializers.ModelSerializer): class Meta: model= FileUploader fields = ('id_project','ProjectSurvey','Trajectories','ViewpointSurvey','screenshots','Campaign_Name')
文件存储重命名函数
def path_and_rename_base(instance, filename): upload_to = 'files/' ext = filename.split('.')[-1] # 文件名替换为随机UUID filename = '{}.{}'.format(uuid.uuid4(), ext) return os.path.join(upload_to, filename)
配置核对说明
- 已完成Postman请求头、请求体格式配置
- 测试文件大小对照:仅文件列表中高亮的最小体积CSV可正常上传,其余大体积CSV、ZIP文件上传后均损坏
修复方案
按优先级逐一排查,可覆盖绝大多数同类大文件上传损坏场景:
- 排查请求体被提前读取/错误转义问题
检查项目所有自定义中间件、视图装饰器,是否存在视图执行前调用request.body并将其转为字符串、做HTML转义/特殊字符替换的逻辑。二进制文件流一旦被按文本编码(如utf-8)读取,非文本字节会被解码为乱码,部分特殊字符会被转义为"这类HTML实体,和当前出现的乱码特征完全匹配。如果有日志打印等必须读取请求体的场景,读取后必须重置request.META['wsgi.input']的指针位置,避免后续解析器拿到损坏的流数据。 - 调整Django上传配置阈值
在settings.py中补充以下配置,避免文件超过内存阈值转存临时文件时出现截断:# 单次请求允许的总内容大小,可根据业务需求调整,示例为100MB DATA_UPLOAD_MAX_MEMORY_SIZE = 104857600 # 单文件超过该大小时写入临时文件,示例为10MB FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 上传文件统一使用二进制权限写入,避免Windows系统自动转换文本换行符导致文件损坏 FILE_UPLOAD_PERMISSIONS = 0o644 # 确认使用默认上传处理器,不要追加自定义的文本处理类上传处理器 FILE_UPLOAD_HANDLERS = [ 'django.core.files.uploadhandler.MemoryFileUploadHandler', 'django.core.files.uploadhandler.TemporaryFileUploadHandler', ] - 排查Postman配置问题
- 不要手动在请求头中添加
Content-Type字段,让Postman自动生成带boundary分隔符的multipart请求头,手动填写的头缺少正确分隔符会导致请求体解析截断 - 关闭Postman的请求参数自动编码开关,不要强制使用文本类编码格式处理二进制文件
- 可直接用curl命令传文件测试,排除Postman本身的bug:
curl -X PUT http://localhost:8000/your-upload-path \ -H "Authorization: Bearer 你的JWT令牌" \ -F "id_project=测试项目ID" \ -F "Campaign_Name=测试活动名" \ -F "ViewpointSurvey=@/本地/文件/路径/ViewpointSurvey.csv" \ -F "ProjectSurvey=@/本地/文件/路径/ProjectSurvey.csv" \ -F "Trajectories=@/本地/文件/路径/Trajectories.csv" \ -F "screenshots=@/本地/文件/路径/screenshots.zip"
- 不要手动在请求头中添加
- 排除Windows系统环境干扰
- 临时关闭Windows Defender或第三方杀毒软件的实时文件扫描,这类软件会锁定Django生成的临时上传文件,导致大文件未完全写入就被截断
- 大文件测试时关闭runserver的热重载模式,避免文件写入过程中触发进程重启打断写入流程
- 加校验逻辑定位故障阶段
在视图中增加文件哈希、大小校验逻辑,对比本地原文件参数,确认故障发生在传输解析阶段还是存储写入阶段:
如果接口接收到的文件MD5、大小和本地原文件不一致,问题出在请求传输/解析环节;如果接收时参数一致,保存到磁盘后损坏,问题出在文件写入逻辑。import hashlib def put(self, request): # 取ZIP文件做校验示例 f = request.data['screenshots'] file_hash = hashlib.md5() for chunk in f.chunks(): file_hash.update(chunk) print(f"接口接收到的文件MD5:{file_hash.hexdigest()},文件大小:{f.size}") # 原有业务逻辑保持不变
内容的提问来源于stack exchange,提问作者Gasta153
相关产品推荐
相关产品推荐

