首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 對接天翼開放平台短信發送介面完整實作

     編輯:PHP 相關知識

PHP 對接天翼開放平台短信發送介面完整實作

中國電信天翼開放平台提供短信驗證碼下發能力,流程包含:獲取 access_token → 申請信任碼 → 下發驗證碼。本文提供完整的 PHP 實作代碼,涵蓋簽名生成、HMAC-SHA1 加密、cURL 請求封裝與錯誤處理。(註:平台政策可能變更,請以最新官方文檔為準)

一、核心流程圖解

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│ 1.獲取      │────▶│ 2.申請信任碼  │────▶│ 3.下發驗證碼 │
│ access_token│     │    (token)   │     │   (send)    │
└─────────────┘     └──────────────┘     └─────────────┘
   POST form          GET + 簽名           POST + 簽名
   OAuth2 client_cred  HMAC-SHA1           HMAC-SHA1

二、完整 PHP 實作類

<?php
/**
 * 天翼開放平台短信客戶端
 * 文檔參考:https://open.189.cn/
 */
class CtccSmsClient {
    private $appId;
    private $appSecret;
    private $accessToken = '';
    private $tokenExpire = 0;

    // API 端點
    const URL_TOKEN   = 'https://oauth.api.189.cn/emp/oauth2/v2/access_token';
    const URL_RANDCODE_TOKEN = 'https://api.189.cn/v2/dm/randcode/token';
    const URL_RANDCODE_SEND  = 'https://api.189.cn/v2/dm/randcode/send';

    public function __construct($appId, $appSecret) {
        $this->appId     = $appId;
        $this->appSecret = $appSecret;
    }

    /** 獲取 access_token(client_credentials 模式) */
    private function ensureAccessToken() {
        if ($this->accessToken && time() < $this->tokenExpire - 300) {
            return true; // 仍在有效期內(提前 5 分鐘刷新)
        }

        $data = http_build_query([
            'app_id'      => $this->appId,
            'app_secret'  => $this->appSecret,
            'grant_type'  => 'client_credentials',
        ]);

        $ch = curl_init(self::URL_TOKEN);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $data,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_TIMEOUT        => 10,
        ]);

        $resp = curl_exec($ch);
        $err  = curl_error($ch);
        curl_close($ch);

        if ($err) {
            throw new Exception("獲取 access_token 失敗: $err");
        }

        $json = json_decode($resp, true);
        if (empty($json['access_token'])) {
            throw new Exception("access_token 回應異常: $resp");
        }

        $this->accessToken = $json['access_token'];
        $this->tokenExpire = time() + ($json['expires_in'] ?? 3600);
        return true;
    }

    /** 產生簽名:參數按 key 排序 → 拼接 → HMAC-SHA1 → Base64 → URL編碼 */
    private function buildSign(array $params, $secret = null) {
        $secret = $secret ?? $this->appSecret;
        ksort($params);
        $plaintext = implode('&', $params);
        $hash = hash_hmac('sha1', $plaintext, $secret, true);
        return rawurlencode(base64_encode($hash));
    }

    /** 步驟 2:申請信任碼 */
    private function getRandcodeToken() {
        $this->ensureAccessToken();

        $timestamp = date('Y-m-d H:i:s');
        $params = [
            'app_id'       => "app_id={$this->appId}",
            'access_token' => "access_token={$this->accessToken}",
            'timestamp'    => "timestamp={$timestamp}",
        ];

        $sign = $this->buildSign($params);
        $url = self::URL_RANDCODE_TOKEN . '?' . http_build_query([
            'app_id'       => $this->appId,
            'access_token' => $this->accessToken,
            'timestamp'    => $timestamp,
            'sign'         => $sign,
        ]);

        $resp = @file_get_contents($url);
        $json = json_decode($resp, true);
        if (empty($json['token'])) {
            throw new Exception("獲取信任碼失敗: $resp");
        }
        return $json['token'];
    }

    /** 步驟 3:下發驗證碼 */
    public function sendVerificationCode($phone, $callbackUrl, $expMinutes = 2) {
        $randcodeToken = $this->getRandcodeToken();
        $timestamp = date('Y-m-d H:i:s');

        $params = [
            'app_id'       => "app_id={$this->appId}",
            'access_token' => "access_token={$this->accessToken}",
            'token'        => "token={$randcodeToken}",
            'phone'        => "phone={$phone}",
            'url'          => "url={$callbackUrl}",
            'exp_time'     => "exp_time={$expMinutes}",
            'timestamp'    => "timestamp={$timestamp}",
        ];

        $sign = $this->buildSign($params);

        $postData = http_build_query([
            'app_id'       => $this->appId,
            'access_token' => $this->accessToken,
            'token'        => $randcodeToken,
            'phone'        => $phone,
            'url'          => $callbackUrl,
            'exp_time'     => $expMinutes,
            'timestamp'    => $timestamp,
            'sign'         => $sign,
        ]);

        $ch = curl_init(self::URL_RANDCODE_SEND);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $postData,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_TIMEOUT        => 15,
        ]);

        $resp = curl_exec($ch);
        $err  = curl_error($ch);
        curl_close($ch);

        if ($err) {
            throw new Exception("發送請求失敗: $err");
        }
        return json_decode($resp, true);
    }
}

// ===== 使用範例 =====
try {
    $client = new CtccSmsClient('YOUR_APP_ID', 'YOUR_APP_SECRET');
    $result = $client->sendVerificationCode(
        '15091421612',           // 手機號
        'https://wx.example.com/callback', // 回調地址
        2                        // 有效期(分鐘)
    );
    var_dump($result);
    // 成功範例: ['res_code'=>0, 'res_message'=>'成功', ...]
} catch (Exception $e) {
    error_log('[CtccSms] ' . $e->getMessage());
    // 業務降級:記錄日誌、嘗試備用通道、通知運營
}
?>

三、關鍵技術細節

  1. 參數格式特殊性:平台要求參數值包含鍵名(如 app_id=xxx),簽名時需保持此格式並按 key 排序。
  2. HMAC-SHA1 + Base64 + URLEncode:簽名鏈路固定,注意 hash_hmac(..., true) 產生原始二進制,再 Base64,最後 rawurlencode。
  3. 時間戳格式:Y-m-d H:i:s(含空格),URL 傳輸時會自動編碼為 %20 或 +。
  4. SSL 驗證關閉:測試環境常關閉 CURLOPT_SSL_VERIFYPEER,生產環境建議配置 CA 憑證啟用驗證。

四、生產環境強化建議

  • Token 快取:使用 Redis/Memcached 共享 access_token 與 randcode_token,避免併發重複申請。
  • 重試機制:網路抖動時指數退避重試 3 次,記錄失敗日誌供告警。
  • 限流保護:平台有日發送上限,需在業務層做配額控制與熔斷。
  • 回調驗證:驗證碼下發後,平台會回調 url 參數指定地址,需驗證來源 IP 與簽名。
  • 敏感資訊保護:appSecret 僅存於環境變數或加密配置中心,嚴禁寫入代碼倉庫。

五、常見錯誤代碼對照

res_code含義處理建議
0成功—
400參數錯誤檢查必填欄位、格式、簽名
401未授權/Token 失效強制刷新 access_token 重試
403頻率/額度超限觸發熔斷、走備用通道、告警運營
500平台內部錯誤記錄日誌、延遲重試、聯繫平台技術支援

將上述類封裝為 Composer 套件或服務容器服務,即可在 Laravel/ThinkPHP/Symfony 等框架中優雅注入使用,實現穩健的短信驗證碼下發能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved