前端根目录:/web
后端根目录:/api_server
安装依赖:
npm install express mysql nodemailer randomstring dotenv
其中,nodemailer是用来发送验证码的node.js依赖库, randomstring是用于生成随机字符串的 Node.js 库(即在这里生成验证码),dotenv用于在.env
文件中配置敏感信息,避免直接暴露在代码中
在根目录/api_server
下创建/.env
文件
EMAIL_SERVICE=smtp.126.com // 使用网易邮箱发送验证码
EMAIL_PORT=465 # 使用SSL的端口
EMAIL_SECURE=true # 使用SSL
[email protected] // 用你的邮箱码代替
EMAIL_PASS=YOURPASSWORD // 客户端授权密码
DB_HOST=localhost
DB_USER=YOURUSERNAME // 用你的数据库用户名代替
DB_PASSWORD=YOURUSERPASSWORD // 用你的数据库密码代替
DB_NAME=YOURDBNAME // 用你的数据库名代替
在后端入口文件中导入
require('dotenv').config()
在你的项目数据库中添加列:email(存放邮箱号)、 code(存放验证码)、expires_at(存放验证码过期时间)
/api_server/router/user.js
// 登录路由
router.post('/login', userHandler.login)
// 发送验证码路由
router.post('/send-verification-code', userHandler.sendVerificationCode)
/api_server/router_handler/user.js
// 创建可重用的 transporter 对象用于发送邮件
const transporter = nodemailer.createTransport({
host: 'smtp.126.com', // SMTP服务器地址
port: 465, // 端口号
secure: true, // 使用SSL
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
})
// 辅助函数:生成并存储验证码
const generateAndStoreVerificationCode = async (email) => {
// 生成验证码
const code = randomstring.generate({
length: 6, //验证码长度
charset: 'numeric' //可以选择 'alphabetic', 'numeric', 'hexadecimal', 'binary' 或者自定义字符集
})
// 存储验证码
// const createdAt = new Date()
const expiresAt = new Date(Date.now() + 5 * 60 * 1000) // 验证码有效期为 5 分钟
try {
const sqlStr = 'UPDATE user SET code=?, expires_at=? WHERE email=?'
await db.query(sqlStr, [code, expiresAt, email])
return { code, expiresAt }
} catch (error) {
console.error('存储验证码失败:', error)
throw error
}
}
const util = require('util')
const query = util.promisify(db.query).bind(db)
// 辅助函数:验证验证码
const verifyCode = async (email, inputCode) => {
try {
// 定义查找该用户验证码的sql语句
const sqlStr = 'select * from user where email=? and expires_at > NOW()'
// 执行sql语句
const results = await query(sqlStr, [email])
if (results.length === 0) {
return false
}
// 拿到用户数据库中存储的验证码
const storeCode = results[0].code
// 比较验证
if (storeCode === inputCode) {
// 验证成功后删除验证码
const sqlStr_delete_code = 'UPDATE user SET code = null WHERE email=?'
db.query(sqlStr_delete_code, [email])
return true
}
return false
} catch (error) {
console.error('验证验证码失败:', error)
throw error
}
}
// 邮箱登录时发送验证码的处理函数
exports.sendVerificationCode = async (req, res) => {
// 接收邮箱号
const { email } = req.body
if (!email) {
return res.send({ status: 1, message: '请提供邮箱地址' })
}
try {
const { code, expiresAt } = await generateAndStoreVerificationCode(email)
// 构建邮件内容
const mailOptions = {
from: process.env.EMAIL_USER,
to: email,
subject: `您的验证码 ${code}`,
text: `您好,您的验证码是:${code}。请不要告诉他人。验证码将在 ${expiresAt.toISOString()} 前有效。`,
html: `你的验证码是 ${code},请勿告诉他人。` // HTML 内容
// 发送邮件
await transporter.sendMail(mailOptions)
res.send({status: 0, message: '验证码已发送,请查收邮件' })
} catch (error) {
res.send({status: 1, message: '发送验证码时出错' })
}
}
// 用户登录的处理函数
exports.login = (req, res) => {
// 定义邮箱密码登陆方式的sql语句
const sqlStr_email = 'select * from user where email=?'
// 执行sql语句,根据邮箱查询用户信息
db.query(sqlStr_email, [userInfo.email], async (err, results) => {
// 判断是否查询成功
if (err) {
return res.send({ status: 1, message: err })
}
// 执行sql语句成功,但是获取的条数不等于1
if (results.length === 0) {
return res.send({ status: 1, message: '该用户不存在' })
} else if (results.length !== 1) {
return res.send({ status: 1, message: '登录失败' })
}
// TODO:判断验证码是否正确
const isTrue = await verifyCode(userInfo.email, userInfo.code)
// 验证码错误
console.log('isTrue:', isTrue)
if (!isTrue) {
return res.send({ status: 1, message: '验证码错误或已过期'})
}
})
····
}
【注意】:验证验证码函数使用时需要先导入util 模块,将 Node.js 中传统的基于回调的 API 转换为返回 Promise 的形式,否则无法直接通过const results = await db.query(sqlStr, [email])
拿到查找的返回值
/web/api/user.js
// 登录接口
export const useLoginService = ({ email, code }) => {
return request.post(...', { email, code })
}
// 发送验证码
export const useSendVerificationCode = ({ email }) => {
return request.post('...', { email })
}
/web/views/login
{{ defaultSecond === second ? '发送验证码' : `${second}秒后重新发送` }}
登录