「PHP開發(fā)APP接口實(shí)戰(zhàn)012」發(fā)送短信驗(yàn)證碼之生成并緩存驗(yàn)證碼(Memcache)

配置參數(shù)

  1. Memcache 配置豪嗽, 在 /app/config/config.ini 文件添加以下代碼:
[memcache]
host = 127.0.0.1
port = 11211
prefix = api

在 Visual NMP 中茴厉,默認(rèn)已經(jīng)安裝了 Memcache, 可直接使用寻狂。如服務(wù)未開啟瞬项,直接打開即可蔗蹋。 默認(rèn)連接端口:11211。緩存鍵名前綴:prefix = api囱淋,方便區(qū)分項(xiàng)目媒役,可隨意設(shè)置蜓谋, 一般設(shè)置為項(xiàng)目名。
「PHP開發(fā)APP接口實(shí)戰(zhàn)001」開發(fā)環(huán)境搭建

  1. 短信配置, 在 /app/config/config.php 文件添加以下代碼:
    'sms' => [
        'times' => 3, // 同一手機(jī)一小時(shí)內(nèi)發(fā)送短信次數(shù), 0 為不限制
        'interval' => 60, // 同一手機(jī)兩次發(fā)送間隔時(shí)間(單位:秒)耻讽, 0 為不限制
        'valid_time' => 300, // 短信驗(yàn)證碼有效時(shí)間(單位:秒), 0 為久有效
    ],

創(chuàng)建 Memcache 操作類 XMemcache

/app/library 目錄下創(chuàng)建文件 XMemcache.php, 添加以下代碼:

<?php

/**
 * 緩存
 */
class XMemcache
{

    public static $instance;
    private $memcache = null; // Memcache 對(duì)象
    private $config = null; // 配置參數(shù)
    private $tag = null; // 標(biāo)識(shí)

    private function __construct($tag = null)
    {
        // 初始化 Memcache 對(duì)象
        $this->memcache = new Memcache();
        // 加載配置參數(shù)
        $this->config = Config::instance()->get('memcache', 'ini');
        // 連接Memcache服務(wù)器
        $this->memcache->addServer($this->config['host'], $this->config['port']);
        $this->tag = $tag;
    }

    /**
     * @param null $tag  緩存標(biāo)識(shí)
     * @return XMemcache
     */
    public static function instance($tag = null)
    {
        if (!self::$instance) self::$instance = new self($tag);
        return self::$instance;
    }

    /**
     * 添加緩存,若不存在則追加基公,若不存在則新增
     * @param $key 鍵名
     * @param $value 緩存內(nèi)容
     * @param int $timeout 0永久有效条篷,604800 7天,最大不能超過30天
     * @param int $iszip
     * @return mixed
     */
    public function append($key, $value, $timeout = 604800, $iszip = 0)
    {
        if ($values = $this->get($key)) {
            $values[] = $value;
        } else {
            $values = [$value];
        }
        $this->set($key, $values, $timeout, $iszip);
    }

    /**
     * 設(shè)置緩存
     * @param $key 鍵名
     * @param $value 緩存內(nèi)容
     * @param int $timeout 0永久有效芦倒,604800 7天艺挪,最大不能超過30天
     * @param int $iszip
     * @return mixed
     */
    public function set($key, $value, $timeout = 604800, $iszip = 0)
    {
        $value = serialize($value);
        return $this->memcache->set($this->formatKey($key), $value, $iszip, $timeout);
    }

    /**
     * 根據(jù)KEY獲得緩存內(nèi)容
     * @param $key
     * @return mixed
     */
    public function get($key)
    {
        $value = $this->memcache->get($this->formatKey($key));
        return unserialize($value);
    }

    /**
     * 刪除指定緩存
     * @param $key
     * @return mixed
     */
    public function delete($key)
    {
        return $this->memcache->delete($this->formatKey($key));
    }

    /**
     * 清空緩存
     * @return mixed
     */
    public function flush()
    {
        return $this->memcache->flush();
    }

    /**
     * 重寫緩存鍵名,格式: [prefix]:[tag]:[key]
     * @param $key
     * @return string
     */
    private function formatKey($key)
    {
        return $this->config['prefix'] . ':' . $this->tag . ':' . $key;
    }

}

這里重寫了一些 Memcache 常用的操作函數(shù)兵扬。 如:設(shè)置緩存 set(), 追加緩存 append(), 獲取緩存 get(), 刪除緩存 delete(), 清空緩存 flush()
值得注意的是麻裳,我們還對(duì)緩存鍵名進(jìn)行了重寫,方便區(qū)分項(xiàng)目和模塊器钟。

生成短信驗(yàn)證碼

  1. /app/library 目錄下創(chuàng)建文件 SMS.php, 添加以下代碼:
<?php

/**
 * 短信驗(yàn)證碼
 */
class SMS
{

    public static $instance;

    // 配置參數(shù)
    private $config = null;

