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

Django视图函数外使用redirect问题:STT应用异步转写反馈异常求助

Django+Whisper转写:解决页面阻塞与结果反馈问题

为啥你的线程方案没用?

你当前的线程写法有个致命问题:线程里的redirect完全无效。原HTTP请求已经返回/?pending=True并结束了,后台线程跑起来之后,它的响应根本关联不上用户当前的浏览器会话,所以页面根本不会跳转,哪怕控制台已经输出了转写结果。

靠谱解决方案

方案1:Celery异步任务+前端轮询(工业级方案)

这是Django处理耗时任务的标准操作,稳得一批。

第一步:搭Celery环境(用Redis当消息队列)

  1. 先装依赖:
pip install celery redis
  1. 项目根目录新建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()
  1. 项目__init__.py里加两行:
from .celery import app as celery_app

__all__ = ('celery_app',)
  1. 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/。

几个优化点

  1. 模型复用:别每次转写都加载small模型,启动项目时提前加载一次,用单例模式存起来,省时间。
  2. 状态跟踪:一定要用数据库存转写状态,别依赖Celery的任务状态,避免Celery挂了之后状态丢失。
  3. 超时处理:给转写任务加个超时时间,避免无限等待。

内容的提问来源于stack exchange,提问作者Berk Efe Keskin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 21:54:54