系列文章目录
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),'接口返回数据') } } }
运行结果