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

Vercel生产环境下Next.js API路由Twilio短信/WhatsApp逻辑偶发失效

问题分析与解决方案

核心原因

  • Serverless函数提前终止:代码里client.messages.create用了异步回调.then(),但没等它执行完就直接返回响应。Vercel的Serverless函数在返回响应后会立刻终止进程,导致还在运行的Twilio发送逻辑、数据库更新被中断,所以出现“有时触发有时不触发”的情况。
  • 串行处理效率低:循环里用await逐个处理提醒,待处理数量多的时候,函数执行时间会被拉长,容易触发Vercel的超时限制(默认10秒),没执行完的任务会被强制终止。
  • 无错误捕获机制:异步操作失败时没有日志记录,根本查不出具体哪里出问题。

修复步骤

1. 把回调改成await,确保异步操作完成

把client.messages.create的.then()替换成await,保证消息发送、数据库更新都做完再继续。

2. 用Promise.all并行处理任务

把循环改成并行处理,避免串行等待浪费时间,同时确保所有任务都执行完再返回响应。

3. 加错误捕获和日志

给每个异步操作套try/catch,记录错误信息,方便排查问题。

修改后的完整代码

export default async function handler(request, response) {
  try {
    const accountSid = process.env.TWILIO_ACCOUNT_SID;
    const authToken = process.env.TWILIO_AUTH_TOKEN;
    const client = require('twilio')(accountSid, authToken);
    await connectMongo();
    
    const yesterday = subDays(new Date(), 1); 
    const pendingReminders = await Reminder.find({
      reminderSent: false,
      eventCanceled: false,
      reminderDate: {
        $gte: yesterday,
        $lte: new Date(),
      },
    });

    const currentDate = new Date();
    const currentMonth = currentDate.getMonth() + 1;
    const currentYear = currentDate.getFullYear();

    console.log('pending reminders:', pendingReminders);

    // 并行处理所有符合条件的提醒
    const processingPromises = pendingReminders.map(async (reminder) => {
      try {
        let monthlyCount = await ReminderMonthlyCount.findOne({
          userId: reminder.userId,
          month: currentMonth,
          year: currentYear
        });
        
        if (!monthlyCount) {
          monthlyCount = await ReminderMonthlyCount.create({
            userId: reminder.userId,
            month: currentMonth,
            year: currentYear
          });
        }

        if (monthlyCount.count < 20) {
          let settings = await Settings.findOne({ userId: reminder.userId });
          if (!settings) {
            settings = await Settings.create({ userId: reminder.userId });
          }

          console.log(`准备发送消息给 ${reminder.phoneNumber},用户ID:${reminder.userId},预定时间:${reminder.reminderDate},当前时间:${new Date()}`);
          
          // 等待Twilio消息发送完成
          const message = await client.messages.create({
            from: `whatsapp:${process.env.TWILIO_WHATSAPP_NUMBER}`,
            body: replaceValues(
              settings?.reminderMessage, 
              [
                settings?.personalOrCompanyName || '我们', 
                format(new Date(reminder.reminderDate), "eeee 'at' h:mma"), 
                `${process.env.NEXT_PUBLIC_WEB_URL}appointment/cancel/${reminder['_id']}`
              ]
            ),
            to: `whatsapp:${reminder.phoneNumber}`
          });

          console.log(`消息发送成功,SID:${message.sid},接收方:${reminder.phoneNumber},用户ID:${reminder.userId}`);
          
          // 并行更新计数和提醒状态
          await Promise.all([monthlyCount.save(), reminder.save()]);
        }
      } catch (error) {
        console.error(`处理提醒ID ${reminder._id} 失败:`, error);
        // 可选:添加重试逻辑或标记失败的提醒
      }
    });

    // 等待所有处理任务完成
    await Promise.all(processingPromises);

    response.status(200).json({ ok: true, processedCount: pendingReminders.length });
  } catch (globalError) {
    console.error('全局处理错误:', globalError);
    response.status(500).json({ ok: false, error: globalError.message });
  }
}

额外建议

  • 调整Vercel超时设置:如果待处理提醒多,去Vercel项目设置里把Serverless函数超时调到最大值(60秒),避免超时被终止。
  • 验证Twilio模板:代码里注释提到WhatsApp模板没生效,去Twilio控制台确认模板已经通过审核,且body内容和模板完全匹配(包括变量占位符)。
  • 优化数据库索引:给Reminder的reminderSent、eventCanceled、reminderDate加复合索引,给ReminderMonthlyCount和Settings的userId加索引,加快查询速度。

内容的提问来源于stack exchange,提问作者victor.ja

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:18:25