• 【招聘App】—— React/Nodejs/MongoDB全栈项目:登录注册


    前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅。最终成果github地址:https://github.com/66Web/react-antd-zhaoping,欢迎star。


     一、登录注册

         页面文件结构

    • 基础组件放在Component文件夹下面
    • 页面组件放在Container文件夹下面
    • 页面入口处获取用户信息,决定跳转到哪个页面

         web开发模式

    • 整体前后端交互通过JSON实现
    • 基于cookie用户验证
    1. express使用cookie,需要依赖cookie-parser
      npm install cookie-parser --save
    2. cookie类似于一张身份卡,登录后服务器端返回,带着cookie即可以访问受限资源
    3. cookie的管理浏览器会自动处理
    • 开发流程

        

    二、页面实现

    • component->logo目录下:创建logo.js基础组件,设置顶部Logo位置与样式
    • container->login目录下:创建login.js业务组件
    1. 应用antd-mobile的组件实现登录页面
      import {List, InputItem, WingBlank, WhiteSpace, Button } from 'antd-mobile'
    2. 跳转路由:this.props.history.push('路由')

      register = () => {
              // console.log(this.props)
              this.props.history.push('/register')
      }
    3. 整个页面实现代码

      import React from 'react'
      import Logo from '../../component/logo/logo'
      import {List, InputItem, WingBlank, WhiteSpace, Button } from 'antd-mobile'
      
      class Login extends React.Component{
          register = () => {
              // console.log(this.props)
              this.props.history.push('/register')
          }
          render(){
              return (
                  <div>
                      <Logo></Logo>
                      <WingBlank>
                          <List>
                              <InputItem>用户</InputItem>
                              <WhiteSpace />
                              <InputItem>密码</InputItem>
                          </List>
                          <WhiteSpace />
                          <Button type="primary">登录</Button>
                          <WhiteSpace />
                          <Button type="primary" onClick={this.register}>注册</Button>
                      </WingBlank>
                  </div>
              )
          }
      }
      
      export default  Login
      
    • container->register目录下:创建register.js业务组件
    1. 单选选项RadioItem需要先定义再使用
      import {List, InputItem, Radio, WingBlank, WhiteSpace, Button } from 'antd-mobile'
      const RadioItem = Radio.RadioItem
    2. 通过判断state中的type控制显示不同的单选选项

       constructor(props){
              super(props)
              this.state = {
                  type: 'genius' //或者boss
              }
      }
      <RadioItem checked={this.state.type == 'genius'}>
               牛人
      </RadioItem>
      <WhiteSpace />
      <RadioItem checked={this.state.type == 'boss'}>
               Boss
      </RadioItem>
    3. 整个页面实现代码

      import React from 'react'
      import Logo from '../../component/logo/logo'
      import {List, InputItem, Radio, WingBlank, WhiteSpace, Button } from 'antd-mobile'
      const RadioItem = Radio.RadioItem
      
      class Register extends React.Component{
          constructor(props){
              super(props)
              this.state = {
                  type: 'genius' //或者boss
              }
          }
          render(){
              return (
                  <div>
                      <Logo></Logo>
                      <WingBlank>
                          <InputItem>用户名</InputItem>
                          <WhiteSpace />
                          <InputItem>密码</InputItem>
                          <WhiteSpace />
                          <InputItem>确认密码</InputItem>
                          <WhiteSpace />
                          <RadioItem checked={this.state.type == 'genius'}>
                              牛人
                          </RadioItem>
                          <WhiteSpace />
                          <RadioItem checked={this.state.type == 'boss'}>
                              Boss
                          </RadioItem>
                          <WhiteSpace />
                          <Button type="primary">注册</Button>
                      </WingBlank>
                  </div>
              )
          }
      }
      
      export default  Register
    • index.js项目入口文件中:通过Route匹配登录注册路由及组件
      import Login from './container/login/login'
      import Register from './container/register/register'
      
      ReactDOM.render(
          <Provider store={store}>
              <BrowserRouter>
                  <div>
                      <Route path='/login' component={Login}></Route>
                      <Route path='/register' component={Register}></Route>
                  </div>
              </BrowserRouter>
          </Provider>, 
          document.getElementById('root'));
      

        

    三、判断路由

    • server目录下:创建server.js服务端主入口文件
      const express = require('express')
      const userRouter = require('./user')
      
      const app = express()
      //开启中间件
      app.use('/user', userRouter)
      
      app.listen(9093, function(){
          console.log('Node app start at port 9093')
      })
    • server目录下:创建user.js用户独立中间件

      const express = require('express')
      const Router = express.Router()  //路由对象
      
      Router.get('/info', function(req, res){
          return res.json({code:1}) //测试
      })
      
      module.exports = Router  //对外暴露接口
    • component目录下:创建authroute验证登录组件,通过axios调用服务端接口获取测试数据

      import React from 'react'
      import axios from 'axios'
      
      class AuthRoute extends React.Component{
          componentDidMount() {
             //获取测试信息
             axios.get('/user/info')
                .then(res => {
                   console.log(res)
                   if(res.status == 200){
                       console.log(res.data)
                   }
                })
          }
          render(){
             return null
           }
      }
      
      export default AuthRoute   

    四、用户信息校验&跳转登录 

    • 坑:普通非路由组件获取不到this.props.history对象
    • 解决:利用react-router-dom提供的withRouter可以使其显示
      import {withRouter} from 'react-router-dom'
      
      @withRouter
    • 利用this.props.location.pathname可以获取到当前路由,判断若是登录或注册不执行获取用户信息操作

      const publicList = ['/login', 'register']
      const pathname = this.props.location.pathname;
      if(publicList.indexOf(pathname) > -1){
                 return null
      }
      

       

    • 如果当前路由页不是登录或注册,且获取不到用户信息(未登录),利用this.props.history.push('/login')跳转到登录页

        

    五、注册交互 

    • state中设置初始状态
      this.state = {
              user: '',
              pwd: '',
              repeatpwd: '',
              type: 'genius' //或者boss
      }
    • Input和RadioItem共用handleChange():通过箭头函数传参,改变state中指定key的value

      handleChange(key, val){
            this.setState({
                 [key]: val
            })
      }
      
      <InputItem
             onChange={v => this.handleChange('user', v)}
      >用户名</InputItem>
      <RadioItem
             checked={this.state.type == 'boss'}
             onChange={() => this.handleChange('type', 'boss')}
      >
        Boss
      </RadioItem>
    • 注册按钮提交:测试打印state,成功改变

    六、注册请求发送  

    • redux目录下:创建user.redux.js,专门执行用户信息操作的redux
    1. state存储用户注册信息,通过redux给注册提交给服务端
    2. 关键:使用dispatch调用axios.post异步提交数据
      import axios from 'axios'
      //action type
      const REGISTER_SUCCESS = 'REGISTER_SUCCESS'
      const ERROR_MSG = 'ERROR_MSG'
      
      const initState = {
          isAuth: false,
          msg: '',
          user: '',
          pwd: '',
          type: ''
      }
      
      //reducer
      export function user(state=initState, action){
          switch(action.type){
              case REGISTER_SUCCESS:
                   return {...state, msg:'', isAuth: true, ...action.payload} 
              case ERROR_MSG:
                   return {...state, isAuth:false, msg:action.msg}
              default:
                   return state
          }
          return state
      }
      
      //action
      function registerSuccess(data){
          return {type:REGISTER_SUCCESS, payload:data}
      }
      function errorMsg(msg){
          return { msg, type:ERROR_MSG }
      }
      
      export function register({user,pwd,repeatpwd,type}){
      	if (!user||!pwd||!type) {
      		return errorMsg('用户名密码必须输入')
      	}
      	if (pwd!==repeatpwd) {
      		return errorMsg('密码和确认密码不同')
          }
          //redux异步操作数据
      	return dispatch=>{
      		axios.post('/user/register',{user,pwd,type})
      			.then(res=>{
      				if (res.status==200&&res.data.code===0) {
      					dispatch(registerSuccess({user,pwd,type}))
      				}else{
      					dispatch(errorMsg(res.data.msg))
      				}
      			})		
      	}
      
      }  
    • reducer.js中:合并并传入user(reducer)

      //合并所有reducer,并且返回
      import {combineReducers} from 'redux'
      import { user } from './redux/user.redux'
      
      export default combineReducers({user})
    • register.js中:使用connect连接组件和redux,传入state.user和register方法,调用this.props.register(this.state)提交注册

      import {connect} from 'react-redux'
      import {register} from '../../redux/user.redux'
      
      @connect(
          state => state.user,
          {register}
      )
      
      handleRegister = () => {
          this.props.register(this.state)
          // console.log(this.state)
      }
      

    七、数据库模型的建立  

    • server目录下:创建model.js
    1. 通过mongoose连接Express和MongoDB
    2. 建立User数据模型:通过mongoose.Schema生成model模型,通过mongoose.model生成Entity实体
    3. 导出mongoose的工具函数库:getModel,供外部获取模型
      const mongoose = require('mongoose')
      
      //链接mongo 并且使用react-chat这个集合
      const DB_URL = 'mongodb://localhost:27017/react-chat'
      mongoose.connect(DB_URL)
      
      const models = {
          user: {
              'user':{type:String, require:true},
              'pwd': {type:String, require:true},
              'type':{type:String, require:true},
               //头像
              'avatar':{type:String},
               //个人简介或者职位简介
              'desc':{type:String},
               //职位
              'title':{type:String},
               //如果是boss 还有两个字段
              'company':{type:String},
              'money':{type:String}
          },
          chat: {
          }
      }
      
      //批量动态生成model
      for(let m in models){
          mongoose.model(m, new mongoose.Schema(models[m]))
      }
      
      //mongoose的工具函数库
      module.exports = {
          getModel:function(name){
              return mongoose.model(name)
          }
      }
      
    • server->user.js中:引用model,通过getModel获取user数据模型,在'/list'路由下查找并显示所有数据

      const model = require('./model')
      const User = model.getModel('user')
      
      Router.get('/list', function(req, res){
          User.find({}, function(err, doc){
              return res.json(doc)
          })
      })
      

    八、Express注册功能实现  

    • body-parser中间件插件解析表单数据
      npm install body-parser --save
    • server.js中:使用body-parser中间件解析post传来的JSON数据

      const bodyParser = require('body-parser')
      const cookieParser = require('cookie-parser')
      
      //开启中间件
      app.use(cookieParser())       
      app.use(bodyParser.json()) //解析post 传来的json数据
      
    • user.js中:使用Router.post发送注册请求

    1. 先查找user,若存在则用户名重复

    2. 如不存在,继续使用注册信息创建新用户文档

      //用户注册
      Router.post('/register', function(req, res){
          console.log(req.body)
          const {user, pwd, type} = req.body
          User.findOne({user:user}, function(err, doc){
              if(doc){
                  return res.json({code:1, msg:'用户名重复'})
              }
              User.create({user, pwd, type}, function(err, doc){
                  if(err){
                      return res.json({code:1, msg:'后端出错了'})
                  }
                  return res.json({code:0})
              })
          })
      })
      

    九、注册跳转&密码加密实现  

    • 注册跳转
    1. src目录下:创建utils.js提供工具函数getRedirectPath(),判断用户信息返回跳转地址
      export function getRedirectPath({type, avatar}){
          //根据用户信息 返回跳转地址
          //判断user.type :bose /genius
          //判断user.avtar :bossinfo /geniusinfo
          let url = (type === 'boss') ? '/boss' : '/genius'
          if(!avatar){
             url += 'info'
          }
          return url
      }
    2. user.redux.js中:在初始状态中添加redirectTo跳转地址字段,reducer注册成功后调用工具函数获得跳转地址

      import {getRedirectPath} from '../utils'
      
      const initState = {
          redirectTo:'',
          ……
      }
      
      case REGISTER_SUCCESS:
          return {...state, msg:'', redirectTo:getRedirectPath(action.payload), isAuth: true, ...action.payload} 
    3. register.js中:判断this.props.redirectTo是否有值,有通过react-router-dom的Redirect组件进行跳转,否则返回null

      import {Redirect} from 'react-router-dom'
      
       {this.props.redirectTo ? <Redirect to={this.props.redirectTo}/> : null}

        

    • 密码加密

    1. 安装utility第三方插件

      npm install utility --save
    2. user.js中:定义加密方法md5Pwd(),使用两层utility的md5加密方法,再加一个自定义的字符串

      const utils = require('utility')
      
      //使用加密方法将pwd加密后再储存
      User.create({user, type, pwd:md5Pwd(pwd)}, function(err, doc){
         ……
      })
      
      //密码加密
      function md5Pwd(pwd){
          const salt = 'imooc_is_good_3957x8yza6!@#IUHJh~~'
          return utils.md5(utils.md5(pwd+salt))
      }
      

    十、登录功能实现  

    • user.redux.js中:添加登录成功的相关reducer和异步action
      //action type
      const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
      
      //user reducer中
      case LOGIN_SUCCESS:
                   return {...state, msg:'', redirectTo:getRedirectPath(action.payload), isAuth: true, ...action.payload} 
      
      //登录成功 同步action
      function loginSuccess(data){
          return {type:LOGIN_SUCCESS, payload:data}
      }
      
      //登录  异步action
      export function login({user,pwd}){
          if(!user||!pwd){
              return errorMsg('用户名密码必须输入')
          }
          //redux异步操作数据
      	return dispatch=>{
      		axios.post('/user/login',{user,pwd})
      			.then(res=>{
      				if (res.status==200&&res.data.code===0) {
      					dispatch(loginSuccess(res.data.data))
      				}else{
      					dispatch(errorMsg(res.data.msg))
      				}
      			})		
      	}
      }
    • user.js中:通过Router.post调用'/login'接口,根据接收到的用户名和密码查询数据

      //用户登录
      Router.post('/login', function(req, res){
          const {user, pwd} = req.body
          User.findOne({user, pwd:md5Pwd(pwd)},{'pwd':0}, function(err, doc){
              if(!doc){
                  return res.json({code:1, msg:'用户名或者密码错误'})
              }
              return res.json({code:0, data:doc})
          })
      })
      
    • login.js中:使用connect连接组件与redux,登录互动实现同注册

    1. 调用props中的login异步action执行登录操作

    2. 判断props中的redirectTo和msg,跳转连接或显示错误提示

    3. 实现代码

      import React from 'react'
      import Logo from '../../component/logo/logo'
      import { InputItem, WingBlank, WhiteSpace, Button } from 'antd-mobile'
      import {Redirect} from 'react-router-dom'
      import {connect} from 'react-redux'
      import {login} from '../../redux/user.redux'
      
      @connect(
          state => state.user,
          {login}
      )
      class Login extends React.Component{
          constructor(props){
              super(props)
              this.state = {
                  user: '',
                  pwd: ''
              }
          }
          handleChange(key, val){
              this.setState({
                  [key]: val
              })
          }
          register = () => {
              // console.log(this.props)
              this.props.history.push('/register')
          }
          handleLogin = () => {
              this.props.login(this.state)
          }
          render(){
              return (
                  <div>
                      {this.props.redirectTo ? <Redirect to={this.props.redirectTo}/> : null}
                      <Logo></Logo>
                      <WingBlank>
                              {this.props.msg ? <p className='error-msg'>{this.props.msg}</p> : null}
                              <InputItem
                                  onChange={v => this.handleChange('user', v)}
                              >用户名</InputItem>
                              <WhiteSpace />
                              <InputItem
                                  type='password'
                                  onChange={v => this.handleChange('pwd', v)}
                              >密码</InputItem>
                          <WhiteSpace />
                          <Button type="primary" onClick={this.handleLogin}>登录</Button>
                          <WhiteSpace />
                          <Button type="primary" onClick={this.register}>注册</Button>
                      </WingBlank>
                  </div>
              )
          }
      }
      
      export default  Login

        

    十一、cookie保存登录状态 

    • user.js中:登录注册成功后,将userid存储到cookie中
    1. 注册后改为使用model.save方法创建model获取_id
    2. 用户刷新页面校验用户信息:获取cookie中的userid,通过find by id的方式查找用户数据
    3. 查找条件中加入过滤:不显示密码和版本号,语法为{'pwd': 0},此处0表示不显示
      //查询条件 不显示密码和版本号
      const _filter = {'pwd':0, '__v':0}
      
      //用户登录成功后
      res.cookie('userid', doc._id)
      
      
      //用户注册后存储cookie model.save 获得id
      const userModel = new User({user, type, pwd:md5Pwd(pwd)})
      userModel.save(function(e, d){
             if(e){
                   return res.json({code:1, msg:'后端出错了'})
             }
             const {user, type, _id} = d
             res.cookie('userid', _id)
             return res.json({code:0, data:{user, type, _id}})
      })
      
      //用户信息
      Router.get('/info', function(req, res){
          //用户cookie校验
          const {userid} = req.cookies
          if(!userid){
              return res.json({code:1})
          }
          User.findOne({_id: userid}, _filter, function(err, doc){
              if(err){
                  return res.json({code:1, msg:'后端出错了'})
              }
              if(doc){
                  return res.json({code:0, data:doc})
              }
          })
          
      })
      
    • user.redux.js中:定义loadData存储数据相关的reducer和同步action

      //action type
      const LOAD_DATA = 'LOAD_DATA'
      
      //user reducer中添加
      case LOAD_DATA:
              return {...state, ...action.payload}
      
      //同步action
      export function loadData(userinfo){
          return {type:LOAD_DATA, payload:userinfo} 
      }
      
    • authroute.js中:使用connect连接组件和redux

    1. 在判断当前为登录状态后,调用loadData同步action,将/user/info获取到的用户信息存入redux

    2. 注意:@connect一定要写在@withRouter后

      import {connect} from 'react-redux'
      import {loadData} from '../../redux/user.redux'
      
      @withRouter
      @connect(
         null,
         {loadData}
      )
      
      if(res.data.code == 0){  //登录
          this.props.loadData(res.data.data)
      }

        

        


    注:项目来自慕课网

  • 相关阅读:
    ListBox的数据绑定
    GridView中加入新行方法
    一个事务的例子
    用sql语句查询从N条到M条的记录
    用户注册表中日期输入的解决方案
    对分页控件进行分页的封装
    我的触发器
    缓存DataSet以提高性能
    网站访问统计在Global.asax中的配置
    给表格控件绑定数据库内容的封装
  • 原文地址:https://www.cnblogs.com/ljq66/p/10267478.html
Copyright © 2020-2023  润新知