如何在部署于Heroku的Django项目中保存并访问媒体文件?
解决方案
一、修正Django媒体文件配置(适配Heroku临时文件系统)
Heroku文件系统为临时存储,且本地与线上BASE_DIR路径存在差异,需调整媒体文件配置避免路径冲突:
# settings.py import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # 简化媒体文件存储路径,直接放在项目根目录下的recordings文件夹 MEDIA_ROOT = os.path.join(BASE_DIR, 'recordings') # 设置独立的媒体URL前缀,避免与根路由冲突 MEDIA_URL = '/media/' # 确保存储目录存在,避免部署时因目录缺失报错 if not os.path.exists(MEDIA_ROOT): os.makedirs(MEDIA_ROOT)
二、确保Model与Serializer正确写入文件系统
你的Recording模型必须通过FileField指定存储规则,Django才会自动将上传文件写入文件系统(而非仅在数据库存路径):
# models.py from django.db import models class Recording(models.Model): owner_id = models.IntegerField() # upload_to设为空字符串,文件直接存入MEDIA_ROOT目录 recording = models.FileField(upload_to='') created_at = models.DateTimeField(auto_now_add=True)
对应Serializer需关联该模型:
# serializers.py from rest_framework import serializers from .models import Recording class RecordingSerializer(serializers.ModelSerializer): class Meta: model = Recording fields = ['id', 'owner_id', 'recording', 'created_at']
此时调用file_serializer.save()时,Django会自动将上传文件写入MEDIA_ROOT目录。
三、修正Celery任务中的文件路径
移除硬编码路径,改用Django配置的MEDIA_ROOT拼接路径,避免线上线下路径不匹配:
# tasks.py from django.conf import settings import os def parse_arguments(filename): new_filename = filename.lstrip('v') # 从配置中获取媒体根路径,拼接完整文件路径 audio_path = os.path.join(settings.MEDIA_ROOT, filename) target_path = os.path.join(settings.MEDIA_ROOT, new_filename) if not target_path.endswith(".wav"): target_path += ".wav" if os.path.isfile(audio_path) and audio_path.endswith(".wav"): convert_audio(audio_path, target_path) return target_path else: raise TypeError("指定的音频文件路径无效或不是WAV格式")
四、在Heroku上安装ffmpeg依赖
Heroku默认无ffmpeg,需添加对应buildpack:
# 命令行执行 heroku buildpacks:add https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git
五、确保媒体文件可被访问
生产环境用Whitenoise处理媒体文件访问(Django自带的static函数仅适用于开发环境):
- 安装Whitenoise:
pip install whitenoise
- 修改
settings.py添加中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # 将Whitenoise中间件置于此处 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # 配置Whitenoise处理静态与媒体文件 STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' WHITENOISE_ALLOW_ALL_ORIGINS = True
- 修改
urls.py区分开发/生产环境:
# urls.py from django.conf import settings from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('EmotionTalk.emotion_talk_app.urls')), path('auth/', include('EmotionTalk.auth_app.urls')), path('api-token-auth/', views.obtain_auth_token), ] # 仅开发环境启用Django自带的静态/媒体文件服务 if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
六、添加文件自动删除逻辑
文件使用后立即删除,符合你“仅使用数秒”的需求:
# tasks.py def recognize_emotion(file_name, owner_id): try: target_path = parse_arguments(file_name) # 执行情感识别逻辑... # 处理完成后删除原文件与处理后的文件 audio_path = os.path.join(settings.MEDIA_ROOT, file_name) if os.path.exists(audio_path): os.remove(audio_path) if os.path.exists(target_path): os.remove(target_path) except Exception as e: print(f"处理失败: {str(e)}")
七、Heroku部署补充
- 确保Procfile配置正确(含Web与Celery进程):
web: gunicorn EmotionTalk.wsgi --log-file - worker: celery -A EmotionTalk worker --loglevel=info
- 注意Heroku dyno重启后临时文件会全部清空,无需担心残留文件问题。
内容的提问来源于stack exchange,提问作者Diyan Kalaydzhiev
相关产品推荐
相关产品推荐

