video-flow-b/api/movie_queue.ts
2025-10-10 17:35:18 +08:00

172 lines
4.4 KiB
TypeScript
Raw Permalink 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/QueueNotication';
/** 队列状态枚举 */
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;
let closeModal: (() => void) | null = null;
// 生成唯一的轮询ID
const pollId = Math.random().toString(36).substring(7);
// 创建取消函数
const cancel = () => {
isCancelled = true;
try { closeModal?.(); } catch {}
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) {
// 打开或更新 H5 弹窗(仅允许 Cancel 关闭Refresh 触发刷新)
try { closeModal?.(); } catch {}
closeModal = showQueueNotification(
position,
waiting,
status,
cancel,
async () => {
// 触发一次立刻刷新:重置 attempts 的等待,直接递归调用 poll()
// 不关闭弹窗,由 showQueueNotification 保持打开
attempts = Math.max(0, attempts - 1);
}
);
lastNotificationPosition = position;
}
// 调用轮询回调
onPolling?.(response.data);
// 检查是否达到最大尝试次数
if (attempts >= maxAttempts) {
throw new Error('Exceeded the maximum polling limit');
}
// 继续轮询
await new Promise(resolve => setTimeout(resolve, interval));
return poll();
}
// 如果状态为ready结束轮询
if (response.code !== 202 && response.data) {
try { closeModal?.(); } catch {}
onSuccess?.(response.data);
return response;
}
try { closeModal?.(); } catch {}
return response;
} catch (error) {
try { closeModal?.(); } catch {}
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();
}