如何在Django与DRF中使用Celery上传并后台处理Excel文件——解决InMemoryUploadedFile无法JSON序列化问题
解决Celery传递InMemoryUploadedFile时的序列化错误
你遇到的kombu.exceptions.EncodeError问题,本质是Celery默认的JSON序列化器无法处理Django的InMemoryUploadedFile对象——它不属于JSON原生支持的可序列化类型。下面给你几种实用的解决思路,按推荐程度排序:
方案1:先保存文件到存储,传递文件路径(推荐)
这是最稳妥的方式,尤其适合处理大文件。先把上传的文件保存到本地磁盘、云存储或其他服务,再将文件路径作为字符串传给Celery任务,任务通过路径读取文件即可。
修改视图代码:
class FileUploadView(generics.CreateAPIView): parser_classes = [MultiPartParser] serializer_class = FileSerializerXLSX def post(self, request, format=None): serialized = FileSerializerXLSX(data=request.data) if serialized.is_valid(): file_obj = request.data['file'] # 先保存文件到本地(示例路径,可根据实际需求调整) import os from django.conf import settings # 确保临时存储目录存在 upload_dir = os.path.join(settings.MEDIA_ROOT, 'temp_excels') os.makedirs(upload_dir, exist_ok=True) # 生成唯一文件名避免冲突 file_path = os.path.join(upload_dir, file_obj.name) with open(file_path, 'wb+') as destination: for chunk in file_obj.chunks(): destination.write(chunk) # 传递文件路径给Celery任务 import_excel_task.delay(file_path) return Response(status=204) return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
修改Celery任务代码:
def import_excel_helper(file_path): # 通过路径读取文件 df = extract_excel_to_dataframe(file_path) transform_df_to_clientmodel(df) transform_df_to_productmodel(df) transform_df_to_salesmodel(df) # 可选:处理完成后删除临时文件 import os os.remove(file_path) @shared_task(name="import_excel_task") def import_excel_task(file_path): logger.info("Importing excel file from path: %s", file_path) import_excel_helper(file_path)
方案2:传递文件字节内容(适合小文件)
如果文件体积不大,可以将文件内容转换成字节串(JSON支持字节序列化),在任务中再把字节串重新包装成类文件对象供pandas处理。
修改视图代码:
class FileUploadView(generics.CreateAPIView): parser_classes = [MultiPartParser] serializer_class = FileSerializerXLSX def post(self, request, format=None): serialized = FileSerializerXLSX(data=request.data) if serialized.is_valid(): file_obj = request.data['file'] # 读取文件字节内容 file_bytes = file_obj.read() # 传递字节给任务 import_excel_task.delay(file_bytes) return Response(status=204) return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
修改Celery任务代码:
from io import BytesIO def import_excel_helper(file_bytes): # 把字节串包装成类文件对象 file_like_obj = BytesIO(file_bytes) df = extract_excel_to_dataframe(file_like_obj) transform_df_to_clientmodel(df) transform_df_to_productmodel(df) transform_df_to_salesmodel(df) @shared_task(name="import_excel_task") def import_excel_task(file_bytes): logger.info("Importing excel file from byte content") import_excel_helper(file_bytes)
方案3:改用Pickle序列化器(谨慎使用)
Celery支持用Pickle序列化任意Python对象,但存在严重安全风险——如果任务参数被恶意篡改,可能导致远程代码执行。仅适合完全可信的内部系统使用。
修改Celery配置:
在你的Django配置文件中添加:
CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' CELERY_ACCEPT_CONTENT = ['pickle']
修改配置后,你原有代码不需要改动,直接传递InMemoryUploadedFile对象即可,但一定要充分评估安全风险!
内容的提问来源于stack exchange,提问作者Alex T
相关产品推荐
相关产品推荐

