UNPKG

@truenewx/tnxcore

Version:

互联网技术解决方案:JavaScript核心扩展支持

299 lines (275 loc) 10.7 kB
import axios, {AxiosError, AxiosRequestConfig, AxiosResponse} from 'axios'; import SafePromise from '../foundation/safe-promise.ts'; import util from '../util/index.ts'; axios.defaults.baseURL = ''; axios.defaults.withCredentials = true; // 允许携带Cookie axios.defaults.timeout = util.build.isProduction() ? 3000 : 0; // 默认超时时间,生产模式:3秒,开发模式不限时,以便于服务端调试 Object.assign(axios.defaults.headers.common, {'X-Requested-With': 'XMLHttpRequest'}); // 标记为AJAX请求 type ApiRequestOptions = { url: string; method: 'GET' | 'POST'; params?: Record<string, any>; body?: Record<string, any> | FormData; timeout?: number; onUploadProgress?: (ratio: number) => void; } type BusinessError = { code: string; message: string; type: string; field?: string; detail?: string; } type ReminderMessage = { type: 'ERROR' | 'WARNING' | 'INFO' | 'SUCCESS'; content: string; seconds?: number; } type ApiResponse<R> = { successful: boolean; result?: R; errors?: BusinessError[]; reminders?: ReminderMessage[]; } export type ApiRequestErrorLevel = 'ALERT' | 'TOAST' | 'CONSOLE'; export type ApiRequestError = { url: string; status: number; level: ApiRequestErrorLevel | 'FORWARD'; payload: any; } export function setBaseUrl(baseUrl: string) { axios.defaults.baseURL = baseUrl; } export function getBaseUrl(app?: string) { let baseUrl = axios.defaults.baseURL; if (app) { baseUrl += ':' + app + '/'; } return baseUrl; } const APP_CONTEXT_URLS: Record<string, string> = {}; /** * 设置应用及其上下文根的对应关系 * @param appContextUrls 应用及其上下文根的对应关系,key为应用名,value为应用对应的上下文根,以/开头,末尾没有/ */ export function setAppContextUrls(appContextUrls: Record<string, string>) { Object.keys(appContextUrls).forEach((key: string) => { let contextUrl = appContextUrls[key]; if (contextUrl.startsWith('/')) { // 只接受/开头的上下文根 if (contextUrl.endsWith('/')) { contextUrl = contextUrl.substring(0, contextUrl.length - 1); } APP_CONTEXT_URLS[key] = contextUrl; } }); } export function get<T>(url: string, params?: Record<string, any>, timeout?: number): Promise<T> { return request<T>({url, method: 'GET', params, timeout}); } export function post<T>(url: string, body?: Record<string, any>, timeout?: number): Promise<T> { return request<T>({url, method: 'POST', body, timeout}); } export function upload<T>(url: string, body: FormData, timeout?: number, onUploadProgress?: (ratio: number) => void): Promise<T> { return request<T>({url, method: 'POST', body, timeout, onUploadProgress}); } export function toRequestUrl(url: string) { if (url.startsWith(':')) { let index = url.indexOf('/'); // 含/则:到首个/之间为应用名称,否则:之后均为应用名称 let appName = index > 0 ? url.substring(1, index) : url.substring(1); // 含/则/之后均为相对路径,否则相对路径为空 let relativeUrl = index > 0 ? url.substring(index) : ''; url = (APP_CONTEXT_URLS[appName] || ('/' + appName)) + relativeUrl; } return url; } export function request<T>(options: ApiRequestOptions): Promise<T> { const config: AxiosRequestConfig<Record<string, any> | FormData> = { url: toRequestUrl(options.url), method: options.method, params: options.params, data: options.body, timeout: options.timeout, }; if (config.params) { config.paramsSerializer = params => { return util.net.toParameterString(params); }; } if (config.data && !(config.data instanceof FormData)) { let keys = Object.keys(config.data); for (let key of keys) { let value = config.data[key]; if (value instanceof Date) { config.data[key] = value.formatDateTime(); } } } if (options.onUploadProgress) { config.onUploadProgress = event => { const ratio = event.total ? (event.loaded / event.total) : 0; options.onUploadProgress(ratio); } } return doRequest<T>(config); } function doRequest<T>(config: AxiosRequestConfig<Record<string, any>>): Promise<T> { return new SafePromise<T>((resolve, reject) => { axios.request<ApiResponse<T>>(config).then((response: AxiosResponse<ApiResponse<T>>) => { const data = response.data; if (data.successful) { resolve(data.result); } else { reject({ url: config.url, status: 200, level: 'ALERT', payload: data.errors, } as ApiRequestError); } }).catch((error: AxiosError<any>) => { const response = error.response; if (response) { switch (response.status) { case 401: { reject({ url: config.url, status: response.status, level: 'FORWARD', } as ApiRequestError); break; } case 400: { let errors = response.data.errors; if (errors && errors.length) { // 字段格式异常 reject({ url: config.url, status: response.status, level: 'ALERT', payload: errors, } as ApiRequestError); } else if (response.data.message) { reject({ url: config.url, status: response.status, level: 'TOAST', payload: response.data.message, } as ApiRequestError); } break; } case 404: { console.error('在服务器未启动时也会报该错误'); reject({ url: config.url, status: response.status, level: 'ALERT', payload: '服务器正在更新,请稍候再试', } as ApiRequestError); break; } case 500: { let message: string; if (response.data && response.data.message) { message = response.data.message; } else { message = response.data; } if (message && message.includes('time out')) { console.error(message); reject({ url: config.url, status: response.status, level: 'TOAST', payload: '请求超时' } as ApiRequestError); } else { reject({ url: config.url, status: response.status, level: 'CONSOLE', payload: message, } as ApiRequestError); } break; } default: { reject({ url: config.url, status: response.status, level: 'CONSOLE', payload: error, } as ApiRequestError); } } } else { reject({ url: config.url, status: 0, level: 'CONSOLE', payload: error, } as ApiRequestError); } }); }, handleRequestError); } const errorHandlers: Record<string, (message: string) => void> = {}; export function setErrorHandler(level: ApiRequestErrorLevel, handler: (message: string) => void): void { errorHandlers[level] = handler; } /** * 跳转到登录页的函数,由业务工程覆写 * @param url 出错时的请求地址 */ let fnToLoginPage: (url: string) => void; export function ifToLoginPage(fn: (url: string) => void): void { fnToLoginPage = fn; } export function toLogin(url: string): void { if (fnToLoginPage) { fnToLoginPage(url); } else { console.error('未实现跳转到首页的方法,请初始化时调用:', ifToLoginPage); } } export function handleRequestError(error: ApiRequestError): void { const payload = error.payload; let message = ''; if (Array.isArray(payload)) { for (let e of payload) { if (e.field) { message += e.field + ' '; } message += e.message + '\n'; } message = message.trim(); } else if (typeof payload === 'string') { message = payload; } else if (payload) { message = payload.message || payload.stack || payload.toString(); } let handler = errorHandlers[error.level]; if (handler) { handler(message); } else { switch (error.level) { case 'ALERT': { window.tnx.error(message); break; } case 'TOAST': { window.tnx.toast(message); break; } case 'CONSOLE': { console.error(message); break; } case 'FORWARD': { toLogin(error.url); break; } } } }