import { useCallback, useEffect, useMemo } from 'react'; import { AxiosInterceptorManager, AxiosRequestConfig, AxiosResponse, isAxiosError, isCancel, ValidationError, } from '@szyx-mobile/use-axios'; import { RequestError, RequestOptions, useRequest, } from '@szyx-mobile/use-request'; import { z } from 'zod'; import { Platform } from 'react-native'; import { useAuth } from '@common/contexts/useAuth.ts'; import { useCommon } from '@common/contexts/useCommon.ts'; import { getEnvUrl } from '@common/env/envUtils.ts'; import { md5_hex } from '@common/utils/md5'; import { CLIENT_ID, MD5_KEY } from '@common/constants'; import { showErrorMessage } from '@common/ToastHelper.ts'; import DeviceInfo from 'react-native-device-info'; const SUCCESS_STATUS = /^0$/; // 请求成功的状态码 type Response = { status: string; message: string; data: T; }; const useApi = < S extends z.ZodTypeAny, T extends z.infer = unknown, D = unknown, >( url: string, method: 'GET' | 'POSTJSON' | 'POSTFORM' | 'PUT' | 'UPLOAD', params?: D, validationSchema?: S, options?: RequestOptions, config?: AxiosRequestConfig, ): { response?: T; error?: RequestError; loading: boolean; fetch: (config?: AxiosRequestConfig) => void; fetchAsync: (config?: AxiosRequestConfig) => Promise; cancel: () => void; requestInterceptors: AxiosInterceptorManager>; responseInterceptors: AxiosInterceptorManager< AxiosResponse, unknown> >; } => { const { state: { token, userInfo }, actions: { logout }, } = useAuth(); const { state: { info }, } = useCommon(); // 【这里增加公共参数】 const commonParams = useMemo(() => { return {}; }, []); // 【这里添加额外的 headers】 const commonHeaders = useMemo(() => { try { return { clientId: CLIENT_ID, // 厂商唯一标识,这个值是为医网信app分配的 deviceType: Platform.select({ android: '0', ios: '1' }), version: DeviceInfo.getVersion(), deviceId: DeviceInfo.getDeviceId(), phoneModel: DeviceInfo.getModel(), phoneBrand: DeviceInfo.getBrand(), phoneVersion: DeviceInfo.getSystemVersion(), userId: userInfo?.userId ?? '', sessionId: token ?? '', currentClientId: '', appType: '0', // 医网信是 0,冠新是 1 }; } catch { return {}; } }, [token, userInfo?.userId]); // 【这里定义 Response 的 schema】 const responseSchema = useMemo(() => { // 这里 code 和 message 字段为项目接口中相应的字段名和字段类型 return z.object({ status: z.string().regex(SUCCESS_STATUS), message: z.string(), data: validationSchema ?? z.unknown(), }); }, [validationSchema]); const { response, error, loading, fetch, fetchAsync, cancel, requestInterceptors, responseInterceptors, } = useRequest, unknown>( url, method, { ...params, ...commonParams }, responseSchema, options, { // 【这里配置 baseURL】 baseURL: getEnvUrl(info.env ?? 'production').url, ...config, timeout: 30000, headers: { ...commonHeaders, ...config?.headers, }, }, ); useEffect(() => { const requestInterceptor = requestInterceptors.use(c => { // 这几个header的参数因为是实时的,所以单独拿出来 const timeStamp = Date.now(); const signOrigin = `timeStamp=${timeStamp}#${MD5_KEY}`; const sign = md5_hex(signOrigin); c.headers = { ...c.headers, timeStamp: timeStamp + '', sign: sign, }; return c; }); return () => { requestInterceptors.eject(requestInterceptor); }; }, [requestInterceptors]); useEffect(() => { const responseInterceptor = responseInterceptors.use( r => { if (options?.log) { console.debug(JSON.stringify(r, null, 2)); } return r; }, e => { if (isCancel(e)) { return Promise.reject( new RequestError('网络请求已取消', 'Cancel', e), ); } if (e instanceof ValidationError) { console.error( JSON.stringify( { name: e.name, status: e.response.status, data: e.response.data, config: e.response.config, headers: e.response.headers, issues: e.issues, url: `${e.response.config.baseURL}${e.response.config.url}`, }, null, 2, ), ); // 退出登录逻辑 if ( e.response.data?.status === '017x025' || e.response.data?.status === '017x013' ) { showErrorMessage( e.response.data?.message ?? '该账号已在其他设备登录,请重新登录!', ); logout(); return Promise.reject( new RequestError( e.response.data?.message ?? '该账号已在其他设备登录,请重新登录!', 'ValidationError', e, ), ); } if (e.issues.length <= 0) { return Promise.reject( new RequestError('数据格式校验错误', 'ValidationError', e), ); } // 这里 code 为项目接口中相应的字段名 if (e.issues[0].path.length > 0 && e.issues[0].path[0] === 'status') { return Promise.reject( new RequestError( e.response.data?.message ?? '', // 这里 message 为项目接口中相应的字段名 'ValidationError', e, ), ); } return Promise.reject( new RequestError(e.issues[0].message, 'ValidationError', e), ); } if (isAxiosError(e)) { console.error( JSON.stringify(e, null, 2), `${e.config?.baseURL}${e.config?.url}`, ); return Promise.reject( new RequestError('网络请求失败', 'AxiosError', e), ); } console.error( JSON.stringify(e, null, 2), `${e.config?.baseURL}${e.config?.url}`, ); return Promise.reject( new RequestError('网络请求失败', 'OtherError', e), ); }, ); return () => { responseInterceptors.eject(responseInterceptor); }; }, [logout, options?.log, options?.tag, responseInterceptors]); const transformedFetchAsync = useCallback( async (c?: AxiosRequestConfig) => { try { const r = await fetchAsync(c); return r.data.data; } catch (e) { return Promise.reject(e); } }, [fetchAsync], ); return { response: response?.data.data, error: error as RequestError, loading, fetch, fetchAsync: transformedFetchAsync, cancel, requestInterceptors, responseInterceptors, }; }; export { useApi };