etherreq 1.1.15 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,80 +1,206 @@
1
- # etherreq
1
+ # EtherReq
2
2
 
3
- 一个轻量级、无感知的 HTTP 请求库,基于 Fetch封装,支持自动 Token 注入、拦截器、TypeScript 类型定义等功能。
3
+ 一个轻量级、无感知的 HTTP 请求库,基于 Fetch 封装,支持自动 Token 注入、拦截器、TypeScript 类型定义和智能缓存功能。
4
4
 
5
- ## 快速使用
5
+ ## 特性
6
6
 
7
- 安装
7
+ - 🚀 **轻量级**: 基于原生 Fetch API 构建
8
+ - 🔐 **自动认证**: login 方法自动管理 token
9
+ - 🔄 **智能缓存**: 支持内存和 localStorage 双重缓存
10
+ - 🎯 **TypeScript**: 完整的类型支持
11
+ - ⚡ **拦截器**: 请求/响应拦截器支持
12
+ - 📦 **零依赖**: 无需额外依赖包
8
13
 
9
- ```
14
+ ## 安装
15
+
16
+ ```bash
10
17
  npm install etherreq
11
18
  # 或者
12
19
  yarn add etherreq
13
20
  ```
14
21
 
15
- ## API 接口
22
+ ## 快速开始
16
23
 
17
- ```javascript
24
+ ```typescript
18
25
  import { etherreq } from 'etherreq';
19
26
 
20
- // GET 请求
21
- import { etherreq } from 'etherreq';
22
- const ab = async () => {
23
- const a= await etherreq.get('http://localhost:8081/api/users')
24
- //获取gat数据
25
- console.log(a);
26
- }
27
+ // GET 请求(自动缓存)
28
+ const users = await etherreq.get('http://localhost:8081/api/users');
29
+ console.log(users);
27
30
 
28
31
  // POST 请求
29
- etherreq.post('/login', { username: 'test', password: '123456' }).then(data => {
30
- console.log('登录成功:', data);
32
+ const result = await etherreq.post('/login', {
33
+ username: 'test',
34
+ password: '123456'
31
35
  });
36
+ console.log('登录成功:', result);
37
+
38
+ // 带数据的POST请求
39
+ const user = {
40
+ id: 1,
41
+ name: '张三',
42
+ sex: '男',
43
+ age: 18,
44
+ };
45
+ const newUser = await etherreq.post('/add', user);
46
+ console.log(newUser);
47
+ ```
32
48
 
33
- const b = async () => {
34
- const user={
35
- id : 1,
36
- name : 'name',
37
- sex : '男',
38
- age : 18,
39
- }
40
- const b1 =await etherreq.post('/add',user);
41
- console.log(b1);
42
- };
49
+ ## 登录认证
43
50
 
51
+ ```typescript
44
52
  // 登录方法(自动保存 token)
45
- const login = async () => {
46
- const user={
47
- "id": 1,
48
- "username": "zhy",
49
- "password": "123456"
50
- }
51
- const login =await etherreq.login('users/login',user);
52
- console.log(login);
53
+ const login = async () => {
54
+ const userData = {
55
+ id: 1,
56
+ username: "zhy",
57
+ password: "123456"
53
58
  };
59
+
60
+ const loginResult = await etherreq.login('users/login', userData);
61
+ console.log(loginResult);
62
+ // 后续请求会自动携带 token
63
+ };
64
+ ```
54
65
 
66
+ ## 缓存功能
55
67
 
56
- ```
68
+ ### 自动缓存
69
+ GET 请求默认启用智能缓存:
70
+ - 内存缓存(快速访问)
71
+ - localStorage 持久化(页面刷新后仍有效)
72
+ - 自动过期清理(默认5分钟)
57
73
 
74
+ ### 缓存控制选项
58
75
 
76
+ ```typescript
77
+ // 禁用缓存
78
+ const data = await etherreq.get('/api/data', {
79
+ cache: false
80
+ });
81
+
82
+ // 自定义缓存时间
83
+ const data = await etherreq.get('/api/data', {
84
+ cache: {
85
+ enabled: true,
86
+ ttl: 10 * 60 * 1000 // 10分钟
87
+ }
88
+ });
89
+
90
+ // 指定存储策略
91
+ const data = await etherreq.get('/api/data', {
92
+ cache: {
93
+ storage: 'memory+local', // 'memory' | 'local' | 'memory+local'
94
+ ttl: 300000
95
+ }
96
+ });
97
+ ```
98
+
99
+ ### 带参数的请求缓存
100
+ ```typescript
101
+ // 不同参数生成不同缓存键
102
+ const page1 = await etherreq.get('/users', {
103
+ params: { page: 1, limit: 10 }
104
+ });
59
105
 
