實戰react技術棧+express先後端博客項目(5)-- 先後端實現登陸功能

項目地址:github.com/Nealyang/Re…html

本想等項目作完再連載一波系列博客,隨着開發的進行,也是的確遇到了很多坑,請教了很多人。遂想,何不一邊記錄踩坑,一邊分享收穫呢。分享固然是好的,
若是能作到集思廣益,那豈不是更美。咱們的口號是:堅定不會爛尾前端

本博客爲連載代碼博客同步更新博客,隨着項目日後開發可能會遇到前面寫的不合適的地方會再回頭修改。若有不妥~歡迎兄弟們不嗇賜教。謝謝!react

登陸部分

  • 登陸截圖

前端部分實現

接上篇,咱們登陸界面已經畫完了,登陸功能,涉及到異步請求。因此大體我須要須要以下幾個action。請求發起action,請求結束action,錯誤信息提醒action,登陸action,註冊action以及後面免登錄咱們用到的自動登陸action。git

由於該登陸功能涉及到的都是全局的信息,因此這裏咱們放到index的reducer中處理github

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', {
                        rules: [{required: true, 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中的代碼以下:express

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。分別爲登陸和註冊兩個方法。在登陸部分咱們如上寫。固然,註冊功能也是如上。json

登陸、註冊saga的處理

由於登陸和註冊都是異步的,因此這裏咱們須要saga去監聽這個action的發起。而後對應的去處理。redux

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, 400, 2, '密碼不可爲空');
        return;
    }
    if (password !== passwordRe) {
        responseClient(res, 400, 2, '兩次密碼不一致');
        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, 200, 0, '註冊成功', data);
                            return;
                        });
                })
        }).catch(err => {
        responseClient(res);
        return;
    });
});複製代碼

後端這邊其實都差很少,咱們拿註冊舉例子。簡單解釋下上面代碼

responseClient是封裝的一個方法。代碼以下:

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', (req, res) => {
     let {username, password} = req.body;
     if (!username) {
         responseClient(res, 400, 2, '用戶名不可爲空');
         return;
     }
     if (!password) {
         responseClient(res, 400, 2, '密碼不可爲空');
         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, 200, 0, '登陸成功', data);
             return;
         }
         responseClient(res, 400, 1, '用戶名密碼錯誤');

     }).catch(err => {
         responseClient(res);
     })
 });複製代碼

總結

基本到這,就是實現了一個查和增的過程。也實現了先後端的基本交互。你們感覺下哈~

而後你們確定也是發現了,登陸了之後,貌似每次刷新咱們都要再從新登陸,這並非咱們想要的。固然,這部分功能,咱們將在下一篇博客中介紹。

項目實現步驟系列博客


歡迎兄弟們加入:

Node.js技術交流羣:209530601

React技術棧:398240621

前端技術雜談:604953717 (新建)

相關文章
相關標籤/搜索