「PHP开发APP接口实战012」发送短信验证码之生成并缓存验证码(Memcache)

配置参数

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

在 Visual NMP 中,默认已经安装了 Memcache, 可直接使用。如服务未开启,直接打开即可。 默认连接端口:11211。缓存键名前缀:prefix = api,方便区分项目,可随意设置, 一般设置为项目名。
「PHP开发APP接口实战001」开发环境搭建

  1. 短信配置, 在 /app/config/config.php 文件添加以下代码:
    'sms' => [
        'times' => 3, // 同一手机一小时内发送短信次数, 0 为不限制
        'interval' => 60, // 同一手机两次发送间隔时间(单位:秒), 0 为不限制
        'valid_time' => 300, // 短信验证码有效时间(单位:秒), 0 为久有效
    ],

创建 Memcache 操作类 XMemcache

/app/library 目录下创建文件 XMemcache.php, 添加以下代码:

<?php

/**
 * 缓存
 */
class XMemcache
{

    public static $instance;
    private $memcache = null; // Memcache 对象
    private $config = null; // 配置参数
    private $tag = null; // 标识

    private function __construct($tag = null)
    {
        // 初始化 Memcache 对象
        $this->memcache = new Memcache();
        // 加载配置参数
        $this->config = Config::instance()->get('memcache', 'ini');
        // 连接Memcache服务器
        $this->memcache->addServer($this->config['host'], $this->config['port']);
        $this->tag = $tag;
    }

    /**
     * @param null $tag  缓存标识
     * @return XMemcache
     */
    public static function instance($tag = null)
    {
        if (!self::$instance) self::$instance = new self($tag);
        return self::$instance;
    }

    /**
     * 添加缓存,若不存在则追加,若不存在则新增
     * @param $key 键名
     * @param $value 缓存内容
     * @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);
    }

    /**
     * 设置缓存
     * @param $key 键名
     * @param $value 缓存内容
     * @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);
    }

    /**
     * 根据KEY获得缓存内容
     * @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 常用的操作函数。 如:设置缓存 set(), 追加缓存 append(), 获取缓存 get(), 删除缓存 delete(), 清空缓存 flush()
值得注意的是,我们还对缓存键名进行了重写,方便区分项目和模块。

生成短信验证码

  1. /app/library 目录下创建文件 SMS.php, 添加以下代码:
<?php

/**
 * 短信验证码
 */
class SMS
{

    public static $instance;

    // 配置参数
    private $config = null;

    private function __construct()
    {
        // 加载短信配置参数
        $this->config = Config::instance()->get('sms');
    }

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

这里实现了实例化时,自己加载配置参数。

  1. 增加验证码改送函数 send(), 用于外部调用。如:
     /**
     * 发送短信验证码
     * @param $mobile
     * @return array
     * @throws Exception
     */
    public function send($mobile)
    {

    }

此函数里面分四步走:

  1. 验证指定手机号,当前是否可以发送验证码
  2. 生成四位数字验证码,并配置上生成时间
  3. 调用 XMemcache 缓存验证码
  4. 调用第三方接口,发送验证码,并返回发送状态。(市场上有许多发送第三方平台,都有)
  1. 这里调换一下顺序,先讲解生成和缓存验证码。
    首先,添加函数generateCode(), 随机生成4位数字验证码
    /**
     * 随机生成4位数字验证码
     * @return int
     */
    private function generateCode()
    {
        return rand(1000, 9999);
    }

然后,在 send() 函数中添加代码:

        $item = [
            'code' => $this->generateCode(), // 生成短信验证码
            'time' => time(), // 生成时间
            'verified' => 0,  // 验证状态: 0 未验证, 1 已验证
        ];

这里除了生成验证码,同时初始化生成时间time, 验证状态verified,用于验证发送时间和检查验证码是否已验证。

  1. 缓存验证码,在 send() 函数中添加代码:
        // 缓存短信验证码
        XMemcache::instance('sms')->append($mobile, $item, 3600);

这里完成了几个工作:

  • $item 存于以指定手机号为键名的缓存下
  • 同一手机号多次发送,都存在同一键名下,用于统计1小时内验证码发送次数。
  • 缓存有效时间设置为 1 小时
  1. 现在我们再回来讲解验证是否允许向指定手机号发送验证码。

验证规则:

  • 同一手机两次发送间隔时间1分钟(可配置间隔时间)
  • 同一手机1小时内最多只能发送3次验证码(可配置发送次数)

首先,添加函数 getCacheCodes()validateSend(), 如:

    /**
     * 获取1小时内发送的验证码
     * @param $mobile
     * @return null
     */
    private function getCacheCodes($mobile)
    {
        $codes = XMemcache::instance('sms')->get($mobile);

        if (!$codes)
            return [];

        foreach ($codes as $index => $item) {
            // 过滤发送超过1小时的验证码
            if (time() - $item['time'] > 3600) {
                unset($codes[$index]);
            }
        }

        // 重置数组索引
        $codes = array_values($codes);

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

    /**
     * 验证是不否允许发送
     * @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('一小时内最多只能发送' . $this->config['times'] . '次短信验证码');
        }

        $lastCode = end($codes);
        if ($this->config['interval'] > 0 && time() - $lastCode['time'] <= $this->config['interval']) {
            throw new Exception('发送频率太快');
        }
    }
  1. 函数 getCacheCodes() 获取指定手机1小时内发送的验证码。
  2. 函数 validateSend() 实现:
    验证一小时内向同一手机发送短信验证码次数是否超过了配置次数;
    验证上次发送验证码是否已经超过配置时间;

然后在 send() 函数中,所有代码之前插入代码 $this->validateSend($mobile);
send() 函数完整代码:

    /**
     * 发送短信验证码
     * @param $mobile
     * @return array
     * @throws Exception
     */
    public function send($mobile)
    {

        // 验证短信发送次数
        $this->validateSend($mobile);

        $item = [
            'code' => $this->generateCode(), // 生成短信验证码
            'time' => time(), // 生成时间
            'verified' => 0,  // 验证状态: 0 未验证, 1 已验证
        ];

        // 缓存短信验证码
        XMemcache::instance('sms')->append($mobile, $item, 3600);

        // 发送短信验证码
        /* ... 调用第三方接口 ... */

        return $item;
    }
  1. 再在控制器 SmsController 类的 sendAction() 函数中加入以下代码:
        // 发送验证码
        $result = SMS::instance()->send($this->getPost('user_mobile'));

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

这里没有真正实现调用第三方接口,而是直接返回了发送的验证码,以供测试使用。正式代码,只返回发送状态。

SmsController.php 完整代码:

<?php

class SmsController extends BaseController
{

    /**
     * 发送短验证码
     */
    public function sendAction()
    {
        // 验证请求方法是否是POST
        $this->isPost();

        // 验证请求参数
        XValidationSms::send($this->getPost());

        // 发送验证码
        $result = SMS::instance()->send($this->getPost('user_mobile'));

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

    }
}

接口调试示例

{
    "status": "1",
    "value": "发送成功"
}

开发调试时返回数据 :

{
    "status": "1",
    "item": {
        "code": "1139",
        "time": "1520663958",
        "verified": "0"
    }
}
接口调试示例图

示例代码下载
链接:https://pan.baidu.com/s/1gvyi8eX3JdlEUzzndpLVoQ 密码:839z

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容