video-flow-b/api/movie_queue.ts
2025-08-29 01:09:46 +08:00

161 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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('操作已取消');
}
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('超过最大轮询次数限制');
}
// 继续轮询
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();
}