From c1745e836ad773ffdb914a7ab9bbf8828d67876a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BA=9A=E6=98=95?= <1549469775@qq.com> Date: Fri, 22 Nov 2024 17:47:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0axios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/http/Repeat.ts | 48 ++++++++++++++++ src/plugins/http/base.ts | 130 ++++++++++++++++++++++++++++--------------- src/plugins/http/index.ts | 112 ++++++++++++++++++++++++++++--------- src/plugins/request.ts | 15 +++++ src/ui/Register/Register.tsx | 2 +- src/views/Home/index.tsx | 17 +++++- 6 files changed, 251 insertions(+), 73 deletions(-) create mode 100644 src/plugins/http/Repeat.ts create mode 100644 src/plugins/request.ts diff --git a/src/plugins/http/Repeat.ts b/src/plugins/http/Repeat.ts new file mode 100644 index 0000000..2644db8 --- /dev/null +++ b/src/plugins/http/Repeat.ts @@ -0,0 +1,48 @@ +import { isCancel, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios" +import { IPlugin } from "./base" + +declare module "axios" { + interface AxiosRequestConfig { + global?: boolean + } +} + +// 取消重复请求 +export class RepeatPlugin implements IPlugin { + static name: string = "repeat" + name: string = "repeat" + pendingPool = new Map() + clearPendingPool(whiteList: string[] = []) { + if (!this.pendingPool.size) return + const pendingUrlList = Array.from(this.pendingPool.keys()).filter(url => !whiteList.includes(url)) + if (!pendingUrlList.length) return + + pendingUrlList.forEach(pendingUrl => { + if (!this.pendingPool.get(pendingUrl).global) { + this.pendingPool.get(pendingUrl).cancelFn(`${pendingUrl} 请求取消`) + this.pendingPool.delete(pendingUrl) + } + }) + return pendingUrlList + } + beforeRequestConfig({ config }: { config: AxiosRequestConfig }) { + const controller = new AbortController(); + config.signal = controller.signal + const cancelFn = (reason?: string) => controller.abort(reason) + this.pendingPool.has(config.url) + ? cancelFn(`${config.url} 请求重复`) + : this.pendingPool.set(config.url, { cancelFn, global: config.global }) + } + beforeResponse({ response }: { response: AxiosResponse }) { + const { config } = response + this.pendingPool.delete(config.url) + } + beforeResponseError({ error }: { error: AxiosError }) { + const requestConfig = error.config! + if (!isCancel(error)) this.pendingPool.delete(requestConfig.url) + if (!error) return + if (isCancel(error)) { + throw new Error((requestConfig.signal as AbortSignal)?.reason || error.message || `请求'${requestConfig.url}' 被取消`) + } + } +} diff --git a/src/plugins/http/base.ts b/src/plugins/http/base.ts index 1b90413..ed4200c 100644 --- a/src/plugins/http/base.ts +++ b/src/plugins/http/base.ts @@ -1,62 +1,104 @@ import axios, { AxiosInstance, CreateAxiosDefaults } from "axios"; import { deepAssign } from "./utils"; -export const enum IHttpMethod { - GET = "GET", - POST = "POST", - DELETE = "DELETE", - PUT = "PUT", +export interface IPlugin { + name: string + created?(...argus: any[]): void + beforeCreate?(...argus: any[]): void + beforeDestory?(...argus: any[]): void + beforeRequestConfig?(...argus: any[]): void + beforeRequestError?(...argus: any[]): void + beforeResponse?(...argus: any[]): void + beforeResponseError?(...argus: any[]): void + destory?(...argus: any[]): void } -export enum IHttpContentType { - FORM = "application/x-www-form-urlencoded", - JSON = "application/json", -} - -class Plugin { - beforeInit() { } +export class Plugin implements IPlugin { + name: string = "" + created() { } + beforeCreate() { } + beforeDestory() { } + beforeRequestConfig() { } + beforeRequestError() { } + beforeResponse() { } + beforeResponseError() { } + destory() { } } export abstract class PluginsManager { - static #plugins: Plugin[] = [] - static use(plugin: Plugin) { - this.#plugins.push(plugin) - } + static plugins: IPlugin[] = [] + static use(plugin: IPlugin) { + if (Array.isArray(plugin)) { + PluginsManager.plugins = this.plugins.concat(plugin) + } else PluginsManager.plugins.push(plugin) + } - callPlugin(key: keyof Plugin, ...argus: any[]) { - PluginsManager.#plugins.forEach(p => p[key].apply(this, argus as [])) + plugins: IPlugin[] = [] + use(plugin: IPlugin) { + if (Array.isArray(plugin)) { + this.plugins = this.plugins.concat(plugin) + } else this.plugins.push(plugin) + } + callPluginByName(name: string, key: keyof T, ...argus: any[]) { + const array = [...PluginsManager.plugins, ...this.plugins] + for (let i = 0; i < array.length; i++) { + let p = array[i] + if (!p.name) continue + if (name === p.name) { + const fn = (p as T)[key] + typeof fn === "function" && fn.apply(p, argus) + } } + } + callPlugin(key: keyof IPlugin, ...argus: any[]) { + const array = [...PluginsManager.plugins, ...this.plugins] + for (let i = 0; i < array.length; i++) { + let p = array[i] + const fn = p[key] + typeof fn === "function" && fn.apply(p, argus) + } + } } export abstract class httpBase extends PluginsManager { - static get method() { - return { - GET: "GET", - POST: "POST", - DELETE: "DELETE", - PUT: "PUT", - } - } - static get contentType() { - return { - FORM: "application/x-www-form-urlencoded", - JSON: "application/json", - } - } - - initDefaultConfig: CreateAxiosDefaults = { - baseURL: process.env.VUE_APP_BASEURL, - timeout: 10000, - headers: { - 'Content-Type': httpBase.contentType.FORM, - } - } + instance: AxiosInstance | null = null + requestInterceptorId: null | number = null + responseInterceptorId: null | number = null - instance: AxiosInstance | null = null + create(config?: CreateAxiosDefaults) { + this.instance = axios.create(deepAssign>(axios.defaults, config ?? {})) + this.requestInterceptorId = this.instance.interceptors.request.use(config => { + const argu = { config } + this.callPlugin("beforeRequestConfig", argu) + return argu.config + }, error => { + const argu = { error } + this.callPlugin("beforeRequestError", argu) + return Promise.reject(error) + }) + this.responseInterceptorId = this.instance.interceptors.response.use(response => { + const argu = { response } + this.callPlugin("beforeResponse", argu) + return Promise.resolve(argu.response) + }, (error) => { + const argu = { error } + this.callPlugin("beforeResponseError", argu) + return Promise.reject(error) + }) + return this.instance + } - create(config?: CreateAxiosDefaults) { - this.instance = axios.create(deepAssign>(axios.defaults, this.initDefaultConfig, config ?? {})) - return this.instance + destory() { + // 清理一些事务 + if (this.requestInterceptorId) { + this.instance?.interceptors.request.eject(this.requestInterceptorId) + } + if (this.responseInterceptorId) { + this.instance?.interceptors.response.eject(this.responseInterceptorId) } + httpBase.plugins = [] + this.plugins = [] + this.instance = null + } } diff --git a/src/plugins/http/index.ts b/src/plugins/http/index.ts index 95d1c66..79303a6 100644 --- a/src/plugins/http/index.ts +++ b/src/plugins/http/index.ts @@ -1,37 +1,99 @@ -import { httpBase, PluginsManager } from "./base" - -export const enum IHttpMethod { - GET = "GET", - POST = "POST", - DELETE = "DELETE", - PUT = "PUT", -} - -export enum IHttpContentType { - FORM = "application/x-www-form-urlencoded", - JSON = "application/json", -} - +import { httpBase, IPlugin } from "./base" +import { CreateAxiosDefaults, AxiosRequestConfig, AxiosResponse } from "axios" +import { RepeatPlugin } from "./Repeat" const FlyPoll: Map = new Map() class Fly extends httpBase { - constructor() { - super() - this.Init() + static dispose(name: string) { + if (FlyPoll.has(name)) { + const instance = FlyPoll.get(name) + instance.#destory() + FlyPoll.delete(name) + } } - static get(name?: string): Fly { - if (!name) name = "$defalt" + static init(name: string, config: any, plugins: IPlugin[] = []) { if (!FlyPoll.has(name)) { - FlyPoll.set(name, new Fly()) + const instance = new Fly(name) + FlyPoll.set(name, instance) + instance.plugins = plugins + instance.#init(config || instance.default) + } + } + static invoke(name?: string): Fly { + if (!name) name = "$defalut" + const instance = FlyPoll.get(name) + if (!instance) throw new Error("未初始化此实例") + return instance + } + + static get method() { + return { + GET: "GET", + POST: "POST", + DELETE: "DELETE", + PUT: "PUT", + } + } + + static get contentType() { + return { + FORM: "application/x-www-form-urlencoded", + JSON: "application/json", + } + } + + default: CreateAxiosDefaults = { + // baseURL: process.env.VUE_APP_BASEURL, + timeout: 10000, + headers: { + 'Content-Type': Fly.contentType.FORM, } - return FlyPoll.get(name) } - Init() { - this.callPlugin("beforeInit") - this.create() + + name: string + + constructor(name: string) { + super() + this.name = name + } + get(...argus: [url: string, config?: AxiosRequestConfig | undefined]) { + return this.instance!.get.apply(this.instance, argus ?? []) as Promise + } + request(...argus: [config: AxiosRequestConfig]): Promise { + return this.instance!.request.apply(this.instance, argus) as Promise + } + #init(config: AxiosRequestConfig) { + this.callPlugin("beforeCreate") + this.create(config) + this.callPlugin("created") + } + #destory() { + this.callPlugin("beforeDestory") + this.destory() + this.callPlugin("destory") } } -Fly.get() +export { + Fly +} + +// https://api.52vmy.cn/ +// Fly.invoke().request({ method: "get", url: "http://localhost:5173/", global: false }).then((res) => console.log(res.data)).catch(console.log) +// Fly.invoke().request({ method: "get", url: "http://localhost:5173/", global: true }).then((res) => console.log(res.data)).catch(console.log) + +// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log) + +// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log) + +// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/wl/yan/bay" }).then((res) => console.log(res.data)).catch(console.log) + +// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/img/tu/girl" }).then((res) => console.log(res.data)).catch(console.log) + + +// setTimeout(()=>{ +// // Fly.invoke("empty").request({method: "get", url: "https://api.52vmy.cn/api/img/tu/man"}).then((res)=>console.log(res.data)).catch(console.log) +// Fly.invoke().callPluginByName(RepeatPlugin.name, "clearPendingPool") +// }, 0) diff --git a/src/plugins/request.ts b/src/plugins/request.ts new file mode 100644 index 0000000..46f0d0a --- /dev/null +++ b/src/plugins/request.ts @@ -0,0 +1,15 @@ +import { Fly } from "./http" +import { RepeatPlugin } from "./http/Repeat" + + +Fly.init("$defalut", { + timeout: 10000, +}, [new RepeatPlugin()]) + +Fly.init("empty", { + timeout: 10000, +}) + +export { + Fly +} \ No newline at end of file diff --git a/src/ui/Register/Register.tsx b/src/ui/Register/Register.tsx index e93b9eb..5afcbe8 100644 --- a/src/ui/Register/Register.tsx +++ b/src/ui/Register/Register.tsx @@ -60,7 +60,7 @@ export function Register({ onSuccess, children }: IProps) { return ( <> -
+ false} onSubmitCapture={clickRegister}> { + Fly.invoke().request({ + method: "get", + url: "https://api.52vmy.cn/api/wl/word/bing/tu", + global: false + }).then((res) => console.log(res.data)).catch(console.log) + }, []) + return ( <>
- +
)