❤️砥砺前行,不负余光,永远在路上❤️
❤️砥砺前行,不负余光,永远在路上❤️
有帮助的话各位哥哥可以点个关注收藏哦
后端生成六位随机验证码,存入redis(key:邮箱号,value:验证码),校验的接口/code/login
通过redis 查询code是否存在,如果满足条件,可以自己加一些登录注册的业务之后在返回需要的值。
有帮助的话各位哥哥可以点个关注收藏哦
代码如下(示例):
const redis = require('redis');
const client = redis.createClient(); //默认没有密码 127.0.0.1 端口也是默认
// 如果是连接远程的话
// redis[s]://[[username][:password]@][host][:port][/db-number]:
// const client = createClient({
// url: 'redis://alice:[email protected]:6380'
// });
client.on('error', (err) =>
console.log('Redis Client Error', err
));
client.on('connect', () => {
console.log('redis connect success');
})
client.connect();
module.exports = client;
代码如下(示例):
//nodemailer.js
const nodemailer = require('nodemailer');
const { mailConfig } = require('../config/index')
const { user, pass } = mailConfig
let transporter = nodemailer.createTransport({
//node_modules/nodemailer/lib/well-known/services.json 查看相关的配置,如果使用qq邮箱,就查看qq邮箱的相关配置
service: 'qq', //类型qq邮箱
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user,
pass
}
});
//pass 不是邮箱账户的密码而是stmp的授权码(必须是相应邮箱的stmp授权码)
//邮箱---设置--账户--POP3/SMTP服务---开启---获取stmp授权码
module.exports = function (email, code) {
// const {username,password,email} = user
let mailOptions = {
from: '' , // 发送方
to: email, //接收者邮箱,多个邮箱用逗号间隔
subject: `欢迎登录,你的验证码${code}`, // 标题
html: `<head><base target="_blank" /><style type="text/css">::-webkit-scrollbar{ display: none; }</style><style id="cloudAttachStyle" type="text/css">#divNeteaseBigAttach, #divNeteaseBigAttach_bak{display:none;}</style><style id="blockquoteStyle" type="text/css">blockquote{display:none;}</style><style type="text/css"> body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px} td, input, button, select, body{font-family:Helvetica, \'Microsoft Yahei\', verdana} pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;width:95%} th,td{font-family:arial,verdana,sans-serif;line-height:1.666} img{ border:0} header,footer,section,aside,article,nav,hgroup,figure,figcaption{display:block} blockquote{margin-right:0px}</style></head><body tabindex="0" role="listitem"><table width="700" border="0" align="center" cellspacing="0" style="width:700px;"><tbody><tr><td><div style="width:700px;margin:0 auto;border-bottom:1px solid #ccc;margin-bottom:30px;"><table border="0" cellpadding="0" cellspacing="0" width="700" height="39" style="font:12px Tahoma, Arial, 宋体;"><tbody><tr><td width="210"></td></tr></tbody></table></div><div style="width:680px;padding:0 10px;margin:0 auto;"><div style="line-height:1.5;font-size:14px;margin-bottom:25px;color:#4d4d4d;"><strong style="display:block;margin-bottom:15px;">尊敬的用户:<span style="color:#f60;font-size: 16px;"></span>您好!</strong><strong style="display:block;margin-bottom:15px;">您正在进行<span style="color: red">用户登录</span>操作,请在验证码输入框中输入:<span style="color:#f60;font-size: 24px">${code}</span>,以完成操作。</strong></div> <div style="margin-bottom:30px;"><small style="display:block;margin-bottom:20px;font-size:12px;"><p style="color:#747474;"> 注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全<br>(工作人员不会向你索取此验证码,请勿泄漏!)</p></small></div></div><div style="width:700px;margin:0 auto;"><div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;"><p>此为系统邮件,请勿回复<br>请保管好您的邮箱,避免账号被他人盗用</p><p>网络科技团队</p></div></div></td></tr></tbody></table></body>`
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('mail sent:', info.response);
});
};
router中引入redis 和 nodemailer 部分
const client = require('../utils/redis');//redis使用
const nodemailer = require('../utils/nodemailer');//发送邮件
//成功返回参数
function success (res, total = null) {
if (total) {
return {
code: 200,
data: res,
msg: '成功',
total
}
} else {
return {
code: 200,
data: res,
msg: '成功'
}
}
}
//失败参数
function fail (msg) {
return {
code: 500,
msg
}
}
// 生成六位随机验证码
function createCode () {
return parseInt(Math.random() * 1000000)
// return 'xxxxxx'.replace(/[xy]/g, function (c) {
// var r = (Math.random() * 16) | 0
// var v = c == 'x' ? r : (r & 0x3) | 0x8
// return v.toString(16)
// })
}
//发送验证码邮件
router.post('/send/email', function (req, response, next) {
let code = createCode() //随机生成验证码
const mail = req.body.mail//请求携带的邮件
client.set(mail, code).then(res => { //存入redis
//设置成功发送邮件
nodemailer(mail, code)
response.send(success())
})
client.expire(mail, 60);//设置过期时间 60s 前端六十秒可以重新获取
});
//通过验证码登录
router.post('/code/login', function (req, response, next) {
/* 这里 用户名就是 邮件 密码就是code */
const { mail, code} = req.body
client.get(mail).then(res => { //从redis查询数据
if (code== res) {
console.log('验证成功')
//do something
// ...
response.send(success({
user: mail,
}))
} else {
console.log('验证失败')
response.send(fail('验证失败'))
}
})
});
const validateUsername = (rule, value, callback) => {
let reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/;
// !reg.test(value)
if (!reg.test(value)) {
callback(new Error('请输入正确邮箱号码'))
} else {
callback()
}
}
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on"
label-position="left">
<div class="title-container">
<h3 class="title">
{{ $t('login.title') }}
</h3>
<!-- <lang-select class="set-language" /> -->
</div>
<el-form-item prop="username">
<el-row style="padding-right:5px">
<el-col :span="18">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input ref="username" v-model="loginForm.username" placeholder="请输入邮箱" name="username" type="text"
tabindex="1" autocomplete="on" />
</el-col>
<el-col :span="6" style="margin-top:7px">
<el-button type="primary" :disabled="disable" :class="{ codeGeting:isGeting }" @click="getVerCode">
{{getCode}}</el-button>
</el-col>
</el-row>
</el-form-item>
<el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
<el-form-item>
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input :key="passwordType" ref="password" v-model="loginForm.password" placeholder="请输入六位验证码"
name="password" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false"
@keyup.enter.native="handleLogin" />
<!-- <span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span> -->
</el-form-item>
</el-tooltip>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin">
{{ $t('login.logIn') }}
</el-button>
<div style="position:relative">
<div class="other-login">
<div class="title">推荐使用其他方式登录</div>
<img src="@/assets/mp.png" class="wx-logo" title="小程序登录" alt="小程序登录" @click="otherLogin">
</div>
</div>
</el-form>
<el-dialog title="微信扫码登录" :visible.sync="showDialog" align="center" width="30%" @close="wxLoginClose">
<div>
<el-image :src="qrUrl" alt="小程序码" height="10%" />
<div style="margin:15px 0">请使用微信扫描小程序码登录{{ bindTimeout ? '(已超时)' : '' }}</div>
<!-- (后期考虑是否启用选择性授权) -->
<!-- <div>
启用授权获取用户信息:
<el-switch v-model="auth" active-color="#13ce66" inactive-color="#ff4949" @change="authChange" />
</div> -->
</div>
</el-dialog>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
import { getCode, getToken, getUUid, sendMail, codeLogin } from '@/api/user'
import { GlobalGetUuidShort } from '@/utils/index'
export default {
name: 'Login',
components: {},
data () {
const validateUsername = (rule, value, callback) => {
let reg = /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,5}$/;
// !reg.test(value)
if (!reg.test(value)) {
callback(new Error('请输入正确邮箱号码'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码不能少于6位'))
} else {
callback()
}
}
return {
qrUrl: '',
auth: true,
bindTimeout: false,
timer: null, // 定时器
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
},
passwordType: 'password',
capsTooltip: false,
loading: false,
showDialog: false,
redirect: undefined,
otherQuery: {},
getCode: '获取验证码',
isGeting: false,
count: 60,
disable: false
}
},
watch: {
$route: {
handler: function (route) {
const query = route.query
if (query) {
this.redirect = query.redirect
this.otherQuery = this.getOtherQuery(query)
}
},
immediate: true
}
},
created () {
// window.addEventListener('storage', this.afterQRScan)
},
mounted () {
if (this.loginForm.username === '') {
this.$refs.username.focus()
} else if (this.loginForm.password === '') {
this.$refs.password.focus()
}
},
destroyed () {
// window.removeEventListener('storage', this.afterQRScan)
},
methods: {
//获取验证码
getVerCode () {
if (this.loginForm.username) {
sendMail(this.loginForm).then(res => {
console.log(res, 'res')
})
var countDown = setInterval(() => {
if (this.count < 1) {
this.isGeting = false
this.disable = false
this.getCode = '获取验证码'
this.count = 60
clearInterval(countDown)
} else {
this.isGeting = true
this.disable = true
this.getCode = this.count-- + '秒后重发'
}
}, 1000)
} else {
this.$notify.error('请必须输入邮箱号码')
}
},
//关闭弹窗清除定时器
wxLoginClose () {
this.timer && clearTimeout(this.timer)
this.bindTimeout = false
},
// 点击其他方式登录
otherLogin () {
getToken().then(r => {
this.showDialog = true
this.getQrUrl()
})
},
changeQr () {
if (this.bindTimeout) {
this.bindTimeout = false
this.getQrUrl()
} else {
this.$notify.warning('请当前二维码过期之后重新获取')
}
},
getQrUrl () {
let uuid = GlobalGetUuidShort(), counter = 1
this.qrUrl = `/api/getCode?useAuth=1&uuid=${uuid}`
this.timer && clearTimeout(this.timer)// 清除定时器重新开启
this.timer = setInterval(() => {
getUUid({ uuid }).then((res) => {// 获取openid
counter++
if (counter === 31) { //超时
clearTimeout(this.timer)
this.bindTimeout = true
}
if (res.data.openid !== '') {
clearTimeout(this.timer)
this.showDialog = false
this.$store.dispatch('user/login', res.data).then(() => {// 登录跳转 (扫码登录)
this.$router.push({ path: this.redirect || '/dashboard', query: this.otherQuery })
}).catch(err => {
console.log(err, 'err')
})
}
}).catch((err) => {
clearTimeout(this.timer)
})
}, 2000)
},
// 修改选项重新获取qr
// authChange (val) {
// console.log(val)
// this.$nextTick(function () {
// this.qrUrl = `/api/getCode?uuid=${this.uuid}` + '&useAuth=' + (val ? 1 : 0)
// })
// },
checkCapslock (e) {
const { key } = e
this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
},
showPwd () {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin () {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
// this.$message.warning('开发中,目前仅支持扫码登录')
codeLogin(this.loginForm).then(res => {
console.log(res, 'res')
this.loading = false
this.$store.dispatch('user/login', res.data)
.then(() => {
console.log(55, '55')
this.$router.push({ path: this.redirect || '/dashboard', query: this.otherQuery })
})
.catch(() => {
// this.loading = false
})
// this.$router.push({ path: this.redirect || '/dashboard', query: this.otherQuery })
})
// this.loading = true
// this.$store.dispatch('user/login', this.loginForm)
// .then(() => {
// this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
// this.loading = false
// })
// .catch(() => {
// this.loading = false
// })
} else {
console.log('error submit!!')
return false
}
})
},
getOtherQuery (query) {
return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
}
// afterQRScan() {
// if (e.key === 'x-admin-oauth-code') {
// const code = getQueryObject(e.newValue)
// const codeMap = {
// wechat: 'code',
// tencent: 'code'
// }
// const type = codeMap[this.auth_type]
// const codeName = code[type]
// if (codeName) {
// this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
// this.$router.push({ path: this.redirect || '/' })
// })
// } else {
// alert('第三方登录失败')
// }
// }
// }
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.codeGeting {
background: #cdcdcd;
border-color: #cdcdcd;
}
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.mask {
opacity: 0.2;
}
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
.other-login {
margin-top: 30px;
text-align: center;
.title {
color: #dcdfe6;
position: relative;
font-size: 14px;
&:before {
position: absolute;
left: 0;
top: 50%;
content: '';
width: 100px;
height: 1px;
background: #dcdfe6;
display: inline-block;
}
&:after {
position: absolute;
right: 0;
top: 50%;
content: '';
width: 100px;
height: 1px;
background: #dcdfe6;
display: inline-block;
}
}
.wx-logo {
margin-top: 20px;
width: 36px;
height: 36px;
border-radius: 100%;
cursor: pointer;
}
}
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
.set-language {
color: #fff;
position: absolute;
top: 3px;
font-size: 18px;
right: 0px;
cursor: pointer;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
.thirdparty-button {
position: absolute;
right: 0;
bottom: 6px;
}
@media only screen and (max-width: 470px) {
.thirdparty-button {
display: none;
}
}
}
</style>
贴的代码应该都是完整的,如果哪里有问题的话可以留言哦