网络框架封装:基于http模块封装拦截器与统一响应类(44)

📅 发布时间:2026/7/24 21:56:51
网络框架封装:基于http模块封装拦截器与统一响应类(44) 一、 定义统一的响应数据模型为了规范后端返回的数据结构建议定义一个包含泛型的响应包装类将code、message和核心data统一管理// ResponseResult.ets export class ResponseResultT { code: number; msg: string; data?: T; constructor(code: number, msg: string, data?: T) { this.code code; this.msg msg; this.data data; } }二、 设计并实现拦截器接口通过定义拦截器接口我们可以将“请求前处理”和“响应后处理”抽离出来实现 AOP面向切面的效果// Interceptors.ets import { http } from kit.NetworkKit; // 1. 请求拦截器接口 export interface HttpRequestInterceptor { intercept: (options: http.HttpRequestOptions) http.HttpRequestOptions; } // 2. 响应拦截器接口 export interface HttpResponseInterceptor { intercept: (response: http.HttpResponse) http.HttpResponse; } // 3. 默认请求拦截器示例自动注入 Token export class DefaultHeadersInterceptor implements HttpRequestInterceptor { intercept(options: http.HttpRequestOptions): http.HttpRequestOptions { const headers options.header as Recordstring, string; const token your_auth_token; // 实际项目中可从安全存储中获取 if (token) { headers[Authorization] Bearer ${token}; } return options; } } // 4. 默认响应拦截器示例统一处理错误码 export class DefaultResponseInterceptor implements HttpResponseInterceptor { intercept(response: http.HttpResponse): http.HttpResponse { if (response.responseCode 401) { // 触发全局提示或跳转登录页 console.error(Token 失效请重新登录); } return response; } }三、 封装核心网络请求类 (NetworkManager)将拦截器链、URL 拼接、超时配置以及泛型解析封装在一个核心类中// NetworkManager.ets import { http } from kit.NetworkKit; import { ResponseResult } from ./ResponseResult; import { HttpRequestInterceptor, HttpResponseInterceptor, DefaultHeadersInterceptor, DefaultResponseInterceptor } from ./Interceptors; export class NetworkManager { private baseURL: string; private requestInterceptors: HttpRequestInterceptor[] []; private responseInterceptors: HttpResponseInterceptor[] []; constructor(baseURL: string) { this.baseURL baseURL; // 默认注入基础拦截器 this.requestInterceptors.push(new DefaultHeadersInterceptor()); this.responseInterceptors.push(new DefaultResponseInterceptor()); } // 支持外部动态添加拦截器 addRequestInterceptor(interceptor: HttpRequestInterceptor) { this.requestInterceptors.push(interceptor); } // 核心请求方法支持泛型 T async requestT(path: string, options: http.HttpRequestOptions): PromiseResponseResultT { let fullUrl this.baseURL path; let requestOptions { ...options }; // 1. 执行请求拦截器链 this.requestInterceptors.forEach(interceptor { requestOptions interceptor.intercept(requestOptions); }); const httpRequest http.createHttp(); try { // 2. 发起网络请求 let response await httpRequest.request(fullUrl, requestOptions); // 3. 执行响应拦截器链 this.responseInterceptors.forEach(interceptor { response interceptor.intercept(response); }); // 4. 解析数据并封装为统一格式 const resultData JSON.parse(response.result.toString()) as T; if (response.responseCode http.ResponseCode.OK) { return new ResponseResultT(200, success, resultData); } else { return new ResponseResultT(response.responseCode, 请求失败: ${response.responseCode}); } } catch (error) { return new ResponseResultT(-1, error.message || 网络请求异常); } finally { // 5. 销毁请求实例释放资源 httpRequest.destroy(); } } // 封装常用的 GET 方法 async getT(path: string, params?: Recordstring, any): PromiseResponseResultT { return this.requestT(path, { method: http.RequestMethod.GET, extraData: params // GET请求参数 }); } // 封装常用的 POST 方法 async postT(path: string, data?: object): PromiseResponseResultT { return this.requestT(path, { method: http.RequestMethod.POST, header: { Content-Type: application/json }, extraData: JSON.stringify(data) }); } }四、 业务层调用示例封装完成后业务层如 ViewModel只需关注接口路径和数据类型无需关心底层的 Token 注入和 JSON 解析// UserViewModel.ets import { NetworkManager } from ./NetworkManager; interface UserInfo { id: number; name: string; } const network new NetworkManager(https://api.example.com); export class UserViewModel { async fetchUserProfile(userId: number) { // 发起请求自动带上 Token自动解析为 UserInfo 类型 const res await network.getUserInfo(/user/${userId}); if (res.code 200 res.data) { console.log(获取到用户: ${res.data.name}); } else { console.error(获取失败: ${res.msg}); } } }五、 请求并发控制与防抖节流在鸿蒙 UI 交互中用户快速滑动列表或连续点击按钮极易触发大量重复请求。请求去重防抖在NetworkManager内部维护一个pendingRequests: Mapstring, Promiseany。发起请求前通过URL 参数生成唯一 Key若该 Key 已存在则直接返回缓存的 Promise避免重复发起网络请求。并发数限制对于列表页的瀑布流加载同时发起几十个图片请求会导致 OOM 或系统限流。可以引入类似p-limit的并发控制逻辑限制同时处于Pending状态的请求数量如最大 5 个超出的请求放入等待队列。1. 请求去重防抖与拦截实现通过维护一个Map来缓存正在执行的 Promise可以完美解决快速滑动或连点导致的重复请求问题。class RequestDeduplicationController { // 核心维护一个正在执行的请求池 private pendingRequests: Mapstring, Promiseany new Map(); /** * param key 请求的唯一标识如 URL 序列化后的参数 * param requestFn 实际的网络请求函数 */ async deduplicatedRequestT(key: string, requestFn: () PromiseT): PromiseT { // 1. 拦截如果该 Key 已有请求在执行直接返回缓存的 Promise if (this.pendingRequests.has(key)) { console.info([RequestDedup] 命中去重拦截: ${key}); return this.pendingRequests.get(key) as PromiseT; } // 2. 发起新请求并加入池中 const requestPromise requestFn(); this.pendingRequests.set(key, requestPromise); try { const result await requestPromise; return result; } finally { // 3. 无论成功或失败请求结束后必须从池中移除 this.pendingRequests.delete(key); } } } // 使用场景示例 const dedupController new RequestDeduplicationController(); // 在组件中触发请求 async function fetchUserDetail(userId: string) { const key user_detail_${userId}; return dedupController.deduplicatedRequest(key, async () { // 实际的 HTTP 请求 return await httpRequest.get(/api/users/${userId}); }); }2. 并发数限制类似 p-limit实现对于瀑布流加载等场景我们需要一个任务队列和并发计数器来限制同时处于 Pending 状态的请求数量。class PLimitController { private queue: Array() void []; // 等待队列 private activeCount: number 0; // 当前正在执行的任务数 private concurrency: number; // 最大并发数 constructor(concurrency: number) { this.concurrency concurrency; } /** * 包装异步任务 */ async enqueueT(taskFn: () PromiseT): PromiseT { return new Promise((resolve, reject) { // 将任务包装后推入队列 this.queue.push(async () { try { const result await taskFn(); resolve(result); } catch (error) { reject(error); } finally { // 任务执行完毕释放并发槽位并触发下一个任务 this.activeCount--; this.runNext(); } }); // 尝试启动任务 this.runNext(); }); } private runNext(): void { // 如果未达到并发上限且队列不为空则取出任务执行 if (this.activeCount this.concurrency this.queue.length 0) { this.activeCount; const nextTask this.queue.shift(); nextTask?.(); } } } // 使用场景示例 // 限制图片请求最大并发数为 5 const imageLimit new PLimitController(5); // 在列表加载时批量发起请求 async function loadWaterfallImages(imageUrls: string[]) { const tasks imageUrls.map(url imageLimit.enqueue(() fetchImageFromNetwork(url))); // 等待所有图片下载完成实际并发被严格控制在5个以内 const results await Promise.all(tasks); return results; }3. 进阶优化结合防抖Debounce对于搜索框输入等高频触发事件除了去重还需要加入时间维度的防抖控制class SearchRequestManager { private debounceTimer: number -1; private readonly DEBOUNCE_DELAY: number 300; // 300ms 防抖 triggerSearch(query: string, callback: (res: any) void) { // 清除上一个定时器 clearTimeout(this.debounceTimer); // 重新计时只有用户停顿 300ms 后才真正发起请求 this.debounceTimer setTimeout(async () { const result await fetchSearchResults(query); callback(result); }, this.DEBOUNCE_DELAY) as unknown as number; } }六、 多级缓存策略Cache-First 模式鸿蒙设备通常对流畅度要求极高纯网络请求在弱网下体验极差。内存缓存LruCache对于高频访问且数据量较小的字典类接口使用内存 LRU 缓存实现毫秒级响应。持久化缓存结合鸿蒙的Preferences或RDB关系型数据库将核心数据落盘。在发起网络请求时采用“先展示缓存数据后台静默刷新刷新成功后更新 UI”的策略即 Stale-While-Revalidate 模式。七、 异常精细化处理与重试机制基础的try-catch往往无法应对复杂的网络环境。网络状态监听利用鸿蒙的ohos.net.connection模块监听网络状态。当检测到断网时直接拦截请求并返回离线提示避免无意义的超时等待。智能重试Retry对于502 Bad Gateway、504 Gateway Timeout或网络抖动导致的异常在响应拦截器中实现指数退避重试算法Exponential Backoff自动重试 1~3 次。Token 无感刷新在响应拦截器中捕获401 Unauthorized。此时不要立即跳转登录页而是挂起当前请求调用刷新 Token 接口。刷新成功后使用新 Token 重新发起被挂起的请求若刷新失败再强制登出。八、 拥抱鸿蒙原生能力升级 kit.NetworkKit在 HarmonyOS NEXTAPI 12中官方对网络模块进行了重构。废弃旧模块ohos.net.http已被标记为即将废弃建议全面迁移至kit.NetworkKit。使用 HttpClient新版HttpClient提供了更现代的 API 设计原生支持配置连接池、TLS 证书校验、以及更优雅的异步回调机制大幅提升了底层网络请求的性能和安全性。九、 安全合规与证书固定Certificate Pinning金融级或高安全要求的鸿蒙应用必须防范中间人攻击MITM。证书固定在NetworkManager初始化时通过鸿蒙的caCerts配置项将服务端的公钥证书或 CA 根证书硬编码到客户端。即使攻击者伪造了系统级证书网络请求也会因证书校验失败而被拦截。敏感数据脱敏在请求拦截器打印日志时务必对 Header 中的Authorization和 Body 中的密码、身份证号等字段进行掩码处理防止在 Debug 模式下发生数据泄露。十、 结合 MVVM 与 DI 的终极形态将网络层与前面讨论的架构深度结合DI 注入网络实例通过provide将配置好拦截器和缓存策略的NetworkManager注入到 IoC 容器中。Repository 模式ViewModel 不再直接调用NetworkManager而是调用UserRepository。Repository 内部决定是先读缓存还是先走网络并将结果封装为Flow或StateFlow响应式流View 层只需绑定状态即可实现极致丝滑的数据驱动 UI。1. 配置网络实例与 IoC 容器注入首先我们需要一个配置好拦截器、缓存策略和并发控制的NetworkManager。在鸿蒙或跨平台架构中可以通过 IoC控制反转容器将其作为单例注入。// 1. 定义网络管理器接口面向抽象编程 export interface INetworkManager { getT(url: string, params?: Recordstring, any): PromiseT; } // 2. 实现网络管理器整合拦截器与缓存 export class NetworkManager implements INetworkManager { private httpClient: HttpClient; constructor() { this.httpClient new HttpClient(); // 注入请求拦截器如统一添加 Token this.httpClient.addRequestInterceptor((config) { config.headers[Authorization] Bearer ${getToken()}; return config; }); // 注入响应拦截器统一处理错误码 this.httpClient.addResponseInterceptor((response) { if (response.code ! 200) throw new Error(response.msg); return response.data; }); } async getT(url: string, params?: Recordstring, any): PromiseT { // 内部可结合前面讨论的并发控制和去重逻辑 return this.httpClient.request(url, { method: GET, params }) as PromiseT; } } // 3. IoC 容器注册以伪代码/通用DI框架为例 // 将配置好的 NetworkManager 作为单例注入到全局容器 Container.provide(INetworkManager, () new NetworkManager(), { singleton: true });2. Repository 模式封装数据策略Repository 是数据的统一入口。它不关心数据是来自本地数据库还是网络而是根据业务逻辑如先读缓存无缓存则请求网络并更新缓存协调数据源。export class UserRepository { // 通过构造函数注入网络实例实现解耦 constructor( private networkManager: INetworkManager, private localCache: ILocalCache ) {} /** * 获取用户列表响应式流 */ async *fetchUsers(page: number): AsyncGeneratorUser[], void, undefined { // 1. 优先读取本地缓存实现首屏秒开 const cachedUsers await this.localCache.getUsers(page); if (cachedUsers) yield cachedUsers; // 2. 发起网络请求 try { const remoteUsers await this.networkManager.getUser[](/api/users, { page }); // 3. 将最新数据写入本地缓存 await this.localCache.saveUsers(page, remoteUsers); // 4. 发射最新网络数据触发 UI 更新 yield remoteUsers; } catch (error) { // 如果网络失败且没有缓存抛出异常交由 ViewModel 处理 if (!cachedUsers) throw error; } } }3. ViewModel 消费响应式流ViewModel 负责持有 Repository 并将数据转化为 UI 层可直接绑定的状态如StateFlow。export class UserViewModel { private userRepository: UserRepository; // 定义 UI 状态密封类/联合类型 private _uiState new StateFlowUserUiState(new LoadingState()); public get uiState(): StateFlowUserUiState { return this._uiState; } constructor() { // 从 IoC 容器获取依赖 const network Container.resolveINetworkManager(INetworkManager); const cache Container.resolveILocalCache(ILocalCache); this.userRepository new UserRepository(network, cache); } public async loadUsers(page: number) { try { this._uiState.value new LoadingState(); // 消费 Repository 提供的响应式流 for await (const users of this.userRepository.fetchUsers(page)) { this._uiState.value new SuccessState(users); } } catch (error) { this._uiState.value new ErrorState((error as Error).message); } } }4. View 层声明式绑定在鸿蒙 ArkUI 等声明式 UI 框架中View 层只需监听 ViewModel 的状态变化实现零业务代码的极致丝滑渲染。Component export struct UserListPage { ObjectLink vm: UserViewModel; State currentState: UserUiState new LoadingState(); aboutToAppear() { // 监听 StateFlow 的变化自动映射到组件状态 this.vm.uiState.subscribe((state) { this.currentState state; }); this.vm.loadUsers(1); } build() { Column() { if (this.currentState instanceof LoadingState) { LoadingProgress() } else if (this.currentState instanceof ErrorState) { Text(加载失败: ${this.currentState.message}) } else if (this.currentState instanceof SuccessState) { List() { ForEach(this.currentState.users, (user: User) { ListItem() { Text(user.name) } }) } } } } }