阿里云短信验证码登录功能

阿里云短信验证码登录功能

陈宇翔

一、准备工作

1、登录阿里云进入到短信服务:阿里云-短信服务
2、按照首页提示一次完成相应的资质认证短信模板审核;

3、获取你的accessKeySecretaccessKeyId 获取方法:获取AccessKey-阿里云帮助中心
4、获取SignName(签名名称)TemolateCode(模板code)

二、代码实现

项目结构

1、首先创建一个node的工程文件夹,我这里是aliyun,然后初始化,如下命令,然后按上图目录创建好对应文件
1
npm init

引入依赖

1、在项目的根目录,打开终端,输入如下命令
1
npm  i body-parser express knex moment mysql @alicloud/sms-sdk --save

修改代码

1、修改配置文件 /config /index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const config = {

// AccessKey ID
accessKeyId: "XXXXXXXXXXXXXXXXXXXXXXXXXX",

// AccessKey Secret
secretAccessKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",

// 签名名称
signName: "XXXX",

// 模板CODE => 登录/注册 - 验证码
templateCode_login: "SMS_4XXXXXXXX",

}

module.exports = config
2、修改数据库文件 /config/db.js
1
2
3
4
5
6
7
8
9
10
11
12
const knex = require('knex')({
client:'mysql',
connection:{
host:'192.168.1.19',
user:'root',
password:'Xaocao2120801',
database:'aliyun' //数据库名称
}
})


module.exports = knex
3、修改主启动文件 /server/index.js (具体登录的业务逻辑根据实际情况进行修改,这里主要是发送验证码和验证功能)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// express框架
const express = require('express');
//获取前端数据的插件
const bodyParser = require('body-parser');
// 阿里云短信sdk
const SMSClient = require('@alicloud/sms-sdk');
// 阿里云短信配置文件
const config = require('../config/index');
// 配置数据库
const knex = require('../config/db')
// 用于处理日期时间相关操作,方便计算和比较时间
const moment = require('moment')

const app = express()
//端口号
const port = 3000

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

//解决跨域问题
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); // 生产环境中限制来源
res.header('Access-Control-Allow-Headers', 'Content-Type'); // 注意Content-Type的大小写
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,PATCH");
res.header('Access-Control-Max-Age', 1728000); // 预请求缓存20天
// 对于OPTIONS请求,直接返回响应
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});

// 发送短信验证码的API接口
app.post('/sendSmsCode', async (req, res) => {
const {phone} = req.body; // 从请求体中获取手机号
try {
// 查询手机号是否存在
const user = await knex('users').where({phone}).first()
if (!user) {
return res.status(400).json({code: 400, message: '该用户不存在,请联系管理员'})
}
// 参数配置 sms_client
const accessKeyId = config.accessKeyId;
const secretAccessKey = config.secretAccessKey;
const signName = config.signName;
const templateCode = config.templateCode_login;
// 随机六位验证码
const verify = Math.random().toString().slice(-6);
const phoneNum = phone;
// 初始化 sms_client
const smsClient = new SMSClient({accessKeyId, secretAccessKey});
// 发送短信
const result = await smsClient.sendSMS({
// 发送对象手机号
PhoneNumbers: phoneNum,
// 签名名称
SignName: signName,
// 模版CODE
TemplateCode: templateCode,
// 短信模板变量对应的实际值,JSON格式
TemplateParam: `{"code":"${verify}"}`
})
// 判断是否发送成功
if (result.Code === 'OK') {
await knex('ver_codes').insert({
phone,
code: verify,
created_at: moment().toDate(),
expired_at: moment().add(5,'minutes').toDate(), //设置有效期(5分钟)
used: 0
})
return res.status(200).json({message: '短信发送成功', code: verify});
} else {
return res.status(500).json({error: '短信发送失败', message: result.Message});
}
}catch (error) {
console.log(error);
return res.status(500).json({ error: '发送短信时发生错误', message: error.message });
}
})


