在 HarmonyOS(鸿蒙)应用开发中,@kit.RemoteCommunicationKit(简称 Rcp)是官方提供的远程通信核心工具,用于实现跨设备 / 跨进程的网络请求。但原生 Rcp 的使用存在参数处理繁琐、拦截器管理混乱、响应解析不统一、错误处理碎片化等问题。本文基于实战封装的RequestRcp工具类,详解如何对 Rcp 进行底层封装,实现 “统一拦截、标准化参数、自动化响应解析、全局错误处理” 的网络请求体系。

一、Rcp 封装的核心价值

原生 Rcp 直接使用时,每个请求都需要重复处理 “URL 拼接、参数转换、拦截器注册、响应解析、错误提示” 等逻辑,而封装后的RequestRcp工具类解决了这些痛点:

  1. 统一拦截器管理:请求拦截器注入 token、响应拦截器处理 401 等通用逻辑,一次封装全局生效;
  2. 标准化参数处理:自动将 JSON 对象转为 URL 参数(如{id:1,name:2}id=1&name=2),无需手动拼接;
  3. 自动化响应解析:统一解析响应数据,只返回业务层需要的核心数据,屏蔽底层 Rcp 响应格式;
  4. 全局错误处理:统一捕获业务错误,自动弹出 Toast 提示,简化业务层异常处理;
  5. 简化请求调用:GET/POST/DELETE/PUT/PATCH 等请求方式封装为静态方法,一行代码发起请求。

二、核心封装代码全解析

以下是基于 Rcp 的完整封装代码(附详细注释),我们将分模块拆解核心逻辑:

完整封装代码(request-rcp.ets)

typescript

运行

import { rcp } from "@kit.RemoteCommunicationKit";
// 自定义响应数据类型(需与后端统一)
import { ResponseData } from "../../../../Index";
// 全局常量(基础地址、超时时间、成功码等)
import { GlobalVariable } from "../constants";
// 提示工具(Toast)
import { promptAction } from "@kit.ArkUI";

/**
 * 1. 请求拦截器:实现rcp.Interceptor接口,处理请求前逻辑(如注入token)
 */
class RcpRequestInterceptor implements rcp.Interceptor {
  /**
   * 拦截方法(必须实现)
   * @param context 请求上下文(包含请求头、参数等)
   * @param next 执行链,传递到下一个拦截器/请求处理
   * @returns 响应结果
   */
  async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
    // TODO 通用请求拦截逻辑:注入token、添加通用请求头
    // 示例:context.request.headers.set('Authorization', GlobalVariable.TOKEN);
    // 必须执行next.handle(context),否则请求无法继续
    return next.handle(context);
  }
}

/**
 * 2. 响应拦截器:实现rcp.Interceptor接口,处理响应后逻辑(如401、统一错误)
 */
class RcpResponseInterceptor implements rcp.Interceptor {
  async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
    // 先执行请求,获取响应结果
    const res = await next.handle(context);
    
    // TODO 通用响应拦截逻辑:处理401未授权、500服务器错误等
    // 示例:401时跳转到登录页
    // if (res.statusCode === 401) {
    //   router.pushUrl({ url: 'pages/Login' });
    // }
    
    return res;
  }
}

/**
 * 3. 创建Rcp会话实例(全局唯一)
 * 配置基础地址、拦截器、超时时间等通用参数
 */
const rcpSession = rcp.createSession({
  // 基础请求地址(全局常量,如https://api.example.com)
  baseAddress: GlobalVariable.BASE_URL,
  // 拦截器列表(不区分请求/响应,按顺序执行)
  interceptors: [new RcpRequestInterceptor(), new RcpResponseInterceptor()],
  // 请求配置:超时时间等
  requestConfiguration: {
    transfer: {
      timeout: {
        connectMs: GlobalVariable.TIME_OUT, // 连接超时(如5000ms)
        transferMs: GlobalVariable.TIME_OUT, // 传输超时
        inactivityMs: GlobalVariable.TIME_OUT, // 会话超时
      }
    }
  }
});

