专栏首页即时通信IMserverless+腾讯云短信实现短信验证码登录
原创

serverless+腾讯云短信实现短信验证码登录

云函数(Serverless Cloud Function,SCF)是腾讯云为企业和开发者们提供的无服务器执行环境,帮助我们在无需购买和管理服务器的情况下运行代码。 本文会演示使用serverless云函数开发一个短信验证码登录注册服务。

传统开发协作流程(多角色参与): 1.后台开发:短信接口发送短信API、校验短信验证码是否有效API、存储用户验证状态 2.运维开发:接口部署,容灾 3.前端(客户端)开发:前端逻辑开发(调用接口,查询状态等)

传统开发模式的问题:多角色参与、工作量大、维护成本高

Serverless云函数开发模式:全流程基本上可一个人完成所有功能

这里是我们要实现的短信验证码登录的流程图,主要涉及到serverless云函数开发、短信服务、云db存储用户信息。

准备工作

相关资料

步骤1:配置短信内容

短信签名、短信正文模板提交后,我们会在2个小时左右完成审核,您可以 配置告警联系人 并设置接收模板和签名审核通知,便于及时接收审核通知。

步骤1.1:创建签名

  1. 登录 短信控制台
  2. 在左侧导航栏选择【国内短信】>【签名管理】,单击【创建签名】。

参数

取值样例

签名用途

自用(签名为本账号实名认证的公司、网站、产品名等)

签名类型

APP

签名内容

测试 Demo

证明类型

小程序设置页面截图

证明上传

  1. 单击【确定】。 等待签名审核,当状态变为【已通过】时,短信签名才可用。

步骤1.2:创建正文模板

  1. 登录 短信控制台
  2. 在左侧导航栏选择【国内短信】>【正文模板管理】,单击【创建正文模板】。

参数

取值样例

模板名称

验证码短信

短信类型

普通短信

短信内容

您的注册验证码:{1},请于{2}分钟内填写,如非本人操作,请忽略本短信。

  1. 单击【确定】。 等待正文模板审核,当状态变为【已通过】时,正文模板才可用,请记录模板 ID。

步骤2:设置短信发送频率限制(可选)

!个人认证用户不支持修改频率限制,如需使用该功能,请将 “个人认证” 变更为 “企业认证”,具体操作请参见 实名认证变更指引

为了保障业务和通道安全,减少业务被刷后的经济损失,建议 设置发送频率限制。另外,您也可以结合使用 腾讯云验证码 以便最大程度地保护业务安全。

本文以短信的默认频率限制策略为例。

  • 同一号码同一内容30秒内最多发送1条。
  • 同一手机号一个自然日最多发送10条。

步骤3:配置私有网络和子网

默认情况下,云函数部署在公共网络中,只可以访问公网。如果开发者需要访问腾讯云的 TencentDB 等资源,需要建立私有网络来确保数据安全及连接安全。

  1. 按需 规划网络

参数

取值样例

所属地域

华南地区(广州)

名称

Demo VPC

IPv4 CIDR

10.0.0.0/16

子网名称

Demo 子网

IPv4 CIDR

10.0.0.0/16

可用区

广州三区

步骤4:配置 MySQL 数据库

云数据库 MySQL 实例需与 步骤3 配置私有网络的地域和子网的可用区保持一致。

参数

取值样例

计费模式

按量计费

地域

广州

数据库版本

MySQL5.7

架构

高可用版

主可用区

广州三区

备可用区

广州四区

实例规格

4核8000MB

硬盘

200GB

网络

Demo VPC,Demo 子网

实例名

立即命名:Demo 数据库

购买数量

1

参数

取值样例

支持字符集

UTF8

表名大小写敏感

自定义端口

3306

设置 root 帐号密码

请自定义设置

确认密码

请再次输入密码

  1. 登录 MySQL 数据库,具体操作请参见 登录 phpMyAdmin
  2. 根据实际需求,创建数据表和字段用于存储用户的手机号、头像、用户昵称等信息,具体操作请参见 创建数据库和表

步骤5:新建云函数

云函数目前支持 Python、Node.js、PHP、Java 以及 Golang 语言开发,本文以 Node.js 为例。

参数

取值样例

函数名称

Demo

运行环境

Nodejs 8.9

创建方式

模板函数:helloworld

  1. 部署函数并配置触发方式为【API网关触发器】,具体操作请参见 部署函数

步骤6:配置 NAT 网关

部署在 VPC 中的云函数默认隔离外网。若想使云函数同时具备内网访问和外网访问能力,可通过以下两种方式实现:

