react中登录注册 使用验证码验证
后端接口
var express = require('express'); var router = express.Router(); var User = require('./../sql/collection/users'); var sql = require('./../sql'); var utils = require('./../utils') var uuid = require('node-uuid'); var bcrypt = require('bcryptjs'); var jwt = require('jsonwebtoken'); var salt = bcrypt.genSaltSync(10); // 加密级别 var code = require('./../utils/code'); // 快速登陆 router.post('/quicklogin', (req, res, next) => { let { tel } = req.body; sql.find(User, { tel }, { _id: 0 }).then(data => { if (data.length === 0) { res.send({ code: '10086', msg: '该用户未注册' }) } else { let userid = data[0].userid let username = data[0].username let token = jwt.sign({ userid }, 'daxunxun', { expiresIn: 60*60*24*7 }) res.send({ code: '10010', message: '登陆成功', token: token, userid, username }) } }) }) // 快速登陆(验证码) router.post('/quick', (req, res, next) => { let { tel } = req.body; sql.find(User, { tel }, { _id: 0}).then(data => { if (data.length !== 0) { let str = ''; for (var i=0; i<5; i++) { str += Math.round(Math.random()*9) } let num = Math.round(str) // console.log(num) code.sendCode(tel, num).then(data => { if (data === 1) { // console.log('验证码发送成功') res.send({ code: '200', msg: '发送验证码成功', data: num }) } }).catch(() => { // console.log('验证码发送失败') res.send({ code: '201', msg: '发送验证码失败' }) }) } else { res.send({ code: '202', msg: '该用户未注册' }) } }) }) // 发送手机验证码 router.post('/check', (req, res, next) => { // 生成5位随机验证码 let { tel } = req.body; let str = ''; for (var i=0; i<5; i++) { str += Math.round(Math.random()*9) } let num = Math.round(str) console.log(num) code.sendCode(tel, num).then(data => { if (data === 1) { // console.log('验证码发送成功') res.send({ code: '200', msg: '发送验证码成功', data: num }) } }).catch(() => { // console.log('验证码发送失败') res.send({ code: '201', msg: '发送验证码失败' }) }) }) /* GET users listing. */ router.get('/', function(req, res, next) { res.send('respond with a resource'); }); // 实现注册接口 -- post提交方式 router.post('/register', (req, res, next) => { let { username, password, tel } = req.body; sql.find(User, { tel }, { _id: 0 }).then(data => { if (data.length === 0) { let userid = 'users_' + uuid.v1(); password = bcrypt.hashSync(password, salt) sql.insert(User, { userid, username, password, tel}).then(() => { res.send(utils.registersuccess) }) } else { res.send(utils.registered) } }) }) // 实现登陆功能 router.post('/login', (req, res, next) => { let { tel, password } = req.body; sql.find(User, { tel }, { _id: 0 }).then(data => { if (data.length === 0) { res.send(utils.unregister) } else { let pwd = data[0].password; var flag = bcrypt.compareSync(password, pwd) if (flag) { let userid = data[0].userid let username = data[0].username let token = jwt.sign({ userid }, 'daxunxun', { expiresIn: 60*60*24*7// 授权时效7天 }) res.send({ code: '10010', message: '登陆成功', token: token, userid, username }) } else { res.send({ code: '10100', message: '密码错误' }) } } }) }) module.exports = router;
前端渲染
账号密码登录:
import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import { login } from '@/utils/api'; import { Toast } from 'antd-mobile'; import { withRouter } from 'react-router-dom'; import './style.scss'; class Com extends Component { constructor (props) { super (props); this.state = { tel: '', password: '' } } loginBtn () { let tel = this.state.tel; let password = this.state.password; if (tel === '' || password === '') { Toast.fail('请先输入用户名和密码', 2); } else { login(tel, password).then(data => { console.log(data) if (data.code === '10010') { localStorage.setItem('token', data.token) localStorage.setItem('username', data.username) localStorage.setItem('userid', data.userid) localStorage.setItem('isLogin', 1) Toast.success('登陆成功', 1); this.props.history.push('/user') } else if (data.code === '10086') { Toast.offline('该用户未注册,请先注册', 2); } else if (data.code === '10100') { Toast.fail('密码错误', 2); } }) } } // 手机号 loginTel (event) { let val = event.currentTarget.value; this.setState({ tel: val }) } // 密码 passwordLogin (event) { let val = event.currentTarget.value; this.setState({ password: val }) } render () { return ( <div className="box"> <header className="header loginHeader"> <div className="imgbox"> <img src="//img.58cdn.com.cn/jxedt/logos/logo3.gif" alt="" /> </div> </header> <div className="content loginColor"> <h2 className="title">登陆</h2> <div className="loginFrom"> <p> <i className="iconfont icon-shoujihao"></i> <input type="text" placeholder="请输入您的手机号" onBlur={ this.loginTel.bind(this) }/> </p> <p> <i className="iconfont icon-mima"></i> <input type="password" placeholder="请输入您的密码" onBlur={ this.passwordLogin.bind(this) }/> </p> <div className="loginBtn" onClick={ this.loginBtn.bind(this) }>登陆</div> <Link className="tabQuick" to="/o/quicklogin">切换至快速登陆</Link> </div> <div className="noUser">还没有注册?,点击这里去<Link to="/o/register">注册</Link></div> </div> </div> ) } } export default withRouter(Com);
样式
@import '@/lib/reset.scss';
.box {
@include rect(100%, 100%);
@include flexbox();
flex-direction: column;
.loginHeader {
@include rect(100%, 0.5rem);
@include background-color(#54B143);
.imgbox {
@include rect(auto, 100%);
padding: 0.12rem 0 0 0.12rem;
box-sizing: border-box;
img {
display: inline-block;
@include rect(1rem, 0.25rem)
}
}
}
.loginColor {
@include flex();
@include background-color(#fff);
}
.content {
@include flex();
width: 100%;
// height: 100%;
.title {
padding-top: 0.2rem;
@include rect(100%, 0.3rem);
text-align: center;
line-height: 0.3rem;
font-size: 16px;
color: #666;
margin-bottom: 0.2rem;
}
.loginFrom {
width: 100%;
padding: 0 0.2rem;
p {
@include rect(100%, 0.6rem);
@include flexbox();
@include align-items();
// padding-left: 0.2rem;
border-bottom: 1px solid #999;
i {
font-size: 26px;
margin-right: 0.07rem;
}
input {
display: inline-block;
padding-left: 0.1rem;
@include rect(60%, 0.4rem);
border: none;
background: #fff;
}
}
}
.loginBtn {
margin: 0.3rem 0 0 0.34rem;
@include rect(80%, 0.4rem);
@include background-color(#54B143);
border-radius: 20px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 0.4rem;
}
.tabQuick {
@include rect(100%, 0.3rem);
line-height: 0.3rem;
text-align: center;
margin-top: 0.15rem;
color: limegreen;
display: block;
}
}
.noUser {
@include rect(100%, 0.3rem);
line-height: 0.3rem;
text-align: center;
margin-top: 0.15rem;
}
}
验证码登录
import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import { quick, quickLogin } from '@/utils/api'; import { Toast } from 'antd-mobile'; import cookie from 'react-cookies'; import { withRouter } from 'react-router-dom' import './style.scss'; class Com extends Component { constructor (props) { super (props); this.state = { tel: '', checkNum: '', num: '', flag: false, text: '获取验证码', _dura: 0 } } componentDidMount () { if (cookie.load('code')) { this.sendCode(); } } loginBtn () { } // 手机号 quickTel (event) { let val = event.currentTarget.value; this.setState({ tel: val }) } // 改变验证码状态 check (event) { let val = event.currentTarget.value; console.log(val) this.setState({ num: val }) } // 判断cookie中是否存在倒计时我 sendCode () { // console.log(111) this.setState({ flag: true }) let _dura = cookie.load('code'); let timer = setInterval(() => { // console.log(this) _dura--; let text = '重新获取' + '(' + _dura + ')'; this.setState({ _dura, text }) cookie.save('code', _dura, _dura) if (_dura === 0) { text = '点击获取验证码'; this.setState({ text, flag: false }) clearInterval(timer); timer = null; cookie.remove('code'); } },1000) } // 发送登陆验证码 getQuickCheck () { let tel = this.state.tel; if (tel.length === 0) { Toast.fail('请先输入您的手机号', 1); } else { quick(tel).then(data => { if (data.code === '200') { cookie.save('code', 60, 60) Toast.success('验证码发送成功,请注意查收', 2); this.setState({ checkNum: data.data, flag: true }) this.sendCode(); } else if (data.code === '201') { Toast.fail('验证码发送失败,请不要频繁点击', 2); } else { Toast.offline('该用户还没有注册,请先注册', 2); } }) } } // 快速登陆 quickLogin () { let tel = this.state.tel; let num = this.state.num; let checkNum = this.state.checkNum; if (tel.length === 0 || num.length === 0) { Toast.fail('请先输入手机号和验证码', 2); } else { num = Math.round(num) console.log(num) console.log(checkNum) if ( num === checkNum ) { quickLogin(tel).then(data => { if (data.code === '10010') { Toast.success('登陆成功', 2); localStorage.setItem('token', data.token); localStorage.setItem('userid', data.userid); localStorage.setItem('username', data.username); localStorage.setItem('isLogin', 1) this.props.history.push('/user') } }) console.log(1111) } else { Toast.fail('验证码不正确,请重新输入', 2); } } } render () { return ( <div className="box"> <header className="header loginHeader"> <div className="imgbox"> <img src="//img.58cdn.com.cn/jxedt/logos/logo3.gif" alt="" /> </div> </header> <div className="content loginColor"> <h2 className="title">快速登陆</h2> <div className="loginFrom"> <p> <i className="iconfont icon-shoujihao"></i> <input type="text" placeholder="请输入您的手机号" onBlur={ this.quickTel.bind(this) }/> </p> <p> <i className="iconfont icon-yanzhengma"></i> <input type="text" placeholder="请输入验证码" onBlur={ this.check.bind(this) }/> <button className="checkBtn" disabled={ this.state.flag } onClick={ this.getQuickCheck.bind(this) }>{ this.state.text }</button> </p> <div className="loginBtn" onClick={ this.quickLogin.bind(this) }>登陆</div> <Link className="tabLogin" to="/o/login">切换至密码登陆</Link> </div> <div className="noUser">还没有注册?,点击这里去<Link to="/o/register">注册</Link></div> </div> </div> ) } } export default withRouter(Com)
style.css
@import '@/lib/reset.scss';
.box {
@include rect(100%, 100%);
@include flexbox();
flex-direction: column;
.loginHeader {
@include rect(100%, 0.5rem);
@include background-color(#54B143);
.imgbox {
@include rect(auto, 100%);
padding: 0.12rem 0 0 0.12rem;
box-sizing: border-box;
img {
display: inline-block;
@include rect(1rem, 0.25rem)
}
}
}
.loginColor {
@include flex();
@include background-color(#fff);
}
.content {
@include flex();
width: 100%;
// height: 100%;
.title {
padding-top: 0.2rem;
@include rect(100%, 0.3rem);
text-align: center;
line-height: 0.3rem;
font-size: 16px;
color: #666;
margin-bottom: 0.2rem;
}
.loginFrom {
width: 100%;
padding: 0 0.2rem;
p {
@include rect(100%, 0.6rem);
@include flexbox();
@include align-items();
// padding-left: 0.2rem;
border-bottom: 1px solid #999;
i {
font-size: 26px;
margin-right: 0.07rem;
}
input {
display: inline-block;
padding-left: 0.1rem;
@include rect(60%, 0.4rem);
border: none;
background: #fff;
}
.checkBtn {
// display: block;
border: none;
@include rect(1.2rem, 0.3rem);
// background: #fff;
border-radius: 5px;
color: #333;
}
}
}
.loginBtn {
margin: 0.3rem 0 0 0.34rem;
@include rect(80%, 0.4rem);
@include background-color(#54B143);
border-radius: 20px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 0.4rem;
}
.tabLogin {
@include rect(100%, 0.3rem);
line-height: 0.3rem;
text-align: center;
margin-top: 0.15rem;
color: limegreen;
display: block;
}
}
.noUser {
@include rect(100%, 0.3rem);
line-height: 0.3rem;
text-align: center;
margin-top: 0.15rem;
}
}
注册
import React, { Component } from 'react'; import { getCheck, register } from '@/utils/api'; import './style.scss' import { Toast } from 'antd-mobile'; import { Link, withRouter } from 'react-router-dom'; import cookie from 'react-cookies'; class Com extends Component { constructor (props) { super (props); this.state = { username: '', usernameTip: '', tel: '', telTip: '', password: '', passwordTip: '', codeNum: 0, check: '', checkTip: '', _dura: 0, text: '点击获取验证码', flag: false } } componentDidMount () { if (cookie.load('sendCode')) { this.sendCode(); } } // 判断cookie中是否存在倒计时 sendCode () { console.log(111) this.setState({ flag: true }) let _dura = cookie.load('sendCode'); let timer = setInterval(() => { // console.log(this) _dura--; let text = '重新获取' + '(' + _dura + ')'; this.setState({ _dura, text }) cookie.save('sendCode', _dura, _dura) if (_dura === 0) { text = '点击获取验证码'; this.setState({ text, flag: false }) clearInterval(timer); timer = null; cookie.remove('sendCode'); } },1000) } // 验证用户名格式 username (event) { let val = event.currentTarget.value; let tip = ''; tip = val === '' ? '' : val.length < 2 ? '用户名要为2位以上的字符哦' : ''; this.setState({ username: val, usernameTip: tip }) } // 验证手机号格式 tel (event) { let val = event.currentTarget.value; let tip = ''; if ( val.length === 0 ) { tip = '' }else if ( !(/^1[34578]\d{9}$/.test(val)) ) { tip = '请输入正确的手机号' } else { tip = '' } this.setState({ tel: val, telTip: tip }) } // 验证密码 password (event) { let val = event.currentTarget.value; let tip = ''; if ( val.length === 0) { tip = '' } else if (!(/^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){5,15}$/.test(val)) ) { tip = '密码必须以字母开头,6-16位数字、字母、下划线和.' } else { tip = '' } this.setState({ password: val, passwordTip: tip }) } // 获取手机验证码 getCheck () { let tel = this.state.tel // console.log(tel) if (tel.length !== 0) { getCheck(tel).then(data => { // 设置cookie保存时间 cookie.save('sendCode', 60, 60); // console.log(data) this.setState({ codeNum: data.data.data, // 保存随机验证码,后期用来验证 flag: true }) this.sendCode(); }) } else { Toast.fail('请先输入手机号', 1); } } // 填写验证码,保存状态 check (event) { const val = event.currentTarget.value; // val = Math.round(val) this.setState({ check: val }) } // 注册按钮,点击验证 register () { let usernameTip = this.state.usernameTip; let telTip = this.state.telTip; let passwordTip = this.state.passwordTip; let username = this.state.username; let tel = this.state.tel; let password = this.state.password; let codeNum = this.state.codeNum; // 如果用户名,手机号,密码格式都正确 if (usernameTip === '' && telTip === '' && passwordTip === '') { let val = this.state.check; val = Math.round(val) if ( val === codeNum ) { // console.log('success') register(tel, username, password).then(data => { if (data.code === '10000') { // console.log('该用户已注册,请直接登陆') Toast.info('该用户已注册,请直接登陆', 1); } else { Toast.success('恭喜您注册成功', 1); } }) } else { // console.log('验证码不正确') Toast.fail('验证码不正确', 1); } } else { // console.log('请输入正确格式的用户名,手机号和密码') Toast.fail('请输入正确格式的用户名,手机号和密码', 1); } } render() { return ( <div className="box"> <header className="header registerHeader"> <div className="imgbox"> <img src="//img.58cdn.com.cn/jxedt/logos/logo3.gif" alt="" /> </div> </header> <div className="content registerColor"> <h2 className="title">注册</h2> <div className="registerFrom"> <p> <i className="iconfont icon-yonghu"></i><input type="text" placeholder="请输入用户名" onChange={ this.username.bind(this) }/> </p> <div className="tip">{ this.state.usernameTip }</div> <p> <i className="iconfont icon-shoujihao"></i><input type="text" placeholder="请输入手机号" onChange={ this.tel.bind(this) }/> </p> <div className="tip">{ this.state.telTip }</div> <p> <i className="iconfont icon-mima"></i><input type="password" placeholder="请输入密码" onChange={ this.password.bind(this) }/> </p> <div className="tip">{ this.state.passwordTip }</div> <p> <i className="iconfont icon-yanzhengma"></i> <input type="text" placeholder="请输入验证码" onChange={ this.check.bind(this) }/> <button className="checkBtn" disabled={ this.state.flag } onClick={ this.getCheck.bind(this) }>{ this.state.text }</button> </p> <div className="tip">{ this.state.checkTip }</div> <div className="registerBtn" onClick={ this.register.bind(this) }>确认注册</div> </div> <div className="toLogin">如您已有账号,请直接<Link to="/o/login">登陆</Link></div> </div> </div> ) } } export default withRouter(Com)
style.css
@import '@/lib/reset.scss';
.box {
@include rect(100%, 100%);
@include flexbox();
flex-direction: column;
.registerHeader {
@include rect(100%, 0.5rem);
@include background-color(#54B143);
.imgbox {
@include rect(auto, 100%);
padding: 0.12rem 0 0 0.12rem;
box-sizing: border-box;
img {
display: inline-block;
@include rect(1rem, 0.25rem)
}
}
}
.registerColor {
@include flex();
@include background-color(#fff);
}
.content {
@include flex();
width: 100%;
// height: 100%;
.title {
padding-top: 0.2rem;
@include rect(100%, 0.3rem);
text-align: center;
line-height: 0.3rem;
font-size: 16px;
color: #666;
margin-bottom: 0.2rem;
}
.registerFrom {
width: 100%;
padding: 0 0.2rem;
.tip {
@include rect(100%, 0.2rem);
text-align: center;
// line-height: 0.2rem;
color: lightsalmon;
}
p {
@include rect(100%, 0.6rem);
@include flexbox();
@include align-items();
// padding-left: 0.2rem;
border-bottom: 1px solid #999;
i {
font-size: 26px;
margin-right: 0.07rem;
}
input {
display: inline-block;
padding-left: 0.1rem;
@include rect(60%, 0.4rem);
border: none;
background: #fff;
}
.checkBtn {
display: inline-block;
border: none;
@include rect(1.2rem, 0.4rem);
color: #333;
border-radius: 8px;
text-align: center;
line-height: 0.4rem;
}
}
}
.registerBtn {
margin: 0.3rem 0 0 0.34rem;
@include rect(80%, 0.4rem);
@include background-color(#54B143);
border-radius: 20px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 0.4rem;
}
}
.toLogin {
@include rect(100%, 0.3rem);
line-height: 0.3rem;
text-align: center;
margin-top: 0.15rem;
}
}
短信验证码工具
// 发送短信验证码
const Core = require('@alicloud/pop-core');
var client = new Core({
accessKeyId: 'LTAIZQoVVoPuBjU9', // 自己的id
accessKeySecret: 'GfJuI2dLsCQh7Q56TmFxPTniXjkVnB', // 自己的secret
endpoint: 'https://dysmsapi.aliyuncs.com',
apiVersion: '2017-05-25'
});
module.exports = {
sendCode (tel, code) {
var params = {
"RegionId": "cn-hangzhou",
"PhoneNumbers": tel,
"SignName": "吴勋勋", // 自己的签名
"TemplateCode": "SMS_111785721", // 自己的模板代码
"TemplateParam": "{code: " + code + "}"
}
var requestOption = {
method: 'POST'
};
return new Promise((resolve, reject) => {
client.request('SendSms', params, requestOption).then((result) => {
console.log(JSON.stringify(result));
resolve(1)
}, (ex) => {
console.log(ex);
reject()
})
})
}
}
长风破浪会有时,直挂云帆济沧海