/**
 * 4. 标准化请求工具类:封装所有请求方法,统一参数/响应处理
 */
export class RequestRcp {
  /**
   * GET请求封装
   * @param url 接口路径(拼接在baseAddress后)
   * @param params URL参数(JSON对象)
   * @returns 业务层需要的核心数据
   */
  static get<T>(url: string, params?: object): Promise<T> {
    // 将JSON参数转为URL拼接字符串(如{id:1}→id=1)
    const urlWithParams = url + "?" + RequestRcp.transJSONToParams(params);
    return RequestRcp.toRightData<T>(rcpSession.get(urlWithParams));
  }

  /**
   * POST请求封装(JSON参数)
   * @param url 接口路径
   * @param data 请求体参数
   * @returns 业务层需要的核心数据
   */
  static post<T>(url: string, data?: object): Promise<T> {
    return RequestRcp.toRightData<T>(rcpSession.post(url, data));
  }

  /**
   * DELETE请求封装(支持URL参数+请求体)
   * 原生rcp.delete不支持body,需手动创建Request实例
   * @param url 接口路径
   * @param params URL参数
   * @param data 请求体参数
   * @returns 业务层需要的核心数据
   */
  static delete<T>(url: string, params?: object, data?: object): Promise<T> {
    const req = new rcp.Request(
      url + "?" + RequestRcp.transJSONToParams(params), // 带参数的URL
      "DELETE", // 请求方法
      {}, // 请求头
      data // 请求体
    );
    return RequestRcp.toRightData<T>(rcpSession.fetch(req));
  }

  /**
   * PUT请求封装
   * @param url 接口路径
   * @param data 请求体参数
   * @returns 业务层需要的核心数据
   */
  static put<T>(url: string, data?: object): Promise<T> {
    return RequestRcp.toRightData<T>(rcpSession.put(url, data));
  }

  /**
   * PATCH请求封装
   * 原生rcp无patch方法,需手动创建Request实例
   * @param url 接口路径
   * @param data 请求体参数
   * @returns 业务层需要的核心数据
   */
  static patch<T>(url: string, data?: object): Promise<T> {
    const req = new rcp.Request(url, "PATCH", {}, data);
    return RequestRcp.toRightData<T>(rcpSession.fetch(req));
  }

  /**
   * 上传文件(预留扩展)
   */
  static upload<T>() {
    // TODO 实现文件上传逻辑
    return Promise.reject(new Error("上传功能待实现"));
  }

  /**
   * 下载文件(预留扩展)
   */
  static download<T>() {
    // TODO 实现文件下载逻辑
    return Promise.reject(new Error("下载功能待实现"));
  }

  /**
   * 统一响应解析:将Rcp响应转为业务层需要的数据格式
   * @param res Rcp原始响应Promise
   * @returns 业务核心数据(result字段)
   */
  static async toRightData<T>(res: Promise<rcp.Response>): Promise<T> {
    // 等待Rcp响应返回
    const rcpRes = await res;
    // 将Rcp响应转为JSON对象(适配自定义ResponseData类型)
    const result = rcpRes.toJSON() as ResponseData<T>;

    // 业务成功:仅返回result字段(屏蔽code/message等冗余字段)
    if (result.code === GlobalVariable.SUCCESS_CODE) {
      return result.result;
    }

    // 业务失败:弹出Toast提示,抛出异常供业务层捕获
    promptAction.showToast({ message: result.message || "请求失败" });
    return Promise.reject(new Error(result.message || "请求失败"));
  }

  /**
   * JSON对象转URL参数
   * @param params 待转换的JSON参数
   * @returns 拼接后的参数字符串(如id=1&name=2)
   */
  static transJSONToParams(params?: object): string {
    if (!params) return "";
    // 过滤空值参数 → 转换为key=value → 拼接&
    return Object.keys(params)
      .filter(key => !!params[key]) // 过滤null/undefined/''的参数
      .map(key => `${key}=${encodeURIComponent(params[key])}`) // 编码特殊字符(如空格、中文)
      .join("&");
  }
}

