博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue+koa2实现最简单的注册登录功能
阅读量:4083 次
发布时间:2019-05-25

本文共 14532 字,大约阅读时间需要 48 分钟。

作为一个前端,工作之中总离不开前后端的数据联调,奈何前端的数据一直只知道怎么用,却不知道是怎么出来的,正好借着放假的期间,琢磨琢磨后端的一些逻辑。先从最最基本的注册登录来看一个完整的数据交互流程,初步的窥探一下全栈的世界。

因为vue项目使用较多,前端项目直接使用了vue,避免造轮子,UI框架也直接使用了常用的Element-UI,后端使用koa2,数据库引入mongodb,都是前端熟悉的JavaScript和json。

前端

  • 基本项目搭建部分忽略(可以直接使用vue-cli3进行项目的启动)
  • 本项目包括vue-router、vuex
  • 本项目为了尽量简化流程,只展示注册、登录、首页三个部分,首页包括退出登录的功能

基本页面

引入Element-UI、router等

src目录下的main.js需加上:

import Vue from 'vue'import App from './App.vue'import router from './router'import store from './store'import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'Vue.config.productionTip = falseVue.use(ElementUI)new Vue({  router,  store,  render: h => h(App)}).$mount('#app')复制代码

1. 登录界面

复制代码

2.注册界面

复制代码

3. 首页部分

4. router部分

