From 758d8056dc3dbc6bf92c298aa3627e66b933b5a0 Mon Sep 17 00:00:00 2001 From: wupengfei <834520024@qq.com> Date: 星期四, 22 五月 2025 13:25:21 +0800 Subject: [PATCH] feat: UI --- apps/h5/src/utils/request/index.ts | 303 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 303 insertions(+), 0 deletions(-) diff --git a/apps/h5/src/utils/request/index.ts b/apps/h5/src/utils/request/index.ts new file mode 100644 index 0000000..3ebb819 --- /dev/null +++ b/apps/h5/src/utils/request/index.ts @@ -0,0 +1,303 @@ +import { type IRequestOptions, BoleRequest, type RequestConfig } from '@bole-core/core'; +import { type AxiosRequestConfig, type AxiosError } from 'axios'; +import qs from 'qs'; +import NProgress from '../progress'; +import { loadEnv } from '@build/index'; +import { getToken, getUserInfo } from '../storage'; +import { useUserStoreHook } from '@/store/modules/user'; +import { ElLoading as Loading, ElNotification } from 'element-plus'; +import { router } from '@/router'; +import { Message, tokenIsExpired } from '@/utils'; + +// 鍔犺浇鐜鍙橀噺 VITE_PROXY_DOMAIN锛堝紑鍙戠幆澧冿級 VITE_PROXY_DOMAIN_REAL锛堟墦鍖呭悗鐨勭嚎涓婄幆澧冿級 +const { VITE_PROXY_DOMAIN, VITE_PROXY_DOMAIN_REAL, DEV } = loadEnv(); + +const RefreshTokenUrl = '/GetTokenByRefreshToken'; + +/** 璇锋眰鐧藉悕鍗曪紝鏀剧疆涓�浜涗笉闇�瑕乼oken鐨勬帴鍙o紙閫氳繃璁剧疆璇锋眰鐧藉悕鍗曪紝闃叉token杩囨湡鍚庡啀璇锋眰閫犳垚鐨勬寰幆闂锛� */ +const whiteList = [RefreshTokenUrl, '/GetToken']; + +let loadingInstance: ReturnType<typeof Loading.service>; + +function startLoading() { + loadingInstance = Loading.service({ + fullscreen: true, + lock: true, + background: 'transparent', + }); +} + +function endLoading() { + if (loadingInstance) { + loadingInstance.close(); + } +} + +// 閿欒澶勭悊鏂规锛� 閿欒绫诲瀷 +enum ErrorShowType { + /** + * 鍟ヤ篃涓嶅共 + */ + SILENT = 0, + + WARN_MESSAGE = 1, + ERROR_MESSAGE = 2, + /** + * 閫氱煡鎶ラ敊 + */ + NOTIFICATION = 3, + /** + * 閲嶅畾鍚� + */ + REDIRECT = 9, +} +// 涓庡悗绔害瀹氱殑鍝嶅簲鏁版嵁鏍煎紡 +interface ResponseStructure { + success: boolean; + data: any; + /** + * 閿欒鐮� + */ + error?: number; + /** + * 閿欒淇℃伅 + */ + msg?: string; + showType?: ErrorShowType; +} + +interface ErrorResponse { + error?: { + data: any; + code: number; + message: string; + }; +} + +interface ErrorInfo { + errorCode?: number; + errorMessage?: string; + showType?: ErrorShowType; + data: any; +} + +const AxiosOptions: AxiosRequestConfig = { + baseURL: DEV ? VITE_PROXY_DOMAIN : VITE_PROXY_DOMAIN_REAL, + timeout: 30000, + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, + // 鏁扮粍鏍煎紡鍙傛暟搴忓垪鍖� + paramsSerializer: { + //@ts-ignore + serialize: (params) => qs.stringify(params, { indices: false }), + }, + withCredentials: true, +}; + +const config: RequestConfig<ResponseStructure, IRequestOptions> = { + ...AxiosOptions, + + errorConfig: { + // 閿欒鎺ユ敹鍙婂鐞� + errorHandler: (error, opts) => { + console.log('error: ', error); + if (opts?.skipErrorHandler) throw error; + + if (opts?.customErrorHandler) { + if (opts?.customErrorHandler(error)) { + return; + } + } + + // 鎴戜滑鐨� errorThrower 鎶涘嚭鐨勯敊璇�� + if (error.name === 'BizError') { + const errorInfo: ErrorInfo | undefined = (error as any).info; + if (errorInfo) { + const { errorMessage, errorCode } = errorInfo; + switch (errorInfo.showType) { + case ErrorShowType.SILENT: + // do nothing + break; + case ErrorShowType.WARN_MESSAGE: + Message.warnMessage(errorMessage); + break; + case ErrorShowType.ERROR_MESSAGE: + Message.errorMessage(errorMessage); + break; + case ErrorShowType.NOTIFICATION: + ElNotification.error({ + message: `${errorCode}: ${errorMessage}`, + }); + break; + case ErrorShowType.REDIRECT: + // TODO: redirect + break; + default: + Message.errorMessage(errorMessage); + } + } + } else if ((error as AxiosError<ResponseStructure, IRequestOptions>).response) { + // Axios 鐨勯敊璇� + // 璇锋眰鎴愬姛鍙戝嚭涓旀湇鍔″櫒涔熷搷搴斾簡鐘舵�佺爜锛屼絾鐘舵�佷唬鐮佽秴鍑轰簡 2xx 鐨勮寖鍥� + handleAxiosResponseError(error as AxiosError<ErrorResponse, IRequestOptions>); + // Message.errorMessage(`Response status:${(error as AxiosError).response.status}`); + } else if ((error as AxiosError).request) { + // 璇锋眰宸茬粡鎴愬姛鍙戣捣锛屼絾娌℃湁鏀跺埌鍝嶅簲 + // \`error.request\` 鍦ㄦ祻瑙堝櫒涓槸 XMLHttpRequest 鐨勫疄渚嬶紝 + // 鑰屽湪node.js涓槸 http.ClientRequest 鐨勫疄渚� + Message.errorMessage('鏈嶅姟鍣ㄦ棤鍝嶅簲锛岃閲嶈瘯'); + } else { + // 鍙戦�佽姹傛椂鍑轰簡鐐归棶棰� + Message.errorMessage('鍙戦�佽姹傛椂鍑轰簡鐐归棶棰�'); + } + }, + + // 閿欒鎶涘嚭 + errorThrower: (res) => { + const { success, data, error: errorCode, msg, showType } = res; + if (!success) { + const error: any = new Error(msg); + error.name = 'BizError'; + error.info = { errorCode, errorMessage: msg, showType, data }; + throw error; // 鎶涘嚭鑷埗鐨勯敊璇� + } + }, + }, + + requestInterceptors: [ + [ + (config) => { + const $config = config; + // 寮�鍚繘搴︽潯鍔ㄧ敾 + if (config.needNProcess) { + NProgress.start(); + } + + const token = getToken(); + + const userInfo = getUserInfo(); + + const { showLoading = true, mock } = $config; + + if (mock) { + $config.url = $config.url.replace(/^\/api/, ''); + } + + /** + * 濡傛灉鏄痳efreshToken杩欎釜鏂规硶 灏辩洿鎺ヨ繑鍥� 闃叉鍐呭瓨娉勬紡 + */ + if (whiteList.some((url) => $config.url.toLowerCase().includes(url.toLowerCase()))) { + return $config; + } + + if (showLoading) { + startLoading(); + } + + return new Promise((resolve) => { + if (token && $config.withCredentials) { + if (tokenIsExpired(userInfo)) { + if (!BoleRequest.refreshTokenPending) { + const userStore = useUserStoreHook(); + BoleRequest.refreshTokenPending = true; + // token杩囨湡鍒锋柊 + userStore + .refreshToken({ + refreshToken: userInfo.refreshToken, + clientId: userStore.accountInfo.client_id, + }) + .then((res) => { + $config.headers['Authorization'] = 'Bearer ' + res.accessToken; + BoleRequest.requests.forEach((cb) => cb(res.accessToken)); + BoleRequest.requests = []; + }) + .finally(() => { + BoleRequest.refreshTokenPending = false; + }); + } + resolve(BoleRequest.retryOriginalRequest($config)); + } else { + $config.headers['Authorization'] = 'Bearer ' + token; + resolve($config); + } + } else { + resolve($config); + } + }); + }, + (error: AxiosError) => { + endLoading(); + return Promise.reject(error); + }, + ], + ], + responseInterceptors: [ + [ + (response) => { + const $config = response.config as IRequestOptions; + + const { needNProcess, getResponse = false } = $config; + + // 鍏抽棴杩涘害鏉″姩鐢� + if (needNProcess) { + NProgress.done(); + } + + endLoading(); + + return getResponse ? response : (response.data as any).result; + }, + (error) => { + endLoading(); + return Promise.reject(error); + }, + ], + ], +}; + +const ErrorMessageMap = { + [400]: '璇锋眰閿欒', + [401]: '鏈巿鏉冿紝璇风櫥褰�', + [403]: '鎷掔粷璁块棶', + [404]: '璇锋眰鍦板潃鍑洪敊', + [408]: '璇锋眰瓒呮椂', + [500]: '鏈嶅姟鍣ㄥ唴閮ㄩ敊璇�', + [501]: '鏈嶅姟鏈疄鐜�', + [502]: '缃戝叧閿欒', + [503]: '鏈嶅姟涓嶅彲鐢�', + [504]: '缃戝叧瓒呮椂', + [505]: 'HTTP鐗堟湰涓嶅彈鏀寔', +}; + +function handleAxiosResponseError(error: AxiosError<ErrorResponse, IRequestOptions>) { + if (error.response.config.url.toLowerCase().includes(RefreshTokenUrl.toLowerCase())) { + handleLogout(); + return; + } + if (error && error.response) { + let message = ErrorMessageMap[error.response?.status] ?? '璇锋眰閿欒'; + if (error.response.data?.error?.message) { + message = error.response.data?.error?.message; + } + + if (error.response?.status === 401) { + handleLogout(); + } + Message.errorMessage(message); + } +} + +function handleLogout() { + const userStore = useUserStoreHook(); + let path = '/'; + if (userStore.token) { + path = router.currentRoute.value.fullPath; + } + userStore.logout(); + router.replace(`/login?redirect=${path}`); +} + +export const request = BoleRequest.create(config); -- Gitblit v1.9.1