鸿蒙实战开发:网络层的艺术——优雅封装与搭建指南(上)

在鸿蒙的广袤开发世界中,网络层作为信息交换的桥梁,其重要性不言而喻。今天,我将带领大家一同探索如何以艺术般的手法,优雅地封装鸿蒙官方的网络库,为我们的应用搭建一个高效、灵活的网络层。我们在下一篇章中,将深入阐述如何利用这一封装完善的网络库,轻松驾驭网络层的开发与使用。

一、封装目的:可拓展与可拦截

在鸿蒙应用开发中,网络请求的封装不仅是为了简化开发流程,更是为了提高代码的复用性和可维护性。我们的封装目标主要围绕以下两点:

  1. 可拓展性:允许开发者根据业务需求,轻松扩展网络请求的功能,如添加自定义请求头、设置请求超时时间等。
  2. 可拦截性:提供网络请求的拦截机制,使得我们可以在请求发送前或响应返回后进行一系列操作,如添加日志记录、错误处理等。

二、定义基础元素:错误常量与字符串

1. 错误常量定义

为了统一管理网络请求中的错误码,我们定义了一个NetworkServiceErrorConst类,用于存储各种网络请求可能遇到的错误码:

typescript 复制代码
export class NetworkServiceErrorConst {
  // 网络不可用
  static readonly UN_AVAILABLE: number = 100000;
  // URL错误
  static readonly URL_ERROR: number = 100001;
  // URL不存在错误
  static readonly URL_NOT_EXIST_ERROR: number = 100002;
  // 网络错误
  static readonly NET_ERROR: number = 100003;
  // ...其他可能的错误码
}

2. 错误字符串定义

同时,我们还需要定义与错误码对应的错误字符串,以便在应用中展示给用户:

json 复制代码
{
"name": "network_unavailable",
"value": "网络不可用"
},
{
"name": "invalid_url_format",
"value": "URL格式不合法"
},
{
"name": "invalid_url_not_exist",
"value": "URL不存在"
}

三、实用工具集

URL 校验

为了确保网络请求中的URL格式正确,我们提供了一个isValidUrl函数,它使用正则表达式来验证URL的有效性。

typescript 复制代码
private isValidUrl(url: string): boolean {
    // 正则表达式匹配各种可能的URL格式
    const urlPattern = new RegExp(
        '^(https?:\\/\\/)?' + // 协议
        '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // 域名
        '((\\d{1,3}\\.){3}\\d{1,3}))' + // 或IPv4地址
        '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // 端口和路径
        '(\\?[;&a-z\\d%_.~+=-]*)?' + // 查询字符串
        '(\\#[-a-z\\d_]*)?$', // 片段定位符
        'i' // 忽略大小写
    );
    return urlPattern.test(url); // 返回验证结果
}

// 使用示例
if (isValidUrl("http://example.com")) {
    console.log("URL is valid.");
} else {
    console.log("URL is invalid.");
}

参数拼接

当需要在URL中附加查询参数时,appendQueryParams函数可以帮助我们轻松实现。它支持处理单个值或数组值的参数,并自动处理编码。

typescript 复制代码
private appendQueryParams(url: string, queryParams: Map<string, any> | undefined): string {
    if (!queryParams || queryParams.size === 0) {
        return url;
    }

    const paramsArray: string[] = [];
    queryParams.forEach((value, key) => {
        if (Array.isArray(value)) {
            for (let i = 0; i < value.length; i++) {
                paramsArray.push(`${encodeURIComponent(`${key}[${i}]`)}=${encodeURIComponent(value[i].toString())}`);
            }
        } else {
            paramsArray.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
        }
    });

    // 检查URL是否已包含查询参数,并据此决定使用'?'或'&'作为分隔符
    const separator = url.includes('?') ? '&' : '?';
    return url + separator + paramsArray.join('&');
}

// 使用示例
const baseUrl = "http://example.com/search";
const params = new Map<string, any>();
params.set("q", "test");
params.set("page", 2);
const urlWithParams = appendQueryParams(baseUrl, params);
console.log(urlWithParams); // 输出: http://example.com/search?q=test&page=2

通过上述两个工具函数,我们可以确保网络请求的URL既正确又包含了所需的查询参数,从而提高了网络请求的准确性和可靠性。

四、编写网络拦截器

在网络请求和响应的过程中,网络拦截器(Interceptor)是一个非常重要的概念。它们允许我们在请求发送前、响应接收后或发生错误时执行特定的逻辑,比如添加网络参数、记录日志、处理错误等。

首先,我们定义一个NetworkInterceptor接口,它规定了拦截器必须实现的方法:

typescript 复制代码
import { http } from '@kit.NetworkKit'; 
import { RequestOptions } from '../NetworkService'; 

