先演示效果
- 这个gif图是真难做啊,剪辑视频 + gif图制作花了我半个小时?
发送邮件的过程是,你的客户端给邮件服务器发信息,然后邮件服务器发给目标邮件服务器,目标邮件服务器再通知到客户端,目标用户就收到了。
如果我们使用QQ邮箱客户端可以直接用账号密码登录自己的邮箱。使用node server(自己搭的服务器)发邮件,相当于第三方客户端。
第三方客户端指的就是除了QQ邮箱自己的客户端外的其他客户端。
发邮件流程变成了
这里有个问题,使用QQ邮箱客户端可以直接通过账号密码登录邮箱,第三方客户端使用账号密码登录肯定不安全,因此QQ邮箱提供了授权码,第三方客户端可以通过授权码以用户身份访问邮箱服务器。由于授权码的权限可以控制,比如可以关闭服务,第三方客户端就无法使用授权码登录了,因此授权码比较安全。
node server(自己搭的服务器)发送邮件时候就是一个第三方客户端。
废话不多说,直接开始吧…
获取QQ邮箱授权码
- 登录QQ邮箱,点击设置
- 点击账户
- 点击如何设置
- 点击账号安全
- 点击安全设置 -> 生成授权码
- 根据提示操作即可
- 短信发送成功后,点击我已发送,获取授权码成功,如下图:
要注意,授权码只显示一次,因此弹窗时候要复制记录下来
获取到授权码后,就可以用node发送邮件了
前端代码
简单的搭一个页面
这里主要讲一下整个代码实现流程,就随便写了几行,凑合着看吧(当然你也可以使用像postman,apipost之类的接口测试工具也可以),如下图:
<template>
<h1>属性管理</h1>
<el-card>
<el-input placeholder="请输入邮箱" v-model="emailAccount" />
<el-input placeholder="请输入验证码" v-model="code" />
<hr />
<el-button type="primary" @click.once="sendEmail">发送邮箱验证码</el-button>
<br />
<br />
<el-button style="width: 100%;" type="primary" @click="login">登录</el-button>
</el-card>
</template>
<script setup lang="ts">
import axios from 'axios'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
// 邮箱账户
let emailAccount = ref<string>('')
// 定义邮箱验证码code
let code = ref<string>('')
// 发送邮箱验证码
const sendEmail = () => {
// 这里需要做表单正则验证,合法后才可发请求
if (!(emailAccount.value.trim())) {
ElMessage({
message: '邮箱不能为空...',
type: 'error',
})
return
}
axios({
method: 'POST',
url: 'http://127.0.0.1:4000/email',
data: {
emailAccount: emailAccount.value
}
}).then(res => {
ElMessage({
message: '邮箱发送成功,请注意查收!',
type: 'success',
})
}, error => {
ElMessage({
message: '邮箱不合法',
type: 'error',
})
})
}
// 登录
const login = () => {
// 这里需要做表单正则验证,合法后才可发请求
if (!(emailAccount.value.trim()) || code.value.length !== 6) {
ElMessage({
message: '参数不合法',
type: 'error',
})
return
}
axios({
method: "POST",
url: "http://127.0.0.1:4000/login",
data: {
emailAccount: emailAccount.value,
emailCode: code.value
}
}).then(res => {
console.log("res", res)
if (res.data.code == 200) {
ElMessage({
message: '登录成功',
type: 'success',
})
} else {
ElMessage({
message: '验证码错误',
type: 'error',
})
}
}, error => {
ElMessage({
message: '验证码已过期',
type: 'warning',
})
})
}
</script>
后端代码
koaweb开发框架
nodemailer是一个node库,提供了发邮件的功能
knex框架是一个ORM框架,使用knex可以用JavaScript语法指令来操作SQL语句
MySQL
数据库
项目目录结构
// myKnex.js
const knex = require("knex")({
// 操纵的数据库客户端
client: "mysql",
// 数据库连接
connection: {
// 地址
host: "127.0.0.1",
// 用户名
user: "root",
// 密码
password: "root",
// 库名
database: "xiangyang",
},
});
module.exports = knex;
// email.js
const router = require("koa-router")();
const nodemailer = require("nodemailer");
const knex = require("./../utils/myKnex");
let timer = null;
// 获取邮箱验证码接口
router.post("/email", async (ctx) => {
// 接受前端发送过来的数据
// 这里接受前端发过来的邮箱账号
const { emailAccount } = ctx.request.body;
// 创建连接对象
const transporter = nodemailer.createTransport({
host: "smtp.qq.com", // 服务 由于我用的QQ邮箱
secure: true, // 安全的发送模式
secureConnection: false,
port: 465, // smtp端口 默认无需改动
auth: {
user: "1524187180@qq.com", // 发件人邮箱
pass: "", // 授权码 ---》这里填写你刚才获取到的授权码即可
},
});
// 随机生成6位数验证码
// 方式一:
// const code = Math.ceil(Math.random() * (1000000 - 1000) + 1000);
// 方式二:
const code = () => {
// 生成6位随机数
let code = "";
for (let i = 0; i < 6; i++) {
code += parseInt(Math.random() * 10);
}
return code;
};
// 生成6位的验证码
const codeNumber = code();
// 发送邮箱
transporter.sendMail(
{
// 发件人
from: "1524187180@qq.com",
// 收件人
to: String(emailAccount),
// 邮箱标题
subject: "欢迎使用工院派大星, 迎接更高效的校园生活",
// 邮箱内容
text: `【工院派大星】验证码${codeNumber},您正在身份验证,请勿泄露,谨防被骗。`
},
(err, data) => {
if (err) {
console.log("发送失败:" + err);
ctx.body = {
code: 201,
messge: "邮箱不合法",
};
transporter.close(); // 如果没用,关闭连接池
} else {
console.log("发送成功");
// 操作email表数据库
// 新增操作
// hero是表名
knex("email")
.insert({
// insert写对象即可 key就是字段的名字
emailAccount: String(emailAccount),
emailCode: String(codeNumber),
})
.then((res) => {
// res 可以获取到 新增数据的 id
// 数据插入成功之后 执行 then 回调函数
console.log("数据添加数据库成功!");
});
// 为了保证邮箱的唯一性,需要在n时间内删除该条记录
// 5分钟后从数据库中删除(且在5分钟类使用了该验证码则该验证码失效!!!)(从数据库中删除)
const delay = 1000 * 60 * 5
timer = setTimeout(() => {
knex("email")
.delete()
.where({
emailAccount: String(emailAccount),
})
.then((res) => {
console.log("删除记录成功," + res);
});
}, delay);
ctx.body = {
code: 200,
messge: "发送成功!",
};
}
}
);
});
// 邮箱登录接口
router.post("/login", async (ctx) => {
// 拿到前端邮箱账号和验证码
const { emailAccount, emailCode } = ctx.request.body;
// 从数据库中查询数据
const result = await knex("email")
.select()
.where({
emailAccount: String(emailAccount),
});
// 以最后一次接受的验证码为主(前端需要节流,不能一直访问后台数据)
// 验证码输入正确(拿到前端传过来的邮箱账号和邮箱验证码在数据库中查找是否存在并且还要保证邮箱验证码一致)
if (
result[result.length - 1].emailAccount == String(emailAccount) &&
result[result.length - 1].emailCode == String(emailCode)
) {
ctx.body = {
code: 200,
message: "success",
};
// 调用后立马删除数据库email表中的当前账号所有信息
knex("email")
.delete()
.where({
emailAccount: String(emailAccount),
})
.then((res) => {
console.log("res:+" + res);
clearTimeout(timer);
});
} else {
// 错误
ctx.body = {
code: 201,
message: "验证码错误",
};
}
});
module.exports = router;
将数据存储到MySQL数据库
数据库表设计
调用接口,存储数据到数据库
验证码发送成功后会在5分钟后自动清除数据库中的数据,当验证码使用之后会立马从数据库中删除当前账号所有的验证码信息
写在最后
至此,文章就分享完毕了。
本章demo仓库地址:gitee.com/xiangyang66…
个人介绍:本人还是一名高三毕业生(18),自学前端已有快1年了,9月份还要回西安上大学(大专)。目前在广州实习(以实习2个月)。如果你有什么技术上不懂的地方可以加我微信:zxy7471082
一起探讨(当然我也很菜?)。
个人博客:xiangyang666.gitee.io/vitepress/
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注?