    private function __construct()
    {
        // 加載短信配置參數(shù)
        $this->config = Config::instance()->get('sms');
    }

    public static function instance()
    {
        if (!self::$instance) self::$instance = new self();
        return self::$instance;
    }

}

這里實(shí)現(xiàn)了實(shí)例化時(shí)津坑,自己加載配置參數(shù)。

  1. 增加驗(yàn)證碼改送函數(shù) send(), 用于外部調(diào)用傲霸。如:
     /**
     * 發(fā)送短信驗(yàn)證碼
     * @param $mobile
     * @return array
     * @throws Exception
     */
    public function send($mobile)
    {

    }

此函數(shù)里面分四步走:

  1. 驗(yàn)證指定手機(jī)號(hào)疆瑰,當(dāng)前是否可以發(fā)送驗(yàn)證碼
  2. 生成四位數(shù)字驗(yàn)證碼,并配置上生成時(shí)間
  3. 調(diào)用 XMemcache 緩存驗(yàn)證碼
  4. 調(diào)用第三方接口昙啄,發(fā)送驗(yàn)證碼乃摹,并返回發(fā)送狀態(tài)。(市場上有許多發(fā)送第三方平臺(tái)跟衅,都有)
  1. 這里調(diào)換一下順序孵睬,先講解生成和緩存驗(yàn)證碼。
    首先伶跷,添加函數(shù)generateCode(), 隨機(jī)生成4位數(shù)字驗(yàn)證碼
    /**
     * 隨機(jī)生成4位數(shù)字驗(yàn)證碼
     * @return int
     */
    private function generateCode()
    {
        return rand(1000, 9999);
    }

然后掰读,在 send() 函數(shù)中添加代碼:

        $item = [
            'code' => $this->generateCode(), // 生成短信驗(yàn)證碼
            'time' => time(), // 生成時(shí)間
            'verified' => 0,  // 驗(yàn)證狀態(tài): 0 未驗(yàn)證, 1 已驗(yàn)證
        ];

這里除了生成驗(yàn)證碼叭莫,同時(shí)初始化生成時(shí)間time蹈集, 驗(yàn)證狀態(tài)verified,用于驗(yàn)證發(fā)送時(shí)間和檢查驗(yàn)證碼是否已驗(yàn)證雇初。

  1. 緩存驗(yàn)證碼拢肆,在 send() 函數(shù)中添加代碼:
        // 緩存短信驗(yàn)證碼
        XMemcache::instance('sms')->append($mobile, $item, 3600);

這里完成了幾個(gè)工作:

  • $item 存于以指定手機(jī)號(hào)為鍵名的緩存下
  • 同一手機(jī)號(hào)多次發(fā)送,都存在同一鍵名下,用于統(tǒng)計(jì)1小時(shí)內(nèi)驗(yàn)證碼發(fā)送次數(shù)郭怪。
  • 緩存有效時(shí)間設(shè)置為 1 小時(shí)
  1. 現(xiàn)在我們?cè)倩貋碇v解驗(yàn)證是否允許向指定手機(jī)號(hào)發(fā)送驗(yàn)證碼支示。

驗(yàn)證規(guī)則:

  • 同一手機(jī)兩次發(fā)送間隔時(shí)間1分鐘(可配置間隔時(shí)間)
  • 同一手機(jī)1小時(shí)內(nèi)最多只能發(fā)送3次驗(yàn)證碼(可配置發(fā)送次數(shù))

首先,添加函數(shù) getCacheCodes()validateSend()鄙才, 如:

    /**
     * 獲取1小時(shí)內(nèi)發(fā)送的驗(yàn)證碼
     * @param $mobile
     * @return null
     */
    private function getCacheCodes($mobile)
    {
        $codes = XMemcache::instance('sms')->get($mobile);

        if (!$codes)
            return [];

        foreach ($codes as $index => $item) {
            // 過濾發(fā)送超過1小時(shí)的驗(yàn)證碼
            if (time() - $item['time'] > 3600) {
                unset($codes[$index]);
            }
        }

        // 重置數(shù)組索引
        $codes = array_values($codes);

        // 更新緩存
        XMemcache::instance('sms')->set($mobile, $codes);
        return $codes;
    }

    /**
     * 驗(yàn)證是不否允許發(fā)送
     * @param $mobile
     * @throws Exception
     */
    private function validateSend($mobile)
    {
        $codes = $this->getCacheCodes($mobile);
        if ($this->config['times'] > 0 && count($codes) >= $this->config['times']) {
            throw new Exception('一小時(shí)內(nèi)最多只能發(fā)送' . $this->config['times'] . '次短信驗(yàn)證碼');
        }

        $lastCode = end($codes);
        if ($this->config['interval'] > 0 && time() - $lastCode['time'] <= $this->config['interval']) {
            throw new Exception('發(fā)送頻率太快');
        }
    }

  1. 函數(shù) getCacheCodes() 獲取指定手機(jī)1小時(shí)內(nèi)發(fā)送的驗(yàn)證碼颂鸿。
  2. 函數(shù) validateSend() 實(shí)現(xiàn):
    驗(yàn)證一小時(shí)內(nèi)向同一手機(jī)發(fā)送短信驗(yàn)證碼次數(shù)是否超過了配置次數(shù);
    驗(yàn)證上次發(fā)送驗(yàn)證碼是否已經(jīng)超過配置時(shí)間攒庵;

