Skip to content

Commit

Permalink
Merge pull request #5 from mengxinssfd/实现
Browse files Browse the repository at this point in the history
feat(实现): 完成
  • Loading branch information
mengxinssfd committed Apr 18, 2022
2 parents 4a0ea72 + cb4aefe commit f856b65
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/Cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CustomConfig } from './types';

type KeyHandler<K> = (k: K) => string;

export default class Cache<K, V> {
private readonly cache = new Map<string, { value: V; expires: number }>();

private readonly keyHandler: KeyHandler<K>;

constructor(keyHandler?: KeyHandler<K>) {
this.keyHandler = keyHandler || ((k) => JSON.stringify(k));
}

clearDeadCache() {
const now = Date.now();
for (const [k, v] of this.cache.entries()) {
if (v.expires > now) continue;
this.cache.delete(k);
}
}

get(key: K) {
const k = this.keyHandler(key);
const v = this.cache.get(k);
if (!v) return null;
const now = Date.now();
if (now > v.expires) {
this.cache.delete(k);
return null;
}
return v.value;
}

set(key: K, value: V, customConfig: CustomConfig = {}) {
const defaultTimeout = 5 * 1000;
const timeout =
typeof customConfig.useCache === 'object' ? customConfig.useCache?.timeout : defaultTimeout;
this.cache.set(this.keyHandler(key), { value, expires: timeout + Date.now() });
this.clearDeadCache();
}

has(key: K): boolean {
return this.get(key) !== null;
}
}
51 changes: 51 additions & 0 deletions src/HttpStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 来源于nestjs @nestjs/common/enums/http-status.enum.d.ts
export enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLYHINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
RESET_TOKEN = 207,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
MISDIRECTED = 421,
UNPROCESSABLE_ENTITY = 422,
FAILED_DEPENDENCY = 424,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
}
130 changes: 130 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { ResType, CustomConfig } from './types';
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import Qs from 'qs';
import Cache from './Cache';

// 使用模板方法模式处理axios请求, 具体类可实现protected方法替换掉原有方法
export default class AxiosWrapper {
private readonly axios: AxiosInstance;
private readonly cache: Cache<AxiosRequestConfig, Promise<any>>;
constructor(config: AxiosRequestConfig = {}, private customConfig: CustomConfig<boolean>) {
// 1、保存基础配置
this.axios = axios.create(config);
this.setInterceptors();
// 缓存初始化
this.cache = new Cache((config) => {
const url = config.url;
const data = config.data || config.params;
const headers = config.headers;
return JSON.stringify({ url, data, headers });
});
}
// 转换数据结构为ResType
protected transferRes<T>(res: AxiosResponse): ResType<T> {
return res.data as ResType;
}
// 获取拦截器
protected get interceptors() {
return this.axios.interceptors;
}
protected setInterceptors() {
// 重写此函数会在Request中调用
// example
// this.interceptors.request.use(() => {
// /* do something */
// });
}
protected handleConfig(url: string, config: AxiosRequestConfig): AxiosRequestConfig {
const finalConfig: AxiosRequestConfig = { ...config, url };
finalConfig.method = finalConfig.method || 'get';
return finalConfig;
}
protected handleParams(data: {}, config: AxiosRequestConfig) {
if (config.method === 'get') {
config.params = data;
return;
}
if (!(data instanceof FormData)) {
// 使用Qs.stringify处理过的数据不会有{}包裹
// 使用Qs.stringify其实就是转成url的参数形式:a=1&b=2&c=3
// 格式化模式有三种:indices、brackets、repeat
data = Qs.stringify(data, { arrayFormat: 'repeat' });
}
config.data = data;
}
protected handleResponse<T>(
res: AxiosResponse<ResType<any>>,
data: ResType<any>,
customConfig: CustomConfig<boolean>,
): Promise<ResType<T>> {
const code = data.code ?? 'default';
const handlers = customConfig.statusHandlers;
const defaultHandlers = this.customConfig.statusHandlers || { default: (res) => res.data };
const statusHandler =
(handlers && (handlers[code] || handlers.default)) ||
defaultHandlers[code] ||
defaultHandlers.default;
return statusHandler(res, data, customConfig as CustomConfig);
}

private _request(customConfig: CustomConfig = {}, axiosConfig: AxiosRequestConfig = {}) {
if (customConfig.useCache) {
const c = this.cache.get(axiosConfig);
if (c) {
return c;
}
}
const res = this.axios(axiosConfig);

if (customConfig.useCache) {
this.cache.set(axiosConfig, res, customConfig);
}

return res;
}

request<T = never>(url: string, data?: {}): Promise<ResType<T>>;
request<T = never, RC extends boolean = false>(
url: string,
data: {},
customConfig?: CustomConfig<RC>,
axiosConfig?: AxiosRequestConfig,
): Promise<RC extends true ? AxiosResponse<ResType<T>> : ResType<T>>;
async request<T>(
url: string,
data: {} = {},
customConfig: CustomConfig = {},
axiosConfig: AxiosRequestConfig = {},
): Promise<any> {
// 1、处理配置
const config = this.handleConfig(url, axiosConfig);
// 2、处理参数
this.handleParams(data, config);
try {
// 3、请求
const response: AxiosResponse = await this._request(customConfig, config);
// 4、请求结果数据结构处理
const data = this.transferRes<T>(response);
// 5、状态码处理,并返回结果
return this.handleResponse<T>(response, data, customConfig);
} catch (e: any) {
// 错误处理
const response: AxiosResponse<ResType<any>> = e.response;
const data = this.transferRes<T>(response);
if (data && data.msg) {
return this.handleResponse<T>(response, data, customConfig);
}
}
}

protected static methodFactory(method: Method, ins: AxiosWrapper) {
return function <T = never, RC extends boolean = false>(
url: string,
data: {},
customConfig: CustomConfig<RC> = {},
axiosConfig: AxiosRequestConfig = {},
) {
return ins.request<T, RC>(url, data, customConfig, { ...axiosConfig, method });
};
}
}
23 changes: 23 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { AxiosResponse } from 'axios';

export type StatusHandler = (
res: AxiosResponse<ResType<any>>,
data: ResType<any>,
requestConfig: CustomConfig,
) => any;

// StatusHandlers
export type StatusHandlers = Record<number, StatusHandler> & { default?: StatusHandler };
// CustomConfig
export interface CustomConfig<T extends boolean = false> {
returnRes?: T; // 返回res
silent?: boolean; // 报错不弹窗
statusHandlers?: StatusHandlers;
useCache?: boolean | { timeout: number };
}

export interface ResType<T = never> {
code: number;
msg: string;
data: T;
}

0 comments on commit f856b65

Please sign in to comment.