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

特立独行的猫
• 阅读 21

在进行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)}`);
});

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

模块化封装后的优势

:该封装库已上架鸿蒙三方库中心(https://ohpm.openharmony.cn/#/cn/home),

地址:https://ohpm.openharmony.cn/#/cn/detail/@nutpi%2Frequest

欢迎体验并提供宝贵意见! 详细使用demo参见知乎日报项目: HarmonyOS NEXT应用开发实战:十二、远场通信RCP简单好用的模块化封装

知乎日报开源地址:https://gitee.com/yyz116/zhihudaily

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

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


type AnyObject = Record<string | number | symbol, any>;
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;
  /** 超时时间 */
  timeout?: number;
  /** DNS解析时优先使用ipv4*/
  firstIpv4?: boolean;
  /** 验证 ssl 证书  */
  sslVerify?: boolean;
  /** 跨域请求时是否携带凭证(cookies) */
  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 fullUrl = buildURL(buildFullPath(this.config.baseURL, config.url), config.params);
       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();
        let resp = {
          config,
          data: responseData as T,
          statusCode: response.statusCode,
          errMsg: response.reasonPhrase,
          cookies:response.cookies?.map(cookie => `${cookie.name}=${cookie.value}`),
          header:response.headers
        };
        resolve(resp);
      }).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)
      });
    })
  }
}

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

封装后的简单使用

在封装完成后,发起网络请求的代码大幅度简化,例如: 先封装个http.ts工具

//utils/http.ts
import HttpRequest, { 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 default httpClient;

网络访问的API接口实现:

// homeapi.ts
import { BaseResponse,SwiperData,HotMovieReq,MovieRespData } from '../bean/ApiTypes';
import { httpClient,setRequestConfig } from '../../utils/http';

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

const http = httpClient;

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

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

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

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

这一改动使得接口调用变得极其简单,清晰易读,减少了出错的可能性。一行代码即可写完一个接口,清晰直观。以上的四行代码,写完了四个接口,极大提高了开发效率。

当然,接口的请求包和响应包格式需要定义,这必不可少。以下是bean/ApiTypes.ts的接口类型定义。

type AnyObject = Record<string | number | symbol, any>
export interface BaseResponse<T>{
  statusCode: number;
  errMsg:string;
  header?: AnyObject;
  data:T;
}

export interface ErrorResp {
  code: number;
  message: string;
  data: [];
}

// 轮播图响应数据
export interface SwiperItem{
  id:string;
  imageUrl:string;
  title:string;
  url:string;
  description:string;
}
export interface SwiperData {
  code: number;
  message: string;
  data: Array<SwiperItem>;
}

// 热门影视请求数据
export interface HotMovieReq {
  start: number;
  count: number;
  city:string;
}
// 热门影视响应数据
interface MovieItem{
  id:string;
  cover:string;
  title:string;
  gener:string;
  rate:number;
}
export interface MovieRespData {
  code: number;
  message: string;
  data: Array<MovieItem>;
  count: number;
  start: number;
  total: number;
  title: string;
}

//==============================知乎日报
export type ZhiNewsItem ={
  id:string;
  image:string;
  title:string;
  url:string;
  hint:string;
  date: string;
}
export interface ZhiNewsRespData {
  code: number;
  message: string;
  stories: Array<ZhiNewsItem>;
  top_stories: Array<ZhiNewsItem>;
  date: string;
}

type ZhiDetailItem={
  types:string;
  value:string;
}
export interface ZhiDetailRespData {
  code: number;
  message: string;
  content: Array<ZhiDetailItem>;
  title: string;
  author: string;
  bio: string;
  avatar: string;
  image: string;
  more: string;

}

总结

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

开源地址: https://gitee.com/yyz116/request

作者:猫哥 blog.csdn.net/qq8864,转载请注明出处。

团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

写在最后

最后,推荐下笔者的业余开源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 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/errorcode-net-http-0000001544704013-V2#ZH-CN_TOPIC_0000001574128897__2300060-

https://blog.csdn.net/wu1004019946/article/details/139768393

点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
HarmonyOS 鸿蒙入门教程之01 基础环境和开发工具 (教程含源码)
HarmonyOS鸿蒙入门教程之01基础环境和开发工具鸿蒙系统是什么HarmonyOS是一款“面向未来”、面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的分布式操作系统。在传统的单设备系统能力的基础上,HarmonyOS提出了基于同一套系统能力、适配多种终端形态的
Stella981 Stella981
3年前
HarmonyOS应用开发学习路线
  2020HDC,HarmonyOS2.0终于揭开神秘面纱,咱的自主操作系统又往前迈了一大步,可喜可贺!  值此激动人心的时刻,相信很多有基础的、没基础的开发者小伙伴早已经跃跃欲试,想对HarmonyOS一探究竟了,包括小编我在内!于是,勤劳的小编按捺不住激动到颤抖的手手,迫不及待整理了HarmonyOS应用开发的学习路线,伙伴们速来围观啦!1
Stella981 Stella981
3年前
HarmonyOS 2.0 手机版使用初体验 ——手机开发者 (Beta版)
12月16日上午10点,华为在北京举办华为开发者日暨HarmonyOS2.0手机开发者Beta版发布活动。华为此次宣布面向手机开发者开放完整的HarmonyOS2.0系统能力、丰富的API(应用开发接口),以及强大的开发工具DevEcoStudio等技术装备,开发者可访问华为开发者联盟官网,申请获取HarmonyOS2.0手机开发者Beta版升级。
爱学it学无止境 爱学it学无止境
4个月前
2024 鸿蒙零基础快速实战-仿抖音App开发( ArkTS版 )|完结
ArkTS:鸿蒙应用开发的高效利器ArkTS,作为HarmonyOS4.0优选的主力应用开发语言,正逐步成为鸿蒙生态系统中的核心开发工具。它基于TypeScript进行扩展,不仅继承了TypeScript的静态类型优势,还通过一系列创新设计,进一步提升了
京东云开发者 京东云开发者
3星期前
Taro 鸿蒙技术内幕系列(二):如何让 W3C 标准的 CSS跑在鸿蒙上
作者:京东零售马银涛基于Taro打造的京东鸿蒙APP已跟随鸿蒙Next系统公测,本系列文章将深入解析Taro如何实现使用React开发高性能鸿蒙应用的技术内幕背景HarmonyOS采用自研的ArkUI框架作为原生UI开发方案,这套方案有完善的布局系统和样式
特立独行的猫
特立独行的猫
Lv1
男 · 全栈工程师
猫哥,全栈工程师。blog.csdn.net/qq8864
文章
1
粉丝
0
获赞
0
热门文章

暂无数据