106
+ const page2 = await etherreq.get('/users', {
107
+ params: { page: 2, limit: 10 }
108
+ });
109
+ // 这两个请求会有独立的缓存
110
+ ```
60
111
 
112
+ ## API 接口
61
113
 
62
114
  ### 请求方法
63
115
 
64
- etherreq.login(url,data)
116
+ ```typescript
117
+ etherreq(url, options?) // 默认 GET
118
+ etherreq.get(url, options?)
119
+ etherreq.post(url, data?, options?)
120
+ etherreq.put(url, data?, options?)
121
+ etherreq.delete(url, options?)
122
+ etherreq.del(url, options?) // delete 别名
123
+ etherreq.head(url, options?)
124
+ etherreq.options(url, options?)
125
+ etherreq.patch(url, data?, options?)
126
+ etherreq.login(url, data?, options?)
127
+ ```
128
+
129
+ ### 配置选项
130
+
131
+ ```typescript
132
+ interface EtherRequestOptions {
133
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
134
+ headers?: Record<string, string>;
135
+ body?: any;
136
+ params?: Record<string, string | number | boolean>;
137
+ baseURL?: string;
138
+ cache?: CacheOptions | false;
139
+ }
140
+ ```
141
+
142
+ ### 缓存选项
65
143
 
66
- etherreq.get(url,data)
144
+ ```typescript
145
+ interface CacheOptions {
146
+ enabled?: boolean; // 是否启用缓存
147
+ storage?: 'memory' | 'local' | 'memory+local'; // 存储策略
148
+ ttl?: number; // 过期时间(毫秒)
149
+ }
150
+ ```
67
151
 
68
- etherreq.delete(url,data)
152
+ ## 高级用法
69
153
 
70
- etherreq.head(url,data)
154
+ ### 设置基础URL
155
+ ```typescript
156
+ import { setBaseURL } from 'etherreq';
157
+
158
+ setBaseURL('https://api.example.com');
159
+ // 后续请求将使用此基础URL
160
+ ```
161
+
162
+ ### 并发请求
163
+ ```typescript
164
+ const [users, posts] = await Promise.all([
165
+ etherreq.get('/users'),
166
+ etherreq.get('/posts')
167
+ ]);
168
+ ```
169
+
170
+ ### 错误处理
171
+ ```typescript
172
+ try {
173
+ const data = await etherreq.get('/api/data');
174
+ } catch (error) {
175
+ console.error('请求失败:', error.message);
176
+ }
177
+ ```
178
+
179
+ ## TypeScript 支持
180
+
181
+ 完整的类型定义支持:
182
+
183
+ ```typescript
184
+ interface User {
185
+ id: number;
186
+ name: string;
187
+ email: string;
188
+ }
189
+
190
+ // 类型安全的请求
191
+ const user: User = await etherreq.get<User>('/users/1');
192
+ const users: User[] = await etherreq.get<User[]>('/users');
193
+ ```
71
194
 
72
- etherreq.options(url,data)
195
+ ## 浏览器兼容性
73
196
 
74
- etherreq.post(url,data)
197
+ - Chrome 67+
198
+ - Firefox 61+
199
+ - Safari 12+
200
+ - Edge 79+
75
201
 
76
- etherreq.put(url,data)
202
+ 需要 Promise 和 fetch API 支持。
77
203
 
78
- etherreq.patch(url,data)
204
+ ## License
79
205
 
80
- 在login方法执行后后端需要返回token,之后在执行其他方法时不需要配置token,token会自动携带
206
+ MIT
package/dist/cache.js ADDED
@@ -0,0 +1,281 @@
1
+ /**
2
+ * 生成缓存键的哈希值,避免特殊字符和长度问题
3
+ * @param str 输入字符串
4
+ * @returns 哈希值字符串
5
+ */
6
+ function hashString(str) {
7
+ let hash = 0;
8
+ for (let i = 0; i < str.length; i++) {
9
+ const char = str.charCodeAt(i);
10
+ hash = ((hash << 5) - hash) + char;
11
+ hash = hash & hash; // 转换为32位整数
12
+ }
13
+ return Math.abs(hash).toString(36);
14
+ }
15
+ /**
16
+ * 检查localStorage是否可用
17
+ * @returns boolean
18
+ */
19
+ function isLocalStorageAvailable() {
20
+ try {
21
+ const testKey = '__storage_test__';
22
+ localStorage.setItem(testKey, 'test');
23
+ localStorage.removeItem(testKey);
24
+ return true;
25
+ }
26
+ catch (e) {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * 获取缓存 key(使用哈希算法避免特殊字符)
32
+ * @param url 请求地址
33
+ * @param options 请求参数
34
+ * @returns 缓存键字符串
35
+ */
36
+ export function getCacheKey(url, options) {
37
+ const rawParams = options.params || {};
38
+ // 将所有值转换为 string
39
+ const stringifiedParams = {};
40
+ for (const [key, value] of Object.entries(rawParams)) {
41
+ stringifiedParams[key] = String(value);
42
+ }
43
+ const params = new URLSearchParams(stringifiedParams);
44
+ const keyString = `${options.method}:${url}?${params.toString()}`;
45
+ return `etherreq_cache_${hashString(keyString)}`;
46
+ }
47
+ /**
48
+ * 内存缓存存储类
49
+ */
50
+ class MemoryCache {
51
+ constructor() {
52
+ this.store = new Map();
53
+ this.cleanupTimer = null;
54
+ this.startCleanupInterval();
55
+ }
56
+ /**
57
+ * 启动定期清理过期缓存
58
+ */
59
+ startCleanupInterval() {
60
+ if (this.cleanupTimer)
61
+ return;
62
+ this.cleanupTimer = setInterval(() => {
63
+ this.cleanupExpired();
64
+ }, 30000); // 每30秒清理一次
65
+ }
66
+ /**
67
+ * 清理过期缓存
68
+ */
69
+ cleanupExpired() {
70
+ const now = Date.now();
71
+ for (const [key, entry] of this.store.entries()) {
72
+ if (entry.expireAt <= now) {
73
+ this.store.delete(key);
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * 设置缓存
79
+ */
80
+ set(key, value, ttl = 5 * 60 * 1000) {
81
+ const expireAt = Date.now() + ttl;
82
+ this.store.set(key, { value, expireAt });
83
+ }
84
+ /**
85
+ * 获取缓存
86
+ */
87
+ get(key) {
88
+ const entry = this.store.get(key);
89
+ if (entry && entry.expireAt > Date.now()) {
90
+ return entry.value;
91
+ }
92
+ // 过期则删除
93
+ if (entry) {
94
+ this.store.delete(key);
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * 删除指定缓存
100
+ */
101
+ delete(key) {
102
+ this.store.delete(key);
103
+ }
104
+ /**
105
+ * 清空所有缓存
106
+ */
107
+ clear() {
108
+ this.store.clear();
109
+ }
110
+ /**
111
+ * 获取缓存大小
112
+ */
113
+ size() {
114
+ return this.store.size;
115
+ }
116
+ /**
117
+ * 停止清理定时器
118
+ */
119
+ destroy() {
120
+ if (this.cleanupTimer) {
121
+ clearInterval(this.cleanupTimer);
122
+ this.cleanupTimer = null;
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * 本地存储缓存类
128
+ */
129
+ class LocalStorageCache {
130
+ constructor(prefix = 'etherreq_') {
131
+ this.prefix = prefix;
132
+ this.available = isLocalStorageAvailable();
133
+ }
134
+ /**
135
+ * 设置缓存到localStorage
136
+ */
137
+ set(key, value, ttl = 5 * 60 * 1000) {
138
+ if (!this.available)
139
+ return;
140
+ try {
141
+ const expireAt = Date.now() + ttl;
142
+ const entry = { value, expireAt };
143
+ localStorage.setItem(`${this.prefix}${key}`, JSON.stringify(entry));
144
+ }
145
+ catch (error) {
146
+ console.warn('LocalStorage缓存设置失败:', error);
147
+ }
148
+ }
149
+ /**
150
+ * 从localStorage获取缓存
151
+ */
152
+ get(key) {
153
+ if (!this.available)
154
+ return null;
155
+ try {
156
+ const item = localStorage.getItem(`${this.prefix}${key}`);
157
+ if (!item)
158
+ return null;
159
+ const entry = JSON.parse(item);
160
+ if (entry.expireAt > Date.now()) {
161
+ return entry.value;
162
+ }
163
+ else {
164
+ // 过期则删除
165
+ localStorage.removeItem(`${this.prefix}${key}`);
166
+ return null;
167
+ }
168
+ }
169
+ catch (error) {
170
+ console.warn('LocalStorage缓存获取失败:', error);
171
+ return null;
172
+ }
173
+ }
174
+ /**
175
+ * 删除指定缓存
176
+ */
177
+ delete(key) {
178
+ if (!this.available)
179
+ return;
180
+ try {
181
+ localStorage.removeItem(`${this.prefix}${key}`);
182
+ }
183
+ catch (error) {
184
+ console.warn('LocalStorage缓存删除失败:', error);
185
+ }
186
+ }
187
+ /**
188
+ * 清空所有缓存(只清空当前应用的缓存)
189
+ */
190
+ clear() {
191
+ if (!this.available)
192
+ return;
193
+ try {
194
+ const keysToRemove = [];
195
+ for (let i = 0; i < localStorage.length; i++) {
196
+ const key = localStorage.key(i);
197
+ if (key && key.startsWith(this.prefix)) {
198
+ keysToRemove.push(key);
199
+ }
200
+ }
201
+ keysToRemove.forEach(key => localStorage.removeItem(key));
202
+ }
203
+ catch (error) {
204
+ console.warn('LocalStorage缓存清空失败:', error);
205
+ }
206
+ }
207
+ }
208
+ /**
209
+ * 复合缓存类 - 结合内存和本地存储
210
+ */
211
+ class HybridCache {
212
+ constructor() {
213
+ this.memoryCache = new MemoryCache();
214
+ this.localStorageCache = new LocalStorageCache();
215
+ }
216
+ /**
217
+ * 设置缓存(同时存储到内存和本地存储)
218
+ */
219
+ set(key, value, ttl = 5 * 60 * 1000) {
220
+ this.memoryCache.set(key, value, ttl);
221
+ this.localStorageCache.set(key, value, ttl);
222
+ }
223
+ /**
224
+ * 获取缓存(优先从内存获取,否则从本地存储获取)
225
+ */
226
+ get(key) {
227
+ // 先从内存获取
228
+ let value = this.memoryCache.get(key);
229
+ if (value !== null) {
230
+ return value;
231
+ }
232
+ // 内存没有则从本地存储获取
233
+ value = this.localStorageCache.get(key);
234
+ if (value !== null) {
235
+ // 同步到内存缓存
236
+ this.memoryCache.set(key, value);
237
+ return value;
238
+ }
239
+ return null;
240
+ }
241
+ /**
242
+ * 删除指定缓存
243
+ */
244
+ delete(key) {
245
+ this.memoryCache.delete(key);
246
+ this.localStorageCache.delete(key);
247
+ }
248
+ /**
249
+ * 清空所有缓存
250
+ */
251
+ clear() {
252
+ this.memoryCache.clear();
253
+ this.localStorageCache.clear();
254
+ }
255
+ /**
256
+ * 销毁缓存实例
257
+ */
258
+ destroy() {
259
+ this.memoryCache.destroy();
260
+ }
261
+ }
262
+ /**
263
+ * 缓存工厂类
264
+ */
265
+ class CacheFactory {
266
+ static create(storageType = 'memory+local') {
267
+ switch (storageType) {
268
+ case 'memory':
269
+ return new MemoryCache();
270
+ case 'local':
271
+ return new LocalStorageCache();
272
+ case 'memory+local':
273
+ default:
274
+ return new HybridCache();
275
+ }
276
+ }
277
+ }
278
+ // 默认导出混合缓存实例
279
+ export const requestCache = CacheFactory.create('memory+local');
280
+ // 导出缓存工厂和工具函数
281
+ export { CacheFactory, MemoryCache, LocalStorageCache, HybridCache, hashString, isLocalStorageAvailable };
@@ -0,0 +1,84 @@
1
+ // src/etherreq.ts
2
+ export const create = (defaultConfig = {}) => {
3
+ const interceptors = {
4
+ request: [],
5
+ response: [],
6
+ };
7
+ const use = (fulfilled, rejected, type = 'request') => {
8
+ interceptors[type].push({ fulfilled, rejected });
9
+ };
10
+ const dispatchRequest = async (config) => {
11
+ // 请求拦截器执行
12
+ for (const interceptor of interceptors.request) {
13
+ const nextConfig = await interceptor.fulfilled(config);
14
+ // ✅ 拦截器可以返回一个 response 对象,表示中断请求
15
+ if (nextConfig && nextConfig.isFromCache) {
16
+ return nextConfig.data; // ✅ 直接返回缓存结果,不再往下执行
17
+ }
18
+ config = nextConfig;
19
+ }
20
+ // 检查 url 是否存在
21
+ if (!config.url) {
22
+ throw new Error('请求配置中缺少 url 属性');
23
+ }
24
+ let response;
25
+ const { url, method = 'GET', headers = {}, body } = config;
26
+ let text = '';
27
+ try {
28
+ const options = {
29
+ method,
30
+ headers,
31
+ body: method !== 'GET' ? JSON.stringify(body) : undefined,
32
+ };
33
+ const res = await fetch(url, options);
34
+ text = await res.text();
35
+ if (!res.ok) {
36
+ throw new Error(`HTTP 错误: ${res.status} - ${res.statusText}`);
37
+ }
38
+ if (text.trim().startsWith('<')) {
39
+ throw new Error('服务器返回了 HTML 页面,预期为 JSON 格式');
40
+ }
41
+ const data = JSON.parse(text);
42
+ response = {
43
+ data,
44
+ status: res.status,
45
+ statusText: res.statusText,
46
+ headers: Object.fromEntries(res.headers.entries()),
47
+ config,
48
+ };
49
+ }
50
+ catch (error) {
51
+ error.message += `\n请求地址: ${url}`;
52
+ if (text) {
53
+ error.message += `\n响应内容: ${text.substring(0, 200)}...`;
54
+ }
55
+ for (const interceptor of interceptors.response) {
56
+ if (interceptor.rejected) {
57
+ return interceptor.rejected(error);
58
+ }
59
+ }
60
+ throw error;
61
+ }
62
+ // 响应拦截器执行
63
+ for (const interceptor of interceptors.response) {
64
+ response = await interceptor.fulfilled(response);
65
+ }
66
+ return response;
67
+ };
68
+ const instance = (config) => {
69
+ return dispatchRequest({
70
+ ...defaultConfig,
71
+ ...config,
72
+ });
73
+ };
74
+ instance.use = (fulfilled, rejected) => use(fulfilled, rejected, 'request');
75
+ instance.interceptors = {
76
+ request: {
77
+ use: (fulfilled, rejected) => use(fulfilled, rejected, 'request'),
78
+ },
79
+ response: {
80
+ use: (fulfilled, rejected) => use(fulfilled, rejected, 'response'),
81
+ },
82
+ };
83
+ return instance;
84
+ };
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ // src/index.ts
2
+ import { request, baseURL as _baseURL, setBaseURL } from './request';
3
+ // 示例封装方法
4
+ const createMethod = (method) => (url, data, callback) => {
5
+ let options;
6
+ if (data &&
7
+ typeof data === 'object' &&
8
+ !Array.isArray(data) &&
9
+ !(data instanceof FormData) &&
10
+ !data.method &&
11
+ !data.headers &&
12
+ !data.params &&
13
+ !data.baseURL) {
14
+ options = { body: data, method: method };
15
+ }
16
+ else {
17
+ options = { ...data, method: method };
18
+ }
19
+ const promise = request(url, options);
20
+ if (typeof callback === 'function') {
21
+ promise.then(data => callback(null, data)).catch(err => callback(err, null));
22
+ }
23
+ else {
24
+ return promise;
25
+ }
26
+ };
27
+ export const etherreq = Object.assign((url, options, callback) => createMethod('GET')(url, options, callback), {
28
+ get: createMethod('GET'),
29
+ post: createMethod('POST'),
30
+ put: createMethod('PUT'),
31
+ delete: createMethod('DELETE'),
32
+ del: createMethod('DELETE'),
33
+ head: createMethod('HEAD'),
34
+ options: createMethod('OPTIONS'),
35
+ patch: createMethod('PATCH'),
36
+ login: (url, data, callback) => {
37
+ // 确保总是获得Promise对象,即使提供了callback
38
+ const loginPromise = createMethod('POST')(url, data);
39
+ // 如果createMethod返回undefined(提供了callback的情况),创建一个新的Promise
40
+ const promiseToUse = loginPromise instanceof Promise ? loginPromise : request(url, {
41
+ ...(typeof data === 'object' && !Array.isArray(data) && !(data instanceof FormData) ? { body: data } : data),
42
+ method: 'POST'
43
+ });
44
+ promiseToUse.then((response) => {
45
+ const token = response.data?.token;
46
+ if (token) {
47
+ localStorage.setItem('token', token); // 保存 token 到 localStorage
48
+ }
49
+ return response;
50
+ });
51
+ if (typeof callback === 'function') {
52
+ promiseToUse.then(data => callback(null, data)).catch(err => callback(err, null));
53
+ }
54
+ else {
55
+ return promiseToUse;
56
+ }
57
+ },
58
+ });
59
+ export { setBaseURL, _baseURL as baseURL };