video-flow-b/api/request.ts
2025-08-29 02:49:53 +08:00

295 lines
7.8 KiB
TypeScript

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, AxiosHeaders } from 'axios';
import { message } from "antd";
import { BASE_URL } from './constants'
import { errorHandle } from './errorHandle';
/**
* 统一的错误处理函数
* @param error - 错误对象
* @param defaultMessage - 默认错误信息
*/
const handleRequestError = (error: any, defaultMessage: string = '请求失败') => {
if (error.response) {
const { status, data } = error.response;
const errorMessage = data?.message || defaultMessage;
errorHandle(status, errorMessage);
} else if (error.request) {
// 请求已发出但没有收到响应
errorHandle(0 );
} else {
// 请求配置出错
errorHandle(0, error.message || defaultMessage);
}
};
// 创建 axios 实例
const request: AxiosInstance = axios.create({
baseURL: BASE_URL, // 设置基础URL
timeout: 300000, // 请求超时时间
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
request.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 从 localStorage 获取 token
const token = localStorage?.getItem('token');
if (token && config.headers) {
(config.headers as AxiosHeaders).set('Authorization', `Bearer ${token}`);
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
(response: AxiosResponse) => {
// 检查业务状态码
if (response.data?.code !== 0 && response.data?.code !== 202) {
// 处理业务层面的错误
const businessCode = response.data?.code;
const errorMessage = response.data?.message;
// 特殊处理 401 和 4001 业务状态码
if (businessCode === 401) {
errorHandle(401, errorMessage);
return Promise.reject(new Error(errorMessage));
}
if (businessCode === 4001) {
errorHandle(4001, errorMessage);
return Promise.reject(new Error(errorMessage));
}
// 其他业务错误
errorHandle(0, errorMessage);
return Promise.reject(new Error(errorMessage));
}
return response.data;
},
(error) => {
handleRequestError(error);
return Promise.reject(error);
}
);
// 封装 GET 请求
export const get = <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
return request.get(url, config);
};
// 封装 POST 请求
export const post = <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
return request.post(url, data, config);
};
// 封装 PUT 请求
export const put = <T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> => {
return request.put(url, data, config);
};
// 封装 DELETE 请求
export const del = <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
return request.delete(url, config);
};
// utils/streamJsonPost.ts
export async function streamJsonPost<T = any>(
url: string,
body: any,
onJson: (json: T) => void
) {
try {
const token = localStorage?.getItem('token') || '';
const response = await fetch(`${BASE_URL}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body),
});
// 处理 HTTP 错误状态
if (!response.ok) {
const error = {
response: {
status: response.status,
data: { message: await response.text().then(text => {
try {
const data = JSON.parse(text);
return data.message || `HTTP error! status: ${response.status}`;
} catch {
return `HTTP error! status: ${response.status}`;
}
})}
}
};
handleRequestError(error);
throw error;
}
if (!response.body) {
throw new Error('Stream not supported');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
// Process any remaining data in the buffer
if (buffer.trim()) {
try {
const parsed = JSON.parse(buffer.trim());
onJson(parsed);
} catch (err) {
console.warn('Final JSON parse error:', err, buffer);
}
}
break;
}
// Decode the current chunk and add to buffer
buffer += decoder.decode(value, { stream: true });
// Process complete JSON objects
let boundary = buffer.indexOf('\n');
while (boundary !== -1) {
const chunk = buffer.slice(0, boundary).trim();
buffer = buffer.slice(boundary + 1);
if (chunk) {
try {
const parsed = JSON.parse(chunk);
onJson(parsed);
} catch (err) {
// Only log if it's not an empty line
if (chunk !== '') {
console.warn('JSON parse error:', err, chunk);
}
}
}
boundary = buffer.indexOf('\n');
}
}
} catch (error) {
console.error('Stream processing error:', error);
throw error;
} finally {
// Ensure reader is released
reader.releaseLock();
}
} catch (error) {
console.error('Stream request error:', error);
// Handle specific error types
if (error instanceof TypeError) {
console.error('Network error - check your connection');
}
if (error instanceof SyntaxError) {
console.error('Invalid JSON in the stream');
}
throw error;
}
}
// 封装流式数据请求
export const stream = async <T>({
url,
method = 'POST',
data,
onMessage,
onError,
onComplete,
}: {
url: string;
method?: 'GET' | 'POST';
data?: any;
onMessage: (data: T) => void;
onError?: (error: any) => void;
onComplete?: () => void;
}) => {
try {
const config: AxiosRequestConfig = {
url,
method,
data,
responseType: 'stream',
headers: {
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
onDownloadProgress: (progressEvent: any) => {
const responseText = progressEvent.event?.target?.responseText;
if (!responseText) return;
// 处理接收到的数据
const lines: string[] = responseText.split('\n');
lines.forEach((line: string) => {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6));
onMessage(data);
} catch (e) {
console.warn('Failed to parse stream data:', e);
}
}
});
},
};
const response = await request(config);
onComplete?.();
return response;
} catch (error: any) {
handleRequestError(error, 'Stream request failed');
onError?.(error);
throw error;
}
};
// 封装文件下载流请求
export const downloadStream = async (
url: string,
filename: string,
config?: AxiosRequestConfig
) => {
try {
const response = await request({
url,
method: 'GET',
responseType: 'blob',
...config,
});
// 创建下载链接
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return response;
} catch (error: any) {
console.error('文件下载失败:', error);
handleRequestError(error, 'File download failed');
throw error;
}
};
// 导出 request 实例
export default request;