Vue博客搭建(8)登录系统

673 阅读5分钟

在上一篇文章中我们创建了一个注册页面,现在我们需要创建登录页面,来保存我们的登录状态。

基本实现

登录的后端其实和注册差不多,都是发送两个参数usernamepassword。这里展示我们的后端代码,有两个可能出现的错误:用户不存在以及密码错误。

app.post('/login',(req, res)=>{
    const username = req.body.username;
    let password = req.body.password;
    password = SHA512(password).toString();
    const selectSql = `select * from useraccount where username = '${username}'`;
    connection.query(selectSql,(err,result)=>{
        if(result.length===0){
            res.status(400).send('用户不存在!');
        }else if(result[0].password !== password){
            res.status(400).send('密码错误!');
        }else{
            res.send('登录成功!')
        }
    })
})

/pages文件夹下创建一个login.vue,然后按着相似的方式把前端代码写好,并在主页中添加对应的路由以及导航页面。

<script setup>

import axios from 'axios'
import {ref} from 'vue'
import {useRouter} from 'vue-router'

const router = useRouter();

let usernameInput = ref('');
let passwordInput = ref('');

function login(){
    const username = usernameInput.value;
    const password = passwordInput.value;
    axios.post('/server/login',{
        username: username,
        password: password
    })
    .then((response)=>{
        alert(response.data);
        router.push('/');
    })
    .catch((err)=>{
        if(err.response){
            alert(`登录失败,${err.response.data}`);
        }else{
            alert(`登录失败,本地错误${err}`);
        }
        
    })
}

</script>

<template>
    登录
    <div>
        用户名
        <input v-model="usernameInput"/>
        密码
        <input v-model="passwordInput"/>
        <button @click="login">登录</button>
    </div>
</template>

试一下,发现可以登录了。但是我们居然可以登录无数次,这是无法接受的,因此我们需要在整个Vue应用中设置一个全局变量来判断我们的登录状态。

全局变量的引入

目前的Vue3中的全局变量使用的是pinia插件来进行设置的,虽然这严格来讲叫作“跨组件管理状态”,但是实际上和全局变量并没有什么区别。为什么需要跨组件管理状态呢?因为我们虽然学过了一些传参的方法,但是这种方法很难对兄弟层级的组件传递,而且也很难进行反方向的传递,而且其在服务端上的安全性也很难保证。因此便需要pinia来解决这些问题。

安装后只需要创建一个pinia实例到根节点中,就可以使用了,和vue-router的用法是一样的。

import {createPinia} from 'pinia'

const pinia = createPinia();

const app = createApp(App);
app.use(router)
app.use(pinia);
app.mount('#app')

在pinia实例被创建之后,我们需要定义一个Store对象,这个对象就承载着整个应用的全局变量,分为三个部分,分别是stategetteraction。我们先在stores文件夹下创建一个文件userAccount.js来定义一个简单的Store对象。

import { defineStore } from "pinia";
import { ref } from "vue";

export const useUserAccountStore = defineStore('userAccount',()=>{
    const isLogged = ref(false);
    const username = ref(undefined);

    function login(usernameInput){
        if(isLogged.value === false){
            isLogged.value = true;
            username.value = usernameInput;
        }else{
            throw(new Error('用户已登录'));
        }
    }
    function logout(){
        if(isLogged.value === true){
            isLogged.value = false;
            username.value = undefined;
        }else{
            throw(new Error('请先登录'));
        }
    } 

    return{
        isLogged,
        username,
        login,
        logout
    }
})

我们注意到pinia必须接收一个返回了store中所有属性的结构体(否则变量并不会对外暴露),你也可以使用类似于Vue2中的选项式api来构造对应的函数,那就要为stateactionwgetters分别进行定义,而使用单一的组合式函数,pinia会对其自动进行识别。

useUserAccountStore被定义好后,我们在login.vue中就可以像下面那样使用它。

axios.post('/server/login',{
        username: username,
        password: password
    })
    .then((response)=>{
        userAccountStore.login(usernameInput.value);
        alert(response.data);
        router.push('/');
    })

即使是这样,我们依然会碰到另外的问题:我们在每次页面刷新后登录状态都消失了,这和一般的网站是完全不一样的,因此我们接下来要实现这个功能。

登录状态的保存

为什么我们每次刷新后登录状态都消失了?因为http是无状态会话请求,并不能保存历史记录,每次请求之间互相独立,这就需要我们在http之外的地方进行历史记录的保存。

主流的登录状态保存有三个术语,cookiesessiontoken,接下来我们先讲解一下这三个词的含义。

cookie是客户端中存储的一块内存(以键值对形式存储),用来保存用户数据,在目前的大多数浏览器中,cookie是永久保存的,因此可以用来保存登录状态。当后端收到登录请求时,可以使用对应的函数来给服务器设置cookie,这样就可以返回一个带有cookie的response

session是服务器中的一段内存,服务器发送带有cookie的响应时,便会开辟一块空间存储session,当客户端再次发送带有cookie的请求头时,便可以访问服务器存储的对应的session

token其实是结合了cookie和session的特点后的实现,客户端发送数据后,服务器生成一个令牌返回给客户端。客户端之后再发送请求时会发送这个token,然后服务器会进行校验,校验成功后则可以访问对应的信息。token一般是加密的,而且服务器本身不会进行任何存储,安全性较高。

我们这里就使用简单的cookie来进行登录状态保存。在express中使用res.cookie来进行cookie的发送。这是修改后的代码:

res.cookie('isLogged', true);
res.cookie('username', username);
res.send('登录成功!');

那么我们的前端应该如何获取到这些cookie呢?我们可以使用document.cookie来获取所有的cookie,但是这很难找到我们想要的。因此我们使用js-cookie包来帮助我们处理cookie。处理后的前端代码变成了这样:

import Cookies from "js-cookie";

export const useUserAccountStore = defineStore('userAccount',()=>{
    const isLoggedCookie = Cookies.get('isLogged');
    const usernameCookie = Cookies.get('username');
    const isLogged = isLoggedCookie ? ref(true) : ref(false);
    const username = usernameCookie ? ref(usernameCookie) : ref(undefined);
    // ...
}

v-if

最后,当我们在登录之后,注册和登录按钮就不应该再有了,需要被隐藏起来。因此这里再介绍一个Vue响应式指令——v-if。v-if被用来进行条件渲染,也就是当里面的表达式为true时才会显示。与此配套的有v-else和v-else-if。下面是我们添加了条件渲染之后的导航栏。

<template>
    <button data-to = "/" @click="routerTo">主页</button>
    <button data-to = "/login" @click="routerTo" v-if="!userAccountStore.isLogged">登录</button>
    <button data-to ="/register" @click="routerTo" v-if="!userAccountStore.isLogged">注册</button>
    <router-view></router-view>
</template>

除了v-if之外,还有一个对应的指令叫做v-show,它和v-if的区别在于:v-if是真的不加载这个组件了,而v-show加载这个组件,只是让组件本身变得不可见而已。