HarmonyOS NEXT应用开发实战:十二、远场通信RCP简单好用的模块化封装

2024-11-19 13:11:36
29次阅读
0个评论
最后修改时间:2024-11-19 14:08:29

在进行HarmonyOS的应用开发中,我们常常需要进行网络通信。然而,原始的远场通信(RCP)使用方式较为繁琐,让人感到不够便捷。作为一位前期从事小程序开发的开发者,我深受小程序网络访问的简单性和便利性的吸引。因此,我决定在HarmonyOS中打造一个高效的网络组件,简化网络请求的使用方式。

原始使用方式的复杂性

首先,让我们看一下原始的RCP使用方式:

// 定义请求头
let headers: rcp.RequestHeaders = {
  'accept': 'application/json'
};

// 定义要修改的内容
let modifiedContent: UserInfo = {
  'userName': 'xxxxxx'
};

const securityConfig: rcp.SecurityConfiguration = {
  tlsOptions: {
    tlsVersion: 'TlsV1.3'
  }
};

// 创建通信会话对象
const session = rcp.createSession({ requestConfiguration: { security: securityConfig } });

// 定义请求对象
let req = new rcp.Request('http://example.com/fetch', 'PATCH', headers, modifiedContent);

// 发起请求
session.fetch(req).then((response) => {
  Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
  Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});

从上面的代码来看,整个流程涉及多步骤的配置,包括请求头、请求体的定义,以及会话的创建。这样的流程虽然功能齐全,但对于开发者来说,无疑增加了工作量。

模块化封装后的优势

为了提升开发效率,我着手对RCP进行模块化封装。以下是封装后的核心代码:

import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';


type AnyObject = Record<string | number | symbol, any>;
export type HttpPromise<T> = Promise<HttpResponse<T>>;

export interface RequestTask {
  abort: () => void;
  offHeadersReceived: () => void;
  onHeadersReceived: () => void;
}

type Tasks = RequestTask;

export interface HttpRequestConfig<T = Tasks> {
  /** 请求基地址 */
  baseURL?: string;
  /** 请求服务器接口地址 */
  url?: string;

  /** 请求查询参数,自动拼接为查询字符串 */
  params?: AnyObject;
  /** 请求体参数 */
  data?: AnyObject;

  /** 文件对应的 key */
  name?: string;
  /** HTTP 请求中其他额外的 form data */
  formData?: AnyObject;
  /** 要上传文件资源的路径。 */
  filePath?: string;
  /** 请求头信息 */
  header?: AnyObject;
  /** 请求方式 */
  method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD" | (string & NonNullable<unknown>);
  /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
  dataType?: string;
  /** 设置响应的数据类型,支付宝小程序不支持 */
  responseType?: "text" | "arraybuffer";
  /** 自定义参数 */
  custom?: AnyObject;
  /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
  timeout?: number;
  /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
  firstIpv4?: boolean;
  /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */
  sslVerify?: boolean;
  /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */
  withCredentials?: boolean;

  /** 返回当前请求的task, options。请勿在此处修改options。 */
  getTask?: (task: T, options: HttpRequestConfig<T>) => void;
  /**  全局自定义验证器 */
  validateStatus?: (statusCode: number) => boolean | void;
}

export interface HttpResponse<T = any> {
  config?: HttpRequestConfig;
  statusCode: number;
  data: T;
  errMsg: string;
  cookies: Array<string>;
  header: AnyObject;
}

export interface HttpUploadResponse<T = any> {
  config: HttpRequestConfig;
  statusCode: number;
  data: T;
  errMsg: string;
}
export interface HttpDownloadResponse extends HttpResponse {
  tempFilePath: string;
}

export abstract class HttpRequestAbstract {
  session: rcp.Session;
  config: HttpRequestConfig;

  constructor(public conf: HttpRequestConfig) {
    this.config = {
      ...conf,
      validateStatus: conf.validateStatus || ((status) => status >= 200 && status < 300)
    };

    const sessionConfig: rcp.SessionConfiguration = {
      baseAddress: conf.baseURL,
      headers: conf.header,
      requestConfiguration: {
        security: {
          tlsOptions: {
            tlsVersion: 'TlsV1.3'
          }
        }
      }
    }
    this.session = rcp.createSession(sessionConfig);
  }

  request<T = any>(config: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return new Promise((resolve, reject) => {

      const { method = "GET", url, header, data,params } = config;
      const fullUrl =`${this.config.baseURL}${url}`;
      
      const req = new rcp.Request(fullUrl, method, header, data);
      
      this.session.fetch(req).then((response) => {
        const responseData = response.toJSON();
        resolve({ data: responseData, statusCode: response.statusCode });
      }).catch((err: BusinessError) => {
        reject(err);
      });
    });
  }

