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

PHP 短信驗證碼發送類:IMEI 限流、Redis 狀態管理、服務商適配

     編輯:PHP 綜合知識

核心業務邏輯

  • 手機號格式驗證(正則 /^1\d{10}$/)
  • 驗證碼生成(6 位數字)
  • 發送頻率限制:60 秒/次
  • 單日單手機號發送上限:5 次
  • 單日單手機號設備(IMEI)上限:3 台
  • 驗證碼有效期:30 分鐘
  • Redis 存儲狀態:驗證碼、發送記錄、限流計數器

完整實現代碼

<?php
class Api_Sms {
    const EXPIRE_SEC      = 1800; // 30分鐘
    const RESEND_SEC      = 60;   // 1分鐘
    const ONE_DAY_FREQ    = 5;    // 日發送上限
    const ONE_DAY_IMEI    = 3;    // 日設備上限

    public $error = [];

    public function sendVerifyCode(string $mobile, string $imei): bool {
        // 1. 手機號驗證
        if (!$this->isMobile($mobile)) {
            $this->error = ['code'=>-1, 'msg'=>'手機號格式錯誤'];
            return false;
        }

        $redis = Api_Common::redis();
        $vcKey    = "VC_{$mobile}";
        $limitKey = "VC_LIMIT_{$mobile}";

        // 2. 重發頻率限制
        $data = json_decode($redis->get($vcKey), true);
        if ($data && time() < $data['resend_expire']) {
            $this->error = ['code'=>-1, 'msg'=> '短信已發送,請 60 秒後重試'];
            return false;
        }

        // 3. 單設備日發送上限
        $sendCnt = $redis->zScore($limitKey, $imei);
        if ($sendCnt && $sendCnt >= self::ONE_DAY_FREQ) {
            $this->error = ['code'=>-1, 'msg'=> '該設備今日發送次數超限'];
            return false;
        }

        // 4. 單手機號日設備數上限
        $imeiCnt = $redis->zCard($limitKey);
        if ($imeiCnt >= self::ONE_DAY_IMEI && !$sendCnt) {
            $this->error = ['code'=>-1, 'msg'=> '驗證碼發送設備數超限'];
            return false;
        }

        // 5. 生成/復用驗證碼
        if (!$data) {
            $vc = (string) random_int(100000, 999999); // PHP 7+ 加密安全隨機
            $data = ['vc' => $vc, 'resend_expire' => 0];
            $redis->set($vcKey, json_encode($data));
            $redis->expire($vcKey, self::EXPIRE_SEC);
        }
        $vc = $data['vc'];

        // 6. 調用服務商發送
        $result = $this->send($mobile, "驗證碼:{$vc}");

        if ($result) {
            // 更新重發時間窗
            $data['resend_expire'] = time() + self::RESEND_SEC;
            $ttl = $redis->ttl($vcKey);
            $redis->set($vcKey, json_encode($data));
            $redis->expire($vcKey, $ttl);

            // 記錄發送計數(有序集合:member=IMEI, score=次數)
            $redis->zIncrBy($limitKey, 1, $imei);
            // 設置當日 23:59:59 過期
            $redis->expireAt($limitKey, strtotime('tomorrow'));
        }
        return $result;
    }

    // 服務商適配介面
    protected function send(string $mobile, string $content): bool {
        // TODO: 對接阿里云/騰訊雲/網關 HTTP API
        // 返回 true/false
        return true;
    }

    private function isMobile(string $mobile): bool {
        return (bool) preg_match('/^1\d{10}$/', $mobile);
    }

    public function checkVerifyCode(string $mobile, string $vc): bool {
        $data = json_decode(Api_Common::redis()->get("VC_{$mobile}"), true);
        return $data && $data['vc'] === $vc;
    }

    public function cleanVerifyCode(string $mobile): void {
        $redis = Api_Common::redis();
        $redis->del("VC_{$mobile}", "VC_LIMIT_{$mobile}");
    }
}

Redis 數據結構設計

Key類型結構TTL
VC_{mobile}String (JSON){"vc":"123456","resend_expire":1699999999}1800s
VC_LIMIT_{mobile}Sorted Setmember=IMEI, score=發送次數當日 23:59:59

關鍵設計決策解析

決策點方案理由
隨機數生成random_int()CSPRNG,避免 rand() 可預測性
限流計數器Redis Sorted Set原子增減、自動按分數排序、支援 TTL
重發窗口嵌入驗證碼 JSON單 Key 存儲,減少網絡往返
錯誤碼規範統一 ['code','msg']前端統一處理,國際化友好
服務商解耦protected send()繼承覆蓋適配不同通道

生產環境強化清單

  • 服務商 API 簽名驗證、IP 白名單、HTTPS 強制
  • 發送失敗重試策略(指數退避 + 死信隊列)
  • 驗證碼加密存儲(AES-GCM),防 Redis 洩露
  • 圖形/滑塊驗證碼前置,防機器人刷接口
  • 監控大盤:發送成功率、到達率、延遲 P99、限流觸發率
  • 備用通道自動切換(主通道失效 → 備用通道)
  • 黑名單機制:高投訴/退訂號碼自動攔截

替代實現參考:HTTP 直連網關

function sendSMS($uid, $pwd, $mobile, $content) {
    $url = 'https://api.sms.cn/send/';
    $data = [
        'uid' => $uid,
        'pwd' => md5($pwd),
        'mobile' => $mobile,
        'content' => $content,
    ];
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => 1,
        CURLOPT_POSTFIELDS => http_build_query($data),
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_TIMEOUT => 10,
    ]);
    $resp = curl_exec($ch);
    curl_close($ch);
    return $resp === '100'; // 100=成功,其他見文檔
}

短信驗證碼是帳號體系的關鍵基礎設施,**安全性、可用性、可觀測性**三位一體,容不得半點馬虎。

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