123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- 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<T> = {
- status: string;
- message: string;
- data: T;
- };
- const useApi = <
- S extends z.ZodTypeAny,
- T extends z.infer<S> = unknown,
- D = unknown,
- >(
- url: string,
- method: 'GET' | 'POSTJSON' | 'POSTFORM' | 'PUT' | 'UPLOAD',
- params?: D,
- validationSchema?: S,
- options?: RequestOptions,
- config?: AxiosRequestConfig<D>,
- ): {
- response?: T;
- error?: RequestError<unknown, unknown>;
- loading: boolean;
- fetch: (config?: AxiosRequestConfig<D>) => void;
- fetchAsync: (config?: AxiosRequestConfig<D>) => Promise<T>;
- cancel: () => void;
- requestInterceptors: AxiosInterceptorManager<AxiosRequestConfig<unknown>>;
- responseInterceptors: AxiosInterceptorManager<
- AxiosResponse<Response<T>, 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<typeof responseSchema, Response<T>, 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<D>) => {
- 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<unknown, unknown>,
- loading,
- fetch,
- fetchAsync: transformedFetchAsync,
- cancel,
- requestInterceptors,
- responseInterceptors,
- };
- };
- export { useApi };
|