云函数(Serverless Cloud Function,SCF)是腾讯云为企业和开发者们提供的无服务器执行环境,帮助我们在无需购买和管理服务器的情况下运行代码。 本文会演示使用serverless云函数开发一个短信验证码登录注册服务。
传统开发协作流程(多角色参与): 1.后台开发:短信接口发送短信API、校验短信验证码是否有效API、存储用户验证状态 2.运维开发:接口部署,容灾 3.前端(客户端)开发:前端逻辑开发(调用接口,查询状态等)
传统开发模式的问题:多角色参与、工作量大、维护成本高
Serverless云函数开发模式:全流程基本上可一个人完成所有功能
这里是我们要实现的短信验证码登录的流程图,主要涉及到serverless云函数开发、短信服务、云db存储用户信息。
短信签名、短信正文模板提交后,我们会在2个小时左右完成审核,您可以 配置告警联系人 并设置接收模板和签名审核通知,便于及时接收审核通知。
参数 | 取值样例 |
---|---|
签名用途 | 自用(签名为本账号实名认证的公司、网站、产品名等) |
签名类型 | APP |
签名内容 | 测试 Demo |
证明类型 | 小程序设置页面截图 |
证明上传 |
参数 | 取值样例 |
---|---|
模板名称 | 验证码短信 |
短信类型 | 普通短信 |
短信内容 | 您的注册验证码:{1},请于{2}分钟内填写,如非本人操作,请忽略本短信。 |
!个人认证用户不支持修改频率限制,如需使用该功能,请将 “个人认证” 变更为 “企业认证”,具体操作请参见 实名认证变更指引。
为了保障业务和通道安全,减少业务被刷后的经济损失,建议 设置发送频率限制。另外,您也可以结合使用 腾讯云验证码 以便最大程度地保护业务安全。
本文以短信的默认频率限制策略为例。
默认情况下,云函数部署在公共网络中,只可以访问公网。如果开发者需要访问腾讯云的 TencentDB 等资源,需要建立私有网络来确保数据安全及连接安全。
参数 | 取值样例 |
---|---|
所属地域 | 华南地区(广州) |
名称 | Demo VPC |
IPv4 CIDR | 10.0.0.0/16 |
子网名称 | Demo 子网 |
IPv4 CIDR | 10.0.0.0/16 |
可用区 | 广州三区 |
云数据库 MySQL 实例需与 步骤3 配置私有网络的地域和子网的可用区保持一致。
参数 | 取值样例 |
---|---|
计费模式 | 按量计费 |
地域 | 广州 |
数据库版本 | MySQL5.7 |
架构 | 高可用版 |
主可用区 | 广州三区 |
备可用区 | 广州四区 |
实例规格 | 4核8000MB |
硬盘 | 200GB |
网络 | Demo VPC,Demo 子网 |
实例名 | 立即命名:Demo 数据库 |
购买数量 | 1 |
参数 | 取值样例 |
---|---|
支持字符集 | UTF8 |
表名大小写敏感 | 是 |
自定义端口 | 3306 |
设置 root 帐号密码 | 请自定义设置 |
确认密码 | 请再次输入密码 |
云函数目前支持 Python、Node.js、PHP、Java 以及 Golang 语言开发,本文以 Node.js 为例。
参数 | 取值样例 |
---|---|
函数名称 | Demo |
运行环境 | Nodejs 8.9 |
创建方式 | 模板函数:helloworld |
部署在 VPC 中的云函数默认隔离外网。若想使云函数同时具备内网访问和外网访问能力,可通过以下两种方式实现:
本文以添加 NAT 网关为例。
参数 | 取值样例 |
---|---|
网关名称 | Demo NAT |
所属网络 | Demo VPC |
网关类型 | 小型 |
出带宽上限 | 100Mbps |
弹性 IP | 新建弹性 IP |
/**
*
* @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
}
验证码的时效性要求较高,您可以把验证码存在内存中或存在云数据库 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
登录模块主要用于用户注册或登录,首次登录(即注册)时将保存用户的手机号、用户名、头像、注册时间等信息。
/*
* 功能:登录
*/
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});
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。