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