然后在 send() 函數(shù)中嘴纺,所有代碼之前插入代碼 $this->validateSend($mobile);
send() 函數(shù)完整代碼:

    /**
     * 發(fā)送短信驗(yàn)證碼
     * @param $mobile
     * @return array
     * @throws Exception
     */
    public function send($mobile)
    {

        // 驗(yàn)證短信發(fā)送次數(shù)
        $this->validateSend($mobile);

        $item = [
            'code' => $this->generateCode(), // 生成短信驗(yàn)證碼
            'time' => time(), // 生成時(shí)間
            'verified' => 0,  // 驗(yàn)證狀態(tài): 0 未驗(yàn)證浓冒, 1 已驗(yàn)證
        ];

        // 緩存短信驗(yàn)證碼
        XMemcache::instance('sms')->append($mobile, $item, 3600);

        // 發(fā)送短信驗(yàn)證碼
        /* ... 調(diào)用第三方接口 ... */

        return $item;
    }

  1. 再在控制器 SmsController 類的 sendAction() 函數(shù)中加入以下代碼:
        // 發(fā)送驗(yàn)證碼
        $result = SMS::instance()->send($this->getPost('user_mobile'));

        if ($result) {
//            Output::instance($this->response)->success(’發(fā)送成功‘);
            Output::instance($this->response)->success((object)$result);
        } else {
            Output::instance($this->response)->fail('發(fā)送失敗');
        }

這里沒有真正實(shí)現(xiàn)調(diào)用第三方接口栽渴,而是直接返回了發(fā)送的驗(yàn)證碼,以供測試使用稳懒。正式代碼熔萧,只返回發(fā)送狀態(tài)。

SmsController.php 完整代碼:

<?php

class SmsController extends BaseController
{

    /**
     * 發(fā)送短驗(yàn)證碼
     */
    public function sendAction()
    {
        // 驗(yàn)證請(qǐng)求方法是否是POST
        $this->isPost();

        // 驗(yàn)證請(qǐng)求參數(shù)
        XValidationSms::send($this->getPost());

        // 發(fā)送驗(yàn)證碼
        $result = SMS::instance()->send($this->getPost('user_mobile'));

        if ($result) {
//            Output::instance($this->response)->success(’發(fā)送成功‘);
            Output::instance($this->response)->success((object)$result);
        } else {
            Output::instance($this->response)->fail('發(fā)送失敗');
        }

    }
}

接口調(diào)試示例

  • 請(qǐng)求地址:http://127.0.0.1:20081/sms/send
  • 請(qǐng)求方式:POST
  • 請(qǐng)求參數(shù):user_mobile=18088888888
  • 返回?cái)?shù)據(jù):
{
    "status": "1",
    "value": "發(fā)送成功"
}

開發(fā)調(diào)試時(shí)返回?cái)?shù)據(jù) :

{
    "status": "1",
    "item": {
        "code": "1139",
        "time": "1520663958",
        "verified": "0"
    }
}

image

示例代碼下載
鏈接:https://pan.baidu.com/s/1gvyi8eX3JdlEUzzndpLVoQ 密碼:839z

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末僚祷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贮缕,更是在濱河造成了極大的恐慌辙谜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件感昼,死亡現(xiàn)場離奇詭異装哆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)定嗓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門蜕琴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宵溅,你說我怎么就攤上這事凌简。” “怎么了恃逻?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵雏搂,是天一觀的道長。 經(jīng)常有香客問我寇损,道長凸郑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任矛市,我火速辦了婚禮芙沥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己而昨,他們只是感情好救氯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著配紫,像睡著了一般径密。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躺孝,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天享扔,我揣著相機(jī)與錄音,去河邊找鬼植袍。 笑死惧眠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的于个。 我是一名探鬼主播氛魁,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼厅篓!你這毒婦竟也來了秀存?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤羽氮,失蹤者是張志新(化名)和其女友劉穎或链,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體档押,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澳盐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了令宿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叼耙。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖粒没,靈堂內(nèi)的尸體忽然破棺而出筛婉,到底是詐尸還是另有隱情,我是刑警寧澤癞松,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布倾贰,位于F島的核電站,受9級(jí)特大地震影響拦惋,放射性物質(zhì)發(fā)生泄漏匆浙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一厕妖、第九天 我趴在偏房一處隱蔽的房頂上張望首尼。 院中可真熱鬧,春花似錦、人聲如沸软能。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽查排。三九已至凳枝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跋核,已是汗流浹背岖瑰。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砂代,地道東北人蹋订。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像刻伊,于是被迫代替她去往敵國和親露戒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容