实战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 (新建)
---