实战react技术栈+express前后端博客项目(5)-- 前后端实现登录功能

前端之家收集整理的这篇文章主要介绍了实战react技术栈+express前后端博客项目(5)-- 前后端实现登录功能前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

实战react技术栈+express前后端博客项目(5)-- 前后端实现登录功能

项目地址:https://github.com/Nealyang/R...

本想等项目做完再连载一波系列博客,随着开发的进行,也是的确遇到了不少坑,请教了不少人。遂想,何不一边记录踩坑,一边分享收获呢。分享当然是好的,
如果能做到集思广益,那岂不是更美。我们的口号是:坚决不会烂尾

博客为连载代码博客同步更新博客,随着项目往后开发可能会遇到前面写的不合适的地方会再回头修改。如有不妥~欢迎兄弟们不啬赐教。谢谢!

登录部分

前端部分实现

接上篇,我们登录界面已经画完了,登录功能,涉及到异步请求。所以大致我需要需要如下几个action。请求发起action,请求结束action,错误信息提醒action,登录action,注册action以及后面免登陆我们用到的自动登录action。

因为该登录功能涉及到的都是全局的信息,所以这里我们放到index的reducer中处理

  1. const initialState = {
  2. isFetching: true,msg: {
  3. type: 1,//0失败 1成功
  4. content: ''
  5. },userInfo: {}
  6. };
  7.  
  8. export const actionsTypes = {
  9. 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"
  10. };
  11.  
  12. export const actions = {
  13. get_login: function (username,password) {
  14. return {
  15. type: actionsTypes.USER_LOGIN,username,password
  16. }
  17. },get_register: function (data) {
  18. return {
  19. type: actionsTypes.USER_REGISTER,data
  20. }
  21. },clear_msg: function () {
  22. return {
  23. type: actionsTypes.SET_MESSAGE,msgType: 1,msgContent: ''
  24. }
  25. },user_auth:function () {
  26. return{
  27. type:actionsTypes.USER_AUTH
  28. }
  29. }
  30. };
  31.  
  32. export function reducer(state = initialState,action) {
  33. switch (action.type) {
  34. case actionsTypes.FETCH_START:
  35. return {
  36. ...state,isFetching: true
  37. };
  38. case actionsTypes.FETCH_END:
  39. return {
  40. ...state,isFetching: false
  41. };
  42. case actionsTypes.SET_MESSAGE:
  43. return {
  44. ...state,isFetching: false,msg: {
  45. type: action.msgType,content: action.msgContent
  46. }
  47. };
  48. case actionsTypes.RESPONSE_USER_INFO:
  49. return {
  50. ...state,userInfo: action.data
  51. };
  52. default:
  53. return state
  54. }
  55. }

前端登录注册action发起

  1. class LoginFormCom extends Component {
  2. constructor(props) {
  3. super(props);
  4. }
  5.  
  6. handleLogin = (e) => {
  7. e.preventDefault();
  8. this.props.form.validateFields((err,values) => {
  9. if (!err) {
  10. this.props.login(values.userName,values.password)
  11. }
  12. });
  13. };
  14.  
  15. render() {
  16. const {getFieldDecorator} = this.props.form;
  17. return (
  18. <Form onSubmit={this.handleLogin} className={style.formStyle}>
  19. <FormItem>
  20. {getFieldDecorator('userName',{
  21. rules: [{required: true,message: '请输入用户名!'}],})(
  22. <Input prefix={<Icon type="user" style={{fontSize: 13}}/>} placeholder="Username"/>
  23. )}
  24. </FormItem>
  25. <FormItem>
  26. {getFieldDecorator('password',message: '请输入密码!'}],})(
  27. <Input prefix={<Icon type="lock" style={{fontSize: 13}}/>} type="password"
  28. placeholder="Password"/>
  29. )}
  30. </FormItem>
  31. <FormItem>
  32. <Button className={style.loginButton} type="primary" htmlType="submit">
  33. 登录
  34. </Button>
  35. </FormItem>
  36. </Form>
  37. )
  38. }
  39. }
  40.  
  41. const LoginForm = Form.create()(LoginFormCom);
  42.  
  43. export default LoginForm

如上代码,在handleLogin中,我们调用父组件传进来的login方法。可能得说是爷爷组件吧。罢了,就是其容器组件。

而容器组件Home.js中的代码如下:

  1. Home.defaultProps = {
  2. userInfo:{}
  3. };
  4. Home.propsTypes = {
  5. userInfo:PropTypes.object.isrequired
  6. };
  7. function mapStateToProps(state) {
  8. return{
  9. userInfo:state.globalState.userInfo
  10. }
  11. }
  12. function mapDispatchToProps(dispatch) {
  13. return{
  14. login:bindActionCreators(actions.get_login,dispatch),register:bindActionCreators(actions.get_register,dispatch)
  15. }
  16. }
  17. export default connect(
  18. mapStateToProps,mapDispatchToProps
  19. )(Home);

如上,我们已经定义了login和register。分别为登录注册两个方法。在登录部分我们如上写。当然,注册功能也是如上。

### 登录注册saga的处理

