【招聘App】—— React/Nodejs/MongoDB全栈项目:登录注册
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅。最终成果github地址:https://github.com/66Web/react-antd-zhaoping,欢迎star。
一、登录注册
页面文件结构
- 基础组件放在Component文件夹下面
- 页面组件放在Container文件夹下面
- 页面入口处获取用户信息,决定跳转到哪个页面
web开发模式
- 整体前后端交互通过JSON实现
- 基于cookie用户验证
- express使用cookie,需要依赖cookie-parser
npm install cookie-parser --save
- cookie类似于一张身份卡,登录后服务器端返回,带着cookie即可以访问受限资源
- cookie的管理浏览器会自动处理
- 开发流程
二、页面实现
- component->logo目录下:创建logo.js基础组件,设置顶部Logo位置与样式
- container->login目录下:创建login.js业务组件
- 应用antd-mobile的组件实现登录页面
import {List, InputItem, WingBlank, WhiteSpace, Button } from 'antd-mobile' 跳转路由:this.props.history.push('路由')
register = () => {
// console.log(this.props)
this.props.history.push('/register')
}整个页面实现代码
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业务组件
- 单选选项RadioItem需要先定义再使用
import {List, InputItem, Radio, WingBlank, WhiteSpace, Button } from 'antd-mobile'
const RadioItem = Radio.RadioItem 通过判断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>整个页面实现代码
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
- state存储用户注册信息,通过redux给注册提交给服务端
- 关键:使用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
- 通过mongoose连接Express和MongoDB
- 建立User数据模型:通过mongoose.Schema生成model模型,通过mongoose.model生成Entity实体
- 导出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发送注册请求
先查找user,若存在则用户名重复
如不存在,继续使用注册信息创建新用户文档
//用户注册
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})
})
})
})
九、注册跳转&密码加密实现
- 注册跳转
- 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
} 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}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}
密码加密
安装utility第三方插件
npm install utility --save
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,登录互动实现同注册
调用props中的login异步action执行登录操作
判断props中的redirectTo和msg,跳转连接或显示错误提示
实现代码
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中
- 注册后改为使用model.save方法创建model获取_id
- 用户刷新页面校验用户信息:获取cookie中的userid,通过find by id的方式查找用户数据
- 查找条件中加入过滤:不显示密码和版本号,语法为{'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
在判断当前为登录状态后,调用loadData同步action,将/user/info获取到的用户信息存入redux中
注意:@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全栈项目:消息列表
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅.最终成果github地址:https://github.com/66We ...
- 【招聘App】—— React/Nodejs/MongoDB全栈项目:socket.io&聊天实现
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅.最终成果github地址:https://github.com/66We ...
- 【招聘App】—— React/Nodejs/MongoDB全栈项目:信息完善&用户列表
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅.最终成果github地址:https://github.com/66We ...
- 【招聘App】—— React/Nodejs/MongoDB全栈项目:项目准备
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅.最终成果github地址:https://github.com/66We ...
- 【招聘App】—— React/Nodejs/MongoDB全栈项目:个人中心&退出登录
前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅.最终成果github地址:https://github.com/66We ...
- 全栈项目|小书架|服务器端-NodeJS+Koa2实现首页图书列表接口
通过上篇文章 全栈项目|小书架|微信小程序-首页水平轮播实现 我们实现了前端(小程序)效果图的展示,这篇文章来介绍服务器端的实现. 首页书籍信息 先来回顾一下首页书籍都有哪些信息: 从下面的图片可以看 ...
- 全栈项目|小书架|服务器端-NodeJS+Koa2 实现书籍详情接口
通过上篇文章 全栈项目|小书架|微信小程序-首页水平轮播实现 我们实现了前端(小程序)效果图的展示,这篇文章来介绍服务器端的实现. 书籍详情分析 书籍详情页面如下: 从上图可以分析出详情页面大概有以下 ...
- 全栈项目|小书架|服务器开发-NodeJS 使用 JWT 实现登录认证
通过这篇 全栈项目|小书架|服务器开发-JWT 详解 文章我们对JWT有了深入的了解,那么接下来介绍JWT如何在项目中使用. 安装 $ npm install jsonwebtoken 生成 Toke ...
- Vue、Node全栈项目~面向小白的博客系统~
个人博客系统 前言 ❝ 代码质量问题轻点喷(去年才学的前端),有啥建议欢迎联系我,联系方式见最下方,感谢! 页面有啥bug也可以反馈给我,感谢! 这是一套包含前后端代码的个人博客系统,欢迎各位提出建议 ...
随机推荐
- Linux服务器中OpenSSH的源码编译与升级
Linux服务器中OpenSSH的源码编译与升级 https://www.oschina.net/question/12_7383
- js监听不到组合键
我在js文件中写代码,监听 ctrl + enter 组合键,但是一直监听不到.只能监听到单个键. 后来我将监听的代码放到html页面中去,就能监听到了. 这个问题困扰我很久,记录下!
- selenium 3.0鼠标事件 (java代码)
注意:ActionChains下相关方法在当前的firefox不工作,建议使用谷歌浏览器. public static void main(String[] args) throws Interrup ...
- Mysql缺少可执行的命令
MySQL问题解决:-bash:mysql:command not found 问题: [root@linux115 /]# mysql -uroot -p -bash: m ...
- Android仿QQ登录下拉历史列表
demo中包含了Sqlite数据库增删改查,对存储的账号进行按照最新的时间排序,限制了最多存储5条数据. 效果图: 1.首先创建MyHelper建表: public class MyHelper ex ...
- LightOJ 1012.Guilty Prince-DFS
Guilty Prince Time Limit: 2 second(s) Memory Limit: 32 MB Once there was a king named Akbar. He had ...
- 洛谷 P2415 集合求和【数学公式/模拟】
给定一个集合s(集合元素数量<=30),求出此集合所有子集元素之和. 输入输出格式 输入格式: 集合中的元素(元素<=1000) 输出格式: 和 输入输出样例 输入样例#1: 2 3 输出 ...
- ASP.NET Core 2.2 基础知识(十四) WebAPI Action返回类型(未完待续)
要啥自行车,直接看手表 //返回基元类型 public string Get() { return "hello world"; } //返回复杂类型 public Person ...
- 【动态规划】bzoj1638 [Usaco2007 Mar]Cow Traffic 奶牛交通
设f[u]为从度数0到u的路径条数,f2[u]为从u到n的路径条数. ans=max{f[x[i]]*f2[y[i]]}(1<=i<=m). #include<cstdio> ...
- 【Floyd】【Dilworth定理】【最小路径覆盖】【匈牙利算法】bzoj1143 [CTSC2008]祭祀river
Dilworth定理,将最长反链转化为最小链覆盖.//貌似还能把最长上升子序列转化为不上升子序列的个数? floyd传递闭包,将可以重叠的最小链覆盖转化成不可重叠的最小路径覆盖.(引用:这样其实就是相 ...