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

向Celery任务传递音频文件出现JSON不可序列化错误的解决方案

问题原因

你遇到的报错是因为Celery默认使用JSON作为任务参数的序列化器,而Django的UploadedFile文件对象属于不可序列化的复杂对象,直接作为参数传给delay()方法就会触发序列化失败错误。

正确实现逻辑

核心原则是不要直接传递文件对象给Celery任务,只传递可序列化的基础类型参数(字符串、数字等),文件内容的读取由Celery Worker侧完成。

代码修改步骤

1. 调整Celery任务(tasks.py)

任务接收可序列化的参数:新创建的演唱会ID、用户ID、上传文件的临时路径列表,把原视图中的音轨保存逻辑移到任务内。

import logging
from django.contrib.auth.models import User
from django.core.files import File
from Beyond_April_Base_Backend.celery import app
from .models import Concert, Song
import music_tag
import os

@app.task
def upload_songs(concert_id, user_id, temp_file_paths):
    try:
        user = User.objects.get(pk=user_id)
        concert = Concert.objects.get(pk=concert_id)
    except (User.DoesNotExist, Concert.DoesNotExist) as e:
        logging.warning(f"关联数据不存在:{str(e)}")
        return

    for index, temp_path in enumerate(temp_file_paths):
        # 读取临时文件元数据
        audio_metadata = music_tag.load_file(temp_path)
        song_title = str(audio_metadata['title'])
        # 打开临时文件创建Song实例
        with open(temp_path, 'rb') as f:
            song_file = File(f, name=os.path.basename(temp_path))
            audio_file_instance = Song(
                title=song_title,
                concert=concert,
                user=user,
                concert_order = index + 1,
                audio_file = song_file,
            )
            audio_file_instance.save()
    # 处理完后删除临时文件释放空间
    for temp_path in temp_file_paths:
        os.remove(temp_path)

2. 调整视图逻辑(views.py)

视图侧先完成Concert实例的保存,再把上传的文件转存到持久化临时目录(避免Django请求结束后自动删除临时文件),将可序列化的参数传给Celery任务即可。

import os
from datetime import datetime
from django.conf import settings
from rest_framework import status, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from django.core.exceptions import ObjectDoesNotExist
from .models import Band, Concert
from .tasks import upload_songs

class ConcertUploadView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        track_files = request.FILES.getlist('files')
        current_user = self.request.user
        
        try:
            selected_band = Band.objects.get(name=request.data['band'])
        except ObjectDoesNotExist:
            print('band not received from form')
            selected_band = Band.objects.get(name='Band')
        venue_name = request.data['venue']
        concert_date_str = request.data['concertDate']
        concert_date_split = concert_date_str.split('(')[0]
        concert_date = datetime.strptime(concert_date_split, '%a %b %d %Y %H:%M:%S %Z%z ')
        concert_city = request.data['city']
        concert_state = request.data['state']
        concert_country = request.data['country']

        # 先保存Concert实例,拿到ID传给Celery
        new_concert = Concert(
            venue=venue_name,
            date=concert_date,
            city=concert_city,
            state=concert_state,
            country=concert_country,
            band=selected_band,
            user=current_user,
        )
        new_concert.save()

        # 把上传的文件存到自定义临时目录,避免请求结束后被删除
        temp_file_paths = []
        temp_dir = os.path.join(settings.MEDIA_ROOT, 'temp_uploads')
        os.makedirs(temp_dir, exist_ok=True)
        for track in track_files:
            temp_path = os.path.join(temp_dir, track.name)
            with open(temp_path, 'wb+') as f:
                for chunk in track.chunks():
                    f.write(chunk)
            temp_file_paths.append(temp_path)
        
        # 仅传递可序列化参数给Celery任务
        upload_songs.delay(new_concert.id, current_user.pk, temp_file_paths)

        return Response(status=status.HTTP_201_CREATED)
注意事项
  • 若你使用分布式Celery集群,需要将临时目录改为所有Worker节点都能访问的共享存储(如NAS、对象存储等),否则Worker无法读取到上传的文件。
  • 不建议修改Celery的序列化器为pickle来直接传递文件对象,存在严重的安全风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 17:15:02