如何在Django视图中实现等待Webhook回调或超时处理逻辑
方案选型判断
你最初设想的「每次等待回调时创建异步进程/线程」的方案完全不适配你的场景,尤其不适合Heroku部署环境,核心问题如下:
- Heroku的Web dyno默认30分钟无请求就会休眠,运行中的后台线程/进程会被直接回收,数小时级别的超时等待逻辑根本无法完整执行
- 每发送一条消息就占用一个独立线程/进程,并发稍高就会耗尽服务器内存、连接数资源,稳定性极差
- 内存中运行的线程/进程没有持久化存储,一旦服务重启、版本部署,所有等待中的任务会全部丢失,提醒逻辑直接失效
正确的实现思路是用持久化状态存储+延迟任务调度+Webhook状态更新的架构,不需要常驻等待的进程/线程,资源占用极低,完全适配Heroku的部署规则。
具体实现步骤
1. 定义数据模型存储消息状态
首先在Django模型里记录每一条发出的WhatsApp消息的状态、超时时间、提醒内容,所有状态落库,不会因为服务重启丢失:
# models.py from django.db import models from django.utils import timezone class WhatsAppMessage(models.Model): STATUS_CHOICES = [ ("SENT", "已发送待回复"), ("REPLIED", "客户已回复"), ("REMINDER_SENT", "已发送提醒"), ] customer_phone = models.CharField(max_length=20, verbose_name="客户手机号") message_content = models.TextField(verbose_name="初始发送内容") sent_at = models.DateTimeField(default=timezone.now, verbose_name="发送时间") timeout_at = models.DateTimeField(verbose_name="超时触发提醒时间") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="SENT") reminder_content = models.TextField(verbose_name="提醒内容")
2. 实现延迟提醒任务
用Celery做任务队列(Heroku有官方Redis插件作为任务存储,免费档足够支撑中小规模使用),提醒任务只在超时时间点触发,不需要提前占用资源等待:
# tasks.py from celery import shared_task from django.utils import timezone from .models import WhatsAppMessage # 替换成你自己封装的WhatsApp官方API发消息方法 from .utils import send_whatsapp_message @shared_task def send_timeout_reminder(message_id): # 任务触发时先校验消息存在性 try: msg = WhatsAppMessage.objects.get(id=message_id) except WhatsAppMessage.DoesNotExist: return # 二次校验状态:如果客户已经回复,直接终止逻辑不发提醒 if msg.status != "SENT": return # 校验时间避免任务提前执行导致误发 if timezone.now() < msg.timeout_at: return # 调用API发提醒 send_whatsapp_message(to=msg.customer_phone, content=msg.reminder_content) msg.status = "REMINDER_SENT" msg.save()
3. 初始消息发送视图
给客户发初始消息时,只需要落库+投递延迟任务,不需要开线程等待:
# views.py from django.utils import timezone from datetime import timedelta from django.http import JsonResponse from .models import WhatsAppMessage from .tasks import send_timeout_reminder from .utils import send_whatsapp_message def send_customer_message(request): # 省略参数合法性校验逻辑 customer_phone = request.POST.get("phone") initial_content = request.POST.get("content") reminder_content = request.POST.get("reminder_content") timeout_hours = int(request.POST.get("timeout_hours", 2)) # 默认2小时超时 # 先调用WhatsApp API发初始消息 send_whatsapp_message(to=customer_phone, content=initial_content) # 消息记录落库 msg = WhatsAppMessage.objects.create( customer_phone=customer_phone, message_content=initial_content, timeout_at=timezone.now() + timedelta(hours=timeout_hours), reminder_content=reminder_content ) # 投递延迟任务,到超时时间点自动执行 send_timeout_reminder.apply_async(args=[msg.id], eta=msg.timeout_at) return JsonResponse({"code": 0, "message_id": msg.id})
4. WhatsApp Webhook接收视图
收到WhatsApp推送的客户回复事件时,直接把对应消息的状态更新为已回复即可,延迟任务触发时会自动跳过已回复的消息,不会发提醒:
# views.py from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from .models import WhatsAppMessage import json @csrf_exempt def whatsapp_event_webhook(request): # 处理WhatsApp要求的Webhook校验逻辑 if request.method == "GET": return HttpResponse(request.GET.get("hub.challenge")) # 解析客户回复的事件 payload payload = json.loads(request.body) # 省略官方payload解析逻辑,提取回复客户的手机号 customer_phone = extract_customer_phone(payload) # 找到该客户最新的一条待回复消息,更新状态 latest_pending = WhatsAppMessage.objects.filter( customer_phone=customer_phone, status="SENT" ).order_by("-sent_at").first() if latest_pending: latest_pending.status = "REPLIED" latest_pending.save() return HttpResponse(status=200)
Heroku部署注意事项
常规Celery方案配置
- 开通Heroku Redis插件作为Celery的任务存储
- 在Procfile中添加worker进程配置:
worker: celery -A 你的项目名 worker --loglevel=info - 在Heroku后台手动开启worker dyno(默认是关闭状态)
轻量低成本方案(适合小流量场景)
如果不想维护Celery和Redis,可以直接用Heroku官方免费的Scheduler插件:
- 设置每10分钟触发一次Django自定义管理命令
- 管理命令直接扫描所有已到超时时间、状态仍为
SENT的消息,发送提醒并更新状态 - 这个方案不需要额外的组件资源,唯一的缺点是提醒触发时间最多有10分钟的误差,对于数小时级别的超时场景完全可以接受
对应的管理命令示例:
# management/commands/send_pending_reminders.py from django.core.management.base import BaseCommand from django.utils import timezone from .models import WhatsAppMessage from .utils import send_whatsapp_message class Command(BaseCommand): def handle(self, *args, **options): overdue_msgs = WhatsAppMessage.objects.filter( status="SENT", timeout_at__lte=timezone.now() ) for msg in overdue_msgs: send_whatsapp_message(to=msg.customer_phone, content=msg.reminder_content) msg.status = "REMINDER_SENT" msg.save()
内容的提问来源于stack exchange,提问作者malqurashi
相关产品推荐
相关产品推荐