本文以添加 NAT 网关为例。

  1. 新建 NAT 网关,具体操作请参考 私有网络中配置 NAT
  • NAT 网关要和函数、VPC 部署在同一地域。
  • NAT 网关的所属网络需要选择函数所在的 VPC。

参数

取值样例

网关名称

Demo NAT

所属网络

Demo VPC

网关类型

小型

出带宽上限

100Mbps

弹性 IP

新建弹性 IP

  1. 根据实际需求创建路由策略,具体操作请参考 私有网络中配置 NAT

步骤7:部署短信 SDK

  1. 执行以下命令,安装 SDK。npm install tencentcloud-sdk-nodejs --save
  2. 在代码中引用短信模块代码。
  3. 配置发送短信核心逻辑。
/**
 * 
 * @param {*} 功能:通过 SDK 发送短信 
 * @param {*} 参数:手机号、短信验证码 
 */
async function sendSms(phone, code) {
    const tencentcloud = require('tencentcloud-sdk-nodejs');
    const SmsClient = tencentcloud.sms.v20190711.Client;
    const Credential = tencentcloud.common.Credential;
    const ClientProfile = tencentcloud.common.ClientProfile;
    const HttpProfile = tencentcloud.common.HttpProfile;
    //腾讯云账户 secretId,secretKey,切勿泄露
    const secretId = "secretId";//需要配置为真实的 secretId
    const secretKey = "secretKey";//需要配置为真实的 secretKey
    let cred = new Credential(secretId, secretKey);
    let httpProfile = new HttpProfile();
    httpProfile.endpoint = "sms.tencentcloudapi.com";
    let clientProfile = new ClientProfile();
    clientProfile.httpProfile = httpProfile;
    let client = new SmsClient(cred, "ap-guangzhou", clientProfile);
    phone = "+86" + phone;//国内手机号
    let req = {
        PhoneNumberSet: [phone],//发送短信的手机号
        TemplateID: "",//<a href="#Step1_2">步骤1.2</a> 中创建并记录的模板 ID
        Sign: "",//<a href="#Step1_1">步骤1.1</a> 中创建的签名
        TemplateParamSet: [code],//随机验证码
        SmsSdkAppid: ""//短信应用 ID
    }
    function smsPromise() {
        return new Promise((resolve, reject) => {
            client.SendSms(req, function (errMsg, response) {
                if (errMsg) {
                    reject(errMsg)
                } else {
                    if (response.SendStatusSet && response.SendStatusSet[0] && response.SendStatusSet[0].Code === "Ok") {
                        resolve({
                            errorCode: 0,
                            errorMessage: response.SendStatusSet[0].Message,
                            data: {
                                codeStr: response.SendStatusSet[0].Code,
                                requestId: response.RequestId
                            }
                        })
                    } else {
                        resolve({
                            errorCode: -1003,//短信验证码发送失败
                            errorMessage: response.SendStatusSet[0].Message,
                            data: {
                                codeStr: response.SendStatusSet[0].Code,
                                requestId: response.RequestId
                            }
                        })
                    }
                }
            });
        })
    }
    let queryResult = await smsPromise()
    return queryResult
}

步骤8:检验验证码核心逻辑

验证码的时效性要求较高,您可以把验证码存在内存中或存在云数据库 Redis 中。以手机号作为 key,存储发送时间、验证码、验证次数、是否已验证过等信息。出于安全考虑,建议设置防止暴力破解的限制,本文以验证码最多验证3次为例。

/*
 * 功能:根据手机号获取短信验证码
 */
