后端接口
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()
})
})
}
}
长风破浪会有时,直挂云帆济沧海