本想等專案做完再連載一波系列部落格,隨著開發的進行,也是的確遇到了不少坑,請教了不少人。遂想,何不一邊記錄踩坑,一邊分享收穫呢。分享當然是好的,
如果能做到集思廣益,那豈不是更美。我們的口號是:堅決不會爛尾
本部落格為連載程式碼部落格同步更新部落格,隨著專案往後開發可能會遇到前面寫的不合適的地方會再回頭修改。如有不妥~歡迎兄弟們不嗇賜教。謝謝!
登入部分
- 登入截圖
前端部分實現
接上篇,我們登入介面已經畫完了,登入功能,涉及到非同步請求。所以大致我需要需要如下幾個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);
})
});複製程式碼
總結
基本到這,就是實現了一個查和增的過程。也實現了前後端的基本互動。大家感受下哈~
然後大家肯定也是發現了,登入了以後,貌似每次重新整理我們都要再重新登入,這並不是我們想要的。當然,這部分功能,我們將在下一篇部落格中介紹。
專案實現步驟系列部落格
- [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 (新建)