forked from 77media/video-flow
161 lines
4.0 KiB
TypeScript
161 lines
4.0 KiB
TypeScript
import { ApiResponse } from './common';
|
||
import { showQueueNotification } from '../components/QueueBox/QueueNotification2';
|
||
import { notification } from 'antd';
|
||
|
||
/** 队列状态枚举 */
|
||
export enum QueueStatus {
|
||
WAIT = 'wait', // 排队等待中
|
||
READY = 'ready', // 可以开始处理
|
||
PROCESS = 'process' // 处理中
|
||
}
|
||
|
||
/** 队列响应数据接口 */
|
||
export interface QueueResponseData {
|
||
status: QueueStatus;
|
||
position?: number;
|
||
queue_length?: number;
|
||
waiting?: number;
|
||
message?: string;
|
||
}
|
||
|
||
/** 队列响应接口 */
|
||
export interface QueueResponse extends ApiResponse<QueueResponseData> {
|
||
code: number;
|
||
message: string;
|
||
data: QueueResponseData;
|
||
}
|
||
|
||
/**
|
||
* 处理API轮询的配置选项
|
||
*/
|
||
interface PollConfig {
|
||
/** 轮询间隔(ms), 默认10000ms */
|
||
interval?: number;
|
||
/** 最大轮询次数, 默认120次(20分钟) */
|
||
maxAttempts?: number;
|
||
/** 轮询成功后的回调 */
|
||
onSuccess?: (data: any) => void;
|
||
/** 轮询失败后的回调 */
|
||
onError?: (error: Error) => void;
|
||
/** 每次轮询时的回调 */
|
||
onPolling?: (data: QueueResponseData) => void;
|
||
/** 取消轮询时的回调 */
|
||
onCancel?: () => void;
|
||
}
|
||
|
||
// 用于存储取消函数的映射
|
||
const cancelTokens = new Map<string, () => void>();
|
||
|
||
/**
|
||
* 统一的队列API调用封装
|
||
*/
|
||
export async function withQueuePolling<T>(
|
||
apiCall: (params: T) => Promise<QueueResponse>,
|
||
params: T,
|
||
config: PollConfig = {}
|
||
): Promise<QueueResponse> {
|
||
const {
|
||
interval = 10000,
|
||
maxAttempts = 120,
|
||
onSuccess,
|
||
onError,
|
||
onPolling,
|
||
onCancel
|
||
} = config;
|
||
|
||
let attempts = 0;
|
||
let lastNotificationPosition: number | undefined;
|
||
let isCancelled = false;
|
||
|
||
// 生成唯一的轮询ID
|
||
const pollId = Math.random().toString(36).substring(7);
|
||
|
||
// 创建取消函数
|
||
const cancel = () => {
|
||
isCancelled = true;
|
||
notification.destroy(); // 关闭通知
|
||
onCancel?.();
|
||
cancelTokens.delete(pollId);
|
||
};
|
||
|
||
// 存储取消函数
|
||
cancelTokens.set(pollId, cancel);
|
||
|
||
const poll = async (): Promise<QueueResponse> => {
|
||
try {
|
||
if (isCancelled) {
|
||
throw new Error('Operation canceled');
|
||
}
|
||
|
||
const response = await apiCall(params);
|
||
|
||
// 处理队列状态
|
||
if (response.code === 202 &&
|
||
(response.data.status === QueueStatus.WAIT || response.data.status === QueueStatus.PROCESS)) {
|
||
attempts++;
|
||
|
||
// 获取队列位置和等待时间
|
||
const { position, waiting, status } = response.data;
|
||
|
||
// 如果位置发生变化,更新通知
|
||
if ((position !== lastNotificationPosition || status === QueueStatus.PROCESS) &&
|
||
position !== undefined && waiting !== undefined) {
|
||
showQueueNotification(position, waiting, status, cancel);
|
||
lastNotificationPosition = position;
|
||
}
|
||
|
||
// 调用轮询回调
|
||
onPolling?.(response.data);
|
||
|
||
// 检查是否达到最大尝试次数
|
||
if (attempts >= maxAttempts) {
|
||
notification.destroy(); // 关闭通知
|
||
throw new Error('Exceeded the maximum polling limit');
|
||
}
|
||
|
||
// 继续轮询
|
||
await new Promise(resolve => setTimeout(resolve, interval));
|
||
return poll();
|
||
}
|
||
|
||
// 如果状态为ready,结束轮询
|
||
if (response.code !== 202 && response.data) {
|
||
notification.destroy(); // 关闭通知
|
||
onSuccess?.(response.data);
|
||
return response;
|
||
}
|
||
|
||
notification.destroy(); // 关闭通知
|
||
return response;
|
||
} catch (error) {
|
||
notification.destroy(); // 关闭通知
|
||
if (error instanceof Error) {
|
||
onError?.(error);
|
||
}
|
||
throw error;
|
||
} finally {
|
||
cancelTokens.delete(pollId);
|
||
}
|
||
};
|
||
|
||
return poll();
|
||
}
|
||
|
||
/**
|
||
* 取消指定的轮询
|
||
*/
|
||
export function cancelPolling(pollId: string) {
|
||
const cancel = cancelTokens.get(pollId);
|
||
if (cancel) {
|
||
cancel();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 取消所有轮询
|
||
*/
|
||
export function cancelAllPolling() {
|
||
// 将 Map 转换为数组后再遍历
|
||
Array.from(cancelTokens.values()).forEach(cancel => cancel());
|
||
cancelTokens.clear();
|
||
} |