🌱 初步创建新请求封装

This commit is contained in:
BTMuli
2026-04-03 23:35:34 +08:00
parent 87c6d0e4e8
commit 4b500f6169
3 changed files with 334 additions and 0 deletions

20
src/enum/app.ts Normal file
View File

@@ -0,0 +1,20 @@
/**
* 应用枚举类
* @since Beta v0.10.0
*/
/**
* 请求方法类型枚举
* @since Beta v0.10.0
* @see TGApp.App.Response.ReqMethodEnum
*/
const ReqMethodEnum: typeof TGApp.App.Response.ReqMethod = {
GET: "GET",
POST: "POST",
};
const appEnum = {
req: ReqMethodEnum,
};
export default appEnum;

98
src/types/App/Response.d.ts vendored Normal file
View File

@@ -0,0 +1,98 @@
/**
* 应用请求封装相关类型定义
* @since Beta v0.10.0
*/
declare namespace TGApp.App.Response {
/**
* 请求方法枚举
* @since Beta v0.10.0
* @remarks 只定义了用到的部分
*/
const ReqMethod = <const>{
/** GET */
GET: "GET",
/** POST */
POST: "POST",
};
/**
* 请求方法枚举类型
* @since Beta v0.10.0
*/
type ReqMethodEnum = (typeof ReqMethod)[keyof typeof ReqMethod];
/**
* 请求配置
* @since Beta v0.10.0
* @remarks 只写了用到的部分
*/
type ReqConf = {
/** 请求方法 */
method?: ReqMethodEnum;
/** 请求头 */
headers?: Record<string, string>;
/** URL 查询参数 */
query?: Record<string, string | number | boolean>;
/** 请求体 */
body?: string | Record<string, unknown>;
/** 是否返回 Blob 数据 */
isBlob?: boolean;
/** 是否包含 BigInt 数据 */
hasBigInt?: boolean;
/** 请求超时时间(毫秒) */
timeout?: number;
/** AbortSignal 用于取消请求 */
signal?: AbortSignal;
/** 基础 URL */
baseURL?: string;
};
/**
* 请求配置参数
* @since Beta v0.10.0
*/
type ReqConfParams = Omit<ReqConf, "baseURL">;
/**
* 响应类型
* @since Beta v0.10.0
*/
type Resp<T> = {
/** 响应数据 */
data: T;
/** HTTP 状态码 */
status: number;
/** 状态文本 */
statusText: string;
/** 响应头 */
headers: Headers;
/** 原始 Response 对象 */
raw: Response;
/** 请求配置 */
config: ReqConf;
};
/**
* HTTP错误响应
* @since Beta v0.10.0
*/
type HttpErr = {
/** 错误消息 */
message: string;
/** HTTP 状态码 */
status?: number;
/** 状态文本 */
statusText?: string;
/** 响应数据 */
data?: unknown;
/** 原始错误 */
cause?: unknown;
};
/**
* Http错误构建参数
* @since Beta v0.10.0
*/
type HttpErrParams = Omit<HttpErr, "message">;
}

216
src/utils/TGHttps.ts Normal file
View File