因为登录注册都是异步的,所以这里我们需要saga去监听这个action的发起。然后对应的去处理。

  1. export function* register (data) {
  2. yield put({type:IndexActionTypes.FETCH_START});
  3. try {
  4. return yield call(post,'/user/register',data)
  5. } catch (error) {
  6. yield put({type:IndexActionTypes.SET_MESSAGE,msgContent:'注册失败',msgType:0});
  7. } finally {
  8. yield put({type: IndexActionTypes.FETCH_END});
  9. }
  10. }
  11.  
  12.  
  13. export function* registerFlow () {
  14. while(true){
  15. let request = yield take(IndexActionTypes.USER_REGISTER);
  16. let response = yield call(register,request.data);
  17. if(response&&response.code === 0){
  18. yield put({type:IndexActionTypes.SET_MESSAGE,msgContent:'注册成功!',msgType:1});
  19. yield put({type:IndexActionTypes.RESPONSE_USER_INFO,data:response.data})
  20. }
  21.  
  22. }
  23. }

这里我们就举例说下registerFlow吧,其实也就是监听USER_REGISTER的action。然后调用register方法,发送请求开始action(界面出现Loading),然后请求结束action。接收到请求后,拿出数据,发送拿到数据后的action

基本思路如上,代码如上,大家研究研究哈,不明白的地方,直接issue。

后段部分

  1. router.post('/register',(req,res) => {
  2. let {userName,password,passwordRe} = req.body;
  3. if (!userName) {
  4. responseClient(res,400,2,'用户名不可为空');
  5. return;
  6. }
  7. if (!password) {
  8. responseClient(res,'密码不可为空');
  9. return;
  10. }
  11. if (password !== passwordRe) {
  12. responseClient(res,'两次密码不一致');
  13. return;
  14. }
  15. //验证用户是否已经在数据库
  16. User.findOne({username: userName})
  17. .then(data => {
  18. if (data) {
  19. responseClient(res,200,1,'用户名已存在');
  20. return;
  21. }
  22. //保存到数据库
  23. let user = new User({
  24. username: userName,password: md5(password + MD5_SUFFIX),type: 'user'
  25. });
  26. user.save()
  27. .then(function () {
  28. User.findOne({username: userName})
  29. .then(userInfo=>{
  30. let data = {};
  31. data.username = userInfo.username;
  32. data.userType = userInfo.type;
  33. data.userId = userInfo._id;
  34. responseClient(res,'注册成功',data);
  35. return;
  36. });
  37. })
  38. }).catch(err => {
  39. responseClient(res);
  40. return;
  41. });
  42. });

后端这边其实都差不多,我们拿注册举例子。简单解释下上面代码

responseClient是封装的一个方法代码如下:

  1. module.exports = {
  2. MD5_SUFFIX: 'eiowafnajkdlfjsdkfj大姐夫文姐到了困难额我积分那看到你@#¥%……&)(*&……)',md5: function (pwd) {
  3. let md5 = crypto.createHash('md5');
  4. return md5.update(pwd).digest('hex')
  5. },responseClient(res,httpCode = 500,code = 3,message='服务端异常',data={}) {
  6. let responseData = {};
  7. responseData.code = code;
  8. responseData.message = message;
  9. responseData.data = data;
  10. res.status(httpCode).json(responseData)
  11. }
  12. }

让你简写很多代码。然后判断用户名、密码是否为空以及两次密码是否一致。(虽然这些部分在前端也应该做判断,但是后端也尽量保障一下)。

验证用户是否已经在数据库中。如果不存在,则存储下,然后将用户信息返回。如果存在,则返回给客户端相应的信息。

注意这里我们因为用的saga,所以只要是http请求三次握手成功的,我们都是返回200.对于用户名重复、别的错误,我们统一在返回的数据中给个状态码标识。

存储的时候我们用md5加密,为了防止md5解密,我们在后面添加了一个随机的字符串。在登录的时候,拿到用户登录密码,然后加上随机字符串,进行md5加密再与数据库数据记性比较也就OK了。

后端实现基本思路就是这些,然后对于mongoose的基本操作这里就不赘述,大家可自行查看文档。

  1. router.post('/login',res) => {
  2. let {username,password} = req.body;
  3. if (!username) {
  4. responseClient(res,'用户名不可为空');
  5. return;
  6. }
  7. if (!password) {
  8. responseClient(res,'密码不可为空');
  9. return;
  10. }
  11. User.findOne({
  12. username,password: md5(password + MD5_SUFFIX)
  13. }).then(userInfo => {
  14. if (userInfo) {
  15. //登录成功
  16. let data = {};
  17. data.username = userInfo.username;
  18. data.userType = userInfo.type;
  19. data.userId = userInfo._id;
  20. //登录成功后设置session
  21. req.session.userInfo = data;
  22. responseClient(res,'登录成功',data);
  23. return;
  24. }
  25. responseClient(res,'用户名密码错误');
  26. }).catch(err => {
  27. responseClient(res);
  28. })
  29. });

## 总结

基本到这,就是实现了一个查和增的过程。也实现了前后端的基本交互。大家感受下哈~

然后大家肯定也是发现了,登录了以后,貌似每次刷新我们都要再重新登录,这并不是我们想要的。当然,这部分功能,我们将在下一篇博客中介绍。

## 项目实现步骤系列博客

## 交流

倘若有哪里说的不是很明白,或者有什么需要与我交流,欢迎各位提issue。或者加群联系我~

扫码关注我的个人微信公众号,直接回复,必有回应。分享更多原创文章。点击交流学习加我微信、qq群。一起学习,一起进步

---

欢迎兄弟们加入:

Node.js技术交流群:209530601

React技术栈:398240621

前端技术杂谈:604953717 (新建)

---

猜你在找的React相关文章