import type { ApiRetryOptions } from '../apiClient'; const EDITOR_RETRYABLE_STATUS_CODES = [408, 425, 429, 502, 503, 504]; const EDITOR_RETRY_MAX_RETRIES = 2; const EDITOR_RETRY_BASE_DELAY_MS = 800; const EDITOR_RETRY_MAX_DELAY_MS = 3000; export const EDITOR_REQUEST_RETRY_OPTIONS = { maxRetries: EDITOR_RETRY_MAX_RETRIES, baseDelayMs: EDITOR_RETRY_BASE_DELAY_MS, maxDelayMs: EDITOR_RETRY_MAX_DELAY_MS, retryUnsafeMethods: true, retryableStatusCodes: EDITOR_RETRYABLE_STATUS_CODES, } satisfies ApiRetryOptions; // 生成接口在 inline 模式下会直接调用 provider;服务端尚无结果级幂等前, // 浏览器不能自动重试 POST,否则响应丢失会重复生成与持久化。 export const EDITOR_GENERATION_REQUEST_RETRY_OPTIONS = { maxRetries: 0, retryUnsafeMethods: false, } satisfies ApiRetryOptions; type EditorDirectUploadTarget = { host: string; formFields: Record; }; function buildRetryDelayMs(attempt: number) { return Math.min( EDITOR_RETRY_MAX_DELAY_MS, EDITOR_RETRY_BASE_DELAY_MS * Math.max(1, attempt), ); } function waitForRetryDelay(attempt: number) { return new Promise((resolve) => { setTimeout(resolve, buildRetryDelayMs(attempt)); }); } function isRetryableEditorUploadStatus(status: number) { return EDITOR_RETRYABLE_STATUS_CODES.includes(status); } function buildDirectUploadFormData( upload: EditorDirectUploadTarget, file: File, ) { const formData = new FormData(); Object.entries(upload.formFields).forEach(([key, value]) => { if (value !== null && value !== undefined) { formData.append(key, value); } }); formData.append('file', file, file.name); return formData; } export async function postEditorDirectUploadFile( upload: EditorDirectUploadTarget, file: File, errorMessage: string, // 中文注释:调用方超时后仅停止 await 是不够的——这一步是唯一往 OSS 写实体的动作, // 不取消的话被放弃的上传会继续跑完并注册对象,用户重试再产生一份,留下不可见的孤儿。 signal?: AbortSignal, ) { for (let attempt = 0; ; attempt += 1) { const response = await fetch(upload.host, { method: 'POST', body: buildDirectUploadFormData(upload, file), signal, }); if (response.ok) { return; } if ( attempt >= EDITOR_RETRY_MAX_RETRIES || !isRetryableEditorUploadStatus(response.status) ) { throw new Error(errorMessage); } await waitForRetryDelay(attempt + 1); } }