import Vue from 'vue'import VueRouter from 'vue-router'import Home from '../views/Home.vue'import store from '../store'Vue.use(VueRouter)const routes = [  {    path: '/',    name: 'home',    component: Home,    meta: {      requiresAuth: true // 需要登录才能访问的页面    }  },  {    path: '/login',    name: '/login',    component: (resolve) => require([ '@/views/Login' ], resolve)  },  {    path: '/register',    name: '/register',    component: (resolve) => require([ '@/views/Register' ], resolve)  }]const router = new VueRouter({  routes})// 注册全局钩子用来拦截导航router.beforeEach((to, from, next) => {  let token = store.state.user.token  console.log('token:::', token)  if (to.meta.requiresAuth) {    if (token) {      next()    } else {      next({        path: '/login',        query: { redirect: to.fullPath } // 将刚刚要去的路由path(却无权限)作为参数,方便登录成功后直接跳转到该路由      })    }  } else {    next()  }})export default router复制代码

5. store部分

  • 在main.js中引入store,main.js文件内容上面已给出,为什么这么简单的一个项目还需要vuex呢?解释一下,因为在真实的项目中,各个组件、页面的操作基本都需要获取到token进行验证,如果不通过vuex进行通信,那就需要各个组件之间记住传递状态,这就比较麻烦了,
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({  state: {    token: window.sessionStorage.getItem('token') || '',    user: {      _id: window.sessionStorage.getItem('_id') || '',      user_name: window.sessionStorage.getItem('user_name') || ''    }  },  mutations: {    LOGIN: (state, data) => {      state.token = data      window.sessionStorage.setItem('token', data)    },    LOGOUT: (state) => {      state.token = null      window.sessionStorage.removeItem('token')    },    USERINFOR: (state, data) => {      state.user.user_name = data.user_name      state.user._id = data._id      window.sessionStorage.setItem('_id', data._id)      window.sessionStorage.setItem('user_name', data.user_name)    }  },  actions: {    UserLogin ({ commit }, data) {      commit('LOGIN', data)    },    UserLogout ({ commit }) {      commit('LOGOUT')    },    UserInfo ({ commit }, data) {      commit('USERINFO', data)    }  }})复制代码

6. axios的封装

  • 这一部分主要实现登录注册的接口请求,以及使用拦截器对是否登录进行一个验证,如果登录状态过期或未登录时,后端返回的code为401,则自动跳转到登录界面,同时清除token信息
import axios from 'axios'import store from '../store'import router from '../router'// 全局设置axios.defaults.timeout = 10000 // 时间超时设置10saxios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'// 创建一个axios的实列const instance = axios.create()instance.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'axios.interceptors.request.use = instance.interceptors.request.use// request拦截器,每次发送请求的时候拦截下来instance.interceptors.request.use(  config => {    // 每次发送请求,检查 vuex 中是否有token,如果有放在headers中    if (store.state.user.token) {      config.headers.Authorization = store.state.user.token    }    return config  },  err => {    return Promise.reject(err)  })// respone拦截器instance.interceptors.response.use(  response => {    return response  },  // 除了200以外的请求到这里来,,这里的200不是我们设置的那个code200,,我这里是,没有登录才会不返回200  error => {    let { response } = error    if (response != null) {      // 这里为什么处理401错误,详见,server/untils/token check_token这个函数      if (response.status === 401) {        let msg = response.data || '请重新登录!'        alert(msg)        store.commit('LOGOUT') // token过期,清除        router.replace({ // 跳转到登录页面          path: '/login',          // 添加一个重定向后缀,等登录以后再到这里来          query: { redirect: router.currentRoute.fullPath }        })        return Promise.reject(error.response)      }    } else {      console.log(error)    }  })// 添加API请求export default {  // 用户注册  userRegister (data) {    return instance.post('/api/user/register', data)  },  // 用户登录  userLogin (data) {    return instance.post('/api/user/login', data)  },  // 获取用户  getUser () {    return instance.get('/api/user')  },  // 删除用户  delUser (data) {    return instance.post('/api/user/delete', data)  }}复制代码

koa部分

到目前为止,前端vue部分的工作做好准备了,现在开始服务端部分:

1. 入口文件

在项目根目录下面新建server文件夹,作为整个server端项目目录,server目录下初始化package.json文件,npm init -y命令,并引入koa及其他中间件。

npm i koanpm i koa-router -Snpm i koa-bodyparser -S// koa-bodyparser: 支持x-www-form-urlencoded, application/json等格式的请求体,但不支持form-data的请求体, 用来解析body,比如通过post来传递表单、json或上传文件,数据不容易获取,通过koa-bodyparser解析之后,在koa中this.body就能获取到数据复制代码

index.js :

const Koa = require('koa');const bodyParser = require('koa-bodyparser'); // koa-bodyparser: 支持x-www-form-urlencoded, application/json等格式的请求体,但不支持form-data的请求体// 用来解析body,比如通过post来传递表单、json或上传文件,数据不容易获取,通过koa-bodyparser解析之后,在koa中this.body就能获取到数据const json = require('koa-json') // 告诉客户端『返回的是 JSON 数据』const logger = require('koa-logger') // 提供了输出请求日志的功能,包括请求的url、状态码、响应时间、响应体大小等信息const onerror = require('koa-onerror') // koa有error事件,当发生错误,可以通过error事件,对错误统一处理const staticServe = require('koa-static') // 用于koa的静态文件指定映射路径const {check_token} = require('./utils/token')const app = new Koa();onerror(app)app.use(bodyParser());app.use(json())app.use(logger())app.use(staticServe(__dirname + '/public'))// 添加token 验证中间件app.use(check_token);// loggerapp.use(async (ctx, next) => {    const start = new Date()    await next()    const ms = new Date() - start    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)})// routesconst index = require('./routes/index')app.use(index.routes(), index.allowedMethods())// error-handlingapp.on('error', (err, ctx) => {    console.error('server error', err, ctx)  });app.listen(3000, () => {    console.log('The server is running at http://localhost:' + 3000);});复制代码

2. 路由

新建一个文件夹,routes,路由文件写在index.js文件中

const router = require('koa-router')()const controller = require('../controller')router.get('/', async (ctx, next) => {  ctx.body = "hello,armor"}).post("/api/user/register", controller.user.register)  // 用户注册.post("/api/user/login", controller.user.login) // 用户登录.get('/api/user',controller.user.allUser) // 获取所有用户.post('/api/user/delete',controller.user.deleteUser) // 获取所有用户module.exports = router复制代码
  • 以上四个路由正是之前前端请求的四个接口,处理逻辑我们放在controller文件夹中

3. JWT验证

  • 建立utils文件夹,新建token.js文件,声明create_token和check_token,用于验证登录
  • config配置文件,需要区别出不需要登录的接口,比如注册和登录,不需要鉴权返回401
const jwt = require('jsonwebtoken') // 用于签发、解析 tokenconst { TOKEN_ENCODE_STR, URL_YES_PASS } = require('./config')const User = require('../db').Usermodule.exports = {  // 生成登录 token  create_token (str) {    const token = jwt.sign({ str }, TOKEN_ENCODE_STR, {      expiresIn: '1h'    })    return token  },  /*      验证登录 token 是否正确  => 写成中间件    get 请求与设置的请求不拦截验证,其余均需登录  */  async check_token (ctx, next) {    let url = ctx.url    console.log('ctx.url:::', ctx.url);    if (ctx.method != 'GET' && !URL_YES_PASS.includes(url)) {      let token = ctx.get('Authorization')      if (token === '') {        ctx.response.status = 401        ctx.response.body = '你还没有登录,快去登录吧!'        return      }      try {        // 验证token是否过期        let { str = '' } = await jwt.verify(token, TOKEN_ENCODE_STR)        // 验证token与账号是否匹配        let res = await User.find({ user_id: str, token })        if (res.length === 0) {          ctx.response.status = 401          ctx.response.body = '登录过期,请重新登录!'          return        }        // 保存用户的_id        ctx._id = res[0]._id      } catch (e) {        ctx.response.status = 401        ctx.response.body = '登录已过期请重新登录!'        return      }    }    await next()  }}复制代码

config.js:

module.exports = {    // 用户密码加密字符串    PWD_ENCODE_STR: "pawn_user_encode_str",    // token 加密字符串,    TOKEN_ENCODE_STR: "pawn_token_encode_str",    // 添加非get请求通过的连接    URL_YES_PASS: ['/api/user/login', '/api/user/register']  }复制代码

数据库mongodb

1. 建表

首先需要安装mongodb,可以在网上找到很多下载的方式,这里略过。

至于数据库可视化工具,我这里使用的是adminMongo,直接拉取GitHub上的代码就可以,

  1. git clone https://github.com/mrvautin/adminMongo.git && cd adminMongo
  2. npm install
  3. npm start or node app
  4. 直接访问 , 至此,adminmongo 安装完成。

建立db文件夹,作为连接数据库的入口,建立用户表:

const mongoose = require('mongoose');const db = mongoose.connect("mongodb://localhost:27017/test", {useNewUrlParser:true}, function(err){  if(err){    console.log(err)  }else{    console.log("Connection success!")  }})const Schema = mongoose.Schema; // 用户let userSchema = new Schema({  user_name: String,  user_id: String,  password: String,  create_time: Date,  token: {    type: String,    default: ""  }})exports.User = mongoose.model('User', userSchema); 复制代码

2. 操作数据库

建立controller文件夹,主要对数据库进行操作

const User = require('../db').Userconst sha1 = require('sha1') // 用于密码加密const { PWD_ENCODE_STR } = require('../utils/config')const { create_token } = require('../utils/token')const xss = require('xss')//下面这两个包用来生成时间const moment = require('moment')const objectIdToTimestamp = require('objectid-to-timestamp')//根据用户名查找用户const findUser = (name) => {  return new Promise((resolve, reject) => {    User.findOne({ user_name: name }, (err, doc) => {      if (err) {        reject(err)      }      resolve(doc)    })  })}//删除某个用户const delUser = function (id) {  return new Promise((resolve, reject) => {    User.findOneAndRemove({ _id: id }, (err) => {      if (err) {        reject(err)      }      console.log('删除用户成功')      resolve()    })  })}// 用户注册const register = async (ctx) => {  console.log('注册:::,', ctx.request.body);  let { name = '', pass = '' } = ctx.request.body  try {    if (name === '' || pass === '') {      ctx.body = {        code: 401,        msg: '注册失败,请填写完整表单!'      }      return    }    if (pass.length < 3) {      ctx.body = {        code: 401,        msg: '注册失败,密码最少为3位!'      }      return    }    let doc = await findUser(name)    console.log('doc::', doc);    if (doc) {      ctx.body = {        code: 409,        msg: '注册失败,该用户名已经存在!'      }      return    } else {      pass = sha1(sha1(pass + PWD_ENCODE_STR))      // 防止xss攻击, 转义      name = xss(name)      console.log('pass;::', pass);      console.log('name;::', name);      let token = create_token(name)      let user = new User({        user_name: name,        password: pass,        token //创建token并存入数据库      })      user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss') // 将objectid转换为用户创建时间      console.log('user;::', user);      await new Promise((resolve, reject) => {        user.save((err) => {          if (err) {            reject(err)          }          resolve()        })      })      console.log('注册成功')      ctx.status = 200      ctx.body = {        status: true,        data: {          id: user._id,          token,          user_name: name        }      }    }  } catch (e) {    console.log(e)    ctx.body = {      code: 500,      msg: '注册失败,服务器异常!'    }  }}// 用户登录const login = async (ctx) => {  let { name = '', pass = '' } = ctx.request.body  try {    if (name === '' || pass === '') {      ctx.body = {        code: 401,        msg: '登录失败,请输入登录账号或密码!'      }      return    }    // 解密    pass = sha1(sha1(pass + PWD_ENCODE_STR))    console.log('pass1::', pass);    let res = await findUser(name)    console.log('pass2::', res);    if (!res) {      ctx.body = {        code: 401,        msg: '登录失败,用户名或者密码错误!'      }      return    } else if (pass === res.password) {      let token = create_token(name)      res.token = token      await new Promise((resolve, reject) => {        res.save((err) => {          if (err) {            reject(err)          }          resolve()        })      })      ctx.status = 200      ctx.body = {        status: true,        data: {          id: res._id,          token,          user_name: name        }      }    } else {      ctx.status = 200      ctx.body = {        success: false      }    }  } catch (e) {    console.log(e)    ctx.body = {      code: 500,      msg: '登录失败,服务器异常!'    }  }}const deleteUser = async (ctx) => {  //拿到要删除的用户id  let id = ctx.request.body.id  await delUser(id)  ctx.status = 200  ctx.body = {    success: '删除成功'  }}module.exports = {  register,  login,  deleteUser}复制代码

完善交互逻辑

完成后端以及数据库的开发之后,可以开始整合前端的页面,获取接口返回的响应数据,并进行相应的操作

如,注册页面,提交注册的逻辑如下:

submitForm (formName) {      this.$refs[formName].validate(valid => {        if (valid) {          api.userRegister(this.ruleForm).then(res => {            if (res.data.status) {              this.$message({                message: '注册成功!',                type: 'success'              })              this.$store.dispatch('UserRegister', res.data.data)              this.$router.push('/home')            } else {              this.$message({                message: '注册失败',                type: 'error'              })            }          })        } else {          return false        }      })    },复制代码

登录逻辑:

submitForm (formName) {      this.$refs[formName].validate(valid => {        if (valid) {          api.userLogin(this.ruleForm).then(res => {            if (res.data.status) {              this.$message({                message: '登录成功!',                type: 'success'              })              this.$store.dispatch('UserLogin', res.data.data)              this.$router.push('/home')            } else {              this.$message({                message: '登录失败',                type: 'error'              })            }          })        } else {          console.log('error submit!!')          return false        }      })    },复制代码

现在可以完成首页的设计了,这里只设置了一个退出功能的按钮,具体内容可以继续完善。

首页:Home.vue

复制代码

至此,最简单的一个注册登录demo完成了,具备注册、登录、退出登录三个最基本的功能,技术不难,主要为熟悉整个流程,还不能完全用于实际项目中,还需要去更多的完善功能。

作者:Armor
链接:https://juejin.im/post/5e2fa4b7e51d451c7a436856
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
可以买个好点的电烙铁
查看>>
ACfly调参记录(包括ACfly-F330和ACfly-T265)
查看>>
一定记得每飞几次或者隔一天要把螺丝和浆帽拧一次,确实会松的
查看>>
《多旋翼无人飞行器嵌入式飞控开发指南》里基于FreeRTOS的无人机软件框架
查看>>
思岚A1的SDK其实很好读懂,每个函数清晰明了,可以直接调用
查看>>
pixhawk(PX4)的一些论坛网站(包括中文版的PX4用户手册和PX4开发手册)
查看>>
串级 PID 为什么外环输出是内环的期望?(和我之前对串级PID的总结一样)
查看>>
我刚刚才完全清楚GPS模块的那根杆子是怎么固定安装好的
查看>>
去github里面找找也没有别人无人机+SLAM的工程
查看>>
PX4与ROS关系以及仿真控制(键盘控制无人机)
查看>>
我对无人机重心高度的理解
查看>>
现在明白为什么无名博客里好几篇文章在讲传感器的滞后
查看>>
实际我看Pixhawk定高模式其实也是飞得很稳,飘得也不厉害
查看>>
Pixhawk解锁常见错误
查看>>
C++的模板化等等的确实比C用起来方便多了
查看>>
ROS是不是可以理解成一个虚拟机,就是操作系统之上的操作系统
查看>>
用STL algorithm轻松解决几道算法面试题
查看>>
ACfly之所以不怕炸机因为它觉得某个传感器数据不安全就立马不用了
查看>>
我发觉,不管是弄ROS OPENCV T265二次开发 SDK开发 caffe PX4 都是用的C++
查看>>
ROS的安装(包含文字和视频教程,我的ROS安装教程以这篇为准)
查看>>