實戰react技術棧+express前後端部落格專案(5)-- 前後端實現登入功能

Neal_yang發表於2017-10-10

專案地址:github.com/Nealyang/Re…

本想等專案做完再連載一波系列部落格,隨著開發的進行,也是的確遇到了不少坑,請教了不少人。遂想,何不一邊記錄踩坑,一邊分享收穫呢。分享當然是好的,
如果能做到集思廣益,那豈不是更美。我們的口號是:堅決不會爛尾

本部落格為連載程式碼部落格同步更新部落格,隨著專案往後開發可能會遇到前面寫的不合適的地方會再回頭修改。如有不妥~歡迎兄弟們不嗇賜教。謝謝!

登入部分

  • 登入截圖

前端部分實現

接上篇,我們登入介面已經畫完了,登入功能,涉及到非同步請求。所以大致我需要需要如下幾個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', {
                        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中的程式碼如下:

 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的處理

因為登入和註冊都是非同步的,所以這裡我們需要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, 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 (新建)


相關文章