| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- import Axios, {
- type AxiosInstance,
- type AxiosRequestConfig,
- type CustomParamsSerializer,
- type AxiosResponse,
- type InternalAxiosRequestConfig,
- type AxiosError
- } from 'axios'
- import { stringify } from 'qs'
- // 基础配置
- const defaultConfig: AxiosRequestConfig = {
- timeout: 6000,
- headers: {
- 'Content-Type': 'application/json;charset=utf-8'
- },
- paramsSerializer: {
- serialize: stringify as unknown as CustomParamsSerializer
- }
- }
- // 响应数据基础结构
- export interface BaseResponse {
- code: number
- error?: string
- isAuthorized?: boolean
- isSuccess?: boolean
- }
- type OmitBaseResponse<T> = Omit<T, keyof BaseResponse>
- export type ResponseData<T = any> = BaseResponse & OmitBaseResponse<T>
- export type ResponseValidator<T = any> = (data: ResponseData<T>) => boolean
- // 重试配置
- export interface RetryConfig {
- retries?: number
- retryDelay?: number
- retryCondition?: (error: AxiosError) => boolean
- }
- // 拦截器配置类型
- interface InterceptorsConfig {
- requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
- requestErrorInterceptor?: (error: AxiosError) => Promise<any>
- responseInterceptor?: (response: AxiosResponse<ResponseData<any>>) => any
- responseErrorInterceptor?: (error: AxiosError) => Promise<any>
- }
- // 请求唯一键
- type RequestKey = string | symbol
- /**
- * 获取url参数
- * @param url
- * @returns
- */
- export function getParams(url: string) {
- const paramsString = url.split('?')[1]
- const searchParams = new URLSearchParams(paramsString)
- return Object.fromEntries(searchParams.entries())
- }
- class HttpClient {
- private instance: AxiosInstance
- private requestInterceptorId?: number
- private responseInterceptorId?: number
- private abortControllers: Map<RequestKey, AbortController> = new Map()
- constructor(customConfig?: AxiosRequestConfig, interceptors?: InterceptorsConfig) {
- this.instance = Axios.create({ ...defaultConfig, ...customConfig })
- this.initInterceptors(interceptors)
- }
- private initInterceptors(interceptors?: InterceptorsConfig): void {
- this.initRequestInterceptor(
- interceptors?.requestInterceptor,
- interceptors?.requestErrorInterceptor
- )
- this.initResponseInterceptor(
- interceptors?.responseInterceptor,
- interceptors?.responseErrorInterceptor
- )
- }
- private initRequestInterceptor(
- customInterceptor?: InterceptorsConfig['requestInterceptor'],
- customErrorInterceptor?: InterceptorsConfig['requestErrorInterceptor']
- ): void {
- const defaultInterceptor = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
- const search = getParams(window.location.href)
- const enterpriseCode = search?.['enterpriseCode']
- const token =
- localStorage.getItem('token_' + enterpriseCode) ||
- document.cookie.match(new RegExp('(^| )' + 'x-sessionId_b' + '=([^;]*)(;|$)'))?.[2]
- // 添加token
- if (token) {
- if (!config.headers) {
- config.headers = {} as InternalAxiosRequestConfig['headers']
- }
- config.headers.Authorization = token
- }
- return config
- }
- // 默认请求错误拦截器
- const defaultErrorInterceptor = (error: AxiosError): Promise<any> => {
- // todo 错误处理
- return Promise.reject(error)
- }
- this.requestInterceptorId = this.instance.interceptors.request.use(
- customInterceptor || defaultInterceptor,
- customErrorInterceptor || defaultErrorInterceptor
- )
- }
- private initResponseInterceptor(
- customInterceptor?: InterceptorsConfig['responseInterceptor'],
- customErrorInterceptor?: InterceptorsConfig['responseErrorInterceptor']
- ): void {
- this.responseInterceptorId = this.instance.interceptors.response.use(
- customInterceptor,
- customErrorInterceptor
- )
- }
- private getRequestKey(config: AxiosRequestConfig): RequestKey | undefined {
- if (!config.url) return undefined
- return `${config.method?.toUpperCase()}-${config.url}`
- }
- private setupCancelController(
- config: AxiosRequestConfig,
- requestKey?: RequestKey
- ): AxiosRequestConfig {
- const key = requestKey || this.getRequestKey(config)
- if (!key) return config
- // 如果已有相同key的请求,先取消它
- this.cancelRequest(key)
- const controller = new AbortController()
- this.abortControllers.set(key, controller)
- return {
- ...config,
- signal: controller.signal
- }
- }
- public removeRequestInterceptor(): void {
- if (this.requestInterceptorId !== undefined) {
- this.instance.interceptors.request.eject(this.requestInterceptorId)
- this.requestInterceptorId = undefined // 重置ID,避免重复移除
- }
- }
- public removeResponseInterceptor(): void {
- if (this.responseInterceptorId !== undefined) {
- this.instance.interceptors.response.eject(this.responseInterceptorId)
- this.responseInterceptorId = undefined // 重置ID,避免重复移除
- }
- }
- public setRequestInterceptor(
- customInterceptor?: InterceptorsConfig['requestInterceptor'],
- customErrorInterceptor?: InterceptorsConfig['requestErrorInterceptor']
- ): void {
- this.removeRequestInterceptor()
- this.initRequestInterceptor(customInterceptor, customErrorInterceptor)
- }
- public setResponseInterceptor(
- customInterceptor?: InterceptorsConfig['responseInterceptor'],
- customErrorInterceptor?: InterceptorsConfig['responseErrorInterceptor']
- ): void {
- this.removeResponseInterceptor()
- this.initResponseInterceptor(customInterceptor, customErrorInterceptor)
- }
- public getInstance(): AxiosInstance {
- return this.instance
- }
- public cancelRequest(key: RequestKey, message?: string): boolean {
- const controller = this.abortControllers.get(key)
- if (controller) {
- controller.abort(message || `取消请求: ${String(key)}`)
- this.abortControllers.delete(key)
- return true
- }
- return false
- }
- public cancelAllRequests(message?: string): void {
- this.abortControllers.forEach((controller, key) => {
- controller.abort(message || `取消所有请求: ${String(key)}`)
- })
- this.abortControllers.clear()
- }
- public static isCancel(error: unknown): boolean {
- return Axios.isCancel(error)
- }
- private sleep(ms: number): Promise<void> {
- return new Promise((resolve) => setTimeout(resolve, ms))
- }
- /**
- * 通用请求方法
- * @param url 请求地址
- * @param config 请求配置
- * @returns 响应数据
- */
- public async request<T = any>(
- url: string,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- const { requestKey, retry, method, ...restConfig } = config || {}
- const defaultRetryCondition = (error: AxiosError) => {
- // 默认只重试网络错误或5xx服务器错误
- return !error.response || (error.response.status >= 500 && error.response.status < 600)
- }
- const retryConfig = {
- retries: 0,
- retryDelay: 1000,
- retryCondition: defaultRetryCondition,
- ...retry
- }
- let lastError: any
- const key = requestKey || this.getRequestKey({ ...restConfig, method, url })
- for (let attempt = 0; attempt <= retryConfig.retries; attempt++) {
- try {
- if (attempt > 0 && key) {
- this.abortControllers.delete(key)
- }
- const requestConfig = this.setupCancelController({ ...restConfig, method, url }, requestKey)
- const response = await this.instance.request<ResponseData<T>>(requestConfig)
- return response.data
- } catch (error) {
- lastError = error
- if (
- attempt === retryConfig.retries ||
- !retryConfig.retryCondition(error as AxiosError) ||
- HttpClient.isCancel(error)
- ) {
- break
- }
- // 延迟后重试
- if (retryConfig.retryDelay > 0) {
- await this.sleep(retryConfig.retryDelay)
- }
- }
- }
- return Promise.reject(lastError)
- }
- /**
- * GET 请求
- * @param url 请求地址
- * @param config 请求配置
- * @returns 响应数据
- */
- public get<T = any>(
- url: string,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- return this.request<T>(url, { ...config, method: 'GET' })
- }
- /**
- * POST 请求
- * @param url 请求地址
- * @param data 请求数据
- * @param config 请求配置
- * @returns 响应数据
- */
- public post<T = any>(
- url: string,
- data?: any,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- return this.request<T>(url, { ...config, data, method: 'POST' })
- }
- /**
- * PUT 请求
- * @param url 请求地址
- * @param data 请求数据
- * @param config 请求配置
- * @returns 响应数据
- */
- public put<T = any>(
- url: string,
- data?: any,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- return this.request<T>(url, { ...config, data, method: 'PUT' })
- }
- /**
- * DELETE 请求
- * @param url 请求地址
- * @param config 请求配置
- * @returns 响应数据
- */
- public delete<T = any>(
- url: string,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- return this.request<T>(url, { ...config, method: 'DELETE' })
- }
- /**
- * PATCH 请求
- * @param url 请求地址
- * @param data 请求数据
- * @param config 请求配置
- * @returns 响应数据
- */
- public patch<T = any>(
- url: string,
- data?: any,
- config?: AxiosRequestConfig & { requestKey?: RequestKey; retry?: RetryConfig }
- ): Promise<ResponseData<T>> {
- return this.request<T>(url, { ...config, data, method: 'PATCH' })
- }
- }
- const http = new HttpClient()
- // 导出request供api-service使用
- export const request = http.request.bind(http)
- export default http
|