315 lines
8.4 KiB
TypeScript
315 lines
8.4 KiB
TypeScript
import { adapter, Method, AdapterOpts, methods } from './adapter.ts';
|
||
import type { QueryWs } from './ws.ts';
|
||
/**
|
||
* 请求前处理函数
|
||
* @param opts 请求配置
|
||
* @returns 请求配置
|
||
*/
|
||
export type Fn = (opts: {
|
||
url?: string;
|
||
headers?: Record<string, string>;
|
||
body?: Record<string, any>;
|
||
[key: string]: any;
|
||
timeout?: number;
|
||
}) => Promise<Record<string, any> | false>;
|
||
|
||
export type QueryOpts = {
|
||
adapter?: typeof adapter;
|
||
token?: string;
|
||
[key: string]: any;
|
||
} & AdapterOpts;
|
||
|
||
export type QueryOptions = {
|
||
url?: string;
|
||
baseURL?: string;
|
||
beforeRequest?: Fn;
|
||
adapter?: typeof adapter;
|
||
headers?: Record<string, string>;
|
||
timeout?: number;
|
||
isClient?: boolean;
|
||
tokenName?: string;
|
||
storage?: Storage;
|
||
}
|
||
export type Data = {
|
||
path?: string;
|
||
key?: string;
|
||
payload?: Record<string, any>;
|
||
[key: string]: any;
|
||
};
|
||
export type Result<S = any, U = {}> = {
|
||
code: number;
|
||
data?: S;
|
||
message?: string;
|
||
} & U;
|
||
// 额外功能
|
||
export type DataOpts = Partial<QueryOpts> & {
|
||
beforeRequest?: Fn;
|
||
afterResponse?: <S = any>(result: Result<S>, ctx?: { req?: any; res?: any; fetch?: any }) => Promise<Result<S>>;
|
||
/**
|
||
* 是否在stop的时候不请求
|
||
*/
|
||
noStop?: boolean;
|
||
};
|
||
|
||
export const wrapperError = ({ code, message }: { code?: number; message?: string }) => {
|
||
const result = {
|
||
code: code || 500,
|
||
message: message || '请求错误'
|
||
};
|
||
return result;
|
||
};
|
||
/**
|
||
* const query = new Query();
|
||
* const res = await query.post({
|
||
* path: 'demo',
|
||
* key: '1',
|
||
* });
|
||
*
|
||
* U是参数 V是返回值
|
||
*/
|
||
export class Query {
|
||
adapter: typeof adapter;
|
||
baseURL: string;
|
||
url: string;
|
||
/**
|
||
* 请求前处理函数
|
||
*/
|
||
beforeRequest?: DataOpts['beforeRequest'];
|
||
/**
|
||
* 请求后处理函数
|
||
*/
|
||
afterResponse?: DataOpts['afterResponse'];
|
||
headers?: Record<string, string>;
|
||
timeout?: number;
|
||
/**
|
||
* 需要突然停止请求,比如401的时候
|
||
*/
|
||
stop?: boolean;
|
||
// 默认不使用ws
|
||
qws: QueryWs;
|
||
tokenName: string;
|
||
storage: Storage;
|
||
token: string;
|
||
constructor(opts?: QueryOptions) {
|
||
this.adapter = opts?.adapter || adapter;
|
||
this.tokenName = opts?.tokenName || 'token';
|
||
this.storage = opts?.storage || globalThis?.localStorage;
|
||
const defaultURL = opts?.isClient ? '/client/router' : '/api/router';
|
||
this.url = opts?.url || defaultURL;
|
||
if (this.url.startsWith('http')) {
|
||
const urlObj = new URL(this.url);
|
||
this.baseURL = urlObj.origin;
|
||
}
|
||
this.baseURL = opts?.baseURL || this.baseURL; // 如果opts中有baseURL优先
|
||
this.headers = opts?.headers || {
|
||
'Content-Type': 'application/json',
|
||
};
|
||
this.timeout = opts?.timeout || 60000 * 3; // 默认超时时间为 60s * 3
|
||
if (opts?.beforeRequest) {
|
||
this.beforeRequest = opts.beforeRequest;
|
||
} else {
|
||
this.beforeRequest = async (opts) => {
|
||
const token = this.token || this.storage?.getItem?.(this.tokenName);
|
||
if (token) {
|
||
opts.headers = {
|
||
...opts.headers,
|
||
Authorization: `Bearer ${token}`,
|
||
};
|
||
}
|
||
return opts;
|
||
};
|
||
}
|
||
}
|
||
setQueryWs(qws: QueryWs) {
|
||
this.qws = qws;
|
||
}
|
||
/**
|
||
* 突然停止请求
|
||
*/
|
||
setStop(stop: boolean) {
|
||
this.stop = stop;
|
||
}
|
||
/**
|
||
* 发送 get 请求,转到 post 请求
|
||
* T是请求类型自定义
|
||
* S是返回类型自定义
|
||
* @param params 请求参数
|
||
* @param options 请求配置
|
||
* @returns 请求结果
|
||
*/
|
||
async get<R = any, P = any>(params: Data & P, options?: DataOpts): Promise<Result<R>> {
|
||
return this.post(params, options);
|
||
}
|
||
/**
|
||
* 发送 post 请求
|
||
* T是请求类型自定义
|
||
* S是返回类型自定义
|
||
* @param body 请求体
|
||
* @param options 请求配置
|
||
* @returns 请求结果
|
||
*/
|
||
async post<R = any, P = any>(body: Data & P, options?: DataOpts): Promise<Result<R>> {
|
||
const url = options?.url || this.url;
|
||
const { headers, adapter, beforeRequest, afterResponse, timeout, ...rest } = options || {};
|
||
const _headers = { ...this.headers, ...headers };
|
||
const _adapter = adapter || this.adapter;
|
||
const _beforeRequest = beforeRequest || this.beforeRequest;
|
||
const _afterResponse = afterResponse || this.afterResponse;
|
||
const _timeout = timeout || this.timeout;
|
||
|
||
const req = {
|
||
url: url,
|
||
headers: _headers,
|
||
body,
|
||
timeout: _timeout,
|
||
...rest,
|
||
};
|
||
const isStartsWithHttp = req.url.startsWith('http');
|
||
// 如果是完整的url,直接使用, 如果不是完整的url,且baseURL存在,则拼接baseURL
|
||
if (!isStartsWithHttp) {
|
||
if (this.baseURL) {
|
||
const baseURL = new URL(this.baseURL || globalThis?.location?.origin).origin;
|
||
req.url = baseURL + req.url;
|
||
}
|
||
}
|
||
try {
|
||
if (_beforeRequest) {
|
||
const res = await _beforeRequest(req);
|
||
if (res === false) {
|
||
return wrapperError({
|
||
code: 500,
|
||
message: '请求取消',
|
||
// @ts-ignore
|
||
req: req,
|
||
});
|
||
}
|
||
}
|
||
const headers = req.headers || {};
|
||
if (options?.token && !headers['Authorization']) {
|
||
headers['Authorization'] = `Bearer ${options.token}`;
|
||
}
|
||
} catch (e) {
|
||
console.error('request beforeFn error', e, req);
|
||
return wrapperError({
|
||
code: 500,
|
||
message: '请求在请求前处理时发生错误',
|
||
// @ts-ignore
|
||
req: req,
|
||
});
|
||
}
|
||
if (this.stop && !options?.noStop) {
|
||
const that = this;
|
||
const res = await new Promise((resolve) => {
|
||
let timer = 0;
|
||
const detect = setInterval(() => {
|
||
if (!that.stop) {
|
||
clearInterval(detect);
|
||
resolve(true);
|
||
}
|
||
timer++;
|
||
if (timer > 5) {
|
||
console.error('等待请求失败:', req.url, timer);
|
||
clearInterval(detect);
|
||
resolve(false);
|
||
}
|
||
}, 1000);
|
||
});
|
||
if (!res) {
|
||
return wrapperError({
|
||
code: 500,
|
||
message: '请求取消,可能是因为用户未登录或者token过期',
|
||
// @ts-ignore
|
||
req: req,
|
||
});
|
||
}
|
||
}
|
||
return _adapter(req).then(async (res) => {
|
||
try {
|
||
if (_afterResponse) {
|
||
return await _afterResponse(res, {
|
||
req,
|
||
res,
|
||
fetch: adapter,
|
||
});
|
||
}
|
||
|
||
return res;
|
||
} catch (e) {
|
||
console.error('请求在响应后处理时发生错误', e, req);
|
||
return wrapperError({
|
||
code: 500,
|
||
message: '请求在响应后处理时发生错误',
|
||
// @ts-ignore
|
||
req: req,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
/**
|
||
* 设置请求前处理,设置请求前处理函数
|
||
* @param fn 处理函数
|
||
*/
|
||
before(fn: DataOpts['beforeRequest']) {
|
||
this.beforeRequest = fn;
|
||
}
|
||
/**
|
||
* 设置请求后处理,设置请求后处理函数
|
||
* @param fn 处理函数
|
||
*/
|
||
after(fn: DataOpts['afterResponse']) {
|
||
this.afterResponse = fn;
|
||
}
|
||
async fetchText(urlOrOptions?: string | QueryOpts, options?: QueryOpts): Promise<Result<any>> {
|
||
let _options = { method: 'GET' as const, ...options };
|
||
if (typeof urlOrOptions === 'string' && !_options.url) {
|
||
_options.url = urlOrOptions;
|
||
}
|
||
if (typeof urlOrOptions === 'object') {
|
||
_options = { ...urlOrOptions, ..._options };
|
||
}
|
||
const headers = { ...this.headers, ..._options.headers };
|
||
if (options?.token && !headers['Authorization'] && _options.method !== 'GET') {
|
||
headers['Authorization'] = `Bearer ${options.token}`;
|
||
}
|
||
const res = await adapter({
|
||
..._options,
|
||
headers,
|
||
});
|
||
if (res && !res.code) {
|
||
return {
|
||
code: 200,
|
||
data: res,
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
}
|
||
|
||
export { adapter };
|
||
|
||
|
||
export class BaseQuery<T extends Query = Query, R extends { queryChain?: any; query?: any } = { queryChain: any; query?: T }> {
|
||
query: T;
|
||
queryDefine: R;
|
||
constructor(opts?: { query?: T; queryDefine?: R; clientQuery?: T }) {
|
||
if (opts?.clientQuery) {
|
||
this.query = opts.clientQuery;
|
||
} else {
|
||
this.query = opts?.query;
|
||
}
|
||
if (opts.queryDefine) {
|
||
this.queryDefine = opts.queryDefine;
|
||
this.queryDefine.query = this.query;
|
||
}
|
||
}
|
||
get chain(): R['queryChain'] {
|
||
return this.queryDefine.queryChain;
|
||
}
|
||
post<R = any, P = any>(data: P, options?: DataOpts): Promise<Result<R>> {
|
||
return this.query.post(data, options);
|
||
}
|
||
get<R = any, P = any>(data: P, options?: DataOpts): Promise<Result<R>> {
|
||
return this.query.get(data, options);
|
||
}
|
||
}
|