| 服務商 | 類型 | 特色 | 適用場景 |
|---|---|---|---|
| Twilio | 國際 API | 全球覆蓋、支援 WhatsApp、豐富 SDK | 出海業務 |
| 阿里云/騰訊云/華為云 | 國內雲廠商 | 價格低、模板審核嚴、到達率高 | 國內電商、驗證碼 |
| Submail / 容聯云 / 梦网 | 專業簡訊平台 | 通道穩定、支援營銷/通知分類 | 大規模營銷 |
| 自建網關 (SMPP) | 運營商直連 | 成本最低、完全可控 | 超大規模、特殊合規 |
<?php
namespace App\Sms;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
class SmsSender {
public function __construct(
private ClientInterface $httpClient,
private RequestFactoryInterface $requestFactory,
private string $apiUrl,
private string $accessKeyId,
private string $accessKeySecret,
private string $signName,
private string $templateCode
) {}
/** 發送單條驗證碼/通知
* @param string $phone 手機號 (E.164 格式 +86138xxxxxxxx)
* @param array $params 模板變數 ['code'=>'123456', 'product'=>'iPhone']
* @return array ['success'=>bool, 'request_id'=>string, 'message'=>string]
*/
public function send(string $phone, array $params): array {
$timestamp = date('Y-m-d\TH:i:s\Z');
$paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE);
$query = http_build_query([
'AccessKeyId' => $this->accessKeyId,
'Timestamp' => $timestamp,
'SignatureMethod' => 'HMAC-SHA1',
'SignatureVersion'=> '1.0',
'SignatureNonce' => uniqid('', true),
'Action' => 'SendSms',
'Version' => '2017-05-25',
'RegionId' => 'cn-hangzhou',
'PhoneNumbers' => $phone,
'SignName' => $this->signName,
'TemplateCode' => $this->templateCode,
'TemplateParam' => $paramsJson,
]);
// 簽名 (阿里云風格)
$stringToSign = "GET&%2F&" . rawurlencode($query);
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret . '&', true));
$url = $this->apiUrl . '?' . $query . '&Signature=' . rawurlencode($signature);
$request = $this->requestFactory->createRequest('GET', $url);
$response = $this->httpClient->sendRequest($request);
$body = json_decode((string)$response->getBody(), true);
return [
'success' => ($body['Code'] ?? '') === 'OK',
'request_id' => $body['RequestId'] ?? '',
'message' => $body['Message'] ?? 'Unknown error',
'biz_id' => $body['BizId'] ?? null,
];
}
/** 批量發送 (同一模板、不同手機/參數) */
public function batchSend(array $items): array {
// 多數服務商支援單次最多 1000 號碼,這裡分批
$results = [];
foreach (array_chunk($items, 500) as $chunk) {
// 實作略:組裝 PhoneNumbers=1,2,3... TemplateParam=[{...},{...}]
$results[] = $this->sendChunk($chunk);
}
return $results;
}
}
?>
<?php
// Job 類別
class SendSmsJob implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public string $phone,
public array $params,
public int $tries = 3,
public int $backoff = 60 // 秒
) {}
public function handle(SmsSender $sender): void {
$result = $sender->send($this->phone, $this->params);
if (!$result['success']) {
// 記錄失敗原因、觸發重試
throw new \RuntimeException("SMS failed: {$result['message']}");
}
// 成功可寫入發送日誌表
SmsLog::create([
'phone' => $this->phone,
'template' => $this->params,
'request_id' => $result['request_id'],
'biz_id' => $result['biz_id'],
'status' => 'sent',
]);
}
}
// 控制器調用
public function sendVerifyCode(Request $request) {
$code = random_int(100000, 999999);
Cache::put("sms_code:{$request->phone}", $code, 300); // 5 分鐘有效
SendSmsJob::dispatch($request->phone, ['code' => $code])->onQueue('sms');
return response()->json(['message' => '已發送']);
}
?>
<?php
class SmsCircuitBreaker {
private int $failures = 0;
private int $lastFailure = 0;
private const THRESHOLD = 5;
private const TIMEOUT = 60; // 秒
public function call(callable $action): mixed {
if ($this->isOpen()) throw new \RuntimeException('SMS circuit open');
try {
$result = $action();
$this->onSuccess();
return $result;
} catch (\Throwable $e) {
$this->onFailure();
throw $e;
}
}
private function isOpen(): bool {
if ($this->failures < self::THRESHOLD) return false;
if (time() - $this->lastFailure > self::TIMEOUT) {
$this->failures = 0; // 半開狀態,允許嘗試
return false;
}
return true;
}
private function onSuccess(): void { $this->failures = 0; }
private function onFailure(): void { $this->failures++; $this->lastFailure = time(); }
}
?>
php -S localhost:8000 本地接收回調 (配合 ngrok 穿透)POST /sms/callback 測試簽名驗證、入庫邏輯簡訊發送看似簡單,實則涉及簽名安全、頻控合規、異步解耦、重試熔斷、可觀測性等工程細節。建議:封裝統一 Sender 介面、接入佇列異步、接入熔斷器、完善日誌與告警,才能支撐業務高增長期的穩定送達。