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