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
相关产品推荐
相关产品推荐

