Django视图函数外使用redirect问题:STT应用异步转写反馈异常求助
Django+Whisper转写:解决页面阻塞与结果反馈问题
为啥你的线程方案没用?
你当前的线程写法有个致命问题:线程里的redirect完全无效。原HTTP请求已经返回/?pending=True并结束了,后台线程跑起来之后,它的响应根本关联不上用户当前的浏览器会话,所以页面根本不会跳转,哪怕控制台已经输出了转写结果。
靠谱解决方案
方案1:Celery异步任务+前端轮询(工业级方案)
这是Django处理耗时任务的标准操作,稳得一批。
第一步:搭Celery环境(用Redis当消息队列)
- 先装依赖:
pip install celery redis
- 项目根目录新建
celery.py:
import os from celery import Celery os.environ.setdefault('DJANGO_SETTINGS_MODULE', '你的项目名.settings') app = Celery('你的项目名') app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks()
- 项目
__init__.py里加两行:
from .celery import app as celery_app __all__ = ('celery_app',)
settings.py配置Celery:
CELERY_BROKER_URL = 'redis://localhost:6379/0' CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
第二步:写异步转写任务
在你的app下建tasks.py:
import whisper from celery import shared_task from .models import AudioStore @shared_task(bind=True) def transcribe_audio(self, audio_name): # 模型可以提前预热,别每次都加载,浪费时间 model = whisper.load_model('small') audio = AudioStore.objects.get(name=audio_name) result = model.transcribe(audio.audio_file.path, verbose=False) # 结果存数据库,方便后续查 audio.transcription_result = result['text'] audio.transcription_status = 'done' audio.save() return result['text']
记得给AudioStore模型加两个字段:transcription_status(CharField,可选值pending/done/error)和transcription_result(TextField,允许为空)。
第三步:改视图函数
from django.shortcuts import redirect from .models import AudioStore from .tasks import transcribe_audio from django.http import JsonResponse def stt(request, audio_name): audio = AudioStore.objects.get(name=audio_name) # 先把状态设为pending audio.transcription_status = 'pending' audio.save() # 启动异步任务,拿任务ID task = transcribe_audio.delay(audio_name) # 跳首页,带任务ID给前端轮询 return redirect(f'/?task_id={task.id}&audio_name={audio_name}') def check_task_status(request): task_id = request.GET.get('task_id') audio_name = request.GET.get('audio_name') audio = AudioStore.objects.get(name=audio_name) if audio.transcription_status == 'done': return JsonResponse({'status': 'done', 'result': audio.transcription_result}) elif audio.transcription_status == 'pending': return JsonResponse({'status': 'pending'}) else: return JsonResponse({'status': 'error'})
第四步:前端加加载提示和轮询
在index.html里加一段JS:
<script> document.addEventListener('DOMContentLoaded', function() { const urlParams = new URLSearchParams(window.location.search); const taskId = urlParams.get('task_id'); const audioName = urlParams.get('audio_name'); if (taskId) { // 显示加载提示 const loadingDiv = document.createElement('div'); loadingDiv.style.padding = '10px'; loadingDiv.style.backgroundColor = '#f0f0f0'; loadingDiv.textContent = `正在转写 ${audioName},请稍候...`; document.body.appendChild(loadingDiv); // 每1秒查一次状态 const pollTimer = setInterval(() => { fetch(`/check_task_status/?task_id=${taskId}&audio_name=${audioName}`) .then(res => res.json()) .then(data => { if (data.status === 'done') { clearInterval(pollTimer); loadingDiv.textContent = '转写完成!'; // 这里可以把结果插到页面里,比如创建一个新div const resultDiv = document.createElement('div'); resultDiv.textContent = `结果:${data.result}`; document.body.appendChild(resultDiv); } else if (data.status === 'error') { clearInterval(pollTimer); loadingDiv.textContent = '转写出错了,刷新重试吧'; } }); }, 1000); } }); </script>
方案2:Django原生异步视图(轻量场景用)
不想搞Celery的话,用Django 3.1+支持的异步视图也能凑合用:
改视图函数
import asyncio import whisper from django.shortcuts import redirect from django.http import JsonResponse from .models import AudioStore async def do_transcribe(audio_path, audio_obj): model = whisper.load_model('small') result = model.transcribe(audio_path, verbose=False) audio_obj.transcription_result = result['text'] audio_obj.transcription_status = 'done' audio_obj.save() return result['text'] async def stt(request, audio_name): audio = AudioStore.objects.get(name=audio_name) audio.transcription_status = 'pending' audio.save() # 启动异步转写 loop = asyncio.get_event_loop() loop.create_task(do_transcribe(audio.audio_file.path, audio)) # 返回pending状态,前端轮询 return redirect(f'/?audio_name={audio_name}') async def get_result(request): audio_name = request.GET.get('audio_name') audio = AudioStore.objects.get(name=audio_name) if audio.transcription_status == 'done': return JsonResponse({'status': 'done', 'result': audio.transcription_result}) else: return JsonResponse({'status': 'pending'})
前端轮询逻辑和方案1差不多,只是把接口换成/get_result/。
几个优化点
- 模型复用:别每次转写都加载
small模型,启动项目时提前加载一次,用单例模式存起来,省时间。 - 状态跟踪:一定要用数据库存转写状态,别依赖Celery的任务状态,避免Celery挂了之后状态丢失。
- 超时处理:给转写任务加个超时时间,避免无限等待。
内容的提问来源于stack exchange,提问作者Berk Efe Keskin
相关产品推荐
相关产品推荐

