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; body?: Record; [key: string]: any; timeout?: number; }) => Promise | 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; timeout?: number; isClient?: boolean; tokenName?: string; storage?: Storage; } export type Data = { path?: string; key?: string; payload?: Record; [key: string]: any; }; export type Result = { code: number; data?: S; message?: string; } & U; // 额外功能 export type DataOpts = Partial & { beforeRequest?: Fn; afterResponse?: (result: Result, ctx?: { req?: any; res?: any; fetch?: any }) => Promise>; /** * 是否在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; 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(params: Data & P, options?: DataOpts): Promise> { return this.post(params, options); } /** * 发送 post 请求 * T是请求类型自定义 * S是返回类型自定义 * @param body 请求体 * @param options 请求配置 * @returns 请求结果 */ async post(body: Data & P, options?: DataOpts): Promise> { 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> { 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 { 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(data: P, options?: DataOpts): Promise> { return this.query.post(data, options); } get(data: P, options?: DataOpts): Promise> { return this.query.get(data, options); } }