vue2.0+koa2+mongodb實現註冊登陸

前言

前段時間和公司一個由技術轉產品的同事探討他的職業道路,對我說了一句深覺得然的話:css

「不要把本身禁錮在某一個領域,技術到產品的轉變,首先就是思惟上的轉變。你一直作前端,數據的交互你只知道怎麼進,殊不知道里面是怎麼出的,這就是侷限性。」前端

醍醐灌頂般,恰好學習vue的時候看到有個註冊登陸的項目,索性我也跟着動手作一個vue項目,引入koa和mongodb,實現客戶端(client)提交-服務端(server)接收返回-入數據庫全過程。vue

本項目基於vue-cli搭建,利用token方式進行用戶登陸驗證,並實現註冊入庫、讀取用戶、刪除用戶等功能。文章默認讀者有必定的node和vue基礎,基礎部分不贅述。node

系統環境:MacOS 10.13.3ios

關於npm安裝速度慢或不成功

使用淘寶鏡像安裝git

$ npm install -g cnpm --registry=https://registry.npm.taobao.org

而後全部的npm install改成cnpm installgithub

項目流程圖

爲了讓項目思路和所選技術更加清晰明瞭,畫了一個圖方便理解。web

image

項目啓動

0.項目地址
github:https://github.com/stzhongjie/vue-loginajax

1.初始化項目算法

$ npm install

2.啓動項目

$ npm run dev

3.啓動MongoDB

$ mongod --dbpath XXX

xxx是項目裏data文件夾(也能夠另行新建,數據庫用於存放數據)的路徑,也可直接拖入終端。

4.啓動服務端

$ node server.js

前端UI

vue的首選UI庫我是選擇了餓了麼的Element-UI了,其餘諸如iviewvue-strap好像沒有ele全面。

安裝Element-UI

$ npm i element-ui -S

引入Element-UI

//在項目裏的mian.js裏增長下列代碼
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

利用UI裏面的選項卡切換作註冊和登陸界面的切換,以login組件做爲整個登陸系統的主界面,register組件做爲獨立組件切入。Element-UI的組成方式,表單驗證等API請查閱官網。

//login組件
<template>
  <div class="login">
    <el-tabs v-model="activeName" @tab-click="handleClick">
      <el-tab-pane label="登陸" name="first">
        <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
          <el-form-item label="名稱" prop="name">
            <el-input v-model="ruleForm.name"></el-input>
          </el-form-item>
          <el-form-item label="密碼" prop="pass">
            <el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm('ruleForm')">登陸</el-button>
            <el-button @click="resetForm('ruleForm')">重置</el-button>
          </el-form-item>
        </el-form>
      </el-tab-pane>
      <el-tab-pane label="註冊" name="second">
        <register></register>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import register from '@/components/register'
export default {
  data() {
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('請輸入密碼'));
      } else {
        if (this.ruleForm.checkPass !== '') {
          this.$refs.ruleForm.validateField('checkPass');
        }
        callback();
      }
    };
    return {
      activeName: 'first',
      ruleForm: {
        name: '',
        pass: '',
        checkPass: '',
      },
      rules: {
        name: [
          { required: true, message: '請輸入您的名稱', trigger: 'blur' },
          { min: 2, max: 5, message: '長度在 2 到 5 個字符', trigger: 'blur' }
        ],
        pass: [
          { required: true, validator: validatePass, trigger: 'blur' }
        ]
      },

    };
  },
  methods: {
    //選項卡切換
    handleClick(tab, event) {
    },
    //重置表單
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
    //提交表單
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$message({
            type: 'success',
            message: '登陸成功'
          });
          this.$router.push('HelloWorld');
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
  },
  components: {
    register
  }
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
  width: 400px;
  margin: 0 auto;
}
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.el-tabs__item {
  text-align: center;
  width: 60px;
}
</style>

接下來是註冊組件

