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()
      })
    })
  }
}

 

 

posted @ 2019-11-22 23:01  菜鸟小何  阅读(4728)  评论(0编辑  收藏  举报