video-flow-b/api/request.ts

265 lines
7.0 KiB
TypeScript

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, AxiosHeaders } from 'axios';
import { BASE_URL } from './constants'
// 创建 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') || 'mock-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) => {
// 直接返回响应数据
return response.data;
},
(error) => {
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权,清除 token 并跳转到登录页
localStorage?.removeItem('token');
window.location.href = '/login';
break;
case 403:
// 权限不足
console.error('没有权限访问该资源');
break;
case 404:
// 资源不存在
console.error('请求的资源不存在');
break;
case 500:
// 服务器错误
console.error('服务器错误');
break;
default:
console.error('请求失败:', error.response.data.message || '未知错误');
}
} else if (error.request) {
// 请求已发出但没有收到响应
console.error('网络错误,请检查您的网络连接');
} else {
// 请求配置出错
console.error('请求配置错误:', error.message);
}
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') || 'mock-token';
const response = await fetch(`${BASE_URL}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
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) {
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) {
console.error('文件下载失败:', error);
throw error;
}
};
// 导出 request 实例
export default request;