//register組件
<template>
  <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
    <el-form-item label="名稱" prop="name">
      <el-input v-model="ruleForm.name"></el-input>
    </el-form-item>
    <el-form-item label="密碼" prop="pass">
      <el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item label="確認密碼" prop="checkPass">
      <el-input type="password" v-model="ruleForm.checkPass" auto-complete="off"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm('ruleForm')">註冊</el-button>
      <el-button @click="resetForm('ruleForm')">重置</el-button>
    </el-form-item>
  </el-form>
</template>
<script>
export default {
  data() {
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('請輸入密碼'));
      } else {
        if (this.ruleForm.checkPass !== '') {
          this.$refs.ruleForm.validateField('checkPass');
        }
        callback();
      }
    };
    var validatePass2 = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('請再次輸入密碼'));
      } else if (value !== this.ruleForm.pass) {
        callback(new Error('兩次輸入密碼不一致!'));
      } else {
        callback();
      }
    };
    return {
      activeName: 'second',
      ruleForm: {
        name: '',
        pass: '',
        checkPass: '',
      },
      rules: {
        name: [
          { required: true, message: '請輸入您的名稱', trigger: 'blur' },
          { min: 2, max: 5, message: '長度在 2 到 5 個字符', trigger: 'blur' }
        ],
        pass: [
          { required: true, validator: validatePass, trigger: 'blur' }
        ],
        checkPass: [
          { required: true, validator: validatePass2, trigger: 'blur' }
        ],
      }
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          this.$message({
            type: 'success',
            message: '註冊成功'
          });
          // this.activeName: 'first',
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }
  }
}
</script>

vue-router

vue-router是vue建立單頁項目的核心,能夠經過組合組件來組成應用程序,咱們要作的是將組件(components)映射到路由(routes),而後告訴vue-router 在哪裏渲染它們。
上面的代碼裏已有涉及到一些路由切換,咱們如今來完善路由:

安裝

$ cnpm i vue-router

引入

import Router from 'vue-router'
Vue.use(Router)

在src文件夾下面新建 router(文件夾)/index.js
咱們引入了三個組件:

HelloWorld 登陸後的展現頁

login 登陸主界面

register 註冊組件

路由守衛

利用router.beforeEach路由守衛設置須要先登陸的頁面。經過requiresAuth這個字段來判斷該路由是否須要登陸權限,須要權限的路由就攔截,而後再判斷是否有token(下文會講到token),有就直接登陸,沒有就跳到登陸頁面。

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import login from '@/components/login'
import register from '@/components/register'
Vue.use(Router)

const router = new Router({
  mode: 'history',
  routes: [{
      path: '/',
      name: 'home',
      component: HelloWorld,
      meta: {
        requiresAuth: true
      }
    },
    {
      path: '/HelloWorld',
      name: 'HelloWorld',
      component: HelloWorld,
    },
    {
      path: '/login',
      name: 'login',
      component: login,
    },
    {
      path: '/register',
      name: 'register',
      component: register,
    },
  ]
});

//註冊全局鉤子用來攔截導航
router.beforeEach((to, from, next) => {
  //獲取store裏面的token
  let token = store.state.token;
  //判斷要去的路由有沒有requiresAuth
  if (to.meta.requiresAuth) {
    if (token) {
      next();
    } else {
      next({
        path: '/login',
        query: { redirect: to.fullPath } // 將剛剛要去的路由path做爲參數,方便登陸成功後直接跳轉到該路由
      });
    }
  } else {
    next(); 
  }
});
export default router;

咱們能夠看到路由守衛中token是從store裏面獲取的,意味着咱們是把token的各類狀態存放到store裏面,並進行獲取,更新,刪除等操做,這就須要引入vuex狀態管理。

vuex

解釋一下爲何一個簡單的註冊登陸單頁須要用到vuex:項目中咱們各個組件的操做基本都須要獲取到token進行驗證,若是組件A存儲了一個token,組件B要獲取這個token就涉及到了組件通訊,這會很是繁瑣。引入vuex,再也不是組件間的通訊,而是組件和store的通訊,簡單方便。

安裝

$ cnpm i vuex --S

