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

avatar
作者
猴君
阅读量:0

系列文章目录

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)


文章目录


前言

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),'接口返回数据')         }      } } 

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!