[小黄书后台]手机短信验证码

Posted 天地会珠海分舵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[小黄书后台]手机短信验证码相关的知识,希望对你有一定的参考价值。

上一章我们实现了将图片上传到又拍云上的功能。今天我们会实现手机验证码的功能。

我们现在到处都会碰到手机验证码的验证方式,比如在注册的时候,通过输入手机号码,获取一个验证码,然后输入完成注册。

1. 螺丝帽 vs 云片


一开始的时候,我们往往疲于短信服务商的选择。这里我选择了两个我觉得比较好的进行支持。

  • 一个是螺丝帽: https://luosimao.com
  • 一个是云片: https://www.yunpian.com

两者都提供了短信相关的很多功能,我这里只关心跟我们短信验证码相关的功能。

螺丝帽 vs 云片:

  • 短信单条价格: 初始价格都差不多,云片是¥0.050起,螺丝帽是¥0.055起。都会根据购买的量而下调单条短信的价格。
  • 最少充值金额: 云片是50块起充,螺丝帽是550块起充。
  • 短信模板是否必须: 根据我的实践,云片必须要申请好短信模板消息,验证短信才能发出去;而螺丝帽没有这个限制,不申请短信模板也能正常发出去。之所以把这个纳入比较范围,是因为申请模板是需要审核的,有时候审核不一定能够通过。

2. 云片


#2.1. 注册
云片注册页面如下,其他自便:
https://www.yunpian.com/

#2.2. 子账户和apikey

无论是云片还是螺丝帽,我们都需要apikey才能调用发送短信的api。

注册后,进入云片管理后台,你可以创建自己的子账号,每个子账号都会有对应的apikey来供你使用。

点击APIKEY右边的眼睛icon,通过验证码就能查看到apikey,并记录下来给我们的代码使用。

#2.3. 新增签名

无论是螺丝帽还是云片,在发送短信时,都需要在短信内容中提供签名信息,比如下面这条短信:

【小黄书科技】尊敬的用户,您的验证码是:123456,请在10分钟内输入

前面的”【小黄书科技】“就是签名。我们必须要在申请,通过审核后才能使用。否则发送短信时就会报签名无效之类的错误。

我们可以导航到控制台左侧的国内短信>签名/模板报备中新增签名。

新增的签名必须要等待审核,一般来说,如果是工作时间的话,30分钟内就会有结果。

我们这里申请到的签名就是:

【小黄书科技】

2.4. 新增模板

如前面提及的,云片中必须要申请模板,发送的短信消息必须符合模板中的内容才能正常发送出去。

所以我们这里要申请一个模板:

申请云片的验证码类模板的时候,如果你是想在你的网站上应用短信验证码功能的话,你需要提供你的网站地址,并且页面上有应用上图形验证码的功能,否则审核不会通过。据说这是为了防止短信通道轰炸。

如果你的网站还在开发中,还没有提供相应的图形验证码功能的话,可以在填写网址的同时写上“开发中”的注释,云片也会通过审核。但会在你的网站上线后重新审核。具体云片怎么知道是否上线的,我就不得而知了。估计是人肉吧。

我们这里申请到的模板就是:

【小黄书科技】尊敬的用户,您的验证码是:#code#,请在10分钟内输入

2.5. 云片发送短信消息接口

详细接口描述,请查看官网:
https://www.yunpian.com/api2.0/scene-smsverify.html

具体做法就是将组织好的包含目标手机号码和信息内容的的信息post到下面的地址来进行短信发送:
https://sms.yunpian.com/v2/sms/single_send.json

因为片云没有提供nodejs的示例代码,所以我们这里看下Python的一个调用例子:

# apikey:成功注册后登录云片官网,进入后台可查看
# text:需要使用已审核通过的模板或者默认模板
# mobile:接收的手机号,仅支持单号码发送
def send_sms(apikey, text, mobile):
#服务地址
sms_host = "sms.yunpian.com"
#端口号
port = 443
#版本号
version = "v2"
#智能匹配模板短信接口的URI
sms_send_uri = "/" + version + "/sms/single_send.json"
params = urllib.urlencode('apikey': apikey, 'text': text, 'mobile':mobile)
headers = "Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"
conn = httplib.HTTPSConnection(sms_host, port=port, timeout=30)
conn.request("POST", sms_send_uri, params, headers)
response = conn.getresponse()
response_str = response.read()
conn.close()
return response_str

从代码我们可以看到,整个发送流程就是

  • 将apikey,text,和mobile做成querystring形式的参数
  • 设置http头为‘application/x-www-form-urlencode’的格式,
  • 将QueryString形式的参数POST到‘https://sms.yunpian.com/v2/sms/single_send.json’

也就是说,示例中是将数据放到uri参数中来进行POST的。

其实,采用POST的话,既可以在uri中带有queryString也可以将数据放在body中。body内容可以有多种编码形式,其中application/x-www-form-urlencoded编码其实是基于uri的percent-encoding编码的,所以采用application/x-www-form-urlencoded的POST数据和queryString只是形式不同,本质都是传递参数。