引入

在main.js引入store,vue實例中也要加入store

//引入store
import store from './store'

而後在須要使用vuex的組件中引入

//store index.js
import Vuex from 'vuex'
Vue.use(Vuex)

在src文件夾下面新建 store(文件夾)/index.js

//store index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

//初始化時用sessionStore.getItem('token'),這樣子刷新頁面就無需從新登陸
const state = {
    token: window.sessionStorage.getItem('token'),
    username: ''
};

const mutations = {
    LOGIN: (state, data) => {
        //更改token的值
        state.token = data;
        window.sessionStorage.setItem('token', data);
    },
    LOGOUT: (state) => {
        //登出的時候要清除token
        state.token = null;
        window.sessionStorage.removeItem('token');
    },
    USERNAME: (state, data) => {
        //把用戶名存起來
        state.username = data;
        window.sessionStorage.setItem('username', data);
    }
};

const actions = {
    UserLogin({ commit }, data){
        commit('LOGIN', data);
    },
    UserLogout({ commit }){
        commit('LOGOUT');
    },
    UserName({ commit }, data){
        commit('USERNAME', data);
    }
};

export default new Vuex.Store({
    state,
    mutations,
    actions
});

能夠看到咱們經過actions提交mutation,進行token的更改、清除以及用戶名儲存的操做。

此時啓動項目,能夠看到初步的註冊登陸界面,點擊註冊或登陸按鈕能夠切換到相應界面,並有基礎的表單驗證,登陸後會進入helloworld頁面。

image

咱們寫好了基礎界面,接下來就是要把表單數據發送到後臺並進行一系列處理。如今尚未後端接口不要緊,咱們先寫好前端axios請求。

axios

vue的通信以前使用vue-resource,有不少坑。直到vue2.0來臨,直接拋棄vue-resource,而使用axios

用途:

封裝ajax,用來發送請求,異步獲取數據。以Promise爲基礎的HTTP客戶端,適用於:瀏覽器和node.js。

具體API中文說明:https://www.kancloud.cn/yunye/axios/234845

安裝

$ cnpm i -S axios

引入

import axios from 'axios'

攔截器

在設置vue-router那部分加入了路由守衛攔截須要登陸的路由,但這種方式只是簡單的前端路由控制,並不能真正阻止用戶訪問須要登陸權限的路由。當token失效了,但token依然保存在本地。這時候你去訪問須要登陸權限的路由時,實際上應該讓用戶從新登陸。這時候就須要攔截器interceptors + 後端接口返回的http狀態碼來判斷。

在src文件夾下面新建axios.js(和App.vue同級)

//axios.js
import axios from 'axios'
import store from './store'
import router from './router'

//建立axios實例
var instance = axios.create({
  timeout: 5000, //請求超過5秒即超時返回錯誤
  headers: { 'Content-Type': 'application/json;charset=UTF-8' },
});

//request攔截器
instance.interceptors.request.use(
  config => {
    //判斷是否存在token,若是存在的話,則每一個http header都加上token
    if (store.state.token) {
      config.headers.Authorization = `token ${store.state.token}`;
    }
    return config;
  }
);

//respone攔截器
instance.interceptors.response.use(
  response => {
    return response;
  },
  error => { //默認除了2XX以外的都是錯誤的,就會走這裏
    if (error.response) {
      switch (error.response.status) {
        case 401:
          router.replace({ //跳轉到登陸頁面
            path: 'login',
            query: { redirect: router.currentRoute.fullPath } // 將跳轉的路由path做爲參數,登陸成功後跳轉到該路由
          });
      }
    }
    return Promise.reject(error.response);
  }
);

export default {
    //用戶註冊
    userRegister(data){
        return instance.post('/api/register', data);
    },
    //用戶登陸
    userLogin(data){
        return instance.post('/api/login', data); 
    },
    //獲取用戶
    getUser(){
        return instance.get('/api/user');
    },
    //刪除用戶
    delUser(data){
        return instance.post('/api/delUser', data);
    }
}

