
本文介绍在php中通过策略模式集成多个otp发送通道(如短信api与whatsapp api),并结合队列重试机制提升送达率,避免单点失败导致验证中断。
本文介绍在php中通过策略模式集成多个otp发送通道(如短信api与whatsapp api),并结合队列重试机制提升送达率,避免单点失败导致验证中断。
在构建用户身份验证系统时,仅依赖单一短信API存在明显风险:网络延迟、运营商拦截、API限流或服务宕机都可能导致OTP发送失败,进而影响用户体验甚至业务转化。为提升可靠性,推荐采用多通道降级策略——优先调用主通道(如Jazz SMS API),若失败则自动回退至备用通道(如WhatsApp Business API),而非简单“同时发送”(易引发重复通知与资费浪费)。
✅ 推荐架构:策略模式 + 异步任务重试
核心思想是将不同通道抽象为可互换的「发送策略」,并通过队列任务封装发送逻辑,利用失败回调触发降级流程。
修复 WhatsApp Web 的 428 错误及代理配置问题,适用于连接断开(状态428)、需要配置代理或在新机器部署 OpenClaw 后出现的 WhatsApp 连接故障。
1. 定义统一接口
interface OtpSender
{
public function send(string $phone, string $message): bool;
}2. 实现各通道策略
// 短信策略(Jazz API)
class JazzSmsSender implements OtpSender
{
public function send(string $phone, string $message): bool
{
$phone = preg_replace('/[^0-9]/', '', $phone);
$params = [
'Username' => '030XXXXXX',
'Password' => 'XXXXXX',
'From' => 'XXXXXX',
'To' => $phone,
'Message'=> $message,
];
$url = 'https://connect.jazz.com/sendsms_url.html?' . http_build_query($params);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return $httpCode === 200 && empty($error);
}
}
// WhatsApp策略(示例:使用Meta Graph API)
class WhatsAppSender implements OtpSender
{
private string $accessToken = 'YOUR_WA_ACCESS_TOKEN';
private string $phoneNumberId = 'YOUR_PHONE_NUMBER_ID'; // WhatsApp Business账号ID
public function send(string $phone, string $message): bool
{
// 注意:WhatsApp要求E.164格式(含国家码),如 +923012345678
$e164 = preg_replace('/[^0-9+]/', '', $phone);
if (substr($e164, 0, 1) !== '+') {
$e164 = '+92' . ltrim($e164, '0'); // 示例:巴基斯坦号码补全
}
$payload = [
'messaging_product' => 'whatsapp',
'to' => $e164,
'type' => 'template',
'template' => [
'name' => 'otp_template', // 需提前在Meta审核通过
'language' => ['code' => 'en'],
'components' => [
['type' => 'body', 'parameters' => [['type' => 'text', 'text' => $message]]]
]
]
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://graph.facebook.com/v19.0/{$this->phoneNumberId}/messages",
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->accessToken,
'Content-Type: application/json'
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
}3. 创建可重试的队列任务(Laravel示例)
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendOtpJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private string $phone,
private string $message,
private string $primaryStrategy = 'jazz', // 默认主通道
private int $attempts = 2 // 最多重试2次(含首次)
) {}
public function handle()
{
$sender = match($this->primaryStrategy) {
'jazz' => new JazzSmsSender(),
'whatsapp' => new WhatsAppSender(),
default => new JazzSmsSender(),
};
if (!$sender->send($this->phone, $this->message)) {
throw new \Exception("Primary OTP channel failed");
}
}
public function failed(\Throwable $exception)
{
// 降级到备用通道(仅当当前为主通道且未达最大尝试次数)
if ($this->primaryStrategy === 'jazz' && $this->attempts > 0) {
dispatch(new self(
$this->phone,
$this->message,
'whatsapp',
$this->attempts - 1
))->onQueue('high');
}
// 可选:记录失败日志、触发告警、通知管理员
}
}⚠️ 关键注意事项
- 号码格式一致性:短信API通常需纯数字(如 923012345678),WhatsApp强制E.164格式(如 +923012345678),务必在策略内做适配。
- 模板合规性:WhatsApp必须使用预审通过的模板消息,不可发送纯文本OTP(违反政策)。
- 幂等性保障:OTP本身应有时效性(如5分钟)和单次有效性,避免降级发送导致用户收到两条验证码。
- 监控与告警:在 failed() 方法中集成日志(如Laravel Telescope)或Sentry,实时追踪通道失败率。
- 性能权衡:避免同步等待多通道响应;异步降级是更优解,首通道超时(建议设为8–10秒)即触发重试。
通过该设计,系统具备弹性容错能力:单通道故障时自动切换,无需修改业务代码;新增通道只需实现 OtpSender 接口并注册策略,符合开闭原则。真正的高可用,不在于堆砌通道,而在于智能的失败处理与优雅降级。

















