HarmonyOS Next 之HTTP 请求二次封装实战

2025-06-18 14:37:32
110次阅读
0个评论

一、封装目标设计

功能特性

  1. 请求方法简化:支持 get()/post()/put()/delete() 快捷方法
  2. 全局配置:设置基础URL、超时时间、公共Headers
  3. 拦截器系统:请求/响应双向拦截管道
  4. 多数据格式:自动处理 FormData/JSON 格式转换
  5. 取消请求:通过唯一标识取消进行中的请求

类型定义

interface RequestConfig {
  url: string
  method?: HttpRequestMethod
  params?: Record<string, any>
  data?: any
  headers?: Record<string, string>
  timeout?: number
}

interface HttpResponse<T = any> {
  status: number
  data: T
  headers: Record<string, string>
}

interface HttpError extends Error {
  code?: number
  config: RequestConfig
  response?: HttpResponse
}

二、核心实现代码

步骤1:创建HttpClient类

export class HttpClient {
  private instance: http.HttpClient
  private interceptors = {
    request: [] as RequestInterceptor[],
    response: [] as ResponseInterceptor[]
  }

  constructor(private baseConfig: RequestConfig = {}) {
    this.instance = http.createHttp()
  }

  // 全局配置更新
  setConfig(config: Partial<RequestConfig>) {
    this.baseConfig = { ...this.baseConfig, ...config }
  }
}

步骤2:实现基础请求方法

async request<T = any>(config: RequestConfig): Promise<HttpResponse<T>> {
  const mergedConfig = this.mergeConfig(config)
  
  // 执行请求拦截
  const processedConfig = await this.runRequestInterceptors(mergedConfig)
  
  try {
    const response = await this.dispatchRequest(processedConfig)
    // 执行响应拦截
    return this.runResponseInterceptors(response)
  } catch (error) {
    throw this.normalizeError(error, processedConfig)
  }
}

private dispatchRequest(config: RequestConfig): Promise<HttpResponse> {
  return new Promise((resolve, reject) => {
    const httpRequest = this.instance.request(
      this.buildFullUrl(config.url),
      {
        method: config.method || HttpMethod.GET,
        header: config.headers,
        extraData: config.data,
        connectTimeout: config.timeout
      },
      (err, data) => {
        if (err) {
          reject(err)
        } else {
          resolve({
            status: data.responseCode,
            data: this.parseResponseData(data),
            headers: data.header 
          })
        }
      }
    )
    
    // 存储请求对象用于取消
    this.storeRequest(httpRequest, config)
  })
}

步骤3:添加拦截器支持

// 请求拦截器类型
type RequestInterceptor = (config: RequestConfig) => Promise<RequestConfig>

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

// 添加拦截器
useRequestInterceptor(interceptor: RequestInterceptor) {
  this.interceptors.request.push(interceptor)
}

useResponseInterceptor(interceptor: ResponseInterceptor) {
  this.interceptors.response.push(interceptor)
}

// 执行拦截器链
private async runRequestInterceptors(config: RequestConfig) {
  let processedConfig = config
  for (const interceptor of this.interceptors.request) {
    processedConfig = await interceptor(processedConfig)
  }
  return processedConfig
}

三、快捷方法封装

// 快捷方法实现
get<T = any>(url: string, params?: Record<string, any>, config?: Omit<RequestConfig, 'url' | 'params'>) {
  return this.request<T>({
    url,
    method: HttpMethod.GET,
    params,
    ...config
  })
}

post<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'data'>) {
  return this.request<T>({
    url,
    method: HttpMethod.POST,
    data,
    ...config
  })
}

// 文件上传封装
uploadFile(url: string, file: FileItem) {
  const formData = new FormData()
  formData.append('file', file.uri, {
    filename: file.name,
    contentType: file.type
  })
  
  return this.post(url, formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
}

四、使用示例

基础调用

const http = new HttpClient({
  baseURL: 'https://xxx.xx',
  timeout: 15000
})

// GET请求
const res = await http.get('/user', { id: 123 })

// POST请求
await http.post('/login', {
  username: 'admin',
  password: '******'
})

拦截器应用

// 添加请求拦截器
http.useRequestInterceptor(async (config) => {
  const token = AppStorage.get('token')
  if (token) {
    config.headers = {
      ...config.headers,
      Authorization: `Bearer ${token}`
    }
  }
  return config
})

// 添加响应拦截器
http.useResponseInterceptor(async (response) => {
  if (response.status === 401) {
    router.navigateTo('/login')
    throw new Error('登录过期')
  }
  return response
})
收藏00

登录 后评论。没有帐号? 注册 一个。