|
@@ -1,398 +0,0 @@
|
|
|
-import RequestApiConfig from './RequestApiConfig';
|
|
|
-import { DataModel, type NewDataModel } from '@imengyu/js-request-transform';
|
|
|
-import { isNullOrEmpty, stringHashCode } from '../utils/Utils';
|
|
|
-import { RequestApiError, RequestApiResult } from './RequestApiResult';
|
|
|
-import { defaultResponseDataHandler, defaultResponseErrorHandler } from './RequestHandler';
|
|
|
-import type { HeaderType, QueryParams, TypeSaveable } from '../utils/AllType';
|
|
|
-import type { KeyValue } from '@imengyu/js-request-transform/dist/DataUtils';
|
|
|
-import type { RequestImplementer } from './RequestImplementer';
|
|
|
-
|
|
|
-/**
|
|
|
- * API 请求核心
|
|
|
- *
|
|
|
- * 功能介绍:
|
|
|
- * 本类是对 fetch 的封装,提供了基本的请求功能。
|
|
|
- *
|
|
|
- * Author: imengyu
|
|
|
- * Date: 2022/03/28
|
|
|
- *
|
|
|
- * Copyright (c) 2021 imengyu.top. Licensed under the MIT License.
|
|
|
- * See License.txt in the project root for license information.
|
|
|
- */
|
|
|
-
|
|
|
-/**
|
|
|
- * 请求配置体
|
|
|
- */
|
|
|
-export interface RequestCoreConfig<T extends DataModel> {
|
|
|
- /**
|
|
|
- * 基础URL
|
|
|
- */
|
|
|
- baseUrl: string;
|
|
|
- /**
|
|
|
- * 错误代码字符串数据
|
|
|
- */
|
|
|
- errCodes: { [index: number]: string };
|
|
|
- /**
|
|
|
- * 默认携带header
|
|
|
- */
|
|
|
- defaultHeader: HeaderType,
|
|
|
- /**
|
|
|
- * 超时时间 ms
|
|
|
- */
|
|
|
- timeout: number,
|
|
|
- /**
|
|
|
- * 请求拦截
|
|
|
- */
|
|
|
- requestInceptor?: (url: string, req: RequestOptions) => { newUrl: string, newReq: RequestOptions };
|
|
|
- /**
|
|
|
- * 响应拦截
|
|
|
- */
|
|
|
- responseInceptor?: (response: Response) => Response;
|
|
|
- /**
|
|
|
- * 错误报告拦截。如果返回true,则不进行错误报告
|
|
|
- */
|
|
|
- responseErrReoprtInceptor?: (instance: RequestCoreInstance<T>, err: RequestApiError) => boolean;
|
|
|
- /**
|
|
|
- * 错误报告函数
|
|
|
- */
|
|
|
- reportError?: (instance: RequestCoreInstance<T>, err: RequestApiError|Error) => void;
|
|
|
-
|
|
|
- /**
|
|
|
- * 自定义数据处理函数
|
|
|
- */
|
|
|
- responseDataHandler?: (response: Response, req: RequestOptions, resultModelClass: NewDataModel|undefined, instance: RequestCoreInstance<T>, apiName: string|undefined) => Promise<RequestApiResult<T>>;
|
|
|
- /**
|
|
|
- * 自定义错误处理函数
|
|
|
- */
|
|
|
- responseErrorHandler?: (err: Error, instance: RequestCoreInstance<T>, apiName: string|undefined) => RequestApiError;
|
|
|
- /**
|
|
|
- * 类自定义创建函数
|
|
|
- */
|
|
|
- modelClassCreator: ModelClassCreatorDefine<T>|null;
|
|
|
-}
|
|
|
-
|
|
|
-type ModelClassCreatorDefine<T> = (new () => T);
|
|
|
-
|
|
|
-export interface RequestCacheConfig {
|
|
|
- /**
|
|
|
- * 缓存保存时间,毫秒。超过时间后再请求时会发请求
|
|
|
- */
|
|
|
- cacheTime: number,
|
|
|
- /**
|
|
|
- * 是否启用缓存
|
|
|
- */
|
|
|
- cacheEnable: boolean,
|
|
|
-}
|
|
|
-
|
|
|
-export interface RequestCacheStorage {
|
|
|
- time: number,
|
|
|
- data: TypeSaveable
|
|
|
-}
|
|
|
-
|
|
|
-export class RequestOptions {
|
|
|
- /**
|
|
|
- * 请求的参数
|
|
|
- */
|
|
|
- data?: string | object | ArrayBuffer | FormData;
|
|
|
- /**
|
|
|
- * 设置请求的 header,header 中不能设置 Referer。
|
|
|
- */
|
|
|
- header?: any;
|
|
|
- /**
|
|
|
- * 默认为 GET
|
|
|
- * 可以是:OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT
|
|
|
- */
|
|
|
- method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT';
|
|
|
- /**
|
|
|
- * 超时时间
|
|
|
- */
|
|
|
- timeout?: number;
|
|
|
- /**
|
|
|
- * 如果设为json,会尝试对返回的数据做一次 JSON.parse
|
|
|
- */
|
|
|
- dataType?: string;
|
|
|
- /**
|
|
|
- * 设置响应的数据类型。合法值:text、arraybuffer
|
|
|
- */
|
|
|
- responseType?: string;
|
|
|
- /**
|
|
|
- * 验证 ssl 证书
|
|
|
- */
|
|
|
- sslVerify?: boolean;
|
|
|
- /**
|
|
|
- * 跨域请求时是否携带凭证
|
|
|
- */
|
|
|
- withCredentials?: boolean;
|
|
|
- /**
|
|
|
- * DNS解析时优先使用 ipv4
|
|
|
- */
|
|
|
- firstIpv4?: boolean;
|
|
|
-}
|
|
|
-/**
|
|
|
- * API 请求核心实例类,本类是对 fetch 的封装,提供了基本的请求功能。
|
|
|
- */
|
|
|
-export class RequestCoreInstance<T extends DataModel> {
|
|
|
-
|
|
|
- constructor(implementer: RequestImplementer) {
|
|
|
- this.implementer = implementer;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 当前请求实例的请求配置项
|
|
|
- */
|
|
|
- config : RequestCoreConfig<T> = {
|
|
|
- baseUrl: '',
|
|
|
- errCodes: {},
|
|
|
- timeout: 10000,
|
|
|
- defaultHeader: RequestApiConfig.getConfig().DefaultHeader as HeaderType,
|
|
|
- modelClassCreator: null,
|
|
|
- responseDataHandler: defaultResponseDataHandler,
|
|
|
- responseErrorHandler: defaultResponseErrorHandler,
|
|
|
- };
|
|
|
-
|
|
|
- /**
|
|
|
- * 请求实现类
|
|
|
- */
|
|
|
- implementer: RequestImplementer;
|
|
|
-
|
|
|
- /**
|
|
|
- * 检查是否需要报告错误
|
|
|
- */
|
|
|
- checkShouldReportError(err: RequestApiError) {
|
|
|
- if (typeof this.config.responseErrReoprtInceptor === 'function')
|
|
|
- return this.config.responseErrReoprtInceptor(this, err) !== true;
|
|
|
- return true;
|
|
|
- }
|
|
|
- /**
|
|
|
- * 报告错误
|
|
|
- * @param err 错误
|
|
|
- */
|
|
|
- reportError(err: RequestApiError|Error) {
|
|
|
- if (this.checkShouldReportError(err as RequestApiError)) {
|
|
|
- if (typeof this.config.reportError === 'function')
|
|
|
- this.config.reportError(this, err);
|
|
|
- }
|
|
|
- }
|
|
|
- /**
|
|
|
- * 在配置中查找错误代码的说明文字
|
|
|
- * @param code 错误代码
|
|
|
- * @returns 说明文字,如果找不到,返回 undefined
|
|
|
- */
|
|
|
- findErrCode(code: number) : string|undefined {
|
|
|
- return this.config.errCodes[code];
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 合并URL
|
|
|
- */
|
|
|
- makeUrl(url: string, querys?: QueryParams) {
|
|
|
- let finalUrl = '';
|
|
|
- if (url.indexOf('http') === 0)
|
|
|
- finalUrl = url; //绝对地址
|
|
|
- else
|
|
|
- finalUrl = this.config.baseUrl + url;
|
|
|
- //处理query
|
|
|
- if (querys) {
|
|
|
- let i = finalUrl.indexOf('?') > 0 ? 1 : 0;
|
|
|
- for (const key in querys) {
|
|
|
- if (typeof querys[key] === 'undefined' || querys[key] === null)
|
|
|
- continue;
|
|
|
- finalUrl += i === 0 ? '?' : '&';
|
|
|
- if (typeof querys[key] === 'object')
|
|
|
- finalUrl += `${key}=` + encodeURIComponent(JSON.stringify(querys[key]));
|
|
|
- else
|
|
|
- finalUrl += `${key}=` + '' + querys[key];
|
|
|
- i++;
|
|
|
- }
|
|
|
- }
|
|
|
- return finalUrl;
|
|
|
- }
|
|
|
- //合并默认Header参数
|
|
|
- private mergerDefaultHeader(header: Record<string, unknown>) {
|
|
|
- const myHeaders = {} as Record<string, unknown>;
|
|
|
- for (const key in this.config.defaultHeader)
|
|
|
- myHeaders[key] = this.config.defaultHeader[key];
|
|
|
- if (header) {
|
|
|
- for (const key in header)
|
|
|
- myHeaders[key] = header[key];
|
|
|
- }
|
|
|
- return myHeaders;
|
|
|
- }
|
|
|
- /**
|
|
|
- * 合并两个Header参数
|
|
|
- * @param header 合并目标
|
|
|
- * @param newHeader 新的Header
|
|
|
- * @returns 合并后的Header
|
|
|
- */
|
|
|
- mergerHeaders(header: Record<string, unknown>, newHeader: Record<string, unknown>) {
|
|
|
- if (!newHeader)
|
|
|
- return header;
|
|
|
- if (!header)
|
|
|
- return newHeader;
|
|
|
- for (const key in newHeader)
|
|
|
- header[key] = newHeader[key];
|
|
|
- return header;
|
|
|
- }
|
|
|
-
|
|
|
- //检查缓存参数
|
|
|
- private checkCacheTime(cache?: RequestCacheConfig) {
|
|
|
- return cache && cache.cacheEnable && cache.cacheTime || 0;
|
|
|
- }
|
|
|
- //请求缓存处理
|
|
|
- private solveCache(url: string, req: RequestOptions, cache: RequestCacheConfig|undefined, callback: (cacheTime: number, cacheKey: string, res: TypeSaveable) => void) {
|
|
|
- const cacheTime = req.method === 'GET' ? this.checkCacheTime(cache) : 0;
|
|
|
- let requestHash = '';
|
|
|
- if (cacheTime > 0) {
|
|
|
- requestHash = "RequestCache" + stringHashCode(url + req.method);
|
|
|
- //获取数据
|
|
|
- this.implementer.getCache(requestHash).then((cacheData) => {
|
|
|
- if (!cacheData) {
|
|
|
- callback(cacheTime, requestHash, null);
|
|
|
- return;
|
|
|
- }
|
|
|
- //没有过期
|
|
|
- if (cacheData.time < new Date().getTime()) {
|
|
|
- callback(cacheTime, requestHash, cacheData.time);
|
|
|
- return;
|
|
|
- }
|
|
|
- callback(cacheTime, requestHash, null);
|
|
|
- }).catch(() => {
|
|
|
- callback(cacheTime, requestHash, null);
|
|
|
- });
|
|
|
- } else
|
|
|
- callback(cacheTime, requestHash, null);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 通用的请求包装方法
|
|
|
- * @param url 请求URL
|
|
|
- * @param req 请求参数
|
|
|
- * @param apiName 名称,用于日志和调试
|
|
|
- * @returns 返回 Promise
|
|
|
- */
|
|
|
- request(url: string, req: RequestOptions, apiName: string, modelClassCreator: NewDataModel|undefined, cache?: RequestCacheConfig) : Promise<RequestApiResult<T>> {
|
|
|
- return new Promise<RequestApiResult<T>>((resolve, reject) => {
|
|
|
- //附加请求头
|
|
|
- req.header = this.mergerDefaultHeader(req.header);
|
|
|
-
|
|
|
- //拦截器
|
|
|
- if (this.config.requestInceptor) {
|
|
|
- const { newUrl, newReq } = this.config.requestInceptor(url, req);
|
|
|
- url = newUrl;
|
|
|
- req = newReq;
|
|
|
- }
|
|
|
- if (req.data instanceof FormData) {
|
|
|
- req.header['Content-Type'] = 'multipart/form-data';
|
|
|
- } else if (typeof req.data === 'object' || req.data === undefined) {
|
|
|
- req.header['Content-Type'] = 'application/json';
|
|
|
- }
|
|
|
-
|
|
|
- if (RequestApiConfig.getConfig().EnableApiRequestLog)
|
|
|
- console.log(`[API Debugger] Q > ${apiName} [${req.method || 'GET'}] ` + url, req.data);
|
|
|
-
|
|
|
- //缓存处理
|
|
|
- this.solveCache(url, req, cache, (cacheTime, cacheKey, cacheRes) => {
|
|
|
-
|
|
|
- //有缓存数据,则直接返回
|
|
|
- if (cacheRes) {
|
|
|
- if (RequestApiConfig.getConfig().EnableApiRequestLog)
|
|
|
- console.log(`[API Debugger] C > ${apiName} (${cacheKey}/${cacheTime})`, ( RequestApiConfig.getConfig().EnableApiDataLog ? cacheRes.toString() : ''));
|
|
|
- resolve(cacheRes as unknown as RequestApiResult<T>);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- //发送请求并且处理响应数据
|
|
|
- this.requestAndResponse(url, req, apiName, modelClassCreator, (result) => {
|
|
|
- //保存缓存
|
|
|
- if (cacheTime > 0) {
|
|
|
- this.implementer.setCache(cacheKey, {
|
|
|
- time: new Date().getTime() + cacheTime,
|
|
|
- data: result as unknown as TypeSaveable,
|
|
|
- });
|
|
|
- }
|
|
|
- }).then((d) => {
|
|
|
- resolve(d);
|
|
|
- }).catch((e) => {
|
|
|
- reject(e);
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- //发送请求并且处理
|
|
|
- private requestAndResponse(url: string, req: RequestOptions, apiName: string, resultModelClass: NewDataModel|undefined, saveCache?: (result: unknown) => void) {
|
|
|
- return new Promise<RequestApiResult<T>>((resolve, reject) => {
|
|
|
- //发起请求
|
|
|
- this.implementer.doRequest(url, req, this.config.timeout).then((res) => {
|
|
|
- //响应拦截
|
|
|
- if (this.config.responseInceptor)
|
|
|
- res = this.config.responseInceptor(res);
|
|
|
-
|
|
|
- if (this.config.responseDataHandler) {
|
|
|
- //处理数据
|
|
|
- this.config.responseDataHandler(res, req, resultModelClass, this, apiName).then((result) => {
|
|
|
- //尝试保存缓存
|
|
|
- saveCache && saveCache(result);
|
|
|
- //处理数据
|
|
|
- try {
|
|
|
- if (RequestApiConfig.getConfig().EnableApiRequestLog)
|
|
|
- console.log(`[API Debugger] R > ${apiName} (${res.status}/${result.code})`);
|
|
|
- //返回
|
|
|
- resolve(result);
|
|
|
- } catch (e) {
|
|
|
- //捕获处理代码的异常
|
|
|
- console.error('[API Debugger] E > Catch exception in promise : ' + e + ((e as Error).stack ? ('\n' + (e as Error).stack) : ''));
|
|
|
- reject(new RequestApiError('scriptError', '代码异常,请检查:' + e, '脚本异常', -1, null, e as unknown as KeyValue, req, apiName));
|
|
|
- }
|
|
|
- }).catch((e) => {
|
|
|
- reject(e);
|
|
|
- });
|
|
|
- }
|
|
|
- else
|
|
|
- reject(new RequestApiError('scriptError', 'This RequestCoreInstance is not configured with responsedatahandler and cannot convert data! ', '脚本异常', -1, null, null, req, apiName));
|
|
|
- }).catch((err) => {
|
|
|
- reject(this.config.responseErrorHandler ? this.config.responseErrorHandler(err, this, apiName) : err);
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * GET 请求
|
|
|
- * @param url 请求URL
|
|
|
- * @param querys 请求URL参数
|
|
|
- * @param cache 缓存参数
|
|
|
- */
|
|
|
- get(url: string, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
|
|
|
- return this.request(this.makeUrl(url, querys), { method: 'GET', header: headers }, apiName, modelClassCreator, cache);
|
|
|
- }
|
|
|
- /**
|
|
|
- * POST 请求
|
|
|
- * @param url 请求URL
|
|
|
- * @param data 请求Body参数
|
|
|
- * @param querys 请求URL参数
|
|
|
- * @param cache 缓存参数
|
|
|
- */
|
|
|
- post(url: string, data: KeyValue|FormData, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
|
|
|
- return this.request(this.makeUrl(url, querys), { method: 'POST', data, header: headers }, apiName, modelClassCreator, cache);
|
|
|
- }
|
|
|
- /**
|
|
|
- * PUT 请求
|
|
|
- * @param url 请求URL
|
|
|
- * @param data 请求Body参数
|
|
|
- * @param querys 请求URL参数
|
|
|
- * @param cache 缓存参数
|
|
|
- */
|
|
|
- put(url: string, data: KeyValue, apiName: string,querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
|
|
|
- return this.request(this.makeUrl(url, querys), { method: 'PUT', data, header: headers }, apiName, modelClassCreator, cache);
|
|
|
- }
|
|
|
- /**
|
|
|
- * DELETE 请求
|
|
|
- * @param url 请求URL
|
|
|
- * @param data 请求Body参数
|
|
|
- * @param querys 请求URL参数
|
|
|
- * @param cache 缓存参数
|
|
|
- */
|
|
|
- delete(url: string, data: KeyValue, apiName: string, querys?: QueryParams, modelClassCreator?: NewDataModel, cache?: RequestCacheConfig, headers?: KeyValue) {
|
|
|
- return this.request(this.makeUrl(url, querys), { method: 'DELETE', data, header: headers }, apiName, modelClassCreator, cache);
|
|
|
- }
|
|
|
-}
|