OpenHarmony 网络请求 axios 封装(@ohos.net.http API23 适配)
摘要原生ohos.net.http底层 API 写法繁琐缺少统一拦截、超时、错误统一处理、loading 全局控制、请求取消、token 自动携带等通用能力。基于原生 http 封装类 axios 风格网络工具统一管理全局请求头、请求拦截、响应拦截、异常捕获、加载弹窗适配 GET/POST/PUT/DELETE 文件上传表单提交。API23 重构 http 请求生命周期、连接复用、请求销毁机制修复并发请求堆积、页面销毁未中断请求造成内存泄漏、网络切换请求失败、大文件上传卡顿等问题。本文提供完整可直接复用的 http 工具类搭配登录、资讯列表、文件上传业务页面实战统一规范前后端交互格式汇总网络常见报错解决方案是所有鸿蒙项目必备底层工具。关键词OpenHarmonyAPI23http 网络请求请求拦截响应拦截文件上传全局 loading请求取消一、引言1.1 网络封装开发背景几乎所有应用都需要和后端服务交互原生 http 存在大量重复模板代码每次请求手动添加 token、Content-Type 请求头重复写加载弹窗、失败 toast 提示页面退出时未终止进行中的请求造成回调触发已销毁页面报错无统一错误码处理401 未登录、500 服务异常、超时文件上传表单参数拼接逻辑复杂。统一封装网络工具实现请求拦截统一注入 token、响应拦截统一解析返回数据、全局 loading 自动显示隐藏、页面销毁批量取消请求、统一异常分发大幅减少页面冗余代码。API23 http 模块核心更新HttpRequest 实例支持 abort () 主动中断请求防止页面销毁回调泄漏优化长连接复用并发请求性能提升新增超时强拦截超时直接触发失败回调不会无限阻塞修复 https 证书校验失败闪退问题支持配置忽略证书调试表单上传文件流接口标准化废弃旧版 Blob 兼容写法。1.2 前置权限配置module.json5 添加网络权限jsonrequestPermissions: [ { name: ohos.permission.INTERNET } ]开发调试如需访问 http 明文接口在 module.json5 增加网络安全配置jsonnetwork: { cleartextTraffic: true }二、原生 http 基础 API 示例etsimport http from ohos.net.http // 创建请求实例 let httpReq http.createHttp() // GET请求 httpReq.request( https://xxx/api/list, { method: http.RequestMethod.GET, header: { Content-Type: application/json }, readTimeout: 10000, connectTimeout: 10000 }, (err, res) { httpReq.destroy() // 释放实例 if (!err) { console.info(数据, res.result) } } ) // 中断请求 httpReq.abort()三、完整网络工具封装 utils/http_util.etsetsimport http from ohos.net.http import promptAction from ohos.promptAction import AppStorage from ohos.arkui.state.AppStorage import { STORAGE_KEY } from ../model/Constant // 基础域名 const BASE_URL https://jsonplaceholder.typicode.com // 存储页面请求集合页面销毁批量取消 const requestPool: Sethttp.HttpRequest new Set() // 统一响应数据格式 export interface ResponseDataT { code: number data: T msg: string } // 请求配置 interface RequestOptions { url: string method: http.RequestMethod data?: Object | string showLoading?: boolean timeout?: number } class HttpUtil { private static instance: HttpUtil static getInstance() { if (!HttpUtil.instance) { HttpUtil.instance new HttpUtil() } return HttpUtil.instance } // 清除全部进行中的请求页面销毁调用 clearAllRequest() { requestPool.forEach(req { req.abort() req.destroy() }) requestPool.clear() } // 核心请求方法 async requestT(options: RequestOptions): PromiseResponseDataT | null { const { url, method, data, showLoading true, timeout 10000 } options const fullUrl BASE_URL url // 全局token const token AppStorage.Getstring(STORAGE_KEY.TOKEN) // 请求头 const header: Recordstring, string { Content-Type: application/json } if (token) header.Authorization Bearer ${token} let httpReq http.createHttp() requestPool.add(httpReq) // 加载弹窗 if (showLoading) promptAction.showLoading() try { const params: http.HttpRequestOptions { method, header, readTimeout: timeout, connectTimeout: timeout } // POST传参 if (data (method http.RequestMethod.POST || method http.RequestMethod.PUT)) { params.extraData JSON.stringify(data) } const res await httpReq.request(fullUrl, params) // 释放请求实例 httpReq.destroy() requestPool.delete(httpReq) if (showLoading) promptAction.closeLoading() // 状态码判断 if (res.responseCode ! 200) { promptAction.showToast({ message: 请求错误${res.responseCode} }) return null } const result: ResponseDataT JSON.parse(res.result as string) // 业务错误码统一处理 if (result.code ! 200) { // 401 未登录清空登录状态跳转登录页 if (result.code 401) { AppStorage.Set(STORAGE_KEY.IS_LOGIN, false) promptAction.showToast({ message: 登录失效请重新登录 }) } else { promptAction.showToast({ message: result.msg || 接口异常 }) } return null } return result } catch (err) { httpReq.destroy() requestPool.delete(httpReq) if (showLoading) promptAction.closeLoading() // 判断是否主动中断 if ((err as Error).message.includes(abort)) { return null } promptAction.showToast({ message: 网络请求失败请检查网络 }) console.error(http请求异常, err) return null } } // GET封装 getT(url: string, showLoading true) { return this.requestT({ url, method: http.RequestMethod.GET, showLoading }) } // POST封装 postT(url: string, data?: Object, showLoading true) { return this.requestT({ url, method: http.RequestMethod.POST, data, showLoading }) } // PUT putT(url: string, data?: Object, showLoading true) { return this.requestT({ url, method: http.RequestMethod.PUT, data, showLoading }) } // DELETE deleteT(url: string, data?: Object, showLoading true) { return this.requestT({ url, method: http.RequestMethod.DELETE, data, showLoading }) } } export default HttpUtil.getInstance()四、业务页面实战调用4.1 资讯列表页面GET 请求etsimport HttpUtil, { ResponseData } from ../utils/http_util interface NewsItem { id: number title: string } Entry Component struct NewsPage { State newsList: NewsItem[] [] async aboutToAppear() { await this.loadNews() } async loadNews() { const res await HttpUtil.getNewsItem[](/posts) if (res) { this.newsList res.data.slice(0, 10) } } build() { Column({ space: 10 }) { Text(网络请求资讯列表演示).fontSize(22).padding(12) List({ space: 10 }).layoutWeight(1) { ForEach(this.newsList, item { ListItem() { Text(item.title) .width(100%) .padding(16) .backgroundColor(Color.White) .borderRadius(10) } }) } } .width(100%) .height(100%) .backgroundColor(#f5f5f5) } aboutToDisappear() { // 页面退出取消所有请求防止内存泄漏 HttpUtil.clearAllRequest() } }4.2 登录页面POST 提交表单etsimport HttpUtil from ../utils/http_util import RouterUtil from ../utils/router_util import { STORAGE_KEY, PAGE_ROUTE } from ../model/Constant import AppStorage from ohos.arkui.state.AppStorage Entry Component struct LoginPage { State account: string State pwd: string async doLogin() { if (!this.account || !this.pwd) return const params { username: this.account, password: this.pwd } const res await HttpUtil.post{ token: string }(/login, params) if (res) { // 存储全局token AppStorage.Set(STORAGE_KEY.TOKEN, res.data.token) AppStorage.Set(STORAGE_KEY.IS_LOGIN, true) RouterUtil.replace(PAGE_ROUTE.INDEX) } } build() { Column({ space: 20 }) { Text(用户登录).fontSize(26).margin({ top: 60 }) TextInput({ text: this.account, placeholder: 账号 }).width(90%).height(48) TextInput({ text: this.pwd, placeholder: 密码 }).type(InputType.Password).width(90%).height(48) Button(登录).width(90%).height(48).onClick(() this.doLogin()) } .width(100%) .height(100%) .padding(20) } aboutToDisappear() { HttpUtil.clearAllRequest() } }五、文件上传扩展方法补充进 HttpUtilets// 表单文件上传 async uploadFileT(url: string, filePath: string, fileName: string) { const fullUrl BASE_URL url const token AppStorage.Getstring(STORAGE_KEY.TOKEN) const header: Recordstring, string {} if (token) header.Authorization Bearer ${token} let httpReq http.createHttp() requestPool.add(httpReq) promptAction.showLoading() try { // 表单参数 const formData: Arrayhttp.MultiFormData [ { name: file, filePath, fileName } ] const res await httpReq.request(fullUrl, { method: http.RequestMethod.POST, header, multiFormDataList: formData }) httpReq.destroy() requestPool.delete(httpReq) promptAction.closeLoading() if (res.responseCode ! 200) return null const result: ResponseDataT JSON.parse(res.result as string) return result } catch (e) { httpReq.destroy() requestPool.delete(httpReq) promptAction.closeLoading() promptAction.showToast({ message: 文件上传失败 }) return null } }六、API23 网络开发编码规范6.1 请求生命周期规范页面aboutToDisappear必须调用clearAllRequest()终止全部请求避免销毁页面后回调执行报错每个 HttpRequest 使用完毕执行 destroy 释放底层连接并发请求统一存入请求池统一管理销毁。6.2 拦截与业务处理规范请求拦截自动携带全局 token页面无需重复处理响应拦截统一处理 HTTP 状态码与后端业务 code401 自动清空登录跳转登录页全局 loading 自动弹出关闭减少页面重复弹窗代码。6.3 性能与网络适配规范统一设置超时 10s避免弱网无限等待频繁列表刷新增加防抖短时间重复请求直接取消上一次大文件上传拆分分片防止单次请求内存占用过高。6.4 安全规范线上正式环境关闭 cleartextTraffic 明文 http仅使用 httpstoken 存放 AppStorage退出登录清空敏感接口参数禁止拼接在 url统一使用 post extraData 传参。七、高频问题解决方案问题 1页面销毁后接口回调执行页面变量报错 解决页面退出调用 clearAllRequest 批量 abort 所有请求。问题 2接口 401 登录失效多处页面重复写跳转逻辑 解决工具内部统一捕获 code401自动清空登录状态并弹窗提示。问题 3多次快速点击按钮发起重复请求 解决增加 loading 状态锁加载期间拦截点击。问题 4文件上传图片无法识别 解决使用 multiFormDataList 表单上传传入本地沙盒文件完整路径。问题 5升级 API23 旧版 http 代码编译报错 解决替换废弃回调写法统一使用 async/await 异步请求手动 destroy 释放实例。问题 6弱网环境长时间卡死无提示 解决配置 connectTimeout、readTimeout超时自动抛出异常并提示用户。八、总结基于原生ohos.net.http封装的 axios 风格网络工具统一完成请求头注入、全局加载弹窗、响应数据解析、业务错误分发、页面销毁请求销毁、文件上传全套通用逻辑大幅简化页面网络交互代码。API23 完善 HttpRequest 中断与销毁机制解决旧版请求泄漏、弱网卡死、并发卡顿等问题可直接整合进前面整套四层架构项目是课程设计、毕设前后端交互核心底层工具。