async function getSms(queryString) {
  const code = Math.random().toString().slice(-6);//生成6位数随机验证码
  const sessionId = Math.random().toString().slice(-8);//生成8位随机数
  const sessionCode = {
      code: code,
      sessionId: sessionId,
      sendTime: new Date().getTime(),
      num: 0,//验证次数,最多可验证3次
      used: 1//1-未使用,2-已使用
  }
  clearCacheCode()

  cacheCode[queryString.phone] = sessionCode

步骤9:配置登录模块

登录模块主要用于用户注册或登录,首次登录(即注册)时将保存用户的手机号、用户名、头像、注册时间等信息。

/*
* 功能:登录
*/
async function loginSms(queryString) {
  const connection = mysql.createConnection({
    host: '', // The ip address of cloud database instance, 云数据库实例 IP 地址
    user: '', // The name of cloud database, for example, root, 云数据库用户名,例如 root
    password: '', // Password of cloud database, 云数据库密码
    database: '' // Name of the cloud database, 数据库名称
  });
  connection.connect();

  if(queryString.token) {
    return await verifyToken(connection, queryString)
  }

  if(!queryString.code || !queryString.sessionId) {
    return {
        errorCode: -1001,
        errorMessage: "缺少参数"
    }
  }

  let result = cacheCode[queryString.phone]
  if(!result || result.used === 2 || result.num >= 3) {
    return {
      errorCode: -1100,
      errorMessage: "验证码已失效"
    }
  }
  if(result.sessionId !== queryString.sessionId) {
    return {
      errorCode: -1103,
      errorMessage: "sessionId不匹配"
    }
  }
  
  if(result.code == queryString.code) {
    cacheCode[queryString.phone].used = 2;//将验证码更新为已使用
    const queryInfoSql = `select * from info where phone = ?`
    let queryInfoResult = await wrapPromise(connection, queryInfoSql, [queryString.phone])
    if(queryInfoResult.length === 0) {//没有找到记录,未注册
      return await generateInfo(connection, queryString)
    } else {
      let infoResult = queryInfoResult[0]
      return {
        errorCode: 0,
        errorMessage: "登录成功",
        data: {
          phone: infoResult.phone,
          token: getToken(infoResult.userId, infoResult),
          name: infoResult.name,
          avatar: infoResult.avatar,
          userId: infoResult.userId.toString()
        }
      }
    }
  } else {
    updateCacheCode(queryString.phone, result)
    return {
      errorCode: -1102,
      errorMessage: "验证码错误,请重新输入"
    }
  }
}

另外,为了登录更便捷,您可以通过 Json web token 标准来生成 token 维护登录状态,实现短时间内登录无需短信验证码的功能。

/*
* 功能:利用 json web token 签发一个 token
*/
function getToken(userId, infoResult) {
  return jwt.sign({
    phone: infoResult.phone,
    userId: userId,
    name: infoResult.name,
    avatar: infoResult.avatar
  }, privateKey, {expiresIn: tokenExpireTime});
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 腾讯云短信拍了拍你,并向你扔出了短链服务!

    2020年6月10日,腾讯云短信(下文简称短信)面向广大企业客户正式推出【短链服务】,支持客户在控制台一键生成带统计功能的短链接,便于客户进一步监测短信触达的实...

    腾讯即时通信IM
  • 腾讯实时音视频又放大招!移动端语音聊天室组件正式来袭!

    随着移动互联网技术飞速发展,语音社交逐渐崛起。越来越多的社交App增加了语音聊天功能,更有专门的语音社交软件应运而生,占据了不少“90后”“00后”的空闲时间。...

    腾讯即时通信IM
  • 即时通信 IM丨全员推送的玩法,你一定要pick起来!

    1、假如现在有一款游戏 App 打算在圣诞节推出优惠活动,需要推送给全部用户,这时候运营人员一般会使用短信进行用户触达。但是可能会有部分用户因为没留意短信而错过...

    腾讯即时通信IM
  • 一个关于Definer和Invoker的权限问题

    1. 用户user1定义的存储过程,即使用sys用户执行,需要参考user1权限?

    bisal
  • Python中的单例模式的几种实现方式的及优化

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只...

    用户1214487
  • 牛逼的电商BI分析工具:Looker

    电商——少数能在商业实践中,直接能够依靠数据理解显著提升业绩的领域。电商对于数据的运用和理解都需不凡造诣,而能为百余家电商提供不同来源数据可视化BI分析工具的公...

    iCDO互联网数据官
  • Eclipse-Eclipse常用操作 原

    1. 项目导入Eclipse后项目头顶上显示"!"号,/project/bin下没有编译后的文件,点击Project>Clean..没有编程项目导bin目录下。

    秋日芒草
  • mapboxGL测量实现

    讲真,MapboxGL里面虽然有测量的功能,但是不太好用,于是就萌生了自己实现的方法。本文几个turf.js来说说mapboxGL中测量的实现。

    lzugis
  • “OpenAI,请开源你的模型!越公开越安全,本来也不危险。”

    对此,多数小伙伴选取了嘲讽的语调,激励团队早日开源:不如改名ClosedAI算了。

    量子位
  • dotNET Core WebAPI 统一处理(返回值、参数验证、异常)

    现在 Web 开发比较流行前后端分离,我们的产品也是一样,前端使用Vue,后端使用 dotNet Core WebAPI ,在写 API 的过程中有很多地方需要...

    oec2003

扫码关注云+社区

领取腾讯云代金券