
本文介绍在php中通过策略模式集成多个otp发送通道(如短信api和whatsapp api),并利用队列任务失败重试机制提升送达率,确保用户及时接收验证码。
本文介绍在php中通过策略模式集成多个otp发送通道(如短信api和whatsapp api),并利用队列任务失败重试机制提升送达率,确保用户及时接收验证码。
在高可用认证系统中,单点依赖短信通道存在明显风险:网络延迟、运营商拦截、API限流或服务不可用均可能导致OTP发送失败,直接影响用户注册/登录体验。为提升可靠性,推荐采用多通道冗余 + 异步容错架构——即优先调用主通道(如Jazz SMS),若失败则自动降级至备用通道(如WhatsApp Business API),而非简单地“同时发送”(易造成用户重复打扰且增加成本)。
✅ 推荐架构:策略模式 + 队列任务重试
核心思想是将不同通道抽象为可互换的「发送策略」,并通过异步任务封装发送逻辑,利用任务失败回调触发降级流程:
// 1. 定义发送策略接口
interface OtpSenderStrategy
{
public function send(string $phone, string $message): bool;
}
// 2. 短信策略实现(适配 Jazz API)
class JazzSmsStrategy implements OtpSenderStrategy
{
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);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
$result = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);
curl_close($curl);
return ($httpCode === 200 && empty($error));
}
}
// 3. WhatsApp策略实现(示例:使用Meta Graph API)
class WhatsAppStrategy implements OtpSenderStrategy
{
private string $accessToken = 'YOUR_WA_ACCESS_TOKEN';
private string $phoneNumberId = 'YOUR_PHONE_NUMBER_ID';
public function send(string $phone, string $message): bool
{
// 注意:WhatsApp要求E.164格式(如+923001234567),需转换
$e164 = '+'.ltrim($phone, '0');
$url = "https://graph.facebook.com/v19.0/{$this->phoneNumberId}/messages";
$payload = [
'messaging_product' => 'whatsapp',
'to' => $e164,
'type' => 'text',
'text' => ['body' => $message]
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->accessToken,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
}? 异步任务实现(Laravel 示例)
将发送逻辑封装为队列任务,并在 failed() 方法中调度备用通道:
<?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 $primaryChannel = 'jazz', // 默认主通道
private int $attempt = 1
) {}
public function handle()
{
$strategy = match($this->primaryChannel) {
'jazz' => new JazzSmsStrategy(),
'whatsapp' => new WhatsAppStrategy(),
default => new JazzSmsStrategy(),
};
if (!$strategy->send($this->phone, $this->message)) {
throw new \Exception("{$this->primaryChannel} send failed on attempt #{$this->attempt}");
}
}
public function failed(\Throwable $exception)
{
// 仅在首次失败时降级到WhatsApp;避免循环重试
if ($this->attempt === 1 && $this->primaryChannel === 'jazz') {
dispatch(new self($this->phone, $this->message, 'whatsapp', 2))
->onQueue('high-priority')
->delay(now()->addSeconds(3)); // 延迟3秒再发备用通道
}
}
}⚠️ 关键注意事项
- 号码格式统一:短信API通常接受纯数字(如923001234567),而WhatsApp强制要求E.164格式(+923001234567),务必在策略内部做标准化处理;
- 错误分类处理:区分临时性错误(如网络超时)与永久性错误(如手机号无效),前者应重试,后者需记录告警而非盲目降级;
- 速率与成本控制:WhatsApp API有严格频次限制,建议对同一用户10分钟内仅允许1次降级发送,避免滥用;
- 日志与监控:记录每次发送的通道、耗时、结果及降级原因,便于后续优化通道权重(如根据历史成功率动态调整主备顺序);
- 合规性:向用户发送WhatsApp消息前,必须获得其明确授权(Opt-in),且首条消息需为预设模板(Template Message)。
通过该设计,系统不再脆弱地绑定单一供应商,而是具备弹性、可观测性与可维护性——这才是生产级OTP服务应有的健壮形态。


















