| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- import { API_BASE_URL } from '../api';
- import { LOCAL_SIGN } from '../constants/Localkey';
- import { localStorageGet } from '../utils/Helper';
- import { getComParams } from './CommonParams';
- import {
- ApiEndpoint,
- ApiEnvelope,
- ApiParams,
- HttpError,
- HttpMethod,
- } from './RequestTypes';
- export interface ApiCodec {
- encode(json: string): string;
- decode(payload: string): string;
- }
- export interface HttpClientOptions {
- baseUrl: string;
- timeoutMs?: number;
- retryCount?: number;
- retryDelayMs?: number;
- compress?: boolean;
- codec?: ApiCodec;
- }
- export class HttpClient {
- private readonly timeoutMs: number;
- private readonly retryCount: number;
- private readonly retryDelayMs: number;
- constructor(private readonly options: HttpClientOptions) {
- this.timeoutMs = options.timeoutMs ?? 10_000;
- this.retryCount = options.retryCount ?? 2;
- this.retryDelayMs = options.retryDelayMs ?? 1_000;
- }
- get<T>(path: string, query?: ApiParams): Promise<ApiEnvelope<T>> {
- return this.request<T>('GET', path, query);
- }
- post<T>(path: string, body?: ApiParams): Promise<ApiEnvelope<T>> {
- return this.request<T>('POST', path, body);
- }
- call<T>(endpoint: ApiEndpoint, params?: ApiParams): Promise<ApiEnvelope<T>> {
- return this.request<T>(endpoint.method, endpoint.path, params);
- }
- async request<T>(method: HttpMethod, path: string, params: ApiParams = {}): Promise<ApiEnvelope<T>> {
- let lastError: unknown;
- for (let attempt = 0; attempt <= this.retryCount; attempt += 1) {
- try {
- return await this.send<T>(method, path, params);
- } catch (error) {
- lastError = error;
- if (attempt >= this.retryCount) {
- throw error;
- }
- await this.delay(this.retryDelayMs);
- }
- }
- throw lastError;
- }
- private send<T>(method: HttpMethod, path: string, params: ApiParams): Promise<ApiEnvelope<T>> {
- const comParams = getComParams();
- Object.assign(params, comParams);
- const url = this.resolveUrl(path);
- const useCompression = Boolean(this.options.compress && this.options.codec);
- const rawJson = JSON.stringify(params);
- const payload = useCompression ? { lz: this.options.codec!.encode(rawJson) } : params;
- const requestUrl = method === 'GET' ? this.appendQuery(url, payload) : url;
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.open(method, requestUrl, true);
- xhr.timeout = this.timeoutMs;
- xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
- if (method === 'POST') {
- xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
- }
- xhr.onload = () => {
- if (xhr.status < 200 || xhr.status >= 300) {
- reject(new HttpError(`HTTP ${xhr.status}`, xhr.status, requestUrl));
- return;
- }
- try {
- let text = xhr.responseText;
- if (useCompression && text && !text.replace(/^\s+/, '').startsWith('{')) {
- text = this.options.codec!.decode(text);
- }
- resolve(JSON.parse(text) as ApiEnvelope<T>);
- } catch (error) {
- reject(new HttpError(
- `Invalid API response: ${error instanceof Error ? error.message : String(error)}`,
- xhr.status,
- requestUrl,
- ));
- }
- };
- xhr.onerror = () => reject(new HttpError('Network request failed', xhr.status, requestUrl));
- xhr.ontimeout = () => reject(new HttpError('Network request timed out', 0, requestUrl));
- xhr.onabort = () => reject(new HttpError('Network request aborted', 0, requestUrl));
- xhr.send(method === 'POST' ? JSON.stringify(payload) : null);
- });
- }
- private resolveUrl(path: string): string {
- return `${this.options.baseUrl.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
- }
- private appendQuery(url: string, params: ApiParams): string {
- const query: string[] = [];
- for (const key of Object.keys(params)) {
- const value = params[key];
- if (value === undefined || value === null || value === '') continue;
- const encoded = typeof value === 'object' ? JSON.stringify(value) : String(value);
- query.push(`${encodeURIComponent(key)}=${encodeURIComponent(encoded)}`);
- }
- return query.length > 0 ? `${url}?${query.join('&')}` : url;
- }
- private delay(ms: number): Promise<void> {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- }
- /**
- * 全局 HTTP 请求对象
- */
- export const Http = new HttpClient({
- baseUrl: API_BASE_URL,
- timeoutMs: 10_000,
- retryCount: 2,
- retryDelayMs: 1_000,
- });
|