9433cfb9创建于 2025年12月31日历史提交
/**
 * 拦截器使用方式 拦截器是全局拦截
 * import request, { interceptors } from '@/utils/request.uts'
 * import { VITE_APP_TABBAR_HOME_PAGE } from '@/config/page-setting.uts'
 * // 添加请求拦截器(示例:统一加版本号header)
	interceptors.useRequest!(async (config) => {
	  config.requestOpts.header = config.requestOpts.header ?? {}
	  config.requestOpts.header['X-App-Version'] = '1.0.0'
	  return config
	})

	// 添加响应拦截器(示例:处理token过期)
	interceptors.useResponse!(
	  async (response) => {
	    // token过期逻辑
	    response.code === 60030 && uni.switchTab({ url: VITE_APP_TABBAR_HOME_PAGE })
	    return response
	  },
	  async (error) => {
	    // 统一打印错误日志
	    console.error('请求异常:', error)
	    return Promise.reject(error)
	  }
	)
	封装请求示例:
	export function test(data:any)
	{
	  return request<any>({
	    url: '/test',
	    method: 'GET',
	    data,
	  },{uniCloud:false})
	}
 */
import { ERROR_PATH } from '@/config/image-setting.uts'
import { VITE_APP_BASE_API } from '@/config/setting.uts'
import { userState } from '@/store/user-store.uts'
import type {ApiResponse,RequestExtra} from '@/api/youhujun/request/api-type.uts'


// 请求拦截器配置类型
type InterceptorRequestConfig = {
  requestOpts: RequestOptions<ApiResponse>;
  extra: RequestExtra;
}

// 请求拦截器类型(统一返回Promise)
type RequestInterceptor = (config: InterceptorRequestConfig) => Promise<InterceptorRequestConfig>

// 响应拦截器类型
type ResponseInterceptor = (response: ApiResponse) => Promise<ApiResponse>

// 错误拦截器类型
type ErrorInterceptor = (error: any) => Promise<any>

// ====================== 拦截器管理器 ======================
const requestInterceptors: RequestInterceptor[] = []
const responseInterceptors: ResponseInterceptor[] = []
const responseErrorInterceptors: ErrorInterceptor[] = []

const interceptors = {
  // 非空函数兜底:()=>void 确保编译器认为方法不可能为null
  useRequest: ((interceptor: RequestInterceptor) => {
    requestInterceptors.push(interceptor)
  }) as (interceptor: RequestInterceptor) => void,

  useResponse: (((onFulfilled: ResponseInterceptor, onRejected?: ErrorInterceptor) => {
    responseInterceptors.push(onFulfilled)
    onRejected && responseErrorInterceptors.push(onRejected)
  }) as (onFulfilled: ResponseInterceptor, onRejected?: ErrorInterceptor) => void),

  clear: (((type: 'request' | 'response' | 'responseError') => {
    type === 'request' && (requestInterceptors.length = 0)
    type === 'response' && (responseInterceptors.length = 0)
    type === 'responseError' && (responseErrorInterceptors.length = 0)
  }) as (type: 'request' | 'response' | 'responseError') => void)
}

const isUTSJSONObject = (obj: any): obj is UTSJSONObject => {
  if (obj === null || typeof obj !== 'object') return false;
  // 匹配特征:构造函数名含"UTSJSONObject" + 存在它的专属方法(如resolveKeyPath)
  return (
    obj.constructor.name.includes('UTSJSONObject') && // 覆盖UTSJSONObject/UTSJSONObject2
    (typeof obj.resolveKeyPath === 'function' || // 环境里有这个方法
     typeof obj.keys === 'function' || 
     typeof obj.get === 'function')
  );
};

// 递归处理嵌套数据,将所有UTSJSONObject转为普通JS对象
const recursiveHandleUTSObject = (data: any): any => {
    // 1. 如果是UTSJSONObject + data非空 + keys是函数
	if (isUTSJSONObject(data) && data !== null) {
	   const plainObj: any = {};
	   const innerMethods = ['resolveKeyPath', '_g', '_f', 'keys', 'get', 'toMap', '_getValue'];
	   for (const key in data) {
		 if (data.hasOwnProperty(key) && !innerMethods.includes(key)) {
		   //console.log('遍历的业务key:', key);
		   // 递归处理子对象(比如data对应的UTSJSONObject2)
		   plainObj[key] = recursiveHandleUTSObject(data[key]);
		 }
	   }
	   //console.log('当前层级转换结果:', plainObj); // 验证子对象是否转成普通对象
	   return plainObj;
	 }

	// 2. 处理数组(原有逻辑不变)
	if (Array.isArray(data)) {
		return data.map(item => recursiveHandleUTSObject(item));
	}

	  // 3. 处理普通对象(原有逻辑不变)
	  if (data !== null && typeof data === 'object' && !isUTSJSONObject(data)) {
		const plainObj: any = {};
		for (const key in data) {
		  if (data.hasOwnProperty(key)) {
			plainObj[key] = recursiveHandleUTSObject(data[key]);
		  }
		}
		return plainObj;
	  }

  // 4. 基本类型,直接返回(原有逻辑不变)
  return data;
}

