# axios-request
**Repository Path**: siriussupreme/axios-request
## Basic Information
- **Project Name**: axios-request
- **Description**: axios-request
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-04-26
- **Last Updated**: 2026-05-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# axios-request
基于 axios 的增强请求库,提供 Token 自动刷新、防止重复提交、请求取消、失败重试、FormData 转换等功能。
## 特性
- ✅ **零成本接入**:直接继承 axios 配置,无学习成本
- 🔄 **Token 自动刷新**:自动检测 token 过期并刷新,刷新期间请求自动排队
- 🚫 **防止重复提交**:防止表单重复提交,可配置时间窗口
- ❌ **请求自动取消**:搜索类高频请求,自动取消上次请求
- 🔁 **失败自动重试**:支持重试次数、延迟策略(含指数退避)
- 📦 **三种打包格式**:ESM / CommonJS / UMD
- 📘 **完整 TypeScript 支持**:类型提示覆盖所有配置
- 🛠️ **FormData 工具**:智能转换各种数据类型
---
## 安装
```bash
# npm
npm install axios-request axios
# yarn
yarn add axios-request axios
# pnpm
pnpm add axios-request axios
```
### 浏览器引入(UMD)
```html
```
---
## 快速开始
### 基础用法(5 分钟上手)
```typescript
import { AxiosRequest } from 'axios-request';
// 1. 创建实例(直接继承 axios 配置)
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
timeout: 10000,
});
// 2. 使用方式与 axios 完全一致
const users = await client.get('/users'); // GET
const user = await client.post('/users', { name: '张三' }); // POST
const updated = await client.put('/users/1', { age: 30 }); // PUT
await client.delete('/users/1'); // DELETE
```
### 完整功能示例
```typescript
import { AxiosRequest } from 'axios-request';
import axios from 'axios';
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
timeout: 10000,
// Token 自动刷新
token: {
isTokenExpired: (error) => error.response?.status === 401,
refreshToken: async () => {
const res = await axios.post('/auth/refresh', {
refreshToken: localStorage.getItem('refresh_token'),
});
return res.data;
},
getAccessToken: () => localStorage.getItem('access_token'),
setTokens: (result) => {
localStorage.setItem('access_token', result.accessToken);
if (result.refreshToken) {
localStorage.setItem('refresh_token', result.refreshToken);
}
},
},
// 防重复提交(默认开启)
dedupe: {
enabled: true,
timeWindow: 1000, // 1秒内重复请求会被合并
},
// 请求取消(默认开启,仅 GET)
cancel: {
enabled: true,
},
// 失败重试
retry: {
enabled: true,
maxRetries: 3,
retryDelay: 100,
exponentialBackoff: true,
},
});
// 使用
const data = await client.get('/users');
```
---
## 功能详解
### 1. Token 自动刷新
#### 基本配置
```typescript
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
token: {
// 【必须】判断 token 是否失效
isTokenExpired: (error) => error.response?.status === 401,
// 【必须】刷新 token
refreshToken: async (error) => {
const res = await axios.post('/auth/refresh', {
refreshToken: localStorage.getItem('refresh_token'),
});
return res.data; // 返回 { accessToken, refreshToken? }
},
// 【必须】获取当前 token
getAccessToken: () => localStorage.getItem('access_token'),
// 【必须】保存新 token
setTokens: (result) => {
localStorage.setItem('access_token', result.accessToken);
if (result.refreshToken) {
localStorage.setItem('refresh_token', result.refreshToken);
}
},
// 【可选】刷新失败回调
onRefreshFailed: (reason, error) => {
console.error('Token 刷新失败:', reason);
window.location.href = '/login';
},
// 【可选】自定义 token 赋值方式
// 默认:Authorization: Bearer {token}
// 可以自定义 header 字段名或赋值格式
setAuthorization: (config, token) => {
config.headers['X-Access-Token'] = `Bearer ${token}`;
},
},
});
```
#### 工作原理
```
请求 → Token 失效(401) → 刷新 Token → 重试请求 → 成功
↓
刷新失败 → 拒绝所有等待中的请求 → 跳转登录页
```
#### 单个请求禁用 Token
```typescript
// 公开接口不需要 token
await client.get('/public/news', {
token: false,
});
```
#### 白名单 URL(不需要 Token 的请求)
支持通过白名单配置来标记不需要 Token 的请求,提供两种方式:
**方式一:实例级白名单(推荐)**
```typescript
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
token: {
// ...其他配置
whitelistUrls: [
'/public/**', // 公开接口
'/auth/login', // 登录接口
/^\/static\//, // 正则匹配静态资源
],
},
});
// 以下请求自动跳过 Token 注入
await client.get('/public/news'); // 匹配 /public/**
await client.get('/auth/login'); // 匹配 /auth/login
await client.get('/static/logo.png'); // 匹配正则 /^\/static\//
```
**方式二:单个请求禁用**
```typescript
// 单个请求禁用 Token
await client.get('/public/news', {
token: false,
});
// 单个请求配置白名单
await client.get('/public/news', {
token: {
whitelistUrls: ['/public/**'],
},
});
```
#### 单个请求使用不同的 Token
```typescript
// 使用临时的 token(不影响实例配置)
await client.post('/admin/users', data, {
token: {
isTokenExpired: () => false,
refreshToken: async () => ({ accessToken: 'temp-token' }),
getAccessToken: () => 'temp-token',
setTokens: () => {},
},
});
```
#### 单个请求禁用 Token
```typescript
// 公开接口不需要 token
await client.get('/public/news', {
token: false,
});
```
#### 自定义 Token 赋值方式
默认情况下,token 会以 `Authorization: Bearer {token}` 的形式添加到请求头中。如果你的 API 使用其他格式,可以自定义:
```typescript
const client = new AxiosRequest({
token: {
// ...其他配置
// 自定义 header 字段名
setAuthorization: (config, token) => {
config.headers['X-Token'] = token;
},
// 使用其他赋值格式
setAuthorization: (config, token) => {
config.headers['Authorization'] = `token ${token}`;
},
// 同时设置多个 header
setAuthorization: (config, token) => {
config.headers['X-Access-Token'] = token;
config.headers['X-Token-Type'] = 'Bearer';
},
},
});
```
---
### 2. 防止重复提交
#### 场景说明
防止用户快速点击提交按钮,导致重复请求。
```typescript
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
dedupe: {
enabled: true, // 默认 true
timeWindow: 1000, // 默认 1000ms
methods: ['POST', 'PUT', 'PATCH', 'DELETE'], // 默认值
},
});
// 用户快速点击提交
await client.post('/users', { name: '张三' }); // 发起请求 A
await client.post('/users', { name: '张三' }); // 复用请求 A 的 Promise
await client.post('/users', { name: '张三' }); // 复用请求 A 的 Promise
// 实际只发起 1 次请求
```
#### 自定义 Key 生成(字符串模板)
```typescript
const client = new AxiosRequest({
dedupe: {
enabled: true,
// 字符串模板:用冒号分隔字段路径
generateKey: 'method:url:data.userId', // 等价于函数
// 特殊值:只用 url 作为 key
generateKey: 'only-url',
},
});
```
#### 自定义 Key 生成(函数)
```typescript
const client = new AxiosRequest({
dedupe: {
enabled: true,
generateKey: (config) => {
return `${config.method}:${config.url}:${config.data?.id}`;
},
},
});
```
#### 简写方式
```typescript
// 禁用防重复提交
dedupe: false
// 启用(使用默认配置)
dedupe: true
// 完整配置
dedupe: {
enabled: true,
timeWindow: 2000,
}
// 数组简写 - 直接作为 methods(自动转大写)
dedupe: ['post', 'put', 'patch'] // 自动转为 { enabled: true, methods: ['POST', 'PUT', 'PATCH'] }
dedupe: ['get', 'Get', 'GET'] // 混合大小写会被归一化为大写
```
#### 单个请求级别配置
```typescript
// 这次请求禁用防重复
await client.post('/users', data, {
dedupe: false,
});
// 这次请求用更长的防重时间
await client.post('/users', data, {
dedupe: {
enabled: true,
timeWindow: 3000,
},
});
```
---
### 3. 请求自动取消
#### 场景说明
搜索框输入时,自动取消上一次请求,确保只显示最新结果。
```typescript
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
cancel: {
enabled: true, // 默认 true
methods: ['GET'], // 默认值
},
});
// 用户在搜索框输入
client.get('/search', { params: { q: 'a' } }); // 请求 A
client.get('/search', { params: { q: 'ab' } }); // 自动取消 A,请求 B
client.get('/search', { params: { q: 'abc' } }); // 自动取消 B,请求 C
```
#### 支持 POST 搜索
```typescript
const client = new AxiosRequest({
cancel: {
enabled: true,
methods: ['GET', 'POST'], // 支持 POST
},
});
client.post('/search', { keyword: 'js' }); // 请求 A
client.post('/search', { keyword: 'javascript' }); // 自动取消 A
client.post('/search', { keyword: 'javascript react' }); // 自动取消 B
```
#### 自定义 Key 生成
```typescript
const client = new AxiosRequest({
cancel: {
enabled: true,
generateKey: 'only-url', // 只用 url 作为 key
// 或
generateKey: (config) => `${config.url}:${config.params?.category}`,
},
});
```
#### 单个请求禁用
```typescript
await client.get('/static/data', {
cancel: false, // 不取消上一次请求
});
```
#### 简写方式
```typescript
// 禁用
cancel: false
// 启用(使用默认配置)
cancel: true
// 数组简写 - 直接作为 methods(自动转大写)
cancel: ['get', 'Get', 'POST'] // 自动转为 { enabled: true, methods: ['GET', 'GET', 'POST'] }
```
---
### 4. 请求失败重试
#### 基础配置
```typescript
const client = new AxiosRequest({
baseURL: 'https://api.example.com',
retry: {
enabled: true, // 默认 false
maxRetries: 3, // 默认 3
retryDelay: 100, // 默认 100ms
exponentialBackoff: false,
},
});
```
#### 指数退避(推荐)
```typescript
const client = new AxiosRequest({
retry: {
enabled: true,
maxRetries: 5,
retryDelay: 100,
exponentialBackoff: true, // 100ms → 200ms → 400ms → 800ms → 1600ms
},
});
```
#### 自定义重试条件
```typescript
const client = new AxiosRequest({
retry: {
enabled: true,
maxRetries: 3,
// 只重试网络错误和 5xx 错误
retryCondition: (error, retryCount) => {
// 不重试 4xx 客户端错误
if (error.response?.status >= 400 && error.response?.status < 500) {
return false;
}
return true;
},
},
});
```
#### 简写方式
```typescript
// 数字表示:启用重试,maxRetries = 3
retry: 3
// 布尔表示
retry: false // 禁用
retry: true // 启用,使用默认配置
// 对象表示
retry: {
enabled: true,
maxRetries: 5,
retryDelay: 200,
}
```
#### 单个请求配置
```typescript
// 重试 5 次
await client.get('/unreliable', {
retry: 5,
});
// 这次不用重试
await client.get('/fast-fail', {
retry: false,
});
```
---
### 5. Content-Type 配置
#### 预设值
```typescript
// JSON(默认)
contentType: 'json' // → Content-Type: application/json;charset=UTF-8
// Form 表单
contentType: 'form' // → Content-Type: application/x-www-form-urlencoded
// 文件上传(不设置 Content-Type,让浏览器自动处理)
contentType: 'file' // → 不设置(multipart/form-data 由浏览器自动设置)
```
#### 自定义
```typescript
contentType: 'application/xml' // 直接使用
```
#### 请求级别配置
```typescript
// 文件上传
await client.post('/upload', formData, {
contentType: 'file', // 让浏览器自动处理 Content-Type
});
```
---
### 6. FormData 转换工具
#### toFormData 函数
自动将各种类型数据转换为 FormData:
```typescript
import { toFormData } from 'axios-request';
// 基础类型
const fd1 = toFormData({
name: '张三',
age: 25,
active: true,
});
// → name=张三&age=25&active=true
// 嵌套对象(点号分隔)
const fd2 = toFormData({
user: {
name: '张三',
profile: {
age: 25,
},
},
});
// → user.name=张三&user.profile.age=25
// 数组(括号下标)
const fd3 = toFormData({
tags: ['a', 'b', 'c'],
});
// → tags[0]=a&tags[1]=b&tags[2]=c
// 文件上传
const fd4 = toFormData({
avatar: file,
attachments: [file1, file2],
});
// → avatar=File&attachments[0]=File&attachments[1]=File
// Date 类型
const fd5 = toFormData({
createdAt: new Date('2024-01-01'),
});
// → createdAt=2024-01-01T00:00:00.000Z
// 混合数据
const fd6 = toFormData({
name: '张三',
age: 25,
profile: {
bio: '简介',
},
tags: ['a', 'b'],
avatar: file,
});
```
#### 类型检测工具
```typescript
import { checkType, flattenFormData } from 'axios-request';
// 检测数据类型
const result = checkType(file);
// → { type: 'File', isBlob: true, isPlainObject: false, isArray: false, isPrimitive: false }
const result = checkType({ name: '张三' });
// → { type: 'Object', isBlob: false, isPlainObject: true, isArray: false, isPrimitive: false }
// 将 FormData 展平为数组(调试用)
const entries = flattenFormData(formData);
// → [['name', '张三'], ['avatar', File]]
```
---
### 7. 简写配置汇总
为了简化使用,支持多种简写形式:
| 配置项 | 简写 | 说明 |
|--------|------|------|
| `dedupe` | `false` | 禁用防重复提交 |
| `dedupe` | `true` | 启用,使用默认配置 |
| `dedupe` | `{ timeWindow: 2000 }` | 只需改一个参数 |
| `dedupe` | `['post', 'put']` | 数组直接作为 methods(自动转大写)|
| `cancel` | `false` | 禁用请求取消 |
| `cancel` | `true` | 启用,使用默认配置 |
| `cancel` | `['get', 'post']` | 数组直接作为 methods(自动转大写)|
| `retry` | `false` | 禁用重试 |
| `retry` | `true` | 启用,使用默认配置 |
| `retry` | `3` | 启用并设置 maxRetries=3 |
| `dedupe` | `false` | 单个请求禁用 |
| `dedupe` | `['post']` | 单个请求使用数组指定 methods |
| `retry` | `5` | 单个请求重试 5 次 |
| `contentType` | `'json'` | JSON 格式 |
| `contentType` | `'form'` | 表单格式 |
| `contentType` | `'file'` | 文件上传 |
---
## API 文档
### AxiosRequest 构造函数
```typescript
import { AxiosRequest } from 'axios-request';
// 直接继承 axios 配置 + 新增功能配置
const client = new AxiosRequest({
// axios 原生配置(直接写在顶层)
baseURL?: string,
timeout?: number,
headers?: object,
// ...其他 axios 配置
// 新增功能配置
token?: TokenManagerConfig,
dedupe?: DedupeConfig | boolean,
cancel?: CancelConfig | boolean,
retry?: RetryConfig | boolean | number,
});
```
### 实例方法
| 方法 | 说明 |
|------|------|
| `get(url, data?, config?)` | GET 请求 |
| `post(url, data?, config?)` | POST 请求 |
| `put(url, data?, config?)` | PUT 请求 |
| `patch(url, data?, config?)` | PATCH 请求 |
| `delete(url, config?)` | DELETE 请求 |
| `head(url, config?)` | HEAD 请求 |
| `options(url, config?)` | OPTIONS 请求 |
| `request(config)` | 发起任意请求 |
| `getInstance()` | 获取底层 axios 实例 |
| `clear()` | 清除所有待处理请求 |
---
## 类型定义
### DedupeConfig
```typescript
interface DedupeConfig {
enabled?: boolean; // 默认 true
timeWindow?: number; // 默认 1000(毫秒)
methods?: string[]; // 默认 ['POST', 'PUT', 'PATCH', 'DELETE']
generateKey?: GenerateKeyFunction | string; // 函数或字符串模板
}
// 字符串模板示例
generateKey: 'method:url:data.id' // 用冒号分隔路径
generateKey: 'only-url' // 特殊值,只用 url
```
### CancelConfig
```typescript
interface CancelConfig {
enabled?: boolean; // 默认 true
methods?: string[]; // 默认 ['GET']
generateKey?: GenerateKeyFunction | string;
}
```
### RetryConfig
```typescript
interface RetryConfig {
enabled?: boolean; // 默认 false
maxRetries?: number; // 默认 3
retryDelay?: number; // 默认 100(毫秒)
exponentialBackoff?: boolean; // 默认 false
retryCondition?: (error: any, retryCount: number) => boolean;
}
```
### TokenManagerConfig
```typescript
interface TokenManagerConfig {
isTokenExpired: (error: any) => boolean;
refreshToken: (error: any) => Promise;
getAccessToken: () => string | null;
setTokens: (result: TokenRefreshResult) => void | Promise;
getRefreshToken?: () => string | null;
onRefreshFailed?: (reason: TokenRefreshFailureReason, error: any) => void | Promise;
// 自定义 token 赋值方式(可选,默认 Authorization: Bearer {token})
setAuthorization?: (config: AxiosRequestConfig, token: string) => void;
}
interface TokenRefreshResult {
accessToken: string;
refreshToken?: string;
}
type TokenRefreshFailureReason =
| 'unauthorized'
| 'forbidden'
| 'invalid_refresh_token'
| 'refresh_timeout'
| 'network_error'
| 'unknown';
```
### FormData 相关类型
```typescript
// 值类型
type FormDataValue =
| string | number | boolean
| null | undefined
| Date | File | Blob
| FormDataValue[]
| { [key: string]: FormDataValue };
// 类型检测结果
interface TypeCheckResult {
type: 'null' | 'undefined' | 'string' | 'number' | 'boolean' | 'Date' | 'File' | 'Blob' | 'Array' | 'Object';
isBlob: boolean;
isPlainObject: boolean;
isArray: boolean;
isPrimitive: boolean;
}
```
---
## 常见问题
### Q: 如何上传文件?
```typescript
import { toFormData } from 'axios-request';
const formData = toFormData({
file: fileInput.files[0],
name: 'my-file',
});
await client.post('/upload', formData, {
contentType: 'file', // 让浏览器自动设置 Content-Type
});
```
### Q: 如何处理 CORS?
```typescript
const client = new AxiosRequest({
withCredentials: true, // 允许携带 cookie
});
```
### Q: 如何自定义请求头?
```typescript
const client = new AxiosRequest({
headers: {
'X-App-Version': '1.0.0',
},
});
```
### Q: 如何获取完整响应?
```typescript
const response = await client.request({
url: '/users',
transformResponse: [(data) => data], // 禁用默认转换
});
console.log(response.status); // 200
console.log(response.headers); // 响应头
console.log(response.data); // 响应数据
```
### Q: 如何处理超时?
```typescript
try {
await client.get('/slow', { timeout: 5000 });
} catch (error) {
if (error.code === 'ECONNABORTED') {
console.log('请求超时');
}
}
```
### Q: 单个请求配置会影响实例配置吗?
不会。所有管理器都采用"请求上下文"机制,单个请求的配置只在当前请求生效,不会修改实例级别的配置。
```typescript
const client = new AxiosRequest({
dedupe: { timeWindow: 1000 }, // 实例配置
});
// 单个请求的配置只对当前请求生效
await client.post('/api', data, { dedupe: { timeWindow: 5000 } });
// 验证:实例配置未被修改
client.getInstanceConfig().dedupe.timeWindow === 1000; // true
```
### Q: 如何为单个请求创建临时管理器?
当实例没有配置某个管理器,但某个请求需要该功能时,会自动创建临时管理器:
```typescript
const client = new AxiosRequest({
// 没有配置 retry
});
// 单个请求启用重试(自动创建临时管理器)
await client.get('/unreliable', { retry: 5 });
```
---
### 8. 管理器架构设计
本库采用统一的管理器架构,所有功能(Token、Dedupe、Cancel、Retry)都遵循相同的设计原则。
#### 核心规则
```
┌─────────────────────────────────────────────────────────────┐
│ 1. Token:默认关闭,需要显式配置才能开启 │
│ 2. Dedupe/Cancel/Retry:默认开启,可显式配置为 false 关闭 │
└─────────────────────────────────────────────────────────────┘
| 场景 | Token | Dedupe | Cancel | Retry |
|-----------------------------|-------|--------|--------|-------|
| 实例级有配置 | 实例级 | 实例级 | 实例级 | 实例级 |
| 实例级没有 + 请求级有 | 私有级 | 私有级 | 私有级 | 私有级 |
| 都没有 | 无 | 默认 | 默认 | 默认 |
```
#### 设计优势
1. **统一管理**:实例级别的管理器统一管理所有请求
2. **上下文隔离**:每个请求通过独立的上下文对象实现隔离,无 Map 存储
3. **零清理负担**:上下文是普通对象,引用丢失即被 GC,无需手动清理
4. **配置不污染**:单个请求的配置只在当前请求生效,不影响实例配置
5. **私有级缓存**:私有级管理器按类型缓存复用,避免重复创建
#### 实例级管理器 vs 私有级管理器
```typescript
// 实例级管理器:实例化时配置,所有请求共享
const api = new AxiosRequest({
dedupe: true, // 实例级,实例化时创建
cancel: true, // 实例级,所有请求复用同一个 cancelManager
retry: { enabled: true, maxRetries: 3 }, // 实例级
});
// 私有级管理器:按需创建,缓存复用
const api = new AxiosRequest({}); // 没有配置任何管理器
// 第一次使用 retry,创建私有级 retryManager
api.get('/test1', { retry: true });
// 第二次使用 retry,复用同一个私有级 retryManager
api.get('/test2', { retry: true });
// 第三次使用 retry,配置不同,也复用同一个私有级 retryManager
api.get('/test3', { retry: { maxRetries: 5 } });
```
#### 上下文即对象
与传统基于 `requestId + Map` 的方式不同,本库采用**上下文即对象**的设计:
```typescript
// 传统方式:需要 requestId 和 Map
manager.setRequestContext(requestId, { enabled: false });
manager.shouldDedupe(requestId, config);
manager.clearRequestContext(requestId); // 需要手动清理
// 本库方式:上下文就是普通对象
const ctx = manager.createContext({ enabled: false });
manager.shouldDedupe(ctx, config);
// 无需清理,对象引用丢失即被 GC
```
#### 请求上下文机制
```typescript
// 实例有管理器时,请求配置通过上下文覆盖
client.post('/users', data, {
dedupe: { timeWindow: 5000 }, // 只覆盖 timeWindow,其他使用实例配置
});
// 实例无管理器时,自动创建临时管理器
client.post('/special', data, {
dedupe: { enabled: true, timeWindow: 2000 }, // 创建临时管理器
});
```
#### 配置不污染验证
```typescript
const client = new AxiosRequest({
dedupe: {
enabled: true,
timeWindow: 1000,
methods: ['POST'],
},
});
// 单个请求配置不影响实例配置
client.post('/users', data, {
dedupe: { timeWindow: 5000 }, // 只对当前请求生效
});
// 验证实例配置未被修改
client.getInstanceConfig().dedupe.timeWindow === 1000; // true
```
---
## 打包格式
| 格式 | 文件 | 使用场景 |
|------|------|----------|
| ES Module | `dist/axios-request.esm.js` | 现代前端工程(Vite、Webpack) |
| CommonJS | `dist/axios-request.cjs.js` | Node.js、Electron |
| UMD | `dist/axios-request.umd.js` | 浏览器直接引入、CDN |
| UMD (压缩) | `dist/axios-request.umd.min.js` | 生产环境 |
### 引入方式
```typescript
// ES Module
import { AxiosRequest } from 'axios-request';
// CommonJS
const { AxiosRequest } = require('axios-request');
// 浏览器
//
// const client = new AxiosRequest({...});
```
---
## 开发指南
### 环境要求
- Node.js >= 24
### 安装与初始化
```bash
# 安装依赖
npm install
# 初始化 husky(首次安装后执行一次)
npm run prepare
```
### 开发命令
| 命令 | 说明 |
|------|------|
| `npm run dev` | 开发模式,监听文件变化并自动构建 |
| `npm run build` | 构建项目,生成 dist 文件 |
| `npm run test` | 运行测试 |
| `npm run lint` | 代码检查 |
| `npm run typecheck` | TypeScript 类型检查 |
| `npm run guard` | **提交前检查**:类型检查 + lint + 测试,全部通过才能提交 |
| `npm run release` | 发布版本:验证 → 构建 → 更新版本 → 生成 Changelog → 提交 → 推送 → 打标签 |
### 提交代码流程
```
1. 修改代码
2. 运行 npm run guard 验证代码质量
3. 如果验证通过,执行 git commit -m "类型(范围): 描述"
4. 完成提交
```
**提交信息格式**(必须符合 Conventional Commits):
```
类型(范围): 描述
# 示例
feat(core): 新增 Token 自动刷新功能
fix(utils): 修复 FormData 转换日期格式错误
docs: 更新 README
refactor(types): 重构类型定义
```
**可选的类型**:
- `feat` - 新功能
- `fix` - Bug 修复
- `docs` - 文档更新
- `style` - 代码格式
- `refactor` - 重构
- `perf` - 性能优化
- `test` - 测试
- `build` - 构建或依赖
- `ci` - CI/CD
- `chore` - 其他修改
- `revert` - 回滚
**可选的范围**:`core`、`types`、`utils`、`test`、`docs`、`workflow`、`config`、`deps`、`release`
### 发布版本流程
```bash
# 交互式发布(推荐)
npm run release
# 自动发布
npm run release -- patch # 自动 patch 版本 (1.0.0 → 1.0.1)
npm run release -- minor # 自动 minor 版本 (1.0.0 → 1.1.0)
npm run release -- major # 自动 major 版本 (1.0.0 → 2.0.0)
npm run release -- 1.2.3 # 指定版本号
```
发布命令会自动完成:
1. 验证代码(typecheck → lint → test)
2. 构建项目
3. 更新 package.json 版本号
4. 生成 CHANGELOG.md
5. Git 提交
6. 推送到 origin (gitee)
7. 推送到 github(如果已配置)
8. 创建并推送 Git tag
推送标签后,GitHub Actions 自动:安装依赖 → 测试 → 构建 → 发布 npm
### 双仓库推送
项目支持同时推送到 Gitee (origin) 和 GitHub:
```bash
# 添加 GitHub 远程仓库(首次)
git remote add github https://github.com/GuoSirius/axios-request.git
# 发布时会自动推送到两个仓库
```
### Git Hooks
项目使用 husky 管理 Git hooks:
| Hook | 时机 | 作用 |
|------|------|------|
| `pre-commit` | 提交前 | 自动检查暂存的 .ts 文件(typecheck + lint + test) |
| `commit-msg` | 提交信息 | 验证提交信息是否符合 Conventional Commits 格式 |
如果 `pre-commit` 失败,检查输出并修复问题后重新提交。
---
## 许可证
MIT
## 作者
SiriuSSupreme
## 仓库
- Gitee: https://gitee.com/siriussupreme/axios-request
- GitHub: https://github.com/GuoSirius/axios-request