目录
一、功能
存储封装
:处理浏览器存储(localStorage 和 sessionStorage)以及 cookie 的工具类Pinia
: 状态管理模块的封装双token
:双Token机制,特别是结合无感刷新登录状态的设计,主要用来增强用户的访问安全性,同时保持用户体验的流畅性,避免频繁登录取消重复请求
: 完全相同的接口在上一个pending状态时,自动取消下一个请求请求失败自动重试
: 接口请求后台异常时候,自动重新发起多次请求,直到达到所设次数
二、基础
axios请求
axios
是一个基于 promise 的 HTTP 库,广泛用于浏览器和node.js
中执行HTTP
请求。它支持XMLHttpRequests(XHR)
作为浏览器环境下的基础技术,并在 Node.js 环境下使用http/https模块
-
安装 Axios: 首先,你需要在你的项目中安装 Axios。如果你正在使用 npm 或 yarn 管理包,可以在命令行中运行以下命令
npm install axios # 或者 yarn add axios
-
基本用法:
GET 请求
:Axios.get 方法用于发送 GET 请求import axios from 'axios'; axios.get('https://api.example.com/data') .then(response => { console.log(response.data); // 打印服务器返回的数据 }) .catch(error => { if (error.response) { // 请求已发出但服务器响应的状态码不在 2xx 范围内 console.error(`请求失败, 原因: ${error.response.status}`); } else if (error.request) { // 请求已发出,但没有收到响应(如网络故障) console.error('请求未能发出,请检查网络连接'); } else { // 发生了其他错误,如设置适配器时出错 console.error('Error', error.message); } console.error(error.config); // 打印完整的请求配置信息 });
POST 请求
:Axios.post 方法用于发送 POST 请求,通常用来提交数据到服务器const postData = { key: 'value' }; axios.post('https://api.example.com/submit-data', postData, { headers: { 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { // 同样的错误处理逻辑... });
PUT、DELETE 等其他方法
:Axios 提供了对应的方法来发起PUT、DELETE、HEAD、OPTIONS
等 HTTP 请求。
-
配置选项
- axios 支持丰富的配置选项,包括但不限于
baseURL、headers、timeout、params、responseType 和transformRequest/transformResponse 等
。const config = { method: 'post', url: 'https://api.example.com/user', data: { id: 123, name: 'John Doe' }, headers: { Authorization: 'Bearer your_token_here' }, timeout: 5000, // 设置超时时间(毫秒) responseType: 'json', // 指定响应的数据类型 ... }; axios(config) .then(response => { console.log(response.data); }) .catch(error => { // 错误处理逻辑... });
- axios 支持丰富的配置选项,包括但不限于
-
拦截器
- axios 允许你添加全局或实例级别的请求和响应拦截器。这些拦截器可以用来
统一处理认证、日志记录、错误处理等
操作// 添加请求拦截器 axios.interceptors.request.use(config => { // 在每个请求之前进行一些操作 config.headers.common['X-Custom-Header'] = 'foobar'; return config; }); // 添加响应拦截器 axios.interceptors.response.use(response => { // 对任何成功的响应做一些预处理 return response; }, error => { // 对任何错误响应做一些预处理,如自动重试、统一错误提示等 if (error.response.status === 401) { // 处理未授权情况,比如跳转到登录页面 } return Promise.reject(error); });
- axios 允许你添加全局或实例级别的请求和响应拦截器。这些拦截器可以用来
-
Pinia状态管理
此次以Vue的
Pinia
为例,如有React同学可以使用Redux,再此小编为你们推荐一个状态管理zustand
老好使了,感兴趣的同学可以去看看
Pinia
是 Vue.js 生态中的一个轻量级状态管理库,它为 Vue 应用程序提供了一种声明式、可预测且易于测试的状态管理模式
。在 Vue 3 中,Pinia 被官方推荐作为 Vuex 的替代品,充分利用了Vue 3 的Composition API
特性,使得状态管理变得更加直观和高效- 安装 Pinia: 首先,你需要在你的项目中安装 Pinia。如果你正在使用 npm 或 yarn 管理包,可以在命令行中运行以下命令
npm install pinia # 或者 yarn add pinia
- 核心概念与特性
-
定义 Store
- 使用
defineStore
函数创建 store,可以定义state(状态)、getters(计算属性)、actions(异步操作)和 mutations(同步状态修改)
import { defineStore } from 'pinia'; export const useUserStore = defineStore('user', { state: () => ({ id: null, name: '', isLoggedIn: false, }), getters: { fullName: (state) => `${state.name} (${state.id})`, }, actions: { async login(username, password) { // 异步登录逻辑... this.isLoggedIn = true; }, logout() { this.isLoggedIn = false; }, updateName(newName) { this.name = newName; }, }, // 在 Pinia v2 中移除了 mutations,直接在 actions 或其他函数中修改 state 即可触发响应式更新 });
- 使用
-
使用 Store
- 在组件中通过调用 useStore 函数来获取并使用 store。由于利用了 Vue 3 的组合式 API,可以直接访问并操作 store 内部的 state 和方法
import { useUserStore } from '@/stores/user'; export default { setup() { const userStore = useUserStore(); function handleLogin() { userStore.login('username', 'password'); } return { handleLogin, fullName: computed(() => userStore.fullName), isLoggedIn: computed(() => userStore.isLoggedIn), }; } };```
- 在组件中通过调用 useStore 函数来获取并使用 store。由于利用了 Vue 3 的组合式 API,可以直接访问并操作 store 内部的 state 和方法
-
类型安全
- Pinia 支持
TypeScript
,可以为 store 的各个部分添加类型注解,确保代码具有更好的静态检查和智能提示
- Pinia 支持
-
模块化
- 可以根据业务需求创建多个独立的 store,每个 store 都可以单独引入并在需要的地方使用,有助于保持应用的结构清晰和模块化
-
插件系统
- Pinia 提供了一个插件机制,允许开发者
扩展或修改 store
的行为,比如添加全局的中间件来处理所有 store 的 action。
- Pinia 提供了一个插件机制,允许开发者
-
简单易用
- 相比于 Vuex,Pinia 简化了状态管理流程,无需手动调用
commit方法
来修改状态,而是直接修改 state 对象。同时,action 不再区分同步和异步,都可通过返回Promise
来支持异步操作。
- 相比于 Vuex,Pinia 简化了状态管理流程,无需手动调用
-
- 安装 Pinia: 首先,你需要在你的项目中安装 Pinia。如果你正在使用 npm 或 yarn 管理包,可以在命令行中运行以下命令
二、封装
Axios.ts
封装了 Axios 库的功能以进行 HTTP 请求,并添加了诸如令牌管理、请求取消以及在服务器错误时自动重试等额外功能。同时,它还与 Vue.js 存储集成以便处理用户认证令牌。
-
Axios 实例
- Request 类创建了一个 Axios 实例,接收提供的选项(如 baseURL 和 timeout),并配置了
axios-retry
中间件,以便根据特定条件重试失败的请求。
- Request 类创建了一个 Axios 实例,接收提供的选项(如 baseURL 和 timeout),并配置了
-
请求和响应拦截器
- requestInterceptor: 如果从用户存储中获取到了访问令牌,则添加授权头。此外,在发送请求前使用 TokenManager 确保刷新令牌有效,并在需要时注册请求以供取消。
- requestErrorInterceptor: 当请求阶段出现问题时记录错误信息。
- responseSuccessInterceptor: 从待处理请求列表中移除已完成的请求,检查状态码,并相应地处理成功消息。
- responseErrorInterceptor: 处理将失败请求从待处理请求列表中移除,并根据
axios-retry
配置决定是否重试请求。
-
方法
- (
get、post、put 、delete
),简化常见的 HTTP 操作,每个方法都返回一个自定义的 Response 类型,这是一个 Promise,解析为一个元组,包含一个表示成功的布尔值、响应数据以及 AxiosResponse 对象
- (
-
导出
- 创建了一个具有默认选项的 Request 类实例并将其作为
http
导出。在整个应用中可以使用这个实例来进行 HTTP 请求,同时利用所有已配置的功能
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from "axios" import axios from "axios" import axiosRetry from "axios-retry" import TokenManager from "@/utils/http/token-manager" import { checkStatus } from "@/utils/http/check-status" import { AxiosCancel } from "@/utils/http/axios-cancel" import { useUser } from "@/stores/index.ts" export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]> export const TokenOrManager = new TokenManager() export const axiosCancel = new AxiosCancel() export class Request { private readonly axiosInstance: AxiosInstance constructor(options: AxiosRequestConfig) { this.axiosInstance = axios.create(options) // 配置axios-retry axiosRetry(this.axiosInstance, { retries: 2, // 设置最大重试次数 retryDelay: (retryCount) => retryCount * 1000, // 设置每次重试间隔时间 retryCondition: (error: AxiosError): boolean => { // 自定义重试条件 return Boolean(error?.response && error?.response?.status >= 500) } }) this.axiosInstance.interceptors.request.use( (axiosConfig: AxiosRequestConfig) => this.requestInterceptor(axiosConfig), (error) => this.requestErrorInterceptor(error) ) this.axiosInstance.interceptors.response.use( (response: AxiosResponse) => this.responseSuccessInterceptor(response), (error) => this.responseErrorInterceptor(error) ) } async requestInterceptor(config: AxiosRequestConfig): Promise<any> { const userStore = useUser() axiosCancel.addPending(config) if (userStore.getToken.accessToken) { ;(config.headers as Recordable).Authorization = `Bearer ${userStore.getToken.accessToken}` } // 这里可以添加逻辑,如果需要的话 await TokenOrManager.ensureValidRefreshTokens(config) return config } requestErrorInterceptor(error: AxiosError): Promise<AxiosError> { console.error("Request Error:", error.message) return Promise.reject(error) } responseSuccessInterceptor(response: AxiosResponse): any { response && axiosCancel.removePending(response.config) const { status, data } = response if (status !== 200) return Promise.reject(data) ElMessage.success(data.message) console.log("Response Success:", response) return data } private async responseErrorInterceptor(error: any): Promise<any> { axiosCancel.removePending(error?.config) const { status } = error?.response || {} try { await checkStatus(status) await this.axiosInstance(error?.config) } catch (error) { console.error("Response Error:", error) } return Promise.reject(error) } /** * 发起一个请求并返回响应 * @param config 请求配置 * @returns 响应对象 */ request<T, D = any>(config: AxiosRequestConfig<D>): Response<T> { return this.axiosInstance(config) } /** * 发起一个GET请求并返回响应 * @param url 请求的URL * @param config 请求配置 * @returns 响应对象 */ get<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { return this.axiosInstance.get(url, config) } /** * 发起一个POST请求并返回响应 * @param url 请求的URL * @param data 请求的数据 * @param config 请求配置 * @returns 响应对象 */ post<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Response<T> { return this.axiosInstance.post(url, data, config) } /** * 发起一个PUT请求并返回响应 * @param url 请求的URL * @param data 请求的数据 * @param config 请求配置 * @returns 响应对象 */ put<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Response<T> { return this.axiosInstance.put(url, data, config) } /** * 发起一个DELETE请求并返回响应 * @param url 请求的URL * @param config 请求配置 * @returns 响应对象 */ delete<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { return this.axiosInstance.delete(url, config) } } const http = new Request({ baseURL: import.meta.env.VITE_BASE_URL, timeout: 60 * 1000 * 5 }) export default http
- 创建了一个具有默认选项的 Request 类实例并将其作为
Check-status.ts
定义了一个名为 checkStatus 的异步函数,用于处理 HTTP 请求返回的错误状态码。该函数从 @/stores/index.ts 导入了 useUser 方法以访问用户存储,为响应提供了一种统一处理 HTTP 状态错误的方式,并且在需要用户重新登录时自动触发登出操作。
根据传入的错误状态码(errStatus),函数会设置相应的错误信息(errMessage),并根据不同情况执行不同操作:
- 对于 401 错误码(未授权),除了显示错误消息“未授权,请重新登录”之外,还会调用 userStore.loginOutTo() 来登出当前用户。
- 对于其他错误状态码,仅显示对应的错误信息。
import { useUser } from "@/stores/index.ts"
export const checkStatus = async (errStatus: number) => {
const userStore = useUser()
let errMessage = "未知错误"
if (errStatus) {
switch (errStatus) {
case 400:
errMessage = "错误的请求"
break
case 401:
errMessage = "未授权,请重新登录"
await userStore.loginOutTo()
break
case 403:
errMessage = "拒绝访问"
break
case 404:
errMessage = "请求错误,未找到该资源"
break
case 405:
errMessage = "请求方法未允许"
break
case 408:
errMessage = "请求超时"
break
case 500:
errMessage = "服务器端出错, 请联系管理员"
break
case 501:
errMessage = "网络未实现"
break
case 502:
errMessage = "网络错误"
break
case 503:
errMessage = "服务不可用"
break
case 504:
errMessage = "网络超时"
break
case 505:
errMessage = "http版本不支持该请求"
break
default:
errMessage = `其他连接错误 --${errStatus}`
}
} else {
errMessage = `无法连接到服务器!`
}
ElMessage.error(errMessage)
}
Token-manager.ts
用于管理应用程序中的访问令牌(accessToken)和刷新令牌(refreshToken)
refreshTokens
- 通过调用常量的 REFRESH_TOKEN_URL 来刷新令牌。当 access token 即将过期时,使用 refresh token 向服务器请求新的 access token 和 refresh token
isAccessTokenExpired
- 检查给定的 accessToken 的过期时间(以秒为单位),判断是否已过期。如果传入的 expiresIn 不存在或当前时间大于该值,则返回 true,表示已过期。
validateTokens
- 验证提供的 tokens 是否包含必要的字段(accessToken、refreshToken 和 expiresIn)。如果不完整,会触发用户登出操作。
setTokens
- 将新的令牌数据保存到全局状态管理(Vuex store)中,这里使用了从 “@/stores/index.ts” 导入的 useUser() 函数来访问并设置用户的 token 数据。
addAuthorizationHeader
- 将 access token 添加到 Axios 请求配置的 headers 中,以便发送带有身份验证信息的 HTTP 请求。
ensureValidRefreshTokens
- 在发出 HTTP 请求前确保 refresh token 有效。首先验证令牌的有效性,然后检查 access token 是否即将过期。若未过期则直接添加 Authorization 头;若已过期,则先调用 refreshTokens 更新令牌,并将更新后的令牌添加到请求头中。这个方法通常会在请求拦截器中调用,以确保每个请求都有有效的 access token。
import { Token } from "@/types"
import type { AxiosRequestConfig, AxiosResponse } from "axios"
import axios from "axios"
import { useUser } from "@/stores/index.ts"
// 配置额外的请求头接口
const REFRESH_TOKEN_URL = "/api/refresh-token"
// Token管理类
class TokenManager {
async refreshTokens(refreshToken: string): Promise<void> {
const response: AxiosResponse = await axios.post(REFRESH_TOKEN_URL, {
refreshToken: refreshToken
})
return response.data.result
}
// 判断accessToken是否过期的方法 true 代表过期,false代表未过期
private isAccessTokenExpired(expiresIn: number): boolean {
if (!expiresIn) return true
return Math.floor(Date.now() / 1000) < expiresIn
}
async validateTokens(tokens: Token) {
if (!tokens.refreshToken || !tokens.accessToken || !tokens.expiresIn) {
await useUser().loginOutTo()
}
}
setTokens(newTokens: any) {
useUser().setToken(newTokens)
}
// 将访问令牌添加到请求头中
addAuthorizationHeader(config: AxiosRequestConfig, tokens: Token) {
;(config.headers as Recordable).Authorization = `Bearer ${tokens.accessToken}`
}
// 确保请求配置中包含有效的refreshToken
async ensureValidRefreshTokens(config: AxiosRequestConfig): Promise<void> {
const { accessToken, expiresIn, refreshToken } = useUser().getToken
await this.validateTokens({ accessToken, expiresIn, refreshToken })
if (!this.isAccessTokenExpired(expiresIn as number)) {
const refreshedTokens = await this.refreshTokens(refreshToken as string)
const token = Object.assign({}, refreshedTokens, { refreshToken })
this.setTokens(token)
this.addAuthorizationHeader(config, token)
} else {
this.addAuthorizationHeader(config, { accessToken, expiresIn, refreshToken })
}
}
}
// 导出TokenManager类
export default TokenManager
Axios-cancel.ts
用于管理并取消重复的 HTTP 请求。在高并发场景下,当同一个接口有多个完全相同的请求同时发出时,为了防止服务器收到重复数据或客户端接收到过时响应,可以采用此方案来取消后发出的重复请求
getPendingUrl
- 函数将 Axios 请求配置中的方法(method)、URL、参数(params)和数据(data)拼接成一个字符串,作为请求标识符
addPending
- 该方法接收一个 Axios 请求配置对象,并根据 getPendingUrl 得到请求标识符。如果标识符已经存在于 pendingMap 中,则为当前请求创建一个新的 CancelToken,并在其回调中执行取消操作,附带错误信息。若不存在,则将新的 CancelToken 回调函数存入 pendingMap。
removeAllPending
- 移除所有挂起请求的 CancelToken,并清除 pendingMap。
removePending
- 根据传入的请求配置获取请求标识符,若标识符存在pendingMap中,则执行对应的取消函数,并从 pendingMap 中移除此项
import axios, { AxiosRequestConfig, Canceler } from "axios"
import { isFunction } from "lodash-es"
import { isJsonString } from "@/utils/is-method.ts"
import { stringify } from "qs"
// 取消重复请求: 完全相同的接口在上一个pending状态时,自动取消下一个请求
export const getPendingUrl = (config: AxiosRequestConfig) => {
if (config && config.data && isJsonString(config.data)) {
config.data = JSON.parse(config.data)
}
const { method, url, params, data } = config // 请求方式,参数,请求地址,
return [method, url, stringify(params), stringify(data)].join("&") // 拼接
}
const pendingMap = new Map<string, Canceler>() // 取消重复请求
export class AxiosCancel {
/**
* 添加挂起的请求配置
* @param config Axios的请求配置
*/
addPending(config: AxiosRequestConfig) {
const requestKey = getPendingUrl(config)
if (pendingMap.has(requestKey)) {
config.cancelToken = new axios.CancelToken((cancel) => {
// cancel 函数的参数会作为 promise 的 error 被捕获
cancel(`${config.url} 请求已取消`)
})
} else {
config.cancelToken =
config.cancelToken ||
new axios.CancelToken((cancel) => {
pendingMap.set(requestKey, cancel)
})
}
}
/**
* 移除所有挂起的请求配置
*/
removeAllPending() {
pendingMap.forEach((cancel) => {
cancel && isFunction(cancel) && cancel()
})
pendingMap.clear()
}
/**
* 移除指定的挂起的请求配置
* @param config Axios的请求配置
*/
removePending(config: AxiosRequestConfig) {
if (!config) return
const requestKey = getPendingUrl(config)
if (pendingMap.has(requestKey)) {
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = pendingMap.get(requestKey)
cancel && cancel(requestKey)
pendingMap.delete(requestKey)
}
}
}
Store/index.ts
使用 Pinia 创建的一个 Vue.js 状态管理模块,用于处理用户相关的状态和操作,如登录、获取用户信息、登出以及存储和读取用户的访问令牌(access token)和刷新令牌(refresh token)
useUserStore 是定义的用户存储模块,它包含了以下部分:
State
- 定义了 IUserState 接口,描述了用户状态数据结构,包括
token
、userId
、refreshToken
和userInfo
。在初始化时,从本地存储中读取 access token,并设置其他属性为空或默认值。
- 定义了 IUserState 接口,描述了用户状态数据结构,包括
Getters
- 提供了一系列 getter 方法,用于获取用户状态中的各个属性
Actions
- setToken, setRefreshToken, setUserInfo, setUserId:分别用于更新用户状态中的对应属性。
- login:异步方法,接收登录参数并调用 API 进行登录操作。登录成功后,将返回的 token 信息保存到本地存储,并更新用户状态。
- getUserInfoTo:异步方法,调用 API 获取用户信息,成功后更新用户状态的 userInfo 属性。
- loginOutTo:登出方法,弹窗提示用户重新登录,并在确认后清除本地存储的数据。
导出
- useUser 函数导出了一个便捷的方法,以便在 Vue 组件中轻松地注入和使用这个用户存储模块。
import { defineStore } from "pinia"
import { store } from "@/stores/store.ts"
import { Token, UserInfo, Login } from "@/types"
import { login, getUserInfo } from "@/api"
import { storage } from "@/utils/storage.ts" //
export const ACCESS_TOKEN = "ACCESS-TOKEN" // 用户token
export enum ResultEnum {
SUCCESS = 200,
ERROR = -1,
TIMEOUT = 10042,
TYPE = "success"
}
export interface IUserState {
token: Token
userId: string
refreshToken: string
userInfo: UserInfo
}
export const useUserStore = defineStore({
id: "user",
state: (): IUserState => ({
token: storage.get(ACCESS_TOKEN, {}),
userId: "",
refreshToken: "",
userInfo: {} as UserInfo
}),
getters: {
getToken(): Token {
return this.token
},
getRefreshToken(): string {
return this.refreshToken
},
getUserId(): string {
return this.userId
},
getUserInfo(): UserInfo {
return this.userInfo
}
},
actions: {
setToken(token: Token) {
this.token = token
},
setRefreshToken(refreshToken: string) {
this.refreshToken = refreshToken
},
setUserInfo(userInfo: UserInfo) {
this.userInfo = userInfo
},
setUserId(userId: string) {
this.userId = userId
},
async login(params: Login) {
try {
const response = await login(params)
const { result, code } = response as unknown as { result: any; code: string | number }
if (code === ResultEnum.SUCCESS) {
const token = {
accessToken: result.accessToken,
refreshToken: result.refreshToken,
expiresIn: result.expiresIn
}
storage.set(ACCESS_TOKEN, token)
this.setRefreshToken(result.refreshToken)
this.setToken(token)
this.setUserId(result.userId)
} else {
console.error("登录失败:code = " + code)
// 可以考虑在这里添加一个错误处理的逻辑,比如提示用户
}
} catch (error) {
console.error("登录失败:", error)
// 对于 API 调用失败,可以考虑提示用户或者重试逻辑
}
},
async getUserInfoTo() {
try {
const response = await getUserInfo()
const { result, code } = response as unknown as { result: any; code: string | number }
if (code === ResultEnum.SUCCESS) {
this.setUserInfo(result)
} else {
throw new Error("getInfo: permissionsList must be a non-null array !")
}
return result
} catch (error) {
console.error("获取用户信息失败:", error)
// 对于 API 调用失败,可以考虑提示用户或者重试逻辑
throw error
}
},
async loginOutTo() {
// 重新登录应该由组件的用户交互逻辑来触发
// 这里应该仅负责执行登出的逻辑,而不是进行导航
await ElMessageBox.alert("登录已失效,请重新登录", "提示", {
confirmButtonText: "重新登录",
callback: () => {
storage.clear()
}
})
}
}
})
// 导出
export const useUser = () => {
return useUserStore(store)
}
Utils/Storage
用于处理浏览器存储(
localStorage
和sessionStorage
)以及 cookie 的工具类。它提供了对存储数据的设置、获取、删除及清空操作
,并且支持为存储项添加前缀以避免命名冲突,同时也支持缓存过期时间的管理。
构造函数
- 接受两个参数,一个是存储键名的前缀(prefixKey),另一个是使用的存储类型,默认使用 localStorage。
getKey
- 将传入的 key 与前缀结合并转为大写形式,生成实际在存储中使用的键名。
set
- 用于设置存储项,接受一个 key、一个 value 和一个 expire 参数表示缓存过期时间(单位为秒)。如果设置了过期时间,则会把当前时间戳加上过期秒数作为缓存的实际失效时间。
get
- 读取存储项,若找到对应的存储值且未过期,则返回其内容;否则返回默认值或 null。
remove
- 从存储中移除指定 key 的项。
clear
- 清除所有带有前缀的存储项
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7 // 默认缓存期限为7天
type StorageType = typeof localStorage | typeof sessionStorage
export default class Storage {
private storage: StorageType // StorageType 应该是具体的存储类型,如 localStorage 或 sessionStorage
private prefixKey?: string
constructor(prefixKey = "", storage: StorageType = localStorage) {
this.storage = storage
this.prefixKey = prefixKey
}
private getKey(key: string): string {
return `${this.prefixKey}${key}`.toUpperCase()
}
/**
* 设置缓存
* @param {string} key 缓存键
* @param {*} value 缓存值
* @param expire 缓存过期时间,默认为7天
*/
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME): void {
const data = {
value,
expire: expire !== null ? Date.now() + expire * 1000 : null
}
const stringData = JSON.stringify(data)
this.storage.setItem(this.getKey(key), stringData)
}
/**
* 读取缓存
* @param {string} key 缓存键
* @param {*=} def 默认值
*/
get(key: string, def: any = null): any {
const item = this.storage.getItem(this.getKey(key))
if (item) {
try {
const data = JSON.parse(item)
const { value, expire } = data
if (expire === null || expire >= Date.now()) {
return value
}
this.remove(key)
} catch (e) {
return def
}
}
return def
}
/**
* 从缓存删除某项
* @param {string} key
*/
remove(key: string): void {
this.storage.removeItem(this.getKey(key))
}
/**
* 清空所有缓存
*/
clear(): void {
this.storage.clear()
}
/**
* 设置cookie
* @param {string} name cookie 名称
* @param {*} value cookie 值
* @param {number=} expire 过期时间
* 如果过期时间未设置,默认关闭浏览器自动删除
*/
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME): void {
const maxAge = expire !== null ? `; Max-Age=${expire}` : ""
document.cookie = `${this.getKey(name)}=${value}${maxAge}`
}
/**
* 根据名字获取cookie值
* @param name
*/
getCookie(name: string): string {
const cookies = document.cookie.split("; ")
for (const cookie of cookies) {
const [key, value] = cookie.split("=")
if (this.getKey(key) === name) {
return value
}
}
return ""
}
/**
* 根据名字删除指定的cookie
* @param {string} key
*/
removeCookie(key: string): void {
this.setCookie(key, "", -1)
}
/**
* 清空cookie,使所有cookie失效
*/
clearCookie(): void {
const keys = document.cookie.match(/[^ =;]+(?==)/g)
if (keys) {
for (let i = keys.length; i--; ) {
const key = keys[i]
this.setCookie(key, "", -1)
}
}
}
}
export const storage = new Storage("")
三、封装使用
import http from "@/utils/http"
import { Login, BasicResponse, UserInfo } from "@/types"
// 登录
export const login = async (data: Login) => http.post<BasicResponse>("/api/login", data)
// 登出
export const loginOut = async () => http.get<BasicResponse>("/api/logout")
// 获取用户信息
export const getUserInfo = async () => http.get<UserInfo>("/api/user/list")
四、结语
本篇只是实现了上述的功能,其实还有其他的优化:
请求接口数据缓存
:接口在设定时间内
或后不会向后台获取数据,而是直接拿本地缓存(拦截器配置,以及proxy的配置)access token过期处理
: 当access token过期后,可将接口暂时存储,当access token刷新成功继续进行请求.env环境配置
…
后续小编会继续完善,同时也请关注下一篇文章:RBAC权限管理
参考思路
:掘金前端小付、阿飞飞飞
雷同代码
:如有雷同请告知我这边添加地址,少量代码就算了毕竟千千万万程序猿同学中咱们两个思路相同,新手小编写文不容易,当然欢迎品论~~