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

时间:2021-04-15 05:22:11

前言:最近在学习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的管理浏览器会自动处理
  • 开发流程

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

二、页面实现

  • 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   

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

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

  • 坑:普通非路由组件获取不到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
    }

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

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

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

五、注册交互 

  • 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,成功改变

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

六、注册请求发送  

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

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

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

  • 注册跳转
  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}

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

  • 密码加密

  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

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

十一、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)
    }

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

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


注:项目来自慕课网