@@ -0,0 +1,216 @@
/**
* 应用请求客户端封装
* @since Beta v0.10.0
*/
import { type ClientOptions, fetch } from "@tauri-apps/plugin-http";
import JSONBig from "json-bigint";
import TGLogger from "./TGLogger.js";
/**
* 构建 URL 查询字符串
* @since Beta v0.10.0
* @param params - 查询参数
* @returns 查询字符串
*/
function buildQueryString(params: Record<string, string | number | boolean>): string {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
searchParams.append(key, String(value));
}
return searchParams.toString();
}
/**
* 创建 HTTP 错误对象
* @since Beta v0.10.0
* @param message - 错误消息
* @param options - 错误选项
* @returns HTTP 错误对象
*/
function createHttpError(
message: string,
options: TGApp.App.Response.HttpErrParams,
): TGApp.App.Response.HttpErr {
return {
message,
status: options.status,
statusText: options.statusText,
data: options.data,
cause: options.cause,
};
}
/**
* 解析响应数据
* @since Beta v0.10.0
* @param response - 原始响应
* @param config - 请求配置
* @returns 解析后的数据
*/
async function parseResponse<T>(
response: Response,
config: TGApp.App.Response.ReqConf,
): Promise<T> {
if (config.isBlob) {
return <T>await response.arrayBuffer();
}
if (config.hasBigInt) {
return <T>JSONBig.parse(await response.text());
}
return <T>await response.json();
}
/**
* 执行 HTTP 请求
* @since Beta v0.10.0
* @param method - 请求方法
* @param url - 请求地址
* @param config - 请求配置
* @returns 响应对象
*/
async function request<T>(
method: TGApp.App.Response.ReqMethodEnum,
url: string,
config: TGApp.App.Response.ReqConf = {},
): Promise<TGApp.App.Response.Resp<T>> {
const timeout = config.timeout ?? 30000;
// 构建完整 URL
let finalUrl = url;
if (config.query) {
const queryString = buildQueryString(config.query);
if (queryString) {
finalUrl += `?${queryString}`;
}
}
// 构建请求头
const httpHeaders = new Headers();
if (config.headers) {
for (const [key, value] of Object.entries(config.headers)) {
httpHeaders.append(key, value);
}
}
// 构建请求选项
const fetchOptions: RequestInit & ClientOptions = {
method,
headers: httpHeaders,
};
// 添加请求体
if (config.body !== undefined) {
fetchOptions.body = typeof config.body === "string" ? config.body : JSON.stringify(config.body);
}
// 调试日志
if (config.isBlob) {
console.debug(`[TGHttps] Fetch Blob: ${finalUrl}`);
} else {
console.debug(`[TGHttps] ${method} ${finalUrl}`);
}
// 创建超时控制器
const timeoutController = new AbortController();
const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
// 合并 AbortSignal
let combinedSignal: AbortSignal;
if (config.signal) {
combinedSignal = AbortSignal.any([config.signal, timeoutController.signal]);
} else {
combinedSignal = timeoutController.signal;
}
fetchOptions.signal = combinedSignal;
try {
const rawResponse = await fetch(finalUrl, fetchOptions);
// 清除超时定时器
clearTimeout(timeoutId);
// 检查 HTTP 状态
if (!rawResponse.ok) {
const errorText = await rawResponse.text().catch(() => "Unknown error");
throw createHttpError(`HTTP Error: ${rawResponse.status} ${rawResponse.statusText}`, {
status: rawResponse.status,
statusText: rawResponse.statusText,
data: errorText,
});
}
// 解析响应
const data = await parseResponse<T>(rawResponse, config);
return {
data: data,
status: rawResponse.status,
statusText: rawResponse.statusText,
headers: rawResponse.headers,
raw: rawResponse,
config: config,
};
} catch (error) {
// 清除超时定时器
clearTimeout(timeoutId);
let httpError: TGApp.App.Response.HttpErr;
if (typeof error === "object" && error !== null && "message" in error) {
httpError = <TGApp.App.Response.HttpErr>error;
} else if (error instanceof Error) {
httpError = createHttpError(error.message, { cause: error });
} else {
httpError = createHttpError(String(error), { cause: error });
}
// 记录错误日志
await TGLogger.Error(`[TGHttps] Request failed: ${httpError.message}`);
throw httpError;
}
}
const TGHttps = {
/**
* GET 请求
* @since Beta v0.10.0
* @param url - 请求地址
* @param config - 请求配置
* @returns 响应对象
*/
get: <T>(
url: string,
config?: TGApp.App.Response.ReqConfParams,
): Promise<TGApp.App.Response.Resp<T>> => request<T>("GET", url, config),
/**
* POST 请求
* @since Beta v0.10.0
* @param url - 请求地址
* @param config - 请求配置
* @returns 响应对象
*/
post: <T>(
url: string,
config?: TGApp.App.Response.ReqConfParams,
): Promise<TGApp.App.Response.Resp<T>> => request<T>("POST", url, config),
/**
* 通用请求方法
* @since Beta v0.10.0
* @param method - 请求方法
* @param url - 请求地址
* @param config - 请求配置
* @returns 响应对象
*/
request: <T>(
method: TGApp.App.Response.ReqMethodEnum,
url: string,
config?: TGApp.App.Response.ReqConfParams,
): Promise<TGApp.App.Response.Resp<T>> => request<T>(method, url, config),
};
export default TGHttps;