2.6. 实现云片发送验证消息

在nodejs的实现中,我们会用request的promise库request-promise来实现。

参考上面python的代码,我们可以考虑使用第二种POST方法,将数据放到body中来实行发送验证码的请求。

这里我们就需要用到request的options里面的form选项:

form - when passed an object or a querystring, this sets body to a querystring representation of value, and adds Content-type: application/x-www-form-urlencoded header. When passed no options, a FormData instance is returned (and is piped to request). See “Forms” section above.

意思就是该参数能将我们填写的对象(json)或者querystring格式数据转变成querystring格式的数据,并放到body中进行发送,并且会帮我们自动在http头中加入Content-type: application/x-www-form-urlencoded。

也就是说,如果我们request的options是这样的:

const options = 
      url: 'https://sms.yunpian.com/v2/sms/single_send.json',
      method: 'POST',
      form: 
        mobile:  18088821076
        text: '【小黄书科技】尊敬的用户,您的验证码是:20148,请在10分钟内输入 to 18088821076'
        'apikey': xxxxxxx, // 根据你自己情况填写
      ,
      json: true,
    ;

那么requst内部会将我们发送到目标地址的body变成类似以下的querystring的参数字符串格式。

body: 'mobile=18088821076&text=%E3%80%90%E5%B0%8F%E9%BB%84%E4%B9%A6%E7%A7%91%E6%8A%80%E3%80%91%E5%B0%8A%E6%95%AC%E7%9A%84%E7%94%A8%E6%88%B7%EF%BC%8C%E6%82%A8%E7%9A%84%E9%AA%8C%E8%AF%81%E7%A0%81%E6%98%AF%EF%BC%9A20148%EF%BC%8C%E8%AF%B7%E5%9C%A810%E5%88%86%E9%92%9F%E5%86%85%E8%BE%93%E5%85%A5&apikey=xxxxxxx',

同时http头会加上‘Content-type: application/x-www-form-urlencoded’:

header: 'POST /v2/sms/single_send.json HTTP/1.1\\r\\nhost: sms.yunpian.com\\r\\ncontent-type: application/x-www-form-urlencoded\\r\\naccept: application/json\\r\\ncontent-length: 323\\r\\n

以下是是具体的实现代码:

async function send(mobile, text) 
  try 
    const options = 
      url: 'https://sms.yunpian.com/v2/sms/single_send.json',
      method: 'POST',
      form: 
        mobile,
        text,
        'apikey': confidential.yunpian.apiKey,
      ,
      json: true,
    ;
    const response = await request(options);
    if (response.code !== 0) 
      throw new ClientError.SmsError();
    
    return response;
   catch (e) 
    log.error('Exception:', e, e.stack);
    throw e;
  

注意我这里没有做过多的错误处理,为了更好的了解错误原因,其实应该根据response的code进行不同的错误处理。

上面是通用的短信发送接口。我们为了统一发送满足在云片上注册的模版消息,需要提供一个高层级的sendCode方法来发送验证码消息:

async function sendCode(mobile, code) 
    try 
        const message = `【小黄书科技】尊敬的用户,您的验证码是:$code,请在10分钟内输入`;
        return await send(mobile, message);
     catch (e) 
        log.error('Exception:', e);
        throw e;
    

这里的message一定要和我们在云片控制台上已经通过审核的模板消息一致,只允许code那部分有变动。

最后是将这两个方法exports出去给其他模块调用:

module.exports = 
  send,
  sendCode,
;

3. 螺丝帽


3.1. 注册

注册页面:
https://my.luosimao.com

3.2. 新增签名

注册后登陆螺丝帽的管理后台,导航到“短信>签名管理”页面进行签名申请:
https://sms-my.luosimao.com/signature

懒得贴图,相信这个难不到大家。

这里值得指出的是,我在螺丝帽中无法申请如上面云片中的“【小黄书科技】”的签名,因为里面带有“黄书”两个字。所以螺丝帽的示例中我会用【techgogogo】作为签名。

这里同时也提醒我们,在真实的产品研发中,好的产品名是如何的重要。

3.3. 新增模板

到页面“短信>短信模板“中进行模板消息的申请。
https://sms-my.luosimao.com/template/index

需要注意的是,短信模板在螺丝帽中不是必须的,没有模板,我们也照样能通过螺丝帽发送出验证短信。只是,据说有了模板后,发送的短信回不需要经过审核,所以速度回变快很多。

我这里申请到的模板是:

“尊敬的用户,您的验证码是:###,请在10分钟内输入【techgogogo】”

3.4 螺丝帽发送短信消息接口

详细接口描述,请查看官网:
https://luosimao.com/docs/api/20

nodejs版本的示例如下:

var https = require('https');
var querystring = require('querystring');

var postData = 
    mobile:'13761428267',
    message:'验证码:28261【铁壳测试】'
;

var content = querystring.stringify(postData);

var options = 
    host:'sms-api.luosimao.com',
    path:'/v1/send.json',
    method:'POST',
    auth:'api:key-12312389d10fe16c98896ced5a09945188',
    agent:false,
    rejectUnauthorized : false,
    headers:
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Content-Length' :content.length
    
