HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)

系列文章目录

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)
HarmonyOS Next系列之实现一个左右露出中间大两边小带缩放动画的轮播图(十二)



前言

HarmonyOS Next(基于API11)封装一个http请求工具类,自动拦截token失效跳转登录页,以及token持久化存取方案。


一、实现设计

  • 对于接口请求我们最关心两个东西,一个是请求参数另一个是接收服务器返回的数据

(1)请求参数最常设置的有:

请求链接url、请求方式method(post,get等)、请求参数data、请求数据类型Content-Type、登录凭证token

其中token可从本地持久化读取无需传入,剩下四个可设计为动态传参

//接口入参数据类型
interface RequestParams {
  url: string //请求链接
  method?: http.RequestMethod //请求方式
  data?: Object //请求额外数据
  headerContentType?: string //请求数据类型
}

(2)返回数据一般是个对象 常见固定字段有:

code:状态码 ,message:接口响应说明 ,data:返回数据

故接口返回数据类型可定义为:

//接口请求返回数据类型
interface ResponseResult {
  code: number //状态码
  message: string //处理信息
  data: Object | null //返回数据
}

ps:根据实际需要也可按需添加字段

对于返回数据code状态码一般定义:
1、200为请求成功
2、401 token失效(无效或缺失)
3、其他情况归为请求失败

所以对于接口返回数据可根据code值分三种情况处理例如:

if(code==200){//请求成功
   //返回数据
}
else if(code==401){//token失效
 //拦截跳转到登录页
 router.replaceUrl({
      url: "/pages/login"
  })
}
else{//请求失败
 
}

ps:当然状态码也可根据实际定义修改

  • Token持久化存储
    为了配合登录方案实现,方便在EntryAbility使用token,我们这里选择了Preferences作为存储方案。

最后,熟悉web开发的同学都知道web项目中习惯把接口定义放置在api文件夹下统一管理,然后在页面引入使用,再此沿用该开发习惯,方便后期维护。

二、代码实现

目录结构:
在这里插入图片描述

1.http请求工具类request.ets

封装一个http请求工具类文件,默认导出一个请求函数返回Promise(接口返回数据)

utils/request.ets

import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
import promptAction from '@ohos.promptAction'
import { getToken } from './index'
import router from '@ohos.router';

//baseURL接口域名
const BASEURL: string = "https://xxxxxxx.com"
//登录页路由
const LOGINPAGEURL = 'pages/common/login'

//接口入参数据接口
interface RequestParams {
  url: string
  method?: http.RequestMethod
  data?: Object
  headerContentType?: string
}


//接口请求返回数据类
class ResponseResult {
  code: number //状态码
  message: string //处理信息
  data: Object | null //返回数据

  constructor(code?: number, message?: string, data?: Object | null | undefined) {
    this.code = code ?? 0
    this.message = message ?? ''
    this.data = data ?? null
  }
}

/**
 *
 * @param params:接口请求参数(object类型)
 * {
 *  url :请求连接
 *  method :请求方法
 *  data :请求数据
 *  headerContentType :请求头发送的数据格式
 * }
 * @returns Promise<ResponseResult>
 */