  // GET 请求
  get<T = any>(url: string, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, method: 'GET' });
  }

  // POST 请求
  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'POST' });
  }

  // PUT 请求
  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'PUT' });
  }

  // DELETE 请求
  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<RequestTask>): HttpPromise<T> {
    return this.request({ ...config, url, data, method: 'DELETE' });
  }

  // 其他 HTTP 请求方法可以类似实现,如 head、options、trace 等
  // 上传
  upload<T = any>(url: string, config?: HttpRequestConfig<RequestTask>): Promise<HttpUploadResponse>{
    return new Promise((resolve, reject) => {
      let fileDir = config.filePath; // 请根据自身业务定义此路径
      let uploadFromFile : rcp.UploadFromFile = {
        fileOrPath : fileDir
      }
      this.session.uploadFromFile(url, uploadFromFile).then((response) => {
        console.info(`Succeeded in getting the response ${response}`);
        let resp = {
          config,
          data: response.toJSON() as T,
          statusCode: response.statusCode,
          errMsg: response.reasonPhrase
        };
        resolve(resp)
      }).catch((err: BusinessError) => {
        console.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
        reject(err)
      });
    })
  }
  // 下载
  download(url: string, config?: HttpRequestConfig<RequestTask>): Promise<HttpDownloadResponse>{
    return new Promise((resolve, reject) => {
      let downloadToFile: rcp.DownloadToFile = {
        kind: 'folder',
        path: config.filePath //请根据自身业务选择合适的路径
      } as rcp.DownloadToFile
      this.session.downloadToFile(url, downloadToFile).then((response) => {
        console.info(`Succeeded in getting the response ${response}`);
        let resp = {
          config,
          data: response.toJSON(),
          statusCode: response.statusCode,
          errMsg: response.reasonPhrase,
          tempFilePath:config.filePath,
          header:response.headers,
          cookies:response.cookies?.map(cookie => `${cookie.name}=${cookie.value}`),
        };
        resolve(resp)
      }).catch((err: BusinessError) => {
        console.error(`DownloadToFile failed, the error message is ${JSON.stringify(err)}`);
        reject(err)
      });
    })
  }
}

通过这样的封装,我们将网络请求的逻辑进行了抽象,简化了外部调用的复杂性。开发者只需专注于请求的参数,而无需关心底层的实现。

封装后的简单使用

在封装完成后,发起网络请求的代码大幅度简化,例如:

//utils/http.ts
import HttpRequest, { HttpPromise,HttpRequestConfig, HttpResponse } from './core';

const config:HttpRequestConfig = {
  baseURL: "http://175.178.126.10:8000/",
  validateStatus: (status) => {
    return status >= 200 && status < 300;
  }
}

export const httpClient = new HttpRequest(config);

export const setRequestConfig = () => {
  // 请求拦截
  httpClient.requestInterceptor.onFulfilled = (config?: HttpRequestConfig) =>{
    // 返回一个符合 HttpRequestConfig 类型的对象
    console.debug('请求拦截')
    return {

    }
  }

  httpClient.responseInterceptor.onFulfilled = (response?: HttpResponse) =>{
    // 返回一个符合 HttpResponse 类型的对象
    console.debug('响应拦截')
    return {

    }as HttpResponse
  }
}
export {HttpPromise};
export default httpClient;
// homeapi.ts
import { httpClient, HttpPromise, setRequestConfig } from '../../utils/http';

// 调用setRequestConfig函数进行请求配置
setRequestConfig();

const http = httpClient;

// 获取轮播图api接口
export const getSwiperList = (): HttpPromise<BaseResponse<SwiperData>> => http.get('/swiperdata');

// 获取热门影视接口
export const getHotMovie = (req: HotMovieReq): HttpPromise<BaseResponse<MovieRespData>> => http.post('/hotmovie', req);

// 获取知乎列表页api接口
export const getZhiHuNews = (date:string): HttpPromise<BaseResponse<ZhiNewsRespData>> => http.get('/zhihunews/'+date);

// 获取知乎详情页api接口
export const getZhiHuDetail = (id:string): HttpPromise<BaseResponse<ZhiDetailRespData>> => http.get('/zhihudetail/'+id);

这一改动使得接口调用变得极其简单,清晰易读,减少了出错的可能性。只需两行代码即可写完两个接口,完成复杂的网络请求,极大提高了开发效率。

总结

通过对HarmonyOS中远场通信RCP的模块化封装,我们不仅优化了网络请求的流程,还提升了代码的可读性和可维护性。希望这篇文章能够帮助你在HarmonyOS应用开发中更高效地使用网络组件,享受更便捷的开发体验。

写在最后

最后,推荐下笔者的业余开源app影视项目“爱影家”,推荐分享给与我一样喜欢免费观影的朋友。 注:因涉及免费观影,该项目仅限于学习研究使用!请勿用于其他用途!

开源地址:爱影家app开源项目介绍及源码

https://gitee.com/yyz116/imovie

其他资源

https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/remote-communication-interceptor-V13 https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-rcp-based-network-request-V5

收藏01

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