标签:#HarmonyOS #Axios #网络请求 #ArkTS #Token刷新 #架构设计
📉 前言:为什么原生 http 模块不够用?
原生http.createHttp()的痛点:
- 代码冗余:每次都要写
extraData、connectTimeout,还要手动解析 JSON。 - 缺乏拦截器:想给所有请求统一加 Header 或者统一处理错误,只能写个包装函数,很难维护。
- 类型弱:返回的数据全是
string,需要手动JSON.parse并断言类型。
引入@ohos/axios后,我们不仅能复用前端的拦截器思维,还能利用 ArkTS 的泛型系统实现类型安全的网络层。
🏗️ 一、 核心逻辑:Token 自动刷新流程
这是本篇的重难点。当多个并发请求同时触发 401 时,我们不能发起多次刷新请求,而是应该加锁。
并发控制流程图 (Mermaid):
🛠️ 二、 环境准备
安装鸿蒙版 Axios:
ohpminstall@ohos/axios💻 三、 代码实战:企业级封装
我们新建一个AxiosRequest.ts文件。
1. 定义基础结构与类型
为了让调用者用得爽,我们先定义好返回结构。
importaxios,{AxiosInstance,AxiosRequestConfig,AxiosResponse,AxiosError}from'@ohos/axios';// 后端返回的标准结构interfaceBaseResponse<T>{code:number;message:string;data:T;}// 扩展 Axios 配置,允许传递自定义参数(如:是否需要 Loading)interfaceCustomRequestConfigextendsAxiosRequestConfig{showLoading?:boolean;}2. 实现单例类与请求拦截
请求拦截器的作用很简单:有 Token 就带上。
classAxiosHttpRequest{privateinstance:AxiosInstance;constructor(){this.instance=axios.create({baseURL:'https://api.example.com/v1',timeout:10000,headers:{'Content-Type':'application/json'}});// --- 请求拦截器 ---this.instance.interceptors.request.use((config:CustomRequestConfig)=>{// 从 AppStorage 或 Preferences 获取 Tokenconsttoken=AppStorage.Get<string>('accessToken');if(token){config.headers['Authorization']=`Bearer${token}`;}returnconfig;},(error)=>Promise.reject(error));// 响应拦截器在下一步实现...}}3. 核心:响应拦截与无感刷新 (The Magic)
这里我们需要两个辅助变量:
isRefreshing: 防止多次调用刷新接口。requestsQueue: 存储在刷新期间进来的其他请求。
// ... 类内部变量privateisRefreshing=false;privaterequestsQueue:Function[]=[];// ... 在 constructor 中继续添加响应拦截器this.instance.interceptors.response.use((response:AxiosResponse)=>{// 这里的逻辑根据你们后端业务码来定// 假设 http status 200 但 code 401 也是 token 过期constres=response.dataasBaseResponse<any>;if(res.code===401){returnthis.handle401Error(response.config);}returnresponse;},(error:AxiosError)=>{// 处理 HTTP 状态码为 401 的情况if(error.response?.status===401){returnthis.handle401Error(error.config);}returnPromise.reject(error);});// --- 处理 401 的核心逻辑 ---privatehandle401Error(originConfig:AxiosRequestConfig){if(!this.isRefreshing){this.isRefreshing=true;// 1. 发起刷新 Token 请求 (注意:这里最好用一个新的 axios 实例,避免死循环)returnthis.refreshToken().then((newToken)=>{// 2. 刷新成功,保存新 TokenAppStorage.SetOrCreate('accessToken',newToken);// 3. 修改原请求的 HeaderoriginConfig.headers['Authorization']=`Bearer${newToken}`;// 4. 执行队列中的请求this.requestsQueue.forEach(cb=>cb(newToken));this.requestsQueue=[];// 5. 重发当前请求returnthis.instance(originConfig);}).catch((err)=>{// 6. 刷新也失败了?那是真的过期了,去登录页吧this.requestsQueue=[];// router.pushUrl({ url: 'pages/Login' })returnPromise.reject(err);}).finally(()=>{this.isRefreshing=false;});}else{// 如果正在刷新,则把当前请求挂起,放入队列returnnewPromise((resolve)=>{this.requestsQueue.push((newToken:string)=>{originConfig.headers['Authorization']=`Bearer${newToken}`;resolve(this.instance(originConfig));});});}}// 模拟刷新 Token 的接口privateasyncrefreshToken():Promise<string>{// 实际业务中这里调用后端刷新接口// const refreshToken = AppStorage.Get('refreshToken');return"new_generated_token_123";}4. 封装便捷方法 (GET/POST)
最后,暴露简单易用的 API。
// T 是返回数据的类型,D 是请求参数的类型publicget<T>(url:string,params?:any):Promise<T>{returnthis.instance.get<BaseResponse<T>>(url,{params}).then(res=>res.data.data);}publicpost<T>(url:string,data?:any):Promise<T>{returnthis.instance.post<BaseResponse<T>>(url,data).then(res=>res.data.data);}}// 导出单例exportconsthttp=newAxiosHttpRequest();🚀 四、 调用演示:丝滑体验
在你的 UI 组件 (.ets) 中:
import{http}from'../utils/AxiosRequest';interfaceUserProfile{id:number;name:string;}@Entry@Componentstruct ProfilePage{@Stateuser:UserProfile|null=null;asyncaboutToAppear(){try{// 泛型支持:res 自动推断为 UserProfile 类型// 哪怕 Token 过期,这里也会自动重试并成功返回constres=awaithttp.get<UserProfile>('/user/profile');this.user=res;console.info('用户名称:',res.name);}catch(error){console.error('请求失败:',error);}}build(){// UI ...}}🎯 总结
通过这次封装,我们实现了:
- 代码解耦:UI 层不需要关心 Token 怎么传,也不需要关心 401 怎么处理。
- 类型安全:利用 TypeScript 泛型,接口返回什么类型,代码里就是什么类型。
- 极致体验:Request Queue (请求队列)的设计,确保了在并发请求场景下,Token 刷新接口只会被调用一次,避免了资源浪费和逻辑错误。
这是鸿蒙 App 开发中性价比最高的基建工作之一。
Next Step:
现在的封装还没处理Loading 动画。试着修改Interceptor,在请求开始时调用promptAction.showToast或自定义 Loading 组件,请求结束时关闭它,实现全局自动 Loading。