;

var req = https.request(options,function(res)
    res.setEncoding('utf8');
    res.on('data', function (chunk) 
    console.log(JSON.parse(chunk));
    );
    res.on('end',function()
    console.log('over');
    );
);

    req.write(content);
    req.end();

可以看到,和上面的云片的发送方式会有以下异同:

  • 示例中用的是https的库来进行POST请求
  • apikey的格式不一样,key的值前面要带"key-"
  • apikey不再放在body中,而是放在了auth的options选项中
  • body中继续使用querystring的格式提供手机号和发送的信息内容数据
  • header中指定的Content-Type一样是’application/x-www-form-urlencoded’

3.4. 实现云片发送验证消息

根据上面的示例,很明显,我们同样可以通过request-promise库来完成我们的工作。且,为了和云片一致,我们也应该使用request-promise库。

实现的send代码如下:

async function send(mobile, message) 
  try 
    const options = 
      url: 'https://sms-api.luosimao.com/v1/send.json',
      method: 'POST',
      auth: 
        user: 'api',
        pass: `key-$confidential.luosimao.apiKey`,
      ,
      form: 
        mobile,
        message,
      ,
      json: true,
    ;
    const response = await request(options);
    if (response.error !== 0) 
      throw new ClientError.SmsError();
    
    return response;
   catch (e) 
    log.error('Exception', e, e.stack);
    throw e;
  

实现的sendCode代码如下:

async function sendCode(mobile, code) 
  try 
    const message = `尊敬的用户,您的验证码是:$code,请在10分钟内输入【techgogogo】`;
    return await send(mobile, message);
   catch (e) 
    log.error('Exception:', e);
    throw e;
  

最后是将这两个方法exports出去给其他模块调用:

module.exports = 
  send,
  sendCode,
;

对比上面云片的代码,可以看到代码并没有多大的区别,除了request的options有点不一样,其他基本是一致的。

4. 开关控制螺丝帽和云片的切换


我们同时只会让一个短信供应商起效,我们可以选择云片,也可以选择螺丝帽。

我们在config/configuration.js中增加一个配置项:

  sms: 
    yunpian: false,
    luosimao: true,
  

同一时间中我们应该只设置其中一个为true,如果都是false的话,我们会用云片,如果都是true的话,我们会启用第一个。

下面就是具体的实现代码(libs/sms.js):

/**
 * Created by KevinZhu on 17/05/2017.
 */
const config = require('../config/configuration');
const luosimao = require('./luosimao');
const yunpian = require('./yunpian');

if (config.sms.yunpian) 
    module.exports = yunpian;
 else if(config.sms.luosimao)  
    module.exports = luosimao;
 else 
    module.exports = yunpian;  // by default

这样,其他模块只需要在configuration.js文件中配置好使用哪个短信供应商,然后require进libs/sms.js,就可以直接使用sendCode来发送验证码了。

5. 发送验证码逻辑


发送验证码的逻辑是:

  • 生成一个随机整数作为验证码
  • 将该验证码发送给指定的手机号码
  • 将该验证码绑定一个过期时间,如果超过指定时间,该验证码不再有效

随机数我们可以通过Math来获得,发送验证码到指定手机号码我们在上面已经实现,验证码过期时间管理我们可以像我们的访问令牌一样通过redis来实现。

所以我们可以实现一个sms的路由中间件(routes/sms.js):

const express = require('express');
const log = require('../libs/logger');
const sms = require('../libs/sms');
const router = express.Router();
const redis = require('../libs/redisdb');

router.post('/code', async (req, res, next) => 
  try 
    const  mobile  = req.body;

    // 生成4位数字的随机数算法:Math.floor(Math.random() * (max - min + 1)) + min;
    var code = Math.floor(Math.random() * (999999 - 100000 + 1) + 100000);
    
    await sms.sendCode(mobile, code);
    await redis.setAsync(mobile, code);
    await redis.expireAsync(mobile, 60 * 10);
    return res.status(204).end();
   catch (e) 
    next(e);
  
);

module.exports = router;

客户端只需要Post一个body指定了手机号码的信息到我们的服务器,指定的手机号码就能收到我们的验证码模版短信。

6. 结语


详细实现请查看github中的代码。

  • git clone https://github.com/zhubaitian/XiaoHuangShuServer.git
  • cd XiaoHuangShuServer/
  • git checkout CH08
  • npm install
  • gulp dev

这一系列文章其实我写了有段时间了,后来忙起来忘了发布了😓。


最后想推下我最近发布的一个小程序:三日清单,希望朋友们能多支持。.

以上是关于[小黄书后台]手机短信验证码的主要内容,如果未能解决你的问题,请参考以下文章

[小黄书后台]Insomnia及HelloWorld

[小黄书后台]mongodb和用户管理

[小黄书后台]会员管理及微信授权登录

短信验证码最佳实践

[小黄书管理平台]Angularjs2.0基础及ng2-admin概览

[小黄书后台]更高效的nodejs调试