//  统一转换为自定义类型(通用方法,仅修改内部逻辑)
const convertToCustomType = <T>(data: any): T | null => {
  try {
    //console.log('转换前类型:', data.constructor.name, '是否为UTSJSONObject:', isUTSJSONObject(data));
    const processedData = recursiveHandleUTSObject(data);
    //console.log('转换后实际类型(普通对象):', processedData); // 直接打印对象,看属性
    // 去掉JSON序列化,直接返回处理后的普通对象(已无UTS特性)
    return processedData as T;
  } catch (e) {
    console.error("数据转换失败:", e);
    return null;
  }
};

// ====================== 核心请求方法 ======================
const service = async<T = ApiResponse>(
  requestOpts: RequestOptions<ApiResponse>,
  extra: RequestExtra = { uniCloud: false }
) => {
  // 初始化拦截器配置
  let config: InterceptorRequestConfig = {
    requestOpts: { ...requestOpts },
    extra: { ...extra }
  }

  // 执行所有请求拦截器(按顺序)
  for (const interceptor of requestInterceptors) {
    config = await interceptor(config)
  }

  const finalRequestOpts = config.requestOpts
  const finalExtra = config.extra

  // 1. URL必填校验(无try/catch,直接判断+reject)
  if (!finalRequestOpts.url || finalRequestOpts.url.trim() === '') {
    uni.showToast({
      image: ERROR_PATH,
      title: '请求URL不能为空',
      mask: true,
      duration: 2000
    })
    return Promise.reject(new Error('请求URL不能为空'))
  }

  // 2. 提取核心字段
  let finalUrl = finalRequestOpts.url.trim()
  const rawMethod = finalRequestOpts.method || 'GET'
  const finalMethod = rawMethod.toUpperCase() as RequestMethod
  const finalData = finalRequestOpts.data || {}
  const finalHeader = finalRequestOpts.header ?? {}
  const { uniCloud = false } = finalExtra

  // 3. uniCloud逻辑
  if (!uniCloud) {
    userState.token && (finalHeader['X-Token'] = userState.token)
    !/^https?:\/\//.test(finalUrl) && (finalUrl = VITE_APP_BASE_API + finalUrl)
  }

  // 4. 发起请求(返回Promise)
  return new Promise<T | null>((resolve, reject) => {
    uni.request<ApiResponse>({
      url: finalUrl,
      method: finalMethod,
      data: finalData,
      header: finalHeader,
      timeout: finalRequestOpts.timeout || 60000,
      success: async (res: RequestSuccess<ApiResponse>) => {
        // 响应数据为空 → 直接reject
        if (!res.data) {
          uni.showToast({ image: ERROR_PATH, title: '请求数据异常', mask: true, duration: 2000 })
          return reject(new Error('请求数据异常'))
        }

        // 状态码非200 → 直接reject
        if (res.statusCode !== 200) {
          uni.showToast({ image: ERROR_PATH, title: `请求失败(${res.statusCode})`, mask: true, duration: 2000 })
          return reject(new Error(`请求失败(${res.statusCode})`))
        }

        // 执行响应成功拦截器
        let responseData = res.data
        for (const interceptor of responseInterceptors) {
          responseData = await interceptor(responseData)
        }
		
		// 统一转换为自定义类型(核心步骤)
		const finalResponse = convertToCustomType<T>(responseData);

        // 业务码成功 → resolve
        if (responseData.code === 0) {
          return resolve(finalResponse)
        }

        // 业务码失败 → 执行错误拦截器后reject
        uni.showToast({ image: ERROR_PATH, title: responseData.msg || '请求失败', mask: true, duration: 2000 })
        let error = finalResponse;
		for (const interceptor of responseErrorInterceptors) {
		  error = await interceptor(error)
		}
		reject(error)
      },
      fail: async (err: RequestFail) => {
        // 请求失败 → 执行错误拦截器后reject
        uni.showToast({ image: ERROR_PATH, title: '网络异常,请重试', mask: true, duration: 2000 })
        let error = err
        for (const interceptor of responseErrorInterceptors) {
          error = await interceptor(error)
        }
        reject(error)
      }
    })
  })
}

// 导出:拆分导出(无Object.assign,Android兼容)
export { service as default, interceptors }