代碼最後暴露了四個請求方法,分別對應註冊(register)、登陸(login)、獲取(user)、刪除(delUser)用戶,而且都在/api下面,四個請求接口分別是:

http://localhost:8080/api/login
http://localhost:8080/api/register
http://localhost:8080/api/user
http://localhost:8080/api/delUser

後面咱們再利用這四個方法寫相對應的後臺接口。

服務端 server

注意

文章從這裏開始進入服務端,因爲服務端須要和數據庫、http安全通信(jwt)共同搭建,所以請結合本節和下面的數據庫、jwt章節閱讀。

koa2可使用可使用async/await語法,免除重複繁瑣的回調函數嵌套,並使用ctx來訪問Context對象。

如今咱們用koa2寫項目的API服務接口。

安裝

$ cnpm i koa
$ cnpm i koa-router -S      //koa路由中間件
$ cnpm i koa-bodyparser -S  //處理post請求,並把koa2上下文的表單數據解析到ctx.request.body中

引入

const Koa = require('koa');

在項目根目錄下面新建server.js,做爲整個server端的啓動入口。

//server.js
const Koa = require('koa');
const app = new Koa();

//router
const Router = require('koa-router');

//父路由
const router = new Router();

//bodyparser:該中間件用於post請求的數據
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());

//引入數據庫操做方法
const UserController = require('./server/controller/user.js');

//checkToken做爲中間件存在
const checkToken = require('./server/token/checkToken.js');

//登陸
const loginRouter = new Router();
loginRouter.post('/login', UserController.Login);
//註冊
const registerRouter = new Router();
registerRouter.post('/register', UserController.Reg);

//獲取全部用戶
const userRouter = new Router();
userRouter.get('/user', checkToken, UserController.GetAllUsers);
//刪除某個用戶
const delUserRouter = new Router();
delUserRouter.post('/delUser', checkToken, UserController.DelUser);

//裝載上面四個子路由
router.use('/api',loginRouter.routes(),loginRouter.allowedMethods());
router.use('/api',registerRouter.routes(),registerRouter.allowedMethods());
router.use('/api',userRouter.routes(),userRouter.allowedMethods());
router.use('/api',delUserRouter.routes(),delUserRouter.allowedMethods());

//加載路由中間件
app.use(router.routes()).use(router.allowedMethods());

app.listen(8888, () => {
    console.log('The server is running at http://localhost:' + 8888);
});

代碼裏能夠看到,獲取用戶和刪除用戶都須要驗證token(詳見下文jwt章節),而且咱們把四個接口掛在到了/api上,和前面axios的請求路徑一致。

接口地址配置

另外因爲咱們的項目啓動端口是8080,koa接口監聽的端口是8888,因而須要在config/index.js文件裏面,在dev配置里加上:

proxyTable: {
    '/api': {
        target: 'http://localhost:8888',
        changeOrigin: true
    }
},

jsonwebtoken(JWT)

JWT可以在HTTP通訊過程當中,幫助咱們進行身份認證。

具體API詳見:https://segmentfault.com/a/1190000009494020

Json Web Token是怎麼工做的?

一、客戶端經過用戶名和密碼登陸服務器;

二、服務端對客戶端身份進行驗證;

三、服務端對該用戶生成Token,返回給客戶端;

四、客戶端將Token保存到本地瀏覽器,通常保存到cookie(本文是用sessionStorage,看狀況而定)中;

五、客戶端發起請求,須要攜帶該Token;

六、服務端收到請求後,首先驗證Token,以後返回數據。服務端不須要保存Token,只須要對Token中攜帶的信息進行驗證便可。不管客戶端訪問後臺的哪臺服務器,只要能夠經過用戶信息的驗證便可。

在server文件夾,下面新建/token(文件夾)裏面新增checkToken.js和createToken.js,分別放置檢查和新增token的方法。

安裝

$ cnpm i jsonwebtoken -S

createToken.js

