perf(stability): add request metrics and resilient API retries
This commit is contained in:
@@ -4,6 +4,10 @@ import { ElMessage } from 'element-plus'
|
||||
let lastToastKey = ''
|
||||
let lastToastAt = 0
|
||||
|
||||
const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])
|
||||
const MAX_RETRY_COUNT = 1
|
||||
const RETRY_BASE_DELAY_MS = 300
|
||||
|
||||
function toastErrorOnce(key, message, minIntervalMs = 1500) {
|
||||
const now = Date.now()
|
||||
if (key === lastToastKey && now - lastToastAt < minIntervalMs) return
|
||||
@@ -18,6 +22,41 @@ function getCookie(name) {
|
||||
return match ? decodeURIComponent(match[1]) : ''
|
||||
}
|
||||
|
||||
function isIdempotentMethod(method) {
|
||||
return ['GET', 'HEAD', 'OPTIONS'].includes(String(method || 'GET').toUpperCase())
|
||||
}
|
||||
|
||||
function shouldRetryRequest(error) {
|
||||
const config = error?.config
|
||||
if (!config || config.__no_retry) return false
|
||||
if (!isIdempotentMethod(config.method)) return false
|
||||
|
||||
const retried = Number(config.__retry_count || 0)
|
||||
if (retried >= MAX_RETRY_COUNT) return false
|
||||
|
||||
const code = String(error?.code || '')
|
||||
if (code === 'ECONNABORTED' || code === 'ERR_NETWORK') return true
|
||||
|
||||
const status = Number(error?.response?.status || 0)
|
||||
return RETRYABLE_STATUS.has(status)
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(resolve, Math.max(0, Number(ms || 0)))
|
||||
})
|
||||
}
|
||||
|
||||
async function retryRequestOnce(error, client) {
|
||||
const config = error?.config || {}
|
||||
const retried = Number(config.__retry_count || 0)
|
||||
config.__retry_count = retried + 1
|
||||
|
||||
const backoffMs = RETRY_BASE_DELAY_MS * (retried + 1)
|
||||
await delay(backoffMs)
|
||||
return client.request(config)
|
||||
}
|
||||
|
||||
export const publicApi = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 30_000,
|
||||
@@ -39,6 +78,10 @@ publicApi.interceptors.request.use((config) => {
|
||||
publicApi.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (shouldRetryRequest(error)) {
|
||||
return retryRequestOnce(error, publicApi)
|
||||
}
|
||||
|
||||
const status = error?.response?.status
|
||||
const payload = error?.response?.data
|
||||
const message = payload?.error || payload?.message || error?.message || '请求失败'
|
||||
|
||||
Reference in New Issue
Block a user