This commit is contained in:
star7th
2025-04-10 18:06:45 +08:00
commit 54650cd944
154 changed files with 29090 additions and 0 deletions
+348
View File
@@ -0,0 +1,348 @@
import { HttpMethod, HttpResponse, RequestHeader, NetworkType, ResponseData } from './types';
import { apiClient } from '@/lib/api-client';
// 代理响应接口
interface ProxyResponse {
status: number;
statusText: string;
headers: Record<string, string>;
data: ResponseData;
time?: number;
size?: number;
}
/**
* 发送HTTP请求
* @param url 请求URL
* @param method 请求方法
* @param headers 请求头
* @param body 请求体
* @param bodyFormat 请求体格式
* @param formFields 表单字段
* @param networkType 网络类型(公网/本地)
*/
export const sendHttpRequest = async (
url: string,
method: HttpMethod,
headers: RequestHeader[],
body: string,
bodyFormat: 'json' | 'text' | 'form',
formFields: RequestHeader[],
networkType: NetworkType = 'public'
): Promise<{ response: HttpResponse | null; error: string | null }> => {
try {
if (!url.trim()) {
return { response: null, error: 'URL不能为空' };
}
// 准备请求头
const headerObj: Record<string, string> = {};
headers.forEach(h => {
if (h.key.trim() && h.value.trim()) {
headerObj[h.key] = h.value;
}
});
// 准备请求体
let requestBody: string | FormData | undefined;
if (['POST', 'PUT', 'PATCH'].includes(method)) {
if (bodyFormat === 'json') {
try {
// 尝试验证JSON格式
if (body.trim()) {
JSON.parse(body);
}
requestBody = body;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// 忽略具体错误,只返回格式无效的消息
return { response: null, error: '请求体不是有效的JSON格式' };
}
} else if (bodyFormat === 'form') {
try {
// 使用表单字段构建请求体
const formData = new URLSearchParams();
formFields.forEach(field => {
if (field.key.trim() && field.value.trim()) {
formData.append(field.key, field.value);
}
});
if ([...formData.keys()].length === 0) {
return { response: null, error: '表单至少需要一个有效的字段' };
}
requestBody = formData.toString();
// 设置适当的Content-Type
headerObj['Content-Type'] = 'application/x-www-form-urlencoded';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// 忽略具体错误,只返回处理失败的消息
return { response: null, error: '表单数据处理失败,请检查输入的字段值' };
}
} else {
requestBody = body;
}
}
const startTime = performance.now();
// 根据网络类型决定发送请求的方式
let responseData: ProxyResponse;
let clientTime: number;
// 如果是本地/局域网请求,直接发送请求(不通过代理)
if (networkType === 'local') {
try {
// 直接使用fetch API发送请求到目标地址
const response = await fetch(url, {
method,
headers: headerObj,
body: ['POST', 'PUT', 'PATCH'].includes(method) ? requestBody : undefined,
});
const endTime = performance.now();
clientTime = Math.round(endTime - startTime);
// 获取响应头
const headers: Record<string, string> = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// 尝试解析响应体
let data: ResponseData;
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
// 计算响应大小
const bodyText = typeof data === 'string' ? data : JSON.stringify(data);
const size = new Blob([bodyText]).size;
responseData = {
status: response.status,
statusText: response.statusText,
headers,
data,
time: clientTime,
size,
};
} catch (error) {
return {
response: null,
error: `本地请求失败: ${(error as Error).message}。请确保目标服务器已配置CORS,允许跨域请求。`
};
}
} else {
// 通过后端代理发送请求,避免跨域问题
const proxyResponse = await apiClient.post<ProxyResponse>('/api/proxy', {
url,
method,
headers: headerObj,
body: requestBody,
});
const endTime = performance.now();
clientTime = Math.round(endTime - startTime);
responseData = proxyResponse;
}
return {
response: {
status: responseData.status,
statusText: responseData.statusText,
headers: responseData.headers,
data: responseData.data,
// 使用服务器计算的响应时间或客户端时间
time: responseData.time || clientTime,
size: responseData.size || 0,
},
error: null
};
} catch (error) {
console.error('请求错误', error);
return {
response: null,
error: (error as Error).message || '请求失败'
};
}
};
/**
* 生成Markdown格式的接口文档
* @param url 请求URL
* @param method 请求方法
* @param headers 请求头
* @param requestBody 请求体
* @param bodyFormat 请求体格式
* @param formFields 表单字段
* @param response 响应数据
* @param networkType 网络类型(公网/本地)
* @returns Markdown格式的接口文档
*/
export const generateMarkdownDoc = (
url: string,
method: HttpMethod,
headers: RequestHeader[],
requestBody: string,
bodyFormat: 'json' | 'text' | 'form',
formFields: RequestHeader[],
response: HttpResponse | null,
networkType: NetworkType = 'public'
): string => {
// 准备请求体展示
let requestBodyContent = '';
if (['POST', 'PUT', 'PATCH'].includes(method)) {
if (bodyFormat === 'json' && requestBody.trim()) {
try {
// 美化JSON
const parsedJson = JSON.parse(requestBody);
requestBodyContent = JSON.stringify(parsedJson, null, 2);
} catch {
requestBodyContent = requestBody;
}
} else if (bodyFormat === 'form') {
// 构建表单内容,但不需要在这里生成请求体内容
// 因为我们将在下面的请求参数部分直接使用表格展示
const formData = new URLSearchParams();
formFields.forEach(field => {
if (field.key.trim() && field.value.trim()) {
formData.append(field.key, field.value);
}
});
// 在表单模式下,请求体内容为空,因为我们会直接使用表格展示
requestBodyContent = '';
} else {
requestBodyContent = requestBody;
}
}
// 过滤有效的请求头
const validHeaders = headers.filter(h => h.key.trim() && h.value.trim());
// 构建Markdown文档 - 使用ShowDoc风格
let markdown = ``;
// 简要描述部分
markdown += `**简要描述:** \n\n`;
markdown += `- 自动生成的API接口文档\n\n`;
// 请求模式部分(新增)
markdown += `**请求模式:** \n\n`;
markdown += `- ${networkType === 'local' ? '本地/局域网' : '公网代理'} \n\n`;
// 请求URL部分
markdown += `**请求URL** \n\n`;
markdown += `- \`${url}\`\n\n`;
// 请求方式部分
markdown += `**请求方式:**\n\n`;
markdown += `- ${method} \n\n`;
// 请求头部分(如果有)
if (validHeaders.length > 0) {
markdown += `**请求头:** \n\n`;
markdown += `| 参数名 | 必选 | 参数值 | 说明 |\n`;
markdown += `| ------ | ---- | ------ | ---- |\n`;
validHeaders.forEach(header => {
const isContent = header.key.toLowerCase() === 'content-type';
markdown += `| ${header.key} | ${isContent ? '是' : '否'} | ${header.value} | ${isContent ? '请求数据类型' : '-'} |\n`;
});
markdown += `\n`;
}
// 请求参数部分(针对POST、PUT等方法)
if (['POST', 'PUT', 'PATCH'].includes(method) && (requestBodyContent || bodyFormat === 'form')) {
markdown += `**请求参数:** \n\n`;
// 针对表单格式单独处理
if (bodyFormat === 'form') {
// 直接生成表单参数表格
markdown += `| 参数名 | 必选 | 类型 | 说明 |\n`;
markdown += `| ------ | ---- | ---- | ---- |\n`;
const validFormFields = formFields.filter(field => field.key.trim() && field.value.trim());
if (validFormFields.length > 0) {
validFormFields.forEach(field => {
markdown += `| ${field.key} | 是 | string | - |\n`;
});
markdown += `\n`;
// 添加URL编码格式的说明
const formData = new URLSearchParams();
validFormFields.forEach(field => {
formData.append(field.key, field.value);
});
markdown += `**表单URL编码格式:** \n\n`;
markdown += `\`${formData.toString()}\`\n\n`;
} else {
markdown += `| - | - | - | 无参数 |\n\n`;
}
}
// JSON或文本格式的处理
else {
// 尝试解析参数并构建表格
try {
if (bodyFormat === 'json') {
const parsedBody = JSON.parse(requestBodyContent);
if (typeof parsedBody === 'object' && parsedBody !== null) {
markdown += `| 参数名 | 必选 | 类型 | 说明 |\n`;
markdown += `| ------ | ---- | ---- | ---- |\n`;
Object.entries(parsedBody).forEach(([key, value]) => {
const type = Array.isArray(value) ? 'array' : typeof value;
markdown += `| ${key} | 是 | ${type} | - |\n`;
});
markdown += `\n`;
} else {
markdown += `\`\`\`json\n${requestBodyContent}\n\`\`\`\n\n`;
}
} else if (requestBodyContent.trim()) {
markdown += `\`\`\`\n${requestBodyContent}\n\`\`\`\n\n`;
}
} catch {
if (requestBodyContent.trim()) {
markdown += `\`\`\`\n${requestBodyContent}\n\`\`\`\n\n`;
}
}
}
}
// 响应结果部分
if (response) {
// 返回示例
markdown += `**返回示例**\n\n`;
if (typeof response.data === 'object') {
// 直接使用JSON.stringify的缩进参数格式化JSON
markdown += `\`\`\`json\n${JSON.stringify(response.data, null, 2)}\n\`\`\`\n\n`;
} else if (typeof response.data === 'string') {
// 尝试检测是否为JSON字符串
try {
const parsedJson = JSON.parse(response.data);
markdown += `\`\`\`json\n${JSON.stringify(parsedJson, null, 2)}\n\`\`\`\n\n`;
} catch {
// 不是JSON字符串,直接显示
markdown += `\`\`\`\n${response.data}\n\`\`\`\n\n`;
}
} else {
markdown += `\`\`\`\n${response.data}\n\`\`\`\n\n`;
}
}
// 备注
markdown += `**备注** \n\n`;
markdown += `- 此文档由HTTP测试工具自动生成\n`;
markdown += `- 响应时间: ${response?.time || '-'}ms\n`;
markdown += `- 响应大小: ${response?.size || '-'} bytes\n`;
return markdown;
};