// // Copyright (C) 2024 veypi // 2024-10-28 17:46:56 // Distributed under terms of the MIT license. // import axios, { AxiosError, type AxiosInstance, type AxiosResponse } from 'axios'; // Be careful when using SSR for cross-request state pollution // due to creating a Singleton instance here; // If any client changes this (global) instance, it might be a // good idea to move this instance creation inside of the // "export default () => {}" function below (which runs individually // for each client) export const token = { value: '', update: () => { console.warn('token updater not set') return new Promise((resolve) => { resolve(token.value) }) }, set_updator: (fn: () => Promise) => { let locked = false token.update = () => { if (locked) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000) }).then(() => { return token.update() }) } locked = true return new Promise((resolve, reject) => { if (token.value) { locked = false resolve(token.value) return } fn().then((e) => { token.value = e resolve(e) }).catch(() => { reject() }).finally(() => { locked = false }) }) } } } // 请求拦截 const beforeRequest = (config: any) => { config.retryTimes = config.retryTimes || 3 // NOTE 添加自定义头部 token.value && (config.headers.Authorization = `Bearer ${token.value}`) return config } // 响应拦截器 const responseSuccess = (client: AxiosInstance) => { return (response: AxiosResponse) => { // eslint-disable-next-line yoda // 这里没有必要进行判断,axios 内部已经判断 // const isOk = 200 <= response.status && response.status < 300 let data = response.data if (typeof data === 'object') { if (data.code !== 0) { return responseFailed(client)({ response } as any) } data = data.data } return Promise.resolve(data) } } const responseFailed = (client: AxiosInstance) => { return (error: AxiosError) => { const { response } = error const config = response?.config const data = response?.data || {} as any if (!window.navigator.onLine) { alert('没有网络') return Promise.reject(new Error('请检查网络连接')) } // @ts-ignore let needRetry = config?.needRetry !== false if (response?.status == 404) { needRetry = false } else if (response?.status == 401 && needRetry) { needRetry = false // AuthNotFound = New(40100, "auth not found") // AuthExpired = New(40102, "auth expired") if (data.code === 40102 || data.code === 40100) { token.value = '' return token.update().then(() => { return requestRetry(client)(200, response!) }) } } else if (response?.status == 500) { needRetry = false } if (!needRetry) { return Promise.reject(data || response) }; return requestRetry(client)(1000, response!) } } const requestRetry = (client: AxiosInstance) => { return (delay = 0, response: AxiosResponse) => { const config = response?.config // @ts-ignore const { __retryCount = 0, retryDelay = 1000, retryTimes } = config; // 在请求对象上设置重试次数 // @ts-ignore config.__retryCount = __retryCount + 1; // 判断是否超过了重试次数 if (__retryCount >= retryTimes) { return Promise.reject(response?.data || response?.headers.error) } if (delay <= 0) { return client.request(config as any) } // 延时处理 return new Promise((resolve) => { setTimeout(() => { resolve(); }, delay) }).then(() => { return client.request(config as any) }) } } const create_client = (client: AxiosInstance) => { client.interceptors.request.use(beforeRequest) client.interceptors.response.use(responseSuccess(client), responseFailed(client)) return client } interface data { json?: any query?: any form?: any header?: any config?: Object } function transData(d: data) { let opts = { params: d.query, data: {}, headers: {} as any } if (d.form) { opts.data = d.form opts.headers['content-type'] = 'application/x-www-form-urlencoded' } if (d.json) { opts.data = d.json opts.headers['content-type'] = 'application/json' } if (d.header) { opts.headers = Object.assign(opts.headers, d.header) } if (d.config) { opts = Object.assign(opts, d.config) } return opts } export const webapi = { client: create_client(axios.create({ withCredentials: true, baseURL: "/api/", headers: { 'content-type': 'application/json;charset=UTF-8', }, })), set_base_host(host: string) { if (host === '' || host === '/') { return } webapi.client.defaults.baseURL = host + '/api/' }, Get(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'get', url: url }, transData(req))) }, Head(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'head', url: url }, transData(req))) }, Delete(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'delete', url: url }, transData(req))) }, Post(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'post', url: url }, transData(req))) }, Put(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'put', url: url }, transData(req))) }, Patch(url: string, req: data): Promise { return webapi.client.request(Object.assign({ method: 'patch', url: url }, transData(req))) }, } export default webapi