const jwt = require('jsonwebtoken');
module.exports = function(user_id){
    const token = jwt.sign({user_id: user_id}, 'zhangzhongjie', {expiresIn: '60s'
    });
    return token;
};

建立token時,咱們把用戶名做爲JWT Payload的一個屬性,而且把密鑰設置爲‘zhangzhongjie’,token過時時間設置爲60s。意思是登陸以後,60s內刷新頁面不須要再從新登陸。

checkToken.js

const jwt = require('jsonwebtoken');
//檢查token是否過時
module.exports = async ( ctx, next ) => {
    //拿到token
    const authorization = ctx.get('Authorization');
    if (authorization === '') {
        ctx.throw(401, 'no token detected in http headerAuthorization');
    }
    const token = authorization.split(' ')[1];
    let tokenContent;
    try {
        tokenContent = await jwt.verify(token, 'zhangzhongjie');//若是token過時或驗證失敗,將拋出錯誤
    } catch (err) {
        ctx.throw(401, 'invalid token');
    }
    await next();
};

先拿到token再用jwt.verify進行驗證,注意此時密鑰要對應上createToken.js的密鑰‘zhangzhongjie’。若是token爲空、過時、驗證失敗都拋出401錯誤,要求從新登陸。

數據庫 mongodb

MongoDB是一種文檔導向數據庫管理系統,旨在爲 WEB 應用提供可擴展的高性能數據存儲解決方案。用node連接MongoDB很是方便。

安裝

$ cnpm i mongoose -S

MongoDB的鏈接有好幾種方式,這裏咱們用connection。connection是mongoose模塊的默認引用,返回一個Connetion對象。

在server文件夾下新建db.js,做爲數據庫鏈接入口。

//db.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/vue-login');

let db = mongoose.connection;
// 防止Mongoose: mpromise 錯誤
mongoose.Promise = global.Promise;

db.on('error', function(){
    console.log('數據庫鏈接出錯!');
});
db.on('open', function(){
    console.log('數據庫鏈接成功!');
});

//聲明schema
const userSchema = mongoose.Schema({
    username: String,
    password: String,
    token: String,
    create_time: Date
});
//根據schema生成model
const User = mongoose.model('User', userSchema)

module.exports = User;

除了咱們用的connetion,還有connect()createConnection()鏈接方式。

Schema定義表的模板,讓這一類document在數據庫中有一個具體的構成、存儲模式。但也僅僅是定義了Document是什麼樣子的,至於生成document和對document進行各類操做(增刪改查)則是經過相對應的model來進行的,那咱們就須要把userSchema轉換成咱們可使用的model,也就是說model纔是咱們能夠進行操做的handle。

編譯完model咱們就獲得了一個名爲User的model。

注意你在這裏定義的schema表,後面寫註冊入庫時數據的存儲須要對應這個表。

在server文件夾下新建controller(文件夾)/user.js,存放數據庫的操做方法。

先安裝一些功能插件

$ cnpm i moment -s                 //用於生成時間
$ cnpm i objectid-to-timestamp -s  //用於生成時間
$ cnpm i sha1 -s                   //安全哈希算法,用於密碼加密
//user.js
const User = require('../db.js').User;
//下面這兩個包用來生成時間
const moment = require('moment');
const objectIdToTimestamp = require('objectid-to-timestamp');
//用於密碼加密
const sha1 = require('sha1');
//createToken
const createToken = require('../token/createToken.js');