export default function request(params: RequestParams): Promise<ResponseResult> {
  return new Promise(async (resolve: (res: ResponseResult) => void, reject: (res: ResponseResult | string | BusinessError | http.HttpResponse) => void) => {
    //请求头contentType
    let contentType: string = params.headerContentType || 'application/json' //默认提交数据类型为application/json
    //请求数据data
    let requestData: Object | undefined = params.data;
    //application/x-www-form-urlencoded类型参数处理成key&value形式
    if (contentType === 'application/x-www-form-urlencoded') {
      if (typeof params.data === 'object') {
        requestData = Object.entries(requestData as object).reduce((prev: string, cur: Array<Object>) => {
          return (prev && `${prev}&`) + `${cur[0]}=${cur[1]}`
        }, '')
      }
    }
    //从本地存储获取token
    let token: string = await getToken()
    let httpRequest = http.createHttp();
    httpRequest.request(BASEURL + params.url, {
      method: params.method ?? http.RequestMethod.GET, //默认get方法
      header: {
        'Content-Type': contentType,
        token
      },
      extraData: requestData,
      readTimeout: 30000,
      connectTimeout: 30000
    }, (err: BusinessError, data: http.HttpResponse) => {
      if (!err) {
        //请求成功
        if (data.responseCode === 200) {
          let res: ResponseResult = JSON.parse(`${data.result}`);
          let response = new ResponseResult(res.code, res.message, res.data)
          //状态码code=200表示请求成功,状态码可根据实际接口文档修改
          if (res.code === 200) {
            resolve(response);
          }
          //状态码code=401表示token失效,状态码可根据实际接口文档修改
          else if (res.code === 401) { //跳转登录页
            router.clear() //清空历史页面
            //跳转到登录页
            router.replaceUrl({
              url: LOGINPAGEURL
            })
          }
          //其他情况接口异常
          else {
            showToast(response.message)
            reject(response);
          }
        }
        //请求失败
        else {
          showToast()
          reject(data)
        }

      }
      //请求失败
      else {
        showToast(err.message)
        reject(err)
      }
      // 取消订阅HTTP响应头事件
      httpRequest.off('headersReceive');
      // 当该请求使用完毕时,主动销毁该JavaScript Object。
      httpRequest.destroy();
    })
  })
}

//弹窗提示
const showToast = (message?: string) => {
  promptAction.showToast({
    message: message || '请求出错',
    duration: 2000
  })
}

说明:
(1)定义了接口前缀(域名+端口号?+通用匹配符?) BASEURL:可根据实际修改
(2)定义了登录页面路由 LOGINPAGEURL token失效跳转使用 :可根据实际修改
(3)函数request入参是个对象,包含如下属性

{
     url :请求连接
     method ?:请求方法
     data ?:请求数据
     headerContentType? :请求头发送的数据格式
  }

method不传默认get方式,headerContentType 不传默认application/json
当contetn-type为 “application/x-www-form-urlencoded” , data请求参数 自动处理成key&value形式

(4)请求结果返回Promise

  {
  code: number //状态码
  message: string //处理信息
  data: Object | null //返回数据
}

当code=200,promise返回接口数据
当code=401 token失效自动跳转登录页,
当code其他值表示请求失败,showToast显示接口message字段文字

(5)请求头默认添加token数据,从本地存储读取

2.token持久化存取

(1)entryability/EntryAbility.ets

import dataPreferences from '@ohos.data.preferences';
....
....
....
 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  
    globalThis.getPreferences = () => {
      let preferences: Promise<dataPreferences.Preferences> = dataPreferences.getPreferences(this.context, "appStore")
      return preferences
    }
  }

EntryAbility. onCreate周期函数内给全局变量globalThis添加getPreferences 属性方法,方便快速获取Preferences实例 ,添加到globalThis是为了后续页面开发或者工具类使用Preferences

(2)utils/index.ets 工具类

import dataPreferences from '@ohos.data.preferences';
//获取token
export const getToken: Function = async () => {

  try {
    let preferences: dataPreferences.Preferences = await globalThis.getPreferences()
    return preferences.getSync('token', '')
  }
  catch (e) {
  }
  return ''
}

//设置token并本地持久化存储
export const setToken: Function = async (value: string) => {
  try {
    let preferences: dataPreferences.Preferences = await globalThis.getPreferences()
    preferences.putSync('token', encodeURIComponent(value))
    await preferences.flush()
  }
  catch (e) {
    console.log(JSON.stringify(e), 'e')
  }
}

在工具类index.ets封装2个方法(getToken,setToken),分别为获取token值和设置token值,其中setToken在登录成功获取到token值时候调用存入本地持久化

3.页面使用

在这里插入图片描述
新建api文件夹、新建与页面同名的ets文件写入api定义
api/home.ets