核心模块拆解

1. 拦截器封装(请求 / 响应)

Rcp 的拦截器需实现rcp.Interceptor接口的intercept方法,核心逻辑:

  • 请求拦截器(RcpRequestInterceptor):处理请求发送前的通用逻辑,如注入 token、添加通用请求头(如 Content-Type、User-Agent);
  • 响应拦截器(RcpResponseInterceptor):处理响应返回后的通用逻辑,如 401 未授权跳转登录、500 服务器错误日志上报;
  • 必须调用next.handle(context):保证请求执行链不中断,否则请求会被拦截终止。
2. Rcp 会话实例创建

通过rcp.createSession创建全局唯一的会话实例,核心配置:

  • baseAddress:基础请求地址(如https://api.example.com),所有接口路径自动拼接在其后;
  • interceptors:拦截器列表(按顺序执行,不区分请求 / 响应);
  • requestConfiguration:全局超时配置,避免每个请求重复设置超时时间。
3. 请求方法封装(GET/POST/DELETE 等)
  • GET/POST:原生 Rcp 提供对应方法,直接封装即可;
  • DELETE/PATCH:原生 Rcp 的 delete 方法不支持请求体、无 patch 方法,需手动创建rcp.Request实例,通过instance.fetch执行;
  • 参数处理:通过transJSONToParams自动将 JSON 对象转为 URL 参数,过滤空值并编码特殊字符(如中文、空格),避免参数传递异常。
4. 响应统一解析(toRightData)

这是封装的核心亮点,解决了 “业务层重复解析响应” 的问题:

  • 类型适配:将 Rcp 原始响应转为自定义ResponseData类型(需与后端统一,结构如{code: number, message: string, result: T});
  • 成功处理:仅返回result字段,业务层无需关注 code/message;
  • 失败处理:自动弹出 Toast 提示错误信息,抛出异常供业务层捕获(如 try/catch 处理)。

三、实战使用示例

封装完成后,业务层调用网络请求仅需一行代码,无需关注底层 Rcp 逻辑:

1. 定义响应数据类型(示例)

typescript

运行

// ResponseData.ts(与后端统一的响应结构)
export interface ResponseData<T> {
  code: number; // 业务状态码(如200表示成功)
  message: string; // 提示信息
  result: T; // 核心业务数据
}

// GlobalVariable.ts(全局常量)
export const GlobalVariable = {
  BASE_URL: "https://api.example.com", // 基础地址
  TIME_OUT: 5000, // 超时时间5秒
  SUCCESS_CODE: 200, // 业务成功码
  TOKEN: "xxx-xxx-xxx" // 用户token
};

2. 业务层调用示例

typescript

运行

import { RequestRcp } from "./request-rcp";

// 示例1:GET请求(获取用户列表)
async function getUserList() {
  try {
    // 调用GET接口,参数{page:1, size:10}自动转为?page=1&size=10
    const res = await RequestRcp.get<UserItem[]>(`/user/list`, { page: 1, size: 10 });
    console.log("用户列表:", res); // 直接获取result数组,无需解析code
  } catch (e) {
    console.error("获取用户列表失败:", e); // 捕获业务错误
  }
}

// 示例2:POST请求(创建用户)
async function createUser() {
  try {
    const res = await RequestRcp.post<UserItem>(`/user/create`, {
      name: "鸿蒙开发者",
      age: 25
    });
    console.log("创建用户成功:", res);
  } catch (e) {
    console.error("创建用户失败:", e);
  }
}

// 示例3:DELETE请求(删除购物车,带URL参数+请求体)
async function deleteCart() {
  try {
    const res = await RequestRcp.delete<boolean>(
      `/cart/delete`, // 接口路径
      { userId: 1001 }, // URL参数
      { cartIds: [1, 2, 3] } // 请求体参数
    );
    console.log("删除购物车成功:", res);
  } catch (e) {
    console.error("删除购物车失败:", e);
  }
}

// 自定义业务数据类型
interface UserItem {
  id: number;
  name: string;
  age: number;
}

四、封装扩展与最佳实践

1. 拦截器扩展:401 自动刷新 token

在响应拦截器中处理 401 未授权,实现 token 刷新逻辑:

typescript

运行

// RcpResponseInterceptor的intercept方法中补充
if (res.statusCode === 401) {
  // 1. 调用刷新token接口
  const refreshRes = await RequestRcp.post<{ token: string }>('/token/refresh', { refreshToken: GlobalVariable.REFRESH_TOKEN });
  // 2. 更新全局token
  GlobalVariable.TOKEN = refreshRes.token;
  // 3. 重新发起原请求(携带新token)
  context.request.headers.set('Authorization', refreshRes.token);
  return next.handle(context); // 重新执行请求
}

2. 参数处理优化:支持复杂参数

transJSONToParams方法仅支持单层 JSON,可扩展支持嵌套对象:

typescript

运行

// 扩展:嵌套对象转URL参数(如{user: {name: 'test'}} → user.name=test)
static transJSONToParams(params?: object, parentKey = ''): string {
  if (!params) return "";
  return Object.keys(params).reduce((acc, key) => {
    const fullKey = parentKey ? `${parentKey}.${key}` : key;
    const value = params[key];
    if (value === null || value === undefined) return acc;
    // 嵌套对象递归处理
    if (typeof value === 'object' && !Array.isArray(value)) {
      return acc + this.transJSONToParams(value, fullKey) + '&';
    }
    // 数组处理(如ids=[1,2] → ids=1&ids=2)
    if (Array.isArray(value)) {
      return acc + value.map(v => `${fullKey}=${encodeURIComponent(v)}`).join('&') + '&';
    }
    return acc + `${fullKey}=${encodeURIComponent(value)}&`;
  }, '').replace(/&$/, ''); // 移除最后一个&
}

3. 错误处理增强:区分网络错误与业务错误

toRightData中区分网络错误(如超时、断网)和业务错误:

typescript

运行

static async toRightData<T>(res: Promise<rcp.Response>): Promise<T> {
  try {
    const rcpRes = await res;
    const result = rcpRes.toJSON() as ResponseData<T>;
    if (result.code === GlobalVariable.SUCCESS_CODE) {
      return result.result;
    }
    // 业务错误
    promptAction.showToast({ message: result.message });
    return Promise.reject({ type: 'business', message: result.message, code: result.code });
  } catch (e) {
    // 网络错误(超时、断网等)
    promptAction.showToast({ message: "网络异常,请检查网络" });
    return Promise.reject({ type: 'network', message: "网络异常", error: e });
  }
}

4. 上传 / 下载功能实现

扩展upload/download方法,支持文件上传下载:

typescript

运行

// 上传文件示例
static async upload<T>(url: string, file: File): Promise<T> {
  const formData = new FormData();
  formData.append('file', file);
  const req = new rcp.Request(url, "POST", {
    'Content-Type': 'multipart/form-data'
  }, formData);
  return RequestRcp.toRightData<T>(rcpSession.fetch(req));
}

五、总结

基于 Rcp 封装的RequestRcp工具类,将鸿蒙网络请求开发从 “重复造轮子” 升级为 “标准化调用”,核心要点可总结为:

  1. 拦截器统一化:请求拦截注入通用信息,响应拦截处理通用异常,一次封装全局生效;
  2. 参数处理自动化:JSON 转 URL 参数、过滤空值、编码特殊字符,避免手动拼接错误;
  3. 响应解析标准化:只返回业务核心数据,统一错误提示和异常抛出,简化业务层逻辑;
  4. 请求方法简化:GET/POST/DELETE/PUT/PATCH 封装为静态方法,一行代码发起请求。

掌握这套封装思路后,你可快速搭建鸿蒙应用的网络请求体系,大幅提升开发效率,同时保证代码的可维护性和扩展性。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