• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • laravel9 + vue3 前端使用 axios 获取携带 token 访问令牌

    承接上文,现在开始编写 vue 前端的 axios 的请求文件。token 有三类:

    • 客户端令牌:为非登录用户获取前端数据。
    • 密码授权令牌 user:为登录用户,用户中心获取数据。
    • 密码授权令牌 admin:为后台管理员登录后,获取数据。

    携带客户端令牌(token)请求数据

    客户端令牌,实现目标:当没有 token 的时候,先获得 token,然后携带 token 取请求后端数据。若不存在 token,或者 token 验证错误,无法获得数据。

    https://api.example.com/api/home/token/client发送请求,返回数据格式:{"token_type":"Bearer","expires_in":31536000,"access_token":"xxxxxx"}

    文件目录:src/http/index.ts

    import axios from 'axios';
    import type { AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';
    import { storageL } from '@/utils/webStorage';
    
    axios.defaults.timeout = 5000;
    axios.defaults.baseURL = import.meta.env.VITE_HTTP_URL;
    axios.defaults.headers.common['Content-Type'] = 'application/json;charset=UTF-8';
    axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
    axios.defaults.withCredentials = false;
    
    type AccessTokenCallback = (arg: string) => void;
    let callbacks: AccessTokenCallback[] = [];
    function token2ConfigPush(cb: AccessTokenCallback) {
      callbacks.push(cb);
    }
    
    function token2ConfigRun(token: string) {
      callbacks.map((cb) => cb(token));
    }
    
    let isLock = false;
    const tokenClienUrl = '/token/client';
    
    axios.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        const accessToken = storageL.getItem('accessToken');
        if (accessToken) {
          isLock = true;
          config.headers = {
            Authorization: 'Bearer ' + accessToken,
          };
    
          return config;
        } else {
          if (!isLock) {
            isLock = true;
            axios
              .get(tokenClienUrl)
              .then((response) => {
                storageL.setItem('accessToken', response.data.access_token, response.data.expires_in);
                token2ConfigRun(response.data.access_token);
                callbacks = [];
              })
              .catch((error) => {
                isLock = false;
                callbacks = [];
                storageL.removeItem('accessToken');
                return Promise.reject(error);
              });
          }
    
          if (config.url && config.url.indexOf(tokenClienUrl) == -1) {
            const retry = new Promise((resolve) => {
              token2ConfigPush((accessToken: string) => {
                config.headers = {
                  Authorization: 'Bearer ' + accessToken,
                };
                resolve(config);
              });
            });
            return retry;
          } else {
            return config;
          }
        }
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
    axios.interceptors.response.use(
      (response: AxiosResponse) => {
        return response;
      },
      (error: AxiosError) => {
        return Promise.reject(error);
      }
    );
    
    export default axios;
    

    文件目录src//utils/webStorage

    type storageValue = string | number | boolean | null | object | object[] | string[] | number[] | boolean[] | null[];
    
    interface storageItem {
      value: storageValue;
      expires: number;
    }
    
    class webStorage {
      public memory: Storage;
      constructor(type: string) {
        if (type === 'localStorage') {
          this.memory = window.localStorage;
        } else {
          this.memory = window.sessionStorage;
        }
      }
    
      //小时转化为秒
      public hour2Second(hour: number) {
        return hour * 60 * 60;
      }
    
      //当设置了有效期,时间为秒s。默认值0,长期有效。
      public setItem(key: string, value: storageValue, second = 0) {
        const source: storageItem = { value: null, expires: 0 };
        if (second) {
          source.expires = new Date().getTime() + second;
        }
        source.value = value;
        const data = JSON.stringify(source);
        this.memory.setItem(key, data);
      }
    
      //当有效期为0时,永久有效。否则检测是否过期,过期则清除。
      public getItem(key: string) {
        const data = this.memory.getItem(key);
        if (data) {
          if (data.length == 0) return '';
          const source: storageItem = JSON.parse(data);
          const now = new Date().getTime();
          if (source.expires && now > source.expires) {
            this.memory.removeItem(key);
            return '';
          }
          return source.value;
        } else {
          return '';
        }
      }
    
      public removeItem(key: string) {
        this.memory.removeItem(key);
      }
    
      public clear() {
        this.memory.clear();
      }
    }
    
    const storageL = new webStorage('localStorage');
    const storageS = new webStorage('sessionStorage');
    export { storageL, storageS };
    


    Javascript数组中存储函数

    //定义储存函数的数组
    var funs=[
       function(){ echo "1"; },
       function(){ echo "2"; },
       function(){ echo "3"; },
       function(){ echo "3"; },
    ];
    
    //调用执行
    funs[1]();
    
    //定义储存函数的对象
    var functions = {
        blah: function(){ alert("blah"); },
        foo: function() { console.log("foo"); }
    };
    
    //调用执行
    functions.blah();
    functions["blah"]();
    


    携带密码授权令牌(token)请求数据

    管理后台,管理员 admin 登录后,获得密码授权令牌(token),每次请求都需要携带密码授权令牌(token)。若不存在 token,或者 token 验证错误。强制退出登录。

    文件目录:src/http/adminHttp.ts

    import axios from 'axios';
    import type { AxiosResponse,InternalAxiosRequestConfig, AxiosError } from 'axios';
    import { useAdminTokenStore } from '@/stores';
    
    axios.defaults.timeout = 5000;
    axios.defaults.baseURL = import.meta.env.VITE_HTTP_URL;
    axios.defaults.headers.common['Content-Type'] = 'application/json;charset=UTF-8';
    axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
    axios.defaults.withCredentials = false;
    
    axios.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        if (config.url && config.url.includes(import.meta.env.VITE_TOKEN_LOGIN)) {
          return config;
        }
        const adminTokenStore = useAdminTokenStore();
        const adminToken = adminTokenStore.getToken;
        if (adminToken) {
          if (config.headers && !config.headers['Authorization']) {
            config.headers['Authorization'] = 'Bearer ' + adminToken;
          }
          return config;
        } else {
          adminTokenStore.removeToken();
          return Promise.reject();
        }
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
    axios.interceptors.response.use(
      (response: AxiosResponse) => {
        return response;
      },
      (error: AxiosError) => {
        console.log(error.config);
        return Promise.reject(error);
      }
    );
    
    export default axios;
    

    ⚠️注意:新版axious 1.2+,类型是InternalAxiosRequestConfig,旧版是AxiosRequestConfig


    Pinia:@/stores/index

    import { defineStore } from 'pinia';
    
    interface User {
      email: string;
      nickname: string;
      icon: string;
      role: string;
    }
    
    export const useAdminStore = defineStore({
      id: 'admin',
      state: () => ({
        user: {
          email: '',
          nickname: '',
          icon: '',
          role: '',
        } as User,
      }),
      getters: {
        getUser(): User {
          const storage = new webStorage('localStorage');
          this.user = storage.getItem('admin') as User;
          return this.user;
        },
      },
      actions: {
        setUser(data: User, expires_in: number) {
          this.user = data;
          const storage = new webStorage('localStorage');
          storage.setItem('admin', data, expires_in);
        },
        removeUser() {
          this.user = { email: '', nickname: '', icon: '', role: '' };
          const storage = new webStorage('localStorage');
          storage.removeItem('admin');
        },
      },
    });
    



    cookie、localStorage、sessionStorage

    cookie

    Cookie基于HTTP规范,Cookie诞生之初的作用就是解决HTTP的无状态请求,用来记录一些用户相关的一些状态。Cookie是服务器发送到浏览器的一小段数据,通常式用来辨别用户身份的数据,会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

    cookie 格式
    document.cookie="my_cookie_name=my_cookie_value;expires=toDate;domain=www.baidu.com;path=/"
    
    • “my_cookie_name=my_cookie_value”这一字段指定了cookie名“my_cookie_name”和cookie值“my_cookie_value”。
    • expires 指定 cookie 过期时间是“toDate”,注意“toDate”必须是UTC时区的时间字符串(可以通过Date.prototype.toUTCString()转换得到)。
    • domain 指定该 cookie 支持的域。比如:www.baidu.com。
    • path 指定 cookie 的有效路径。指定为‘/’时表示所有路径有效,如果指定为‘/api’则表示仅在‘/api’下的所有路径有效。
    设置 cookie
    function setCookie(name, value, hour, domain, path)
    {
        var exp = new Date()
        exp.setTime(exp.getTime() + (hour*60*60*1000))
        var expires = "expires=" + exp.toUTCString()
        var path_ = path ? "path=" + path : "path=/"
        var domain_ = domain ? "domain=" + domain + ';' : ';'
        document.cookie = name + "=" + escape (value) + ";" + domain_ + expires + ";" + path_
    }
    
    获得 cookie
    function getCookie(name)
    {
        const reg = new RegExp('(^| )'+ name + '=([^;]*)(;|$)')
        if (arr = document.cookie.match(reg)) {
            return unescape(arr[2])
        } else {
            return ''
        }
    }
    
    删除 cookie
    function clearCookie(name)
    {
        setCookie(name, '', -1)
    }
    
    • escape(string):函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。
    • unescape(string):函数可对通过 escape()编码的字符串进行解码。


    webStorage

    webStorage 基于 HTML5 规范。HTML5 提供了两种在客户端存储数据的新方法:localStoragesessionStorage,挂载在window对象下。webStorage 是本地存储,数据不是由服务器请求传递的。从而它可以存储大量的数据,而不影响网站的性能。webStorage的目的是为了克服由cookie带来的一些限制,当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。比如客户端需要保存的一些用户行为或数据,或从接口获取的一些短期内不会更新的数据,我们就可以利用webStorage来存储。

    • localStorage生命周期是永久性的。localStorage存储的数据,即使关闭浏览器,也不会让数据消失,除非主动的去删除数据。localStorage 被大多数浏览器所支持,IE8+。
    • sessionStorage数据只在会话期间有效,刷新页面数据依旧存在。但当页面关闭后,数据就会被清空。sessionStorage 被大多数浏览器所支持,IE8+。

    localStorage 和 sessionStorage 所使用的方法是一样的,下面以sessionStorage为例子:

    var name='sessionData';
    var num=120;
    
    sessionStorage.setItem(name,num);   //存储数据
    sessionStorage.setItem('value2',119);
    
    let dataAll=sessionStorage.valueOf();   //获取全部数据
    console.log(dataAll,'获取全部数据');
    
    var dataSession=sessionStorage.getItem(name);   //获取指定键名数据
    var dataSession2=sessionStorage.sessionData;   //sessionStorage是js对象,也可以使用key的方式来获取值
    console.log(dataSession,dataSession2,'获取指定键名数据');
    
    sessionStorage.removeItem(name);   //删除指定键名数据
    console.log(dataAll,'获取全部数据1');
    
    sessionStorage.clear();   //清空缓存数据:localStorage.clear();
    console.log(dataAll,'获取全部数据2');  
    


    三者异同

    特性CookielocalStoragesessionStorage
    数据的生命期可设置失效时间,默认是关闭浏览器后失效除非被清除,否则永久保存仅在当前会话下有效,关闭页面或浏览器后被清除
    存放数据大小4K左右一般为 5MB一般为 5MB
    与服务器端通信每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题仅在客户端(即浏览器)中保存,不参与和服务器的通信仅在客户端(即浏览器)中保存,不参与和服务器的通信
    易用性需要程序员自己封装,源生的Cookie接口不友好源生接口可以接受,亦可再次封装来对Object和Array有更好的支持源生接口可以接受,亦可再次封装来对Object和Array有更好的支持


    vue储存数据

    在 vue 中储存数据,可以使用本地储存 localstorage 、sessionStorage,还可以储存在 vuex 中。

    • vuex 中的数据具备响应性,会随着数据的改变而渲染视图,另外 vuex 是储存在内存中的,读取速度快,但是一旦刷新浏览器,数据就会丢失。
    • localstorage 、sessionStorage的数据不是响应式的,即使本地存储数据改变了,视图也不会立刻渲染,另外其数据是储存在硬盘上的,读取速度相对vue,要慢很多。但能长期有效。

    所以,在实际开发中,把像 token 这样的数据会分别储存在本地数据中,还会出现在 vuex 中,共储存两份。首先读取 vuex 中数据,若不存在,再读取 localstorage(或 sessionStorage)中数据。每次更新token也是修改vuex中的token,然后再覆盖到localstorage 中。