更新记录
1.0.2(2026-07-19)
修复
- 修正
TokenRefreshManager.getOrCreate方法注释,补全了缺失的@param globalConfig参数说明,明确其用途为传递全局配置(如apiFieldMap字段映射),使 API 文档与实现保持一致。
优化
- 增强代码可读性,完善内部 JSDoc 注释。
1.0.1(2026-07-19)
优化
- token 刷新条件 code === 409 现支持动态字段名
- 根据 globalConfig.apiFieldMap.code 自动适配后端返回的字段名(默认 'code')
- 适配不同后端字段命名差异,如 code、status、errcode 等
1.0.0(2026-07-18)
新增
- Token 无感刷新能力
- 并发请求排队机制:刷新过程中后续请求自动排队,不重复刷新
- 刷新成功后自动重试所有等待的请求
- 自定义刷新策略:支持通过
shouldRefresh自定义触发条件 - 自定义请求头:支持通过
headerKey和headerValue自定义 Token 注入方式 - 多实例灵活共享:支持通过
shareKey控制多个Request实例共享或独立刷新,按需切换 - 支持普通请求(
request)和文件上传(upload)两种模式
平台兼容性
uni-app(3.8.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
uni-app x(3.8.0)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ |
mh-token-refresh
Token 无感刷新插件,Token 过期时自动刷新、并发请求排队不重复刷新、刷新后静默重试所有请求,全程用户无感知。
本插件是 mh-request 的配套插件,需配合使用。
特性
- 🔄 无感刷新:Token 过期自动刷新,用户无感知
- 🚦 并发排队:多个请求同时触发刷新时,仅执行一次,其余排队等待刷新完成后自动重试
- 🔁 静默重试:刷新成功后自动重试所有等待的请求
- 🔗 灵活共享:多
Request实例可共享或独立刷新,按需切换 - 🎯 灵活配置:自定义刷新策略、请求头、Token 存储方式
- 🔌 即插即用:作为
mh-request插件,一行代码集成
快速开始
import { Request } from '@/uni_modules/mh-request'
import { tokenPlugin } from '@/uni_modules/mh-token-refresh'
const request = new Request({
baseURL: 'https://api.example.com',
silentCodes: [409],
plugins: [
{
plugin: tokenPlugin,
config: {
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
const refreshToken = uni.getStorageSync('refreshToken')
const res = await uni.request({
url: '/auth/refresh',
method: 'POST',
data: { refreshToken }
})
return { token: res.data.token }
}
}
}
]
})
配置项
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| getToken | () => string | null |
✅ | - | 获取当前 Token |
| setToken | (token: string) => void |
✅ | - | 设置新 Token |
| refreshToken | () => Promise<{ token: string }> |
✅ | - | 刷新 Token 的异步方法 |
| headerKey | string |
❌ | 'ba-user-token' |
请求头中的 Token Key |
| headerValue | (token: string) => string |
❌ | (token) => token |
请求头 Value 格式化函数 |
| shouldRefresh | (data: Record<string, any> | null) => boolean |
❌ | (data) => data?.code === 409 |
自定义刷新策略 |
| shareKey | string |
❌ | - | 共享标识,相同标识的 Request 实例共用同一个 TokenRefreshManager |
工作原理
并发排队机制
当多个请求同时返回 409(Token 过期)时:
请求A ──┐
请求B ──┼── 同时触发刷新 ──→ 仅执行 1 次刷新 ──→ 刷新成功 ──→ 重试 A、B、C
请求C ──┘
核心流程:
- 请求返回响应后,根据
shouldRefresh判断是否需要刷新 Token - 如果需要刷新且当前没有刷新任务,触发刷新
- 如果正在刷新中,当前请求进入等待队列,等待刷新完成后自动重试,不重复触发刷新
- 刷新成功后,更新 Token 并重试当前请求及队列中所有请求
- 刷新失败则清空队列并重置 Token
多实例模式
| 配置 | 行为 | 适用场景 |
|---|---|---|
不传 shareKey |
每次新建 TokenRefreshManager |
不同模块使用不同的 Token(如主应用 + 第三方服务) |
传入相同 shareKey |
复用同一个 TokenRefreshManager |
同一套登录体系下的多个 Request 实例(如用户模块 + 订单模块) |
高级用法
多实例共享刷新
import { Request } from '@/uni_modules/mh-request'
import { tokenPlugin } from '@/uni_modules/mh-token-refresh'
// 用户模块请求
const userRequest = new Request({
baseURL: 'https://api.example.com/user',
plugins: [{
plugin: tokenPlugin,
config: {
shareKey: 'main', // 共享标识
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
const res = await uni.request({ url: '/auth/refresh' })
return { token: res.data.token }
}
}
}]
})
// 订单模块请求
const orderRequest = new Request({
baseURL: 'https://api.example.com/order',
plugins: [{
plugin: tokenPlugin,
config: {
shareKey: 'main', // 相同标识,复用 TokenRefreshManager
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
const res = await uni.request({ url: '/auth/refresh' })
return { token: res.data.token }
}
}
}]
})
// userRequest 触发刷新时,orderRequest 的请求自动排队等待
// 刷新完成后,两者一起重试
多套 Token 独立刷新(不同标识)
// 主应用 Token
const mainRequest = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
shareKey: 'main',
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => { /* 刷新主应用 token */ }
}
}]
})
// 第三方服务 Token(独立刷新)
const thirdPartyRequest = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
shareKey: 'third_party', // 不同标识,独立刷新
getToken: () => uni.getStorageSync('third_party_token'),
setToken: (token) => uni.setStorageSync('third_party_token', token),
refreshToken: async () => { /* 刷新第三方 token */ }
}
}]
})
独立实例(不传 shareKey)
// 不传 shareKey,每个 Request 实例拥有独立的 TokenRefreshManager
const requestA = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
// 没有 shareKey → 新建独立实例
getToken: () => uni.getStorageSync('token_a'),
setToken: (token) => uni.setStorageSync('token_a', token),
refreshToken: async () => { /* 刷新 A */ }
}
}]
})
const requestB = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
// 没有 shareKey → 新建独立实例
getToken: () => uni.getStorageSync('token_b'),
setToken: (token) => uni.setStorageSync('token_b', token),
refreshToken: async () => { /* 刷新 B */ }
}
}]
})
// requestA 和 requestB 各自维护刷新状态,互不影响
自定义刷新策略
const request = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
const res = await uni.request({ url: '/auth/refresh' })
return { token: res.data.token }
},
shouldRefresh: (data) => {
return [401, 403, 409].includes(data?.code)
}
}
}]
})
使用 Bearer Token
const request = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
const res = await uni.request({ url: '/auth/refresh' })
return { token: res.data.token }
},
headerKey: 'Authorization',
headerValue: (token) => `Bearer ${token}`
}
}]
})
刷新失败跳转登录
const request = new Request({
plugins: [{
plugin: tokenPlugin,
config: {
getToken: () => uni.getStorageSync('token'),
setToken: (token) => uni.setStorageSync('token', token),
refreshToken: async () => {
try {
const refreshToken = uni.getStorageSync('refreshToken')
const res = await uni.request({
url: '/auth/refresh',
method: 'POST',
data: { refreshToken }
})
return { token: res.data.token }
} catch {
// 刷新失败,跳转登录页
uni.reLaunch({ url: '/pages/login/index' })
throw new Error('Token 刷新失败')
}
}
}
}]
})
注意事项
配合 silentCodes
建议将触发刷新的状态码加入 silentCodes,避免同时弹出错误提示:
const request = new Request({
silentCodes: [409],
plugins: [{ plugin: tokenPlugin, config: { ... } }]
})
刷新接口避免循环
刷新接口使用独立的 Request 实例或直接使用 uni.request:
refreshToken: async () => {
return new Promise((resolve, reject) => {
uni.request({
url: '/auth/refresh',
method: 'POST',
success: (res) => resolve({ token: res.data.token }),
fail: reject
})
})
}
请求模式支持
- ✅ 普通请求(
request) - ✅ 文件上传(
upload) - ❌ 文件下载(
download)不处理

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 7
赞赏 0
下载 12441215
赞赏 1934
赞赏
京公网安备:11010802035340号