实战react技术栈+express前后端博客项目(5)-- 前后端实现登录功能
项目地址:https://github.com/Nealyang/R...
本想等项目做完再连载一波系列博客,随着开发的进行,也是的确遇到了不少坑,请教了不少人。遂想,何不一边记录踩坑,一边分享收获呢。分享当然是好的,
如果能做到集思广益,那岂不是更美。我们的口号是:坚决不会烂尾
本博客为连载代码博客同步更新博客,随着项目往后开发可能会遇到前面写的不合适的地方会再回头修改。如有不妥~欢迎兄弟们不啬赐教。谢谢!
登录部分
- 登录截图
前端部分实现
接上篇,我们登录界面已经画完了,登录功能,涉及到异步请求。所以大致我需要需要如下几个action。请求发起action,请求结束action,错误信息提醒action,登录action,注册action以及后面免登陆我们用到的自动登录action。
因为该登录功能涉及到的都是全局的信息,所以这里我们放到index的reducer中处理
const initialState = { isFetching: true,msg: { type: 1,//0失败 1成功 content: '' },userInfo: {} }; export const actionsTypes = { FETCH_START: "FETCH_START",FETCH_END: "FETCH_END",USER_LOGIN: "USER_LOGIN",USER_REGISTER: "USER_REGISTER",RESPONSE_USER_INFO: "RESPONSE_USER_INFO",SET_MESSAGE: "SET_MESSAGE",USER_AUTH:"USER_AUTH" }; export const actions = { get_login: function (username,password) { return { type: actionsTypes.USER_LOGIN,username,password } },get_register: function (data) { return { type: actionsTypes.USER_REGISTER,data } },clear_msg: function () { return { type: actionsTypes.SET_MESSAGE,msgType: 1,msgContent: '' } },user_auth:function () { return{ type:actionsTypes.USER_AUTH } } }; export function reducer(state = initialState,action) { switch (action.type) { case actionsTypes.FETCH_START: return { ...state,isFetching: true }; case actionsTypes.FETCH_END: return { ...state,isFetching: false }; case actionsTypes.SET_MESSAGE: return { ...state,isFetching: false,msg: { type: action.msgType,content: action.msgContent } }; case actionsTypes.RESPONSE_USER_INFO: return { ...state,userInfo: action.data }; default: return state } }
前端登录和注册action发起
class LoginFormCom extends Component { constructor(props) { super(props); } handleLogin = (e) => { e.preventDefault(); this.props.form.validateFields((err,values) => { if (!err) { this.props.login(values.userName,values.password) } }); }; render() { const {getFieldDecorator} = this.props.form; return ( <Form onSubmit={this.handleLogin} className={style.formStyle}> <FormItem> {getFieldDecorator('userName',{ rules: [{required: true,message: '请输入用户名!'}],})( <Input prefix={<Icon type="user" style={{fontSize: 13}}/>} placeholder="Username"/> )} </FormItem> <FormItem> {getFieldDecorator('password',message: '请输入密码!'}],})( <Input prefix={<Icon type="lock" style={{fontSize: 13}}/>} type="password" placeholder="Password"/> )} </FormItem> <FormItem> <Button className={style.loginButton} type="primary" htmlType="submit"> 登录 </Button> </FormItem> </Form> ) } } const LoginForm = Form.create()(LoginFormCom); export default LoginForm
如上代码,在handleLogin中,我们调用父组件传进来的login方法。可能得说是爷爷组件吧。罢了,就是其容器组件。
而容器组件Home.js中的代码如下:
Home.defaultProps = { userInfo:{} }; Home.propsTypes = { userInfo:PropTypes.object.isrequired }; function mapStateToProps(state) { return{ userInfo:state.globalState.userInfo } } function mapDispatchToProps(dispatch) { return{ login:bindActionCreators(actions.get_login,dispatch),register:bindActionCreators(actions.get_register,dispatch) } } export default connect( mapStateToProps,mapDispatchToProps )(Home);
如上,我们已经定义了login和register。分别为登录和注册两个方法。在登录部分我们如上写。当然,注册功能也是如上。
因为登录和注册都是异步的,所以这里我们需要saga去监听这个action的发起。然后对应的去处理。
export function* register (data) { yield put({type:IndexActionTypes.FETCH_START}); try { return yield call(post,'/user/register',data) } catch (error) { yield put({type:IndexActionTypes.SET_MESSAGE,msgContent:'注册失败',msgType:0}); } finally { yield put({type: IndexActionTypes.FETCH_END}); } } export function* registerFlow () { while(true){ let request = yield take(IndexActionTypes.USER_REGISTER); let response = yield call(register,request.data); if(response&&response.code === 0){ yield put({type:IndexActionTypes.SET_MESSAGE,msgContent:'注册成功!',msgType:1}); yield put({type:IndexActionTypes.RESPONSE_USER_INFO,data:response.data}) } } }
这里我们就举例说下registerFlow吧,其实也就是监听USER_REGISTER的action。然后调用register方法,发送请求开始action(界面出现Loading),然后请求结束action。接收到请求后,拿出数据,发送拿到数据后的action
基本思路如上,代码如上,大家研究研究哈,不明白的地方,直接issue。
后段部分
router.post('/register',(req,res) => { let {userName,password,passwordRe} = req.body; if (!userName) { responseClient(res,400,2,'用户名不可为空'); return; } if (!password) { responseClient(res,'密码不可为空'); return; } if (password !== passwordRe) { responseClient(res,'两次密码不一致'); return; } //验证用户是否已经在数据库中 User.findOne({username: userName}) .then(data => { if (data) { responseClient(res,200,1,'用户名已存在'); return; } //保存到数据库 let user = new User({ username: userName,password: md5(password + MD5_SUFFIX),type: 'user' }); user.save() .then(function () { User.findOne({username: userName}) .then(userInfo=>{ let data = {}; data.username = userInfo.username; data.userType = userInfo.type; data.userId = userInfo._id; responseClient(res,'注册成功',data); return; }); }) }).catch(err => { responseClient(res); return; }); });
module.exports = { MD5_SUFFIX: 'eiowafnajkdlfjsdkfj大姐夫文姐到了困难额我积分那看到你@#¥%……&)(*&……)',md5: function (pwd) { let md5 = crypto.createHash('md5'); return md5.update(pwd).digest('hex') },responseClient(res,httpCode = 500,code = 3,message='服务端异常',data={}) { let responseData = {}; responseData.code = code; responseData.message = message; responseData.data = data; res.status(httpCode).json(responseData) } }
让你简写很多代码。然后判断用户名、密码是否为空以及两次密码是否一致。(虽然这些部分在前端也应该做判断,但是后端也尽量保障一下)。
验证用户是否已经在数据库中。如果不存在,则存储下,然后将用户信息返回。如果存在,则返回给客户端相应的信息。
注意这里我们因为用的saga,所以只要是http请求三次握手成功的,我们都是返回200.对于用户名重复、别的错误,我们统一在返回的数据中给个状态码标识。
存储的时候我们用md5加密,为了防止md5解密,我们在后面添加了一个随机的字符串。在登录的时候,拿到用户的登录密码,然后加上随机字符串,进行md5加密再与数据库数据记性比较也就OK了。
后端实现基本思路就是这些,然后对于mongoose的基本操作这里就不赘述,大家可自行查看文档。
router.post('/login',res) => { let {username,password} = req.body; if (!username) { responseClient(res,'用户名不可为空'); return; } if (!password) { responseClient(res,'密码不可为空'); return; } User.findOne({ username,password: md5(password + MD5_SUFFIX) }).then(userInfo => { if (userInfo) { //登录成功 let data = {}; data.username = userInfo.username; data.userType = userInfo.type; data.userId = userInfo._id; //登录成功后设置session req.session.userInfo = data; responseClient(res,'登录成功',data); return; } responseClient(res,'用户名密码错误'); }).catch(err => { responseClient(res); }) });
## 总结
基本到这,就是实现了一个查和增的过程。也实现了前后端的基本交互。大家感受下哈~
然后大家肯定也是发现了,登录了以后,貌似每次刷新我们都要再重新登录,这并不是我们想要的。当然,这部分功能,我们将在下一篇博客中介绍。
## 项目实现步骤系列博客
- [x] 实战react技术栈+express前后端博客项目(0)-- 预热一波
- [x] 实战react技术栈+express前后端博客项目(1)-- 整体项目结构搭建、state状态树设计
- [x] 实战react技术栈+express前后端博客项目(2)-- 前端react-xxx、路由配置
- [x] 实战react技术栈+express前后端博客项目(3)-- 后端路由、代理以及静态资源托管等其他配置说明
- [x] 实战react技术栈+express前后端博客项目(4)-- 博客首页代码编写以及redux-saga组织
- [x] 实战react技术栈+express前后端博客项目(5)-- 前后端实现登录功能
- 实战react技术栈+express前后端博客项目(6)-- 使用session实现免登陆+管理后台权限验证
- 实战react技术栈+express前后端博客项目(7)-- 前端管理界面用户查看功能+后端对应接口开发
- 实战react技术栈+express前后端博客项目(8)-- 前端管理界面标签管理功能+后端对应接口开发
- 实战react技术栈+express前后端博客项目(9)-- 前端管理界面评论管理功能+后端对应接口开发
- 实战react技术栈+express前后端博客项目(10)-- 前端管理界面发表文章功能
- 实战react技术栈+express前后端博客项目(11)-- 后端接口对应文章部分的增删改查
- 实战react技术栈+express前后端博客项目(12)-- 前端对于发文部分的完善(增删改查、分页等)
- 实战react技术栈+express前后端博客项目(13)-- 前端对于发文部分的完善(增删改查等)
- 实战react技术栈+express前后端博客项目(14)-- 内容详情页以及阅读数的展示
- 实战react技术栈+express前后端博客项目(15)-- 博客添加评论功能以及对应后端实现
- 实战react技术栈+express前后端博客项目(16)-- pm2 的使用说明
- 实战react技术栈+express前后端博客项目(17)-- 收工
## 交流
倘若有哪里说的不是很明白,或者有什么需要与我交流,欢迎各位提issue。或者加群联系我~
扫码关注我的个人微信公众号,直接回复,必有回应。分享更多原创文章。点击交流学习加我微信、qq群。一起学习,一起进步
---
欢迎兄弟们加入:
Node.js技术交流群:209530601
React技术栈:398240621
前端技术杂谈:604953717 (新建)
---