npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dd-code/jt-axios-tools

v0.0.7

Published

| 项目 | 说明 | |----------|------| | **目标读者** | 前端架构 / 业务开发 | | **文档目的** | 描述 sdk-tools 请求层的设计:对外暴露 **createAxios**,得到类 axios 实例;Token 默认存 **localStorage**,登录失效以**响应体 data.code** 为准,在**成功回调**中执行刷新与重试;用户信息与 Token 通过 **instance.on('changed:userInfo' / '

Readme

Axios 请求层封装设计文档(sdk-tools)

文档说明

| 项目 | 说明 | |----------|------| | 目标读者 | 前端架构 / 业务开发 | | 文档目的 | 描述 sdk-tools 请求层的设计:对外暴露 createAxios,得到类 axios 实例;Token 默认存 localStorage,登录失效以响应体 data.code 为准,在成功回调中执行刷新与重试;用户信息与 Token 通过 instance.on('changed:userInfo' / 'changed:token', ...) 订阅。 | | 建议阅读顺序 | 概述 → 核心 API → 总体设计 → 接口配置与白名单 → 详细设计(认证与刷新逻辑)→ 使用指南 |


目录


1. 概述

1.1 设计目标

构建通用、可扩展的前端 HTTP 请求层:双 Token 认证、登录失效时单 Promise 刷新、刷新后拉取用户信息并事件通知;认证读写可选注入,不传则默认使用 localStorage,便于接入与替换。

1.2 适用场景

  • 中大型前后端分离项目
  • 需要统一认证、Token 刷新、且希望请求层与具体存储方式解耦的场景

1.3 核心约定

| 维度 | 说明 | |----------|------| | 登录失效判定 | 以响应体 data.code 为准(在响应拦截器的成功回调中判断),非 HTTP 状态码;error 回调不处理登录失效。 | | 认证存储 | createAxios 的 getToken/setToken 等为可选;不传则使用内置 localStorage(key:access_token、refresh_token)。 | | 刷新与重试 | 单 Promise 方案:多个请求同时命中登录失效时共用一个 refreshPromise,刷新完成后各自重试原请求;重试请求通过内部 innerInstance 发送,不再经过外层对实例的响应拦截(如 return res.data),始终返回完整 AxiosResponse。 | | 用户信息与 Token 事件 | 刷新成功后按接口配置拉取用户信息,通过 emit('changed:userInfo', userInfo) 派发;同时触发 emit('changed:token', accessToken)。项目通过 http.on('changed:userInfo', cb)http.on('changed:token', cb) 订阅。 |


2. 核心 API:createAxios

2.1 对项目暴露的用法

  • 创建const http = createAxios(config),得到与 axios 用法一致的实例。
  • 在实例上新增拦截器http.interceptors.request.use(...)http.interceptors.response.use(...),与使用 axios 实例相同。
  • 订阅用户信息更新http.on('changed:userInfo', (userInfo) => { ... })。内部在刷新 Token 成功后会按接口配置拉取用户信息并触发该事件。
  • 订阅 Token 更新http.on('changed:token', (token) => { ... })。刷新成功后会派发新的 accessToken。
  • 订阅登录失效http.on('unauthorized', (message?) => { ... })。当不走刷新或刷新失败时触发。
  • 发请求http.get(...)http.post(...) 等与 axios 一致。

2.2 createAxios 的 config(CreateAxiosConfig)

| 配置项 | 类型 | 必填 | 说明 | |--------|------|------|------| | baseURL、timeout、headers | 同 AxiosRequestConfig | 按需 | 与 axios 一致 | | getToken | () => string | null | 否 | 不传则使用 localStorageAuth.getToken(key: access_token) | | setToken | (token: string) => void | 否 | 不传则使用 localStorageAuth.setToken | | getRefreshToken | () => string | null | 否 | 不传则使用 localStorageAuth.getRefreshToken(key: refresh_token) | | setRefreshToken | (token: string) => void | 否 | 不传则使用 localStorageAuth.setRefreshToken | | clearAuth | () => void | 否 | 不传则使用 localStorageAuth.clearAuth | | onUnauthorized | (message?: string) => void | 否 | 登录失效时调用;也可用 .on('unauthorized', cb) | | loginExpiredCodes | (number | string)[] | 否 | 视为登录失效的业务 code(或 HTTP 状态码),默认 [401] | | noTokenPaths | PathPattern[] | 否 | 不带 Token 的 path,与请求层内置白名单合并 | | skipRefreshPaths | PathPattern[] | 否 | 登录失效时不走刷新、直接登出的 path,与内置白名单合并 |

PathPatternstring(前缀或精确匹配)或 RegExp

刷新 Token、获取用户信息的接口地址不在 config 里传,由 api.config.ts 统一配置,内部直接读取并调用;requestRefresh(instance, refreshToken)requestUserInfo(instance) 也在该文件中定义,接收当前 axios 实例。


3. 总体设计

3.1 架构分层

┌─────────────────────────────────┐
│           业务应用层             │  ← 调用 createAxios 得到的实例:get/post、interceptors、.on('changed:userInfo'/'changed:token'/'unauthorized')
├─────────────────────────────────┤
│           拦截器层               │  ← 请求拦截:按白名单注入 Token(attachTokenToConfig)
│                                 │  ← 响应拦截:成功回调中根据 data.code 判断登录失效 → 刷新 → 重试;error 回调透传
├─────────────────────────────────┤
│         请求核心层               │  ← createAxios:合并白名单、单 Promise 刷新、doRefresh、emit('changed:userInfo'/'changed:token'/'unauthorized')
├─────────────────────────────────┤
│   接口配置与认证默认实现         │  ← api.config(refreshToken/getUserInfo、requestRefresh/requestUserInfo)、storageAuth(localStorage)
└─────────────────────────────────┘

3.2 核心模块与职责

| 模块 | 职责简述 | |----------------|----------| | index.ts | createAxios 实现:创建 axios 实例,合并白名单,挂载请求/响应拦截器;认证未传时使用 localStorageAuth;响应成功回调内根据 data.code 判断登录失效并执行刷新+重试(重试使用不带响应拦截的 innerInstance,始终返回完整 AxiosResponse);error 回调仅 Promise.reject(error)。 | | api.config.ts | apiConfig(refreshToken、getUserInfo 的 method+path)、layerWhitelist(内置 noTokenPaths/skipRefreshPaths)、requestRefresh(instance, refreshToken)、requestUserInfo(instance)。 | | storageAuth.ts | createLocalStorageAuth(options)、localStorageAuth 单例;localStorage 读写,SSR 下安全降级。 | | types.ts | CreateAxiosConfig、CreateAxiosInstance、ApiConfig、WhitelistConfig、PathPattern、CustomAxiosRequestConfig、RefreshTokenResult、LocalStorageAuth 等。 | | matchPath.ts | path 与白名单(PathPattern[])匹配。 | | emitter.ts | 简单事件派发,供 .on/.off 使用。 |

3.3 请求生命周期(简要)

请求发出 → 请求拦截(白名单判断 → 非 noTokenPaths 则 getToken 并 attachTokenToConfig)→ 发请求
         → 响应成功 → 若 data.code 命中 loginExpiredCodes:
                        → 若 skipTokenRefresh 或 path 在 skipRefreshPaths → clearAuth + emit('unauthorized') + reject
                        → 否则:单 Promise 刷新 → setToken/setRefreshToken → emit('changed:token') → requestUserInfo → emit('changed:userInfo') → 通过 innerInstance 重试原请求并返回
                     → 否则 return response
         → 响应失败(error)→ 直接 Promise.reject(error),不处理登录失效

4. 接口配置与白名单

4.1 接口配置文件(api.config.ts)

目的:集中配置“刷新 Token”“获取用户信息”的 method + path,并封装 requestRefreshrequestUserInfo,由 createAxios 内部直接调用;项目在创建实例时无需再传这两个接口地址。

内容

  • apiConfig:refreshToken、getUserInfo 的 method 与 path(可按项目实际接口修改)。
  • layerWhitelist:请求层内置白名单 noTokenPaths、skipRefreshPaths(如登录、刷新、验证码等),与 createAxios 传入的 noTokenPaths、skipRefreshPaths 合并后生效。
  • requestRefresh(instance, refreshToken):调用刷新接口,需传入当前 axios 实例;内部使用 skipTokenRefresh: true,避免刷新接口失败时进入死循环。
  • requestUserInfo(instance):调用获取用户信息接口,需传入当前 axios 实例。

4.2 白名单配置

两类白名单(与 §2.2 对应):

| 白名单 | 含义 | 合并规则 | |--------|------|----------| | noTokenPaths | 这些 path 不携带 Token(不调用 attachTokenToConfig) | 最终列表 = layerWhitelist.noTokenPaths + config.noTokenPaths | | skipRefreshPaths | 这些 path 在登录失效时不尝试刷新,直接 clearAuth + emit('unauthorized') | 最终列表 = layerWhitelist.skipRefreshPaths + config.skipRefreshPaths |

单请求标记:某次请求若设置 config.skipTokenRefresh === true,则该请求命中登录失效时也不走刷新,直接 clearAuth + emit('unauthorized'),优先级与白名单一致。

匹配方式:path 支持字符串(前缀或精确)或 RegExp,由 matchPath 统一实现。


5. 详细设计

5.1 请求拦截器

  • 职责:按合并后的 noTokenPaths 判断当前请求 url 是否需带 Token;若不需要则跳过,否则 getToken() 并调用 attachTokenToConfig(req, token) 写入 x-access-token、Content-Type、Accept 等。
  • 实现:仅此一层,无责任链;项目可在实例上再追加 request/response 拦截器。

5.2 响应拦截器:成功回调中的登录失效与刷新

  • 职责:仅在成功回调中根据 response.data.code 判断是否登录失效;error 回调不处理登录失效,直接 Promise.reject(error)
  • 登录失效判定loginExpiredCodes 包含业务 code(或 HTTP 状态码);当前实现以 data.code 为主(成功响应里才有 data),通过 isLoginExpiredCode(code) 统一比较(支持 number/string)。
  • 流程
    1. !isLoginExpiredCode(response.data?.code) → 直接 return response
    2. 否则取 response.config 为 requestConfig,若 requestConfig.skipTokenRefresh === true 或 url 在 skipRefreshPaths → clearAuth()、fireUnauthorized(...)、Promise.reject(合成错误)
    3. 若没有 refreshToken → clearAuth()、fireUnauthorized()、reject。
    4. 否则:若尚无 refreshPromise,则 refreshPromise = doRefresh().finally(() => { refreshPromise = null; });然后 return refreshPromise.then(() => innerInstance.request(requestConfig)),即刷新完成后用原 config 通过 innerInstance 重试一次并返回结果(不会再经过外层对 instance 的响应拦截)。

5.3 doRefresh:刷新与用户信息与 Token 事件

  • 职责:用 getRefreshToken() 取 refreshToken,调用 requestRefresh(instance, refreshToken)(来自 api.config),拿到新 accessToken 后 setToken、可选 setRefreshToken;随后通过 emitter.emit('changed:token', accessToken) 通知 Token 变更;再调用 requestUserInfo(instance),成功后 emitter.emit('changed:userInfo', userInfo);任一步失败则 clearAuth、fireUnauthorized、reject。
  • 单 Promise:多个并发请求同时命中登录失效时,共用一个 refreshPromise,各自在 .then(() => innerInstance.request(自己的 config)) 中重试,不维护队列、不新建多余 Promise。

5.4 事件与 fireUnauthorized

  • fireUnauthorized(message?):先 emitter.emit('unauthorized', message),再调用 config.onUnauthorized?.(message)。
  • 用户信息事件:仅在 doRefresh 成功拉取用户信息后 emitter.emit('changed:userInfo', userInfo),项目通过 http.on('changed:userInfo', cb) 订阅。
  • Token 事件:在 doRefresh 成功刷新 Token 后 emitter.emit('changed:token', accessToken),项目通过 http.on('changed:token', cb) 订阅。

5.5 Token 存储默认实现(storageAuth)

  • localStorageAuth:单例,getToken/setToken/getRefreshToken/setRefreshToken/clearAuth 均读写 localStorage(默认 key:access_token、refresh_token);无 window 或 localStorage 时安全降级,不抛错。
  • createLocalStorageAuth(options):可自定义 accessTokenKey、refreshTokenKey,返回一组同上方法,便于需要不同 key 或多实例时使用。

5.6 错误回调

  • 响应拦截器 error 回调仅执行 Promise.reject(error),不根据 status 或 data.code 做登录失效判断,也不触发刷新。业务/网络错误由项目在自身追加的 response 拦截器或业务层处理。

6. 使用指南

6.1 最简用法(默认 localStorage)

import { createAxios } from 'sdk-tools';

const http = createAxios({
  baseURL: import.meta.env.VITE_API_URL ?? '/api',
  timeout: 15000,
  loginExpiredCodes: [401, 'OVERDUE'],
  noTokenPaths: ['/api/open/news'],
  skipRefreshPaths: ['/api/auth/logout'],
});

http.on('unauthorized', (message) => {
  console.warn('登录失效', message);
  // router.push('/login');
});

http.on('changed:userInfo', (userInfo) => {
  console.log('用户信息更新', userInfo);
  // 存 store
});

const data = await http.get('/users');

6.2 自定义 Token 存储(如 Pinia/Vuex)

const http = createAxios({
  baseURL: '/api',
  getToken: () => useUserStore().token,
  setToken: (t) => useUserStore().setToken(t),
  getRefreshToken: () => useUserStore().refreshToken,
  setRefreshToken: (t) => useUserStore().setRefreshToken(t),
  clearAuth: () => useUserStore().logout(),
});

6.3 使用 createLocalStorageAuth 自定义 key

import { createAxios, createLocalStorageAuth } from 'sdk-tools';

const auth = createLocalStorageAuth({
  accessTokenKey: 'my_access_token',
  refreshTokenKey: 'my_refresh_token',
});

const http = createAxios({ baseURL: '/api', ...auth });

6.4 单请求不参与刷新

http.post('/api/auth/logout', {}, { skipTokenRefresh: true });

6.5 修改刷新/用户信息接口

api.config.ts 中修改 apiConfig 的 path(或 requestRefresh/requestUserInfo 内部请求的 url),与项目后端约定一致即可;白名单 layerWhitelist 可一并按需调整。


附录

A. 非浏览器环境

SSR/Node 下无 window.localStorage 时,storageAuth 读写会安全降级(返回 null / 不写入),不抛错;若需在服务端发请求并带 Token,建议传入自定义的 getToken/setToken 等实现。

B. 推荐目录结构(本项目)

src/
  utils/requestHttp/
    index.ts         # createAxios、请求/响应拦截器、attachTokenToConfig、事件挂载
    types.ts         # CreateAxiosConfig、CreateAxiosInstance、ApiConfig、WhitelistConfig 等
    api.config.ts    # apiConfig、layerWhitelist、requestRefresh、requestUserInfo
    storageAuth.ts   # createLocalStorageAuth、localStorageAuth
    matchPath.ts     # 白名单匹配
    emitter.ts       # 事件派发
  api.ts             # 示例:创建 http 实例并导出
  index.ts           # 包入口,统一导出

C. 设计取舍摘要

| 决策 | 原因 | |------|------| | 登录失效以 data.code 为准、且在成功回调中处理 | 与当前项目约定一致(接口常返回 200 + body.code),避免依赖 HTTP 状态码;error 回调仅透传,逻辑清晰。 | | 认证回调可选、默认 localStorage | 降低接入成本;需要时可注入 getToken/setToken 等或使用 createLocalStorageAuth 自定义 key。 | | 单 Promise 刷新 | 多个 401 共用一个 refreshPromise,各自 then 后重试,实现简单、无队列维护。 | | 刷新/用户信息接口在 api.config + requestRefresh(instance) | 接口集中配置;刷新与拉用户信息需用当前 instance(baseURL、拦截器一致),故将 requestRefresh/requestUserInfo 收口到 api.config 并传入 instance。 | | 白名单双配置 | 是否带 Token、是否走刷新由白名单控制;请求层内置与项目传入合并,单请求 skipTokenRefresh 优先。 |