export interface NetworkInterceptor {
  beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
  afterResponse(response: http.HttpResponse | object, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
  onError(error: Error, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
}

接下来,我们实现一个默认的拦截器DefaultInterceptor,它实现了NetworkInterceptor接口:

typescript 复制代码
import { http } from '@kit.NetworkKit';
import { RequestOptions } from '../NetworkService';
import { LibLogManager, TAG } from '../LogService'; 

export class DefaultInterceptor implements NetworkInterceptor {

  beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
    // 可以在这里添加网络参数,或者对请求进行其他处理
    httprequest.on('headersReceive', (header) => {
      LibLogManager.getLogger().info(TAG, 'Received headers: ' + JSON.stringify(header));
    });

    // 如果有异步操作,需要返回Promise
    // 这里没有异步操作,所以直接返回
  }

  afterResponse(response: http.HttpResponse | object, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
    // 响应接收后,可以处理响应数据,或者记录日志
    httprequest.off('headersReceive'); // 移除事件监听器
    LibLogManager.getLogger().info(TAG, 'Response received: ' + JSON.stringify(response));

    // 如果有异步操作,需要返回Promise
    // 这里没有异步操作,所以直接返回
  }

  onError(error: Error, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
    // 发生错误时,可以记录错误日志,或者进行错误处理
    httprequest.off('headersReceive'); // 移除事件监听器
    LibLogManager.getLogger().error(TAG, 'Network error occurred: ' + JSON.stringify(error));

    // 如果有异步操作,需要返回Promise
    // 这里没有异步操作,所以直接返回
  }
}

注意

  1. 在上面的beforeRequest方法中,我添加了一个对headersReceive事件的监听。

  2. afterResponseonError方法中,我调用了httprequest.off('headersReceive')来移除之前添加的事件监听器。这是为了避免内存泄漏,因为如果你不断发送新的请求而不移除旧的监听器,那么这些监听器将一直存在于内存中。

  3. 在实际项目中,你可能需要根据你的网络库和项目的需求来调整这些拦截器的实现。例如,你可能需要在beforeRequest方法中添加请求头、身份验证令牌等。在afterResponse方法中,你可能需要处理JSON响应数据,或者将其转换为其他格式。在onError方法中,你可能需要执行更复杂的错误处理逻辑,比如重试机制、错误上报等。

五、网络请求封装核心类

在网络编程中,发起HTTP请求是一项常见的任务。为了简化这一过程并使其更加标准化和可维护,我们创建了一个网络请求封装的核心类。这个类提供了一套灵活的API,允许用户通过配置化的方式发起各种HTTP请求。

1. 请求配置类:RequestOptions

首先,我们定义了一个RequestOptions接口,它包含了发起HTTP请求所需的所有配置参数。这个接口的设计非常灵活,可以适应各种复杂的HTTP请求场景。

typescript 复制代码
export interface RequestOptions {
  baseUrl?: string; // 基础URL
  act?: string; // 请求的动作或路径
  method?: RequestMethod; // 请求方法,默认为GET
  queryParams?: Map<string, any>; // 查询参数,支持多种数据类型
  header?: Object; // 请求头信息
  extraData?: string | Object | ArrayBuffer; // 额外的请求数据
  expectDataType?: http.HttpDataType; // 预期的响应数据类型
  usingCache?: boolean; // 是否使用缓存
  priority?: number; // 请求的优先级
  connectTimeout?: number; // 连接超时时间
  readTimeout?: number; // 读取超时时间
  multiFormDataList?: Array<http.MultiFormData>; // 用于POST表单请求的表单数据列表
}

2. 请求方法枚举:RequestMethod

为了支持各种HTTP请求方法,我们定义了一个RequestMethod枚举。这个枚举包含了所有标准的HTTP请求方法,如GET、POST、PUT等。

typescript 复制代码
export enum RequestMethod {
  OPTIONS = "OPTIONS",
  GET = "GET",
  HEAD = "HEAD",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
  TRACE = "TRACE",
  CONNECT = "CONNECT"
}

3. 网络请求封装核心类:NetworkService

基于RequestOptionsRequestMethod,我们创建了一个名为NetworkService的网络请求封装核心类。这个类提供了request方法,用于发起HTTP请求。request方法接受一个RequestOptions对象作为参数,并根据该对象的配置发起相应的HTTP请求。

除了request方法外,NetworkService类还支持注册拦截器(Interceptor)。拦截器可以在请求发送前和响应返回后进行额外的处理,如添加请求头、处理响应数据等。这使得用户可以灵活地定制网络请求的行为。

typescript 复制代码
export class NetworkService {
  baseUrl:string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  private interceptors: NetworkInterceptor[] = [];