import http from '@ohos.net.http'
import request from '../utils/request'

class params{
    storeId:string=''
}
//获取首页数据
export function getHomeData(data:params){
    return request({
        url:"/api/store/home",
        method:http.RequestMethod.POST, //不传默认GET
        data,
        headerContentType:'application/x-www-form-urlencoded' //不传默认application/json
    })
}



//其他接口
export function xxxxx(data:params){
    return request({
        url:"xxxxxxxx",
        data,
    })
}
......
......
......

页面引入
pages/Home.ets

import {getHomeData} from "../api/home"
@Entry
@Component
struct Home{
     aboutToAppear(): void {
         getHomeData({
          storeId:'17815455885'
        }).then(res=>{
           console.log(JSON.stringify(res),'接口返回数据')
        }
     }
}

运行结果
在这里插入图片描述

### 封装带有 TokenHTTP 请求 在 UniApp 应用程序中,通过合理封装 HTTP 请求可以有效简化开发流程并提高代码可维护性。对于需要附带认证令牌(Token)的请求来说,最佳实践之一是利用拦截器机制来自动化这一过程。 #### 使用拦截器处理 Token 为了确保每一次 API 调用都包含有效的 `Authorization` 头部信息,可以在全局范围内安装一个请求拦截器。此拦截器会在实际发出请求之前读取本地存储中的 token,并将其附加到请求头部[^2]。 ```javascript const TOKEN_KEY = 'token'; // 定义处理函数以向配置对象添加授权字段 const handleToken = (config) => { let token = uni.getStorageSync(TOKEN_KEY); if (token) { config.header['Authorization'] = `Bearer ${token}`; } }; // 注册请求拦截器 uni.addInterceptor('request', { invoke: function (config) { handleToken(config); return config; }, }); ``` 上述代码片段展示了如何创建一个名为 `handleToken` 的辅助函数,该函数负责从本地缓存获取 token 并更新传入的请求配置对象。接着,通过调用 `uni.addInterceptor()` 方法注册了一个针对所有 `request` 类型操作的事前处理器(`invoke`),它会自动执行 `handleToken` 来准备每次请求所需的认证信息。 #### 创建统一的 Request 工具类 除了设置通用的行为外,还可以进一步抽象出专门用于发起网络请求的服务模块。这不仅有助于保持业务逻辑清晰分离,而且使得后续扩展更加容易。下面是一个基于前面提到的方法构建的基础请求工具的例子: ```javascript import { addInterceptor } from '@dcloudio/uni-app'; import axios from 'axios'; class ApiService { constructor(baseURL) { this.instance = axios.create({ baseURL, timeout: 5000, headers: {} }); // 添加响应拦截器以便于集中管理错误情况下的重定向或其他动作 this.instance.interceptors.response.use( response => Promise.resolve(response), error => Promise.reject(error) ); // 如果项目依赖的是原生uni.request,则替换此处为对应的addInterceptor实现 addInterceptor('request', { invoke: config => { const token = uni.getStorageSync('token'); if (token && !config.headers.Authorization) { config.headers.Authorization = `Bearer ${token}`; } return config; } }); } async post(url, params={}) { try { const res = await this.instance.post(url, params); return res.data; } catch (err) { console.error(err.message || err); throw new Error('POST request failed.'); } } async get(url, query={}) { try { const res = await this.instance.get(url, {params:query}); return res.data; } catch (err) { console.error(err.message || err); throw new Error('GET request failed.'); } } } export default ApiService; ``` 这段代码定义了一个简单的 Axios 实例作为底层通信层,并为其设置了默认选项以及必要的拦截器支持。值得注意的是,这里还实现了两个常用的操作——`get` `post` ——它们能够接收 URL 及参数作为输入,并返回经过解析后的 JSON 数据结构。此外,当遇到异常状况时也会抛出自定义的消息提示给上层应用知道发生了什么问题[^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pixle0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值