更新记录
2.0.0(2025-09-26) 下载此版本
新增无感刷新token,优化示例项目,下载即会使用
1.1.2(2025-06-24) 下载此版本
优化method
1.1.1(2025-04-08) 下载此版本
去掉loading,添加示例项目
查看更多平台兼容性
uni-app x(4.76)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | - | 5.0 | √ | √ | √ |
yq-request-uts
uniappx 封装 request 支持 Promise 和 async/await 写法,支持 GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH 方法,内置无感刷新 Token 机制。
主要特性
- ✅ 支持 Promise 和 async/await
- ✅ 支持 GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH 方法
- ✅ 内置无感刷新 Token 机制
- ✅ 自定义请求头,更加灵活
- ✅ 自定义业务层错误处理函数
- ✅ 支持外部接口调用
- ✅ 同时支持 HTTP 状态码 401 和业务错误码 401 触发刷新
安装和使用
1. App.uvue 配置
<script lang="uts">
export default {
globalData: {
baseUrl: '',
devUrl: 'https://restapi.amap.com',
proUrl: 'http://pro.com',
// Token 刷新相关
isRefreshingToken: false,
requestQueue: [] as UTSJSONObject[],
// Token 存储
accessTokenValue: '',
refreshTokenValue: '',
httpSuccessCodes: [200, 201, 202, 203, 204, 205, 206],
http401() {
console.log('HTTP 401 错误');
},
systemError(res : UTSJSONObject) : any {
// 返回三种可能的值:
// - false: 没有错误
// - 'TOKEN_EXPIRED': Token 过期,需要刷新
// - true: 其他业务错误
// 检查业务错误码 401(Token 过期)
if (res['code'] == 401) {
return 'TOKEN_EXPIRED';
}
// 检查 HTTP 状态码(某些接口可能在 data 中返回状态码)
else if (res['status'] == 401) {
return 'TOKEN_EXPIRED';
}
// 其他常见的 Token 过期标识
else if (res['error'] == 'invalid_token' || res['error'] == 'access_denied') {
return 'TOKEN_EXPIRED';
}
// 业务错误
else if (res['code'] == 500) {
uni.showToast({
title: res['message'] as string,
icon: 'none',
duration: 2500
});
return true;
}
// 没有错误
else {
return false;
}
},
setHeader(url : string, auth : boolean, formData : boolean) : UTSJSONObject {
let header = {
'content-type': 'application/json'
};
if (auth && this.accessTokenValue != "") {
header['Authorization'] = 'Bearer ' + this.accessTokenValue;
}
if (formData) {
header['content-type'] = 'application/x-www-form-urlencoded';
}
return header;
},
/**
* 刷新 Token 的方法 - 真实请求
*/
refreshToken() : void {
console.log('开始刷新 Token');
if (this.refreshTokenValue == "") {
console.log('refreshToken 为空,刷新失败');
this.handleRefreshFailure();
return;
}
// 真实的刷新 Token 请求
uni.request<UTSJSONObject>({
url: this.baseUrl + '/api/auth/refresh', // 请替换为您的实际刷新接口
method: 'POST',
header: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + this.refreshTokenValue
},
data: {
refresh_token: this.refreshTokenValue
},
success: (res : RequestSuccess<UTSJSONObject>) => {
console.log('刷新Token请求成功:', res);
// 检查HTTP状态码
if (res.statusCode == 200) {
const data = res.data as UTSJSONObject;
// 检查业务错误
const errorCheck = this.systemError(data);
if (errorCheck === false) {
// 刷新成功,获取新的token
const newAccessToken = data['access_token'] as string;
const newRefreshToken = data['refresh_token'] as string;
const finalAccessToken = newAccessToken != null ? newAccessToken : "";
const finalRefreshToken = newRefreshToken != null ? newRefreshToken : this.refreshTokenValue;
if (finalAccessToken != "") {
console.log('Token刷新成功');
this.handleRefreshSuccess(finalAccessToken, finalRefreshToken);
} else {
console.log('刷新接口返回的access_token为空');
this.handleRefreshFailure();
}
} else if (errorCheck === 'TOKEN_EXPIRED') {
console.log('刷新Token已过期');
this.handleRefreshFailure();
} else {
console.log('刷新Token接口返回业务错误');
this.handleRefreshFailure();
}
} else {
console.log('刷新Token请求失败,状态码:', res.statusCode);
this.handleRefreshFailure();
}
},
fail: (error : any) => {
console.log('刷新Token网络请求失败:', error);
this.handleRefreshFailure();
}
});
},
/**
* Token 刷新成功处理
*/
handleRefreshSuccess(accessToken : string, refreshToken : string) : void {
console.log('处理刷新成功');
this.accessTokenValue = accessToken;
if (refreshToken != "") {
this.refreshTokenValue = refreshToken;
uni.setStorageSync('refreshToken', refreshToken);
}
uni.setStorageSync('accessToken', accessToken);
console.log('当前请求队列长度:', this.requestQueue.length);
this.executeQueuedRequests();
this.isRefreshingToken = false;
console.log('Token 刷新流程完成');
},
/**
* Token 刷新失败处理
*/
handleRefreshFailure() : void {
console.log('处理刷新失败');
this.accessTokenValue = "";
this.refreshTokenValue = "";
uni.removeStorageSync('accessToken');
uni.removeStorageSync('refreshToken');
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
});
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
});
}, 1500);
this.rejectQueuedRequests();
this.isRefreshingToken = false;
},
/**
* 执行队列中的请求
*/
executeQueuedRequests() : void {
console.log('执行队列中的请求,数量:', this.requestQueue.length);
const queue = this.requestQueue;
this.requestQueue = [];
for (let i = 0; i < queue.length; i++) {
const item = queue[i];
try {
console.log('执行第', i + 1, '个重试请求');
const executeFunc = item['execute'] as () => void;
executeFunc();
} catch (error) {
console.error('重试请求失败:' + error);
}
}
},
/**
* 拒绝队列中的请求
*/
rejectQueuedRequests() : void {
console.log('拒绝队列中的请求,数量:', this.requestQueue.length);
const queue = this.requestQueue;
this.requestQueue = [];
for (let i = 0; i < queue.length; i++) {
const item = queue[i];
try {
const rejectFunc = item['reject'] as (reason : string) => void;
rejectFunc('Token刷新失败,请重新登录');
} catch (error) {
console.error('拒绝请求失败:' + error);
}
}
},
/**
* 初始化 Token
*/
initToken() : void {
const accessToken = uni.getStorageSync('accessToken') as string;
const refreshToken = uni.getStorageSync('refreshToken') as string;
if (accessToken != "") {
this.accessTokenValue = accessToken;
}
if (refreshToken != "") {
this.refreshTokenValue = refreshToken;
}
console.log('Token 初始化完成');
}
},
onLaunch: function () {
console.log('App Launch');
this.setBaseUrl();
this.globalData.initToken();
},
methods: {
setBaseUrl() : void {
if (process.env.NODE_ENV == 'development') {
// #ifdef H5
this.globalData.baseUrl = location.origin;
// #endif
// #ifndef H5
this.globalData.baseUrl = this.globalData.devUrl;
// #endif
} else {
this.globalData.baseUrl = this.globalData.proUrl;
}
console.log('Base URL 设置为:', this.globalData.baseUrl);
}
}
}
</script>
2. H5 跨域代理配置(可选)
在项目根目录创建 vite.config.ts:
import { defineConfig } from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
export default defineConfig({
plugins: [
uni(),
],
server: {
proxy: {
'/api/': {
target: 'http://192.168.1.110:8000',
changeOrigin: true,
}
},
},
});
3. 接口文件配置
在项目根目录创建 apis 文件夹(注意:不要使用 api 作为文件夹名,避免 H5 代理冲突),创建 index.uts:
import { request, IParams } from '@/uni_modules/yq-request-uts/utssdk/index';
/**
* 首页商品列表接口 - POST 表单形式
*/
export const goodsApi = (data: UTSJSONObject): Promise<UTSJSONObject> => {
const requestData: IParams = {
method: 'post',
api: '/api/goodsPage',
header: {'X-Custom-Header': 'custom-value'}, // 支持自定义请求头
data,
formData: true, // 设置为表单格式
};
return request(requestData);
};
/**
* 首页商品列表接口 - POST JSON形式
*/
export const goodsJsonApi = (data: UTSJSONObject): Promise<UTSJSONObject> => {
const requestData: IParams = {
method: 'post',
api: '/api/goodsPageJson',
data, // 默认 JSON 格式
};
return request(requestData);
};
/**
* 首页公示公告列表接口 - GET
*/
export const newsApi = (data: UTSJSONObject): Promise<UTSJSONObject> => {
const requestData: IParams = {
method: 'get',
api: '/api/newsPage',
data,
};
return request(requestData);
};
/**
* 更新数据接口 - PUT
*/
export const updateApi = (id: string, data: UTSJSONObject): Promise<UTSJSONObject> => {
const requestData: IParams = {
method: 'put',
api: `/api/update/${id}`,
data,
};
return request(requestData);
};
/**
* 删除数据接口 - DELETE
*/
export const deleteApi = (id: string): Promise<UTSJSONObject> => {
const requestData: IParams = {
method: 'delete',
api: `/api/delete/${id}`,
};
return request(requestData);
};
/**
* 外部接口 - 不需要鉴权(auth设置为false)
*/
export const baiduApi = (data: UTSJSONObject): Promise<UTSJSONObject> => {
const requestData: IParams = {
url: getApp().globalData.baiduUrl, // 使用外部 URL
api: '/api/listIndex',
auth: false, // 不需要鉴权
data,
};
return request(requestData);
};
4. 在页面中使用
<template>
<view>
<view>
<text>API 测试</text>
<button @click="getWeather">get接口</button>
</view>
<view>
<text style="font-size: 20px;">响应数据:</text>
<view>
<view>对象形式:</view>
<text>Status: {{ data.status }}</text>
<text>Info: {{ data.info }}</text>
<text>Infocode: {{ data.infocode }}</text>
<view style="margin-top: 20px;">列表形式:</view>
<view v-for="(item, index) in data.lives" :key="index">
<text >地点:{{ item.province }}</text>
<text>天气:{{ item.weather }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { weatherApi, } from '@/apis/index.uts';
// 定义全局对象类型type
type DataType = {
status: string
info: string
infocode: string
lives:UTSJSONObject[]
}
const data = reactive({
status:"",
info:"",
infocode:"",
lives:[]
} as DataType)
/**
* 获取天气数据
*/
const getWeather = async () => {
try {
const res = await weatherApi({});
// 对象赋值
data.status = res.status as string;
data.info = res.info as string;
data.infocode = res.infocode as string;
// 列表赋值
data.lives = res.lives as UTSJSONObject[]
console.log('天气数据:', res);
} catch (error) {
console.error('获取天气失败:', error);
}
};
</script>
无感刷新 Token 机制
触发条件
以下情况会自动触发无感刷新 Token:
- HTTP 状态码 401:服务器返回 401 状态码
- 业务错误码 401:响应数据中
code字段为 401 - 其他 Token 过期标识:如
status=401、error=invalid_token等
工作原理
- 当检测到 Token 过期时,将当前请求加入等待队列
- 触发 Token 刷新流程
- 刷新成功后,自动重试队列中的所有请求
- 刷新失败时,跳转到登录页面
参数说明
IParams 接口参数
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| url | string | 否 | globalData.baseUrl | 请求基础URL |
| api | string | 是 | - | 接口路径 |
| method | string | 否 | 'get' | 请求方法(小写) |
| header | UTSJSONObject | 否 | - | 自定义请求头 |
| data | any | 否 | null | 请求数据 |
| formData | boolean | 否 | false | 是否表单格式 |
| timeout | number | 否 | 20000 | 请求超时时间 |
| withCredentials | boolean | 否 | false | 是否携带凭证 |
| auth | boolean | 否 | true | 是否需要鉴权 |
注意事项
- 方法名使用小写:如
'get'、'post',插件内部会自动转换为大写 - Token 存储:使用
accessTokenValue和refreshTokenValue存储 Token - H5 代理:接口文件夹名不要使用
api,建议使用apis - 错误处理:业务错误码 401 会触发无感刷新,其他错误码按业务逻辑处理
更新日志
v2.0.0
- 新增无感刷新 Token 机制
- 同时支持 HTTP 401 和业务错误码 401
- 方法名改为小写传参
- 修复属性名冲突问题
- 优化错误处理逻辑
v1.0.0
- 基础请求封装
- 支持 Promise 和 async/await
- 自定义请求头和错误处理

收藏人数:
下载插件并导入HBuilderX
下载示例项目ZIP
赞赏(0)
下载 9490
赞赏 11
下载 10666645
赞赏 1797
赞赏
京公网安备:11010802035340号