HarmonyOS 网络请求封装实战:基于 Rcp 打造标准化 Request 工具类
typescript运行// ResponseData.ts(与后端统一的响应结构)// 业务状态码(如200表示成功)// 提示信息result: T;// 核心业务数据// GlobalVariable.ts(全局常量)BASE_URL: "https://api.example.com", // 基础地址TIME_OUT: 5000, // 超时时间5秒SUCCESS_CODE: 200,
在 HarmonyOS(鸿蒙)应用开发中,@kit.RemoteCommunicationKit(简称 Rcp)是官方提供的远程通信核心工具,用于实现跨设备 / 跨进程的网络请求。但原生 Rcp 的使用存在参数处理繁琐、拦截器管理混乱、响应解析不统一、错误处理碎片化等问题。本文基于实战封装的RequestRcp工具类,详解如何对 Rcp 进行底层封装,实现 “统一拦截、标准化参数、自动化响应解析、全局错误处理” 的网络请求体系。
一、Rcp 封装的核心价值
原生 Rcp 直接使用时,每个请求都需要重复处理 “URL 拼接、参数转换、拦截器注册、响应解析、错误提示” 等逻辑,而封装后的RequestRcp工具类解决了这些痛点:
- 统一拦截器管理:请求拦截器注入 token、响应拦截器处理 401 等通用逻辑,一次封装全局生效;
- 标准化参数处理:自动将 JSON 对象转为 URL 参数(如
{id:1,name:2}→id=1&name=2),无需手动拼接; - 自动化响应解析:统一解析响应数据,只返回业务层需要的核心数据,屏蔽底层 Rcp 响应格式;
- 全局错误处理:统一捕获业务错误,自动弹出 Toast 提示,简化业务层异常处理;
- 简化请求调用: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工具类,将鸿蒙网络请求开发从 “重复造轮子” 升级为 “标准化调用”,核心要点可总结为:
- 拦截器统一化:请求拦截注入通用信息,响应拦截处理通用异常,一次封装全局生效;
- 参数处理自动化:JSON 转 URL 参数、过滤空值、编码特殊字符,避免手动拼接错误;
- 响应解析标准化:只返回业务核心数据,统一错误提示和异常抛出,简化业务层逻辑;
- 请求方法简化:GET/POST/DELETE/PUT/PATCH 封装为静态方法,一行代码发起请求。
掌握这套封装思路后,你可快速搭建鸿蒙应用的网络请求体系,大幅提升开发效率,同时保证代码的可维护性和扩展性。
更多推荐

所有评论(0)