//登录接口
app.post('/login',async(req,res)=>{
const { phone, code } = req.body
try {
//查询数据库中该手机号对应的验证码记录
const verRecord = await knex('ver_codes')
.where({phone,used:0})
.andWhere('expired_at','>',moment().toDate())
.first()
if(!verRecord){
return res.status(401).json({error:'验证码无效,请重新获取',message:'验证码已过期或不存在'})
}
if(phone === verRecord.phone){
if(verRecord.code === code){
//验证码正确,更行该记录的used字段为1,表示已使用
await knex('ver_codes').where({id:verRecord.id}).update({used:1})
return res.status(200).json({code:200,message:'验证通过'})
}else {
return res.status(401).json({ error: '验证码错误,请重新输入', message: '输入的验证码与发送的不一致' });
}
}
}catch (error){
console.log(error);
return res.status(500).json({ error: '登录验证时发生错误', message: error.message });
}
})

// 启动服务器
app.listen(port, () => {
console.log(`服务器正在运行在 http://localhost:${port}`);
});
4、修改前端vue组件 index.vue(需安装axios和element-plus)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus';
import axios from "axios";

const form = ref({phone: '', code: ''})
//是否处于发送验证码状态
const messageCodeVis = ref(false)
//倒计时
let countdown = ref(0)

//发送验证码函数
const sendCode = () =>{
const reg = /^1[3456789]\d{9}$/;
if(!form.value.phone) return ElMessage.error('请输入手机号码')
if (!reg.test(form.value.phone)) return ElMessage.error('请输入有效的手机号码');

axios.post('/sendSmsCode',{phone:form.value.phone}).then(res=>{
if(res.status ===200 && res.data.message==='短信发送成功'){
countdown.value = 60
messageCodeVis.value = true
startCountdown()
ElMessage.success('验证码发送成功')
}
}).catch(err=>{
if(err.response.data.code ===400 && err.response.data.message ==='该用户不存在,请联系管理员'){
ElMessage.warning(err.response.data.message)
}else {
ElMessage.error('发送验证码时出现网络错误')
}
})
}

/* 倒计时 */
const startCountdown = () => {
const intervalId = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
} else {
clearInterval(intervalId)
messageCodeVis.value = false
}
}, 1000)
}

const submitLogin = async ()=>{
const phone = form.value.phone;
const code = form.value.code;
try {
const res = await axios.post('http://localhost:3000/login', {phone,code})
if(res.status === 200 && res.data.message ==='验证通过'){
ElMessage.success(res.data.message)
}
}catch (e){
if(e.response.data.message==='验证码已过期或不存在' && e.status === 401){
ElMessage.error(e.response.data.message)
}else if(e.response.data.message === '输入的验证码与发送的不一致' && e.status ===401){
ElMessage.error(e.response.data.error)
}
}
}
//input输入框规则验证
const rulesForm = {
phone: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ pattern: /^1[3456789]\d{9}$/, message: '请输入有效的手机号码', trigger: 'blur' }
]
}
</script>

<template>
<el-form :model="form" :rules="rulesForm" label-width="120px">

<el-form-item label="手机号码:" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>

<el-form-item label="验证码:">
<el-input v-model="form.code" >
<template #suffix>
<div v-if="messageCodeVis" class="second-text">{{countdown}}秒后重新获取</div>
<el-button v-else type="primary" link @click="sendCode">获取验证码</el-button>
</template>
</el-input>
</el-form-item>

</el-form>
<el-button type="primary" @click="submitLogin">登录</el-button>
</template>

<style scoped>
.read-the-docs {
color: #888;
}
.second-text {
color: #e60707;
}
</style>

5、mysql数据库表的创建代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# users表(用于存储用户基本信息)
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`phone` varchar(20) NOT NULL UNIQUE,
`password` varchar(255) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP, #记录用户创建账号的时间
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP #记录账号信息最后更新时间
);

# ver_codes表 (用于存储验证码相关信息)
CREATE TABLE `ver_codes` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`phone` varchar(20) NOT NULL,
`code` varchar(6) NOT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP, #创建时间
`expired_at` datetime NOT NULL, #设置验证码过期时间
`used` tinyint(1) DEFAULT 0 #表示验证码是否被使用,默认为0表示未使用
);