//數據庫的操做
//根據用戶名查找用戶
const findUser = (username) => {
    return new Promise((resolve, reject) => {
        User.findOne({ username }, (err, doc) => {
            if(err){
                reject(err);
            }
            resolve(doc);
        });
    });
};
//找到全部用戶
const findAllUsers = () => {
    return new Promise((resolve, reject) => {
        User.find({}, (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 Login = async ( ctx ) => {
    //拿到帳號和密碼
    let username = ctx.request.body.name;
    let password = sha1(ctx.request.body.pass);//解密
    let doc = await findUser(username);    
    if(!doc){
        console.log('檢查到用戶名不存在');
        ctx.status = 200;
        ctx.body = {
            info: false
        }
    }else if(doc.password === password){
        console.log('密碼一致!');

         //生成一個新的token,並存到數據庫
        let token = createToken(username);
        console.log(token);
        doc.token = token;
        await new Promise((resolve, reject) => {
            doc.save((err) => {
                if(err){
                    reject(err);
                }
                resolve();
            });
        });
        ctx.status = 200;
        ctx.body = { 
            success: true,
            username,
            token, //登陸成功要建立一個新的token,應該存入數據庫
            create_time: doc.create_time
        };
    }else{
        console.log('密碼錯誤!');
        ctx.status = 200;
        ctx.body = {
            success: false
        };
    }
};
//註冊
const Reg = async ( ctx ) => {
    let user = new User({
        username: ctx.request.body.name,
        password: sha1(ctx.request.body.pass), //加密
        token: createToken(this.username), //建立token並存入數據庫
        create_time: moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss'),//將objectid轉換爲用戶建立時間
    });
    //將objectid轉換爲用戶建立時間(能夠不用)
    user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss');

    let doc = await findUser(user.username);
    if(doc){ 
        console.log('用戶名已經存在');
        ctx.status = 200;
        ctx.body = {
            success: false
        };
    }else{
        await new Promise((resolve, reject) => {
            user.save((err) => {
                if(err){
                    reject(err);
                }   
                resolve();
            });
        });
        console.log('註冊成功');
        ctx.status = 200;
        ctx.body = {
            success: true
        }
    }
};
//得到全部用戶信息
const GetAllUsers = async( ctx ) => {
    //查詢全部用戶信息
    let doc = await findAllUsers();
    ctx.status = 200;
    ctx.body = {
        succsess: '成功',
        result: doc
    };
};

//刪除某個用戶
const DelUser = async( ctx ) => {
    //拿到要刪除的用戶id
    let id = ctx.request.body.id;
    await delUser(id);
    ctx.status = 200;
    ctx.body = {
        success: '刪除成功'
    };
};

module.exports = {
    Login,
    Reg,
    GetAllUsers,
    DelUser
};

上面這些方法構成了項目中數據庫操做的核心,咱們來剖析一下。

首先定義了公用的三個基礎方法:findUser、findAllUsers、delUser。其中findUser須要傳入username參數,delUser須要傳入id參數。

註冊方法

拿到用戶post提交的表單信息,new以前按數據庫設計好的並編譯成model的User,把獲取到的用戶名,密碼(須要用sha1哈希加密),token(利用以前建立好的createToken方法,並把用戶名做爲jwt的payload參數),生成時間存入。

此時要先搜索數據庫這個用戶名是否存在,存在就返回失敗,不然把user存入數據庫並返回成功。

登陸方法

拿到用戶post的表單信息,用戶名和密碼(註冊用了哈希加密,此時要解密)。從數據庫搜索該用戶名,判斷用戶名是否存在,不存在返回錯誤,存在的話判斷數據庫裏存的密碼和用戶提交的密碼是否一致,一致的話給這個用戶生成一個新的token,並存入數據庫,返回成功。

得到全部用戶信息

就是把上面公用findAllUsers方法封裝了一下並把信息放在result裏面,讓後面helloworld頁面能夠獲取到這個數據並展現出來。

刪除某個用戶

注意要先拿到須要刪除的用戶id,做爲參數傳入。

寫完這些方法,就能夠把前面沒有完善的註冊登陸功能完善了。

數據庫可視化

當咱們註冊完,數據入庫,此時咱們想查看一下剛纔註冊入庫的數據,要用到數據庫可視化工具。我是用MongoBooster,操做簡單。

由下圖能夠看到示例中註冊的兩條數據,包含了id、username、password、token、time。那串長長的密碼是因爲哈希加密編譯而成。

image

整合

完善註冊組件

在register.vue的表單驗證後加上下列代碼

//register.vue
if (valid) {
  axios.userRegister(this.ruleForm)
    .then(({}) => {
      if (data.success) {
        this.$message({
          type: 'success',
          message: '註冊成功'
        });
      } else {
        this.$message({
          type: 'info',
          message: '用戶名已經存在'
        });
      }
    })
}

完善登陸組件

登陸組件咱們以前沒有任何數據提交,如今在驗證成功後加入一系列方法完成登陸操做:
引入axios

import axios from '../axios.js'

而後在login.vue的表單驗證後加上下列代碼

//login.vue
if (valid) {
  axios.userLogin(this.ruleForm)
    .then(({ data }) => {
      //帳號不存在
      if (data.info === false) {
        this.$message({
          type: 'info',
          message: '帳號不存在'
        });
        return;
      }
      //帳號存在
      if (data.success) {
        this.$message({
          type: 'success',
          message: '登陸成功'
        });
        //拿到返回的token和username,並存到store
        let token = data.token;
        let username = data.username;
        this.$store.dispatch('UserLogin', token);
        this.$store.dispatch('UserName', username);
        //跳到目標頁
        this.$router.push('HelloWorld');
      }
    });
}

將表單數據提交到後臺,返回data狀態,進行帳號存在與否的判斷操做。登陸成功須要拿到返回的token和username存到store,跳到目標HelloWorld頁。

完善目標頁組件

註冊登陸成功後,終於到了實際的展現頁了——helloworld!

咱們來完善這個組件,讓它展現出目前全部的已註冊用戶名,並給出刪除按鈕。

//Helloworld.vue
<template>
  <div class="hello">
    <ul>
      <li v-for="(item,index) in users" :key="item._id">
        {{ index + 1 }}.{{ item.username }}
        <el-button @click="del_user(index)">刪除</el-button>
      </li>
    </ul>
    <el-button type="primary" @click="logout()">註銷</el-button>
  </div>
</template>

<script>
import axios from '../axios.js'
export default {
  name: 'HelloWorld',
  data () {
    return {
      users:''
    }
  },
  created(){
    axios.getUser().then((response) => {
      if(response.status === 401){
        //不成功跳轉回登陸頁
        this.$router.push('/login');
        //而且清除掉這個token
        this.$store.dispatch('UserLogout');
      }else{
        //成功了就把data.result裏的數據放入users,在頁面展現
        this.users = response.data.result;
      }
    })
  },
  methods:{
    del_user(index, event){
      let thisID = {
        id:this.users[index]._id
      }
      axios.delUser(thisID)
        .then(response => {
          this.$message({
            type: 'success',
            message: '刪除成功'
          });
          //移除節點
          this.users.splice(index, 1);
        }).catch((err) => {
          console.log(err);
      });
    },
    logout(){
      //清除token
      this.$store.dispatch('UserLogout');
      if (!this.$store.state.token) {
        this.$router.push('/login')
        this.$message({
          type: 'success',
          message: '註銷成功'
        })
      } else {
        this.$message({
          type: 'info',
          message: '註銷失敗'
        })
      }
    },
  }
}
</script>

<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
.hello {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  width: 400px;
  margin: 60px auto 0 auto;
}
</style>

輸出頁面比較簡單,這裏說幾個要點:

1.要在實例建立完成後(created())當即請求getUser()接口,請求失敗要清除掉token,請求成功要把返回數據放入user以供頁面渲染。

2.thisID要寫成對象格式,不然會報錯

3.註銷時要清除掉token

總結

人的思惟轉變確實是最難的。按流程來講,應該是koa先設計出接口,前端再根據這個接口去請求,但我反過來,是先寫好前端請求,再根據這個請求去制定接口。

固然,也遇到了不少困難:當我搞好了前端展現頁面,axios也寫好了,但在用koa寫接口這裏卡了好久,徹底沒有概念,就是前言說的「只知道數據怎麼進,不知道怎麼出」。而後遇到接口500報錯又調試了好久,主要是本身對接口沒有調試概念,最後仍是公司的琅琊大佬幫忙解決,感謝。

相關文章
相關標籤/搜索