前言
个人博客上线已经有段时间了,网站的登录方式一直都还是采用的原始的账号密码登录,这对于博客网站来说极不人性,毕竟谁有时间去记住我一个个人博客网站的帐号密码,于是乎趁着周末徒手撸了一个短信登录,游客只需要记住自己的手机号即可,这比密码登录人性多了。
前置准备
1,在开始开发之前,需要到腾讯云开通短信服务,具体流程腾讯云有着详细的介绍:开通腾讯云短信服务
开通后,拿到下面这些信息:应用appid,appkey,秘钥secretid,secretkey,短信模版id
2,同时,需要在服务器部署好redis数据库。
开始开发
1,nest连接redis数据库并且封装
app.modules.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RedisModule } from '@liaoliaots/nestjs-redis'; // redis nestjs-redis包不支持nest8+
import { ConfigModule } from '@nestjs/config';
import { ConfigService } from '@nestjs/config';
import { CacheModule } from './cache/cache.module';
import customConfig from './config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
// envFilePath,
load: [customConfig],
}),
RedisModule.forRootAsync({
imports: [ConfigModule], // 数据库配置项依赖于ConfigModule,需在此引入
useFactory: (configService: ConfigService) => ({ config: { ...configService.get('redisConfig') } }),
inject: [ConfigService], // 记得注入服务,不然useFactory函数中获取不到ConfigService
}),
CacheModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
cache.modules
import { Module } from '@nestjs/common';
import { CacheService } from './cache.service';
import { CacheController } from './cache.controller';
@Module({
controllers: [CacheController],
providers: [CacheService],
exports: [CacheService],
})
export class CacheModule {}
cache.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRedis } from '@liaoliaots/nestjs-redis';
import Redis from 'ioredis';
@Injectable()
export class CacheService {
constructor(@InjectRedis() private readonly client: Redis) {}
async setCache(key: string, value: any, second: number = 30) {
return await this.client.set(key, value, 'EX', second);
}
async getCache(key: string) {
if (!key.length) {
throw new HttpException('key不能为空', HttpStatus.BAD_REQUEST);
}
return await this.client.get(key);
}
async delCache(key: string) {
if (!key.length) {
throw new HttpException('key不能为空', HttpStatus.BAD_REQUEST);
}
return await this.client.del(key);
}
}
2,引入腾讯云sdk
// 腾讯云sdk
const tencentcloud = require("tencentcloud-sdk-nodejs")
// 导入对应产品模块的client models。
const smsClient = tencentcloud.sms.v20210111.Client
3,实例化sms对象,并且通过sms发送短信请求
/* 官方文档:https://cloud.tencent.com/document/product/382/43197 */
import { SmsOptions } from 'waylon-blog-ts-types';
const tencentcloud = require('tencentcloud-sdk-nodejs');
const smsClient = tencentcloud.sms.v20210111.Client;
export const tencentSms = (options: SmsOptions) => {
const { appId, appKey, loginTempId, registerTempId, secretId, secretKey } = options.config;
const tempArr = [registerTempId, loginTempId];
// 导入对应产品模块的client models。
/* 实例化要请求产品(以sms为例)的client对象 */
const client = new smsClient({
credential: {
/* 必填:腾讯云账户密钥对secretId,secretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及你的财产安全。
* SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
secretId,
secretKey,
},
/* 必填:地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
region: 'ap-guangzhou',
/* 非必填:
* 客户端配置对象,可以指定超时时间等配置 */
profile: {
/* SDK默认用TC3-HMAC-SHA256进行签名,非必要请不要修改这个字段 */
signMethod: 'HmacSHA256',
httpProfile: {
/* SDK默认使用POST方法。
* 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */
reqMethod: 'POST',
/* SDK有默认的超时时间,非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
reqTimeout: 30,
/**
* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com
*/
endpoint: 'sms.tencentcloudapi.com',
},
},
});
/* 请求参数,根据调用的接口和实际情况,可以进一步设置请求参数
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
/* 帮助链接:
* 短信控制台: https://console.cloud.tencent.com/smsv2
* 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
const params = {
/* 短信应用ID: 短信SmsSdkAppId在 [短信控制台] 添加应用后生成的实际SmsSdkAppId,示例如1400006666 */
// 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
SmsSdkAppId: appId,
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
// 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
SignName: 'Waylon的树洞个人网',
/* 模板 ID: 必须填写已审核通过的模板 ID */
// 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
TemplateId: tempArr[options.sense],
/* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */
TemplateParamSet: [options.msg],
/* 下发手机号码,采用 e.164 标准,+[国家或地区码][手机号]
* 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/
PhoneNumberSet: [options.phone],
/* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
SessionContext: '',
/* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */
ExtendCode: '',
/* 国际/港澳台短信 senderid(无需要可忽略): 国内短信填空,默认未开通,如需开通请联系 [腾讯云短信小助手] */
SenderId: '',
};
return new Promise((resolve, reject) => {
client.SendSms(params, function (err, response) {
// 请求异常返回,打印异常信息
if (err) {
console.log(err);
reject(err);
}
// 请求正常返回,打印response对象
resolve(response);
});
});
// 通过client对象调用想要访问的接口,需要传入请求对象以及响应回调函数
其中SmsOptions配置如下:
export interface SmsOptions {
config: {
appId: string; // 应用appid
appKey: string; // 应用appkey
loginTempId: string; // 登录短信模版id
registerTempId: string; // 注册短信模版id
secretId: string; // 秘钥id
secretKey: string; // 秘钥key
};
phone: string; // 发送的手机号
msg: string; // 发送的信息 这里传的是一个6位随机数字字符串
sense: number; // 区分不同的短信场景
}
4,编写sms服务,调用刚刚写好的发送短信服务,并且在回调中将短信以 【手机号】-【验证码】的形式存入redis数据库
核心代码如下:
// 发短信
async sms(phone: string, sense: number) {
if (!phone) {
throw new HttpException('手机号不能为空!', HttpStatus.BAD_REQUEST);
}
// 查下redis缓存 2分钟内无法再发短信
const cache = await this.cacheService.getCache(phone);
if (cache) {
throw new HttpException('发短信要钱的喂![○・`Д´・ ○]', HttpStatus.BAD_REQUEST);
}
// 生成验证码
let code = ('000000' + Math.floor(Math.random() * 999999)).slice(-6);
// 发送短信
const smsOptions: SmsOptions = {
config: this.configService.get('smsConfig'),
phone,
sense,
msg: code,
};
try {
const res = await tencentSms(smsOptions);
if ((res as any).SendStatusSet[0].Code === 'Ok') {
// 回调OK 则将验证码存入redis 缓存2分钟
this.cacheService.setCache(phone, code, 600);
}
return res;
} catch (err) {
throw new HttpException(err, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 短信登录
async smsLogin(params: { phone: string; code: string }) {
const { phone, code } = params;
const cache = await this.cacheService.getCache(phone);
// 检查用户输入的二维码是否与redis缓存中的一致
if (cache === code) {
const user = await this.userService.findOneByPhone(phone);
if (user) {
this.cacheService.delCache(phone);
return await this.login(user);
}
// 后续逻辑和登录一样
} else {
throw new HttpException('再确认下你的验证码![○・`Д´・ ○]', HttpStatus.BAD_REQUEST);
}
}
后续的登录流程这里就不过多赘述,自此,一个基于nest.js、redis、腾讯云短信服务打造的登录/注册功能就开发完成了,欢迎大家戳一戳右边旋转的地球🌏体验该功能~(#^.^#)
参考文档:腾讯云Node.js SDK