  addInterceptor(interceptor: NetworkInterceptor): void {
    this.interceptors.push(interceptor);
  }

  async request(requestOption: RequestOptions): Promise<http.HttpResponse | null> {
    let response: http.HttpResponse | null = null;
    let error: Error | null = null;
    // 每一个httpRequest对应一个HTTP请求任务,不可复用
    let httpRequest = http.createHttp();
    //开始发请求
    try {

      //如果url是传入的,则用传入的url
      requestOption.baseUrl = requestOption.baseUrl?requestOption.baseUrl:this.baseUrl;
      // 调用拦截器的beforeRequest方法
      for (const interceptor of this.interceptors) {
        await interceptor.beforeRequest(requestOption, httpRequest);
      }

      if(requestOption.baseUrl === null || requestOption.baseUrl.trim().length === 0){
        throw new NetworkError(NetworkServiceErrorConst.URL_NOT_EXIST_ERROR, Application.getInstance().resourceManager.getStringSync($r("app.string.invalid_url_not_exist")))
      }

      if (!LibNetworkStatus.getInstance().isNetworkAvailable()) {
        LibLogManager.getLogger().error("HttpCore","网络不可用")
        throw new NetworkError(NetworkServiceErrorConst.UN_AVILABLE, Application.getInstance().resourceManager.getStringSync($r("app.string.network_unavailable")))
      }

      if (!this.isValidUrl(requestOption.baseUrl)) {
        LibLogManager.getLogger().error("HttpCore","url格式不合法")
        throw new NetworkError(NetworkServiceErrorConst.URL_ERROR, Application.getInstance().resourceManager.getStringSync($r("app.string.invalid_url_format")))
      }

      let defalutHeader :Record<string,string> = {
        'Content-Type': 'application/json'
      }

      let response = await httpRequest.request(this.appendQueryParams(requestOption.baseUrl, requestOption.queryParams), {
        method: requestOption.method,
        header: requestOption.header || defalutHeader,
        extraData: requestOption.extraData, // 当使用POST请求时此字段用于传递内容
        expectDataType: requestOption.expectDataType||http.HttpDataType.STRING, // 可选,指定返回数据的类型
        usingCache: requestOption.usingCache, // 可选,默认为true
        priority: requestOption.priority, // 可选,默认为1
        connectTimeout: requestOption.connectTimeout, // 可选,默认为60000ms
        readTimeout: requestOption.readTimeout, // 可选,默认为60000ms
        multiFormDataList: requestOption.multiFormDataList,
      })

      if (http.ResponseCode.OK !== response.responseCode) {
        response = response;
      } else{
        throw new NetworkResponseError(response.responseCode,Application.getInstance().resourceManager.getStringSync($r("app.string.network_unavailable")))
      }

      // 调用拦截器的afterResponse方法
      for (const interceptor of this.interceptors) {
        await interceptor.afterResponse(response, requestOption, httpRequest );
      }

    } catch (e) {
      error = e;
    }

    // 根据是否有错误来调用拦截器的afterResponse或onError方法
    if (error) {
      for (const interceptor of this.interceptors) {
        await interceptor.onError(error, requestOption, httpRequest);
      }
      httpRequest.destroy();
      throw error; // 重新抛出错误以便调用者可以处理
    } else{
      httpRequest.destroy();
      return response;
    }

  }

  private isValidUrl(url: string): boolean {
    
  }

  private appendQueryParams(url: string, queryParams: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >|undefined): string {
    
  }
}

通过使用NetworkService类,用户可以以更加专业和简洁的方式发起HTTP请求,并享受到配置化、拦截器等高级功能带来的便利。这不仅可以提高开发效率,还可以使代码更加清晰、易于维护。

相关推荐
Random_index1 小时前
#Uniapp篇:支持纯血鸿蒙&发布&适配&UIUI
uni-app·harmonyos
鸿蒙自习室5 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu77 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
zhangjr05759 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
诗歌难吟46416 小时前
初识ArkUI
harmonyos
SameX16 小时前
HarmonyOS Next 设备安全特性深度剖析学习
harmonyos
郭梧悠17 小时前
HarmonyOS(57) UI性能优化
ui·性能优化·harmonyos
郝晨妤1 天前
鸿蒙原生应用开发元服务 元服务是什么?和App的关系?(保姆级步骤)
android·ios·华为od·华为·华为云·harmonyos·鸿蒙
Peace*1 天前
HarmonyOs鸿蒙开发实战(16)=>沉浸式效果第一种方案一窗口全屏布局方案
harmonyos·鸿蒙·鸿蒙系统
howard20051 天前
鸿蒙实战:页面跳转传参
harmonyos·跳转·router·传参