@hzab/data-model 2.0.2 → 2.0.3-alpha.1

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.
@@ -0,0 +1,202 @@
1
+ import { InternalAxiosRequestConfig } from "axios";
2
+ import { axiosDef } from "./axios";
3
+
4
+ /**
5
+ * 缓存配置
6
+ */
7
+ export interface CacheOpt {
8
+ /** 缓存时间 毫秒 */
9
+ cacheTTL?: number;
10
+ }
11
+
12
+ /**
13
+ * 缓存方法入参配置
14
+ */
15
+ export interface RequestCacheParams {
16
+ options?: CacheOpt;
17
+ }
18
+
19
+ /**
20
+ * 接口入参(用于生成缓存 key)
21
+ */
22
+ export interface Req {
23
+ method?: string;
24
+ baseURL?: string;
25
+ url?: string;
26
+ params?: unknown;
27
+ data?: unknown;
28
+ }
29
+
30
+ /**
31
+ * 缓存对象
32
+ */
33
+ export interface CacheItem {
34
+ key: string;
35
+ /** 过期时间戳 */
36
+ expires?: number;
37
+ /** 缓存的 promise */
38
+ promise?: Promise<unknown>;
39
+ /** 缓存的 axios config 配置 */
40
+ config?: InternalAxiosRequestConfig;
41
+ resolve: (d) => void;
42
+ reject: (err) => void;
43
+ }
44
+
45
+ /**
46
+ * 接口缓存类
47
+ */
48
+ export class RequestCache {
49
+ options: CacheOpt;
50
+ cacheTTL = 3000;
51
+ /** 存储为 CacheItem(类型为 unknown,返回时断言) */
52
+ _cacheMap: Map<string, CacheItem> = new Map();
53
+
54
+ constructor(params: RequestCacheParams) {
55
+ const { options = {} } = params || {};
56
+ this.cacheTTL = options?.cacheTTL ?? 3000;
57
+ this.options = options;
58
+ }
59
+
60
+ /**
61
+ * 处理 axios request 拦截逻辑
62
+ * @param config
63
+ * @returns
64
+ */
65
+ handleAxRequest(config) {
66
+ if (this.hasCache(config)) {
67
+ const cache = this.getCache(config);
68
+ // 存在缓存,
69
+ return Promise.reject({
70
+ code: 200,
71
+ key: this.getCacheKey(config),
72
+ config,
73
+ from: "cache",
74
+ cache,
75
+ });
76
+ }
77
+ this.rmCache(config);
78
+
79
+ const cacheData = {
80
+ code: 200,
81
+ key: this.getCacheKey(config),
82
+ config,
83
+ from: "cache",
84
+ promise: undefined,
85
+ resolve: undefined,
86
+ reject: undefined,
87
+ };
88
+ // 设置 promise 保持请求挂起状态
89
+ cacheData.promise = new Promise((resolve, reject) => {
90
+ cacheData.resolve = resolve;
91
+ cacheData.reject = reject;
92
+ });
93
+
94
+ config.cache = this.addCache(cacheData);
95
+ return config;
96
+ }
97
+
98
+ /**
99
+ * 判断是否存在缓存
100
+ * @param axConf
101
+ * @returns
102
+ */
103
+ hasCache(axConf) {
104
+ const key = this.getCacheKey(axConf);
105
+ const cache = this._cacheMap.get(key);
106
+ if (cache && cache.expires >= Date.now()) {
107
+ return true;
108
+ }
109
+ // 过期清除
110
+ this._cacheMap.delete(key);
111
+ return false;
112
+ }
113
+
114
+ /**
115
+ * 获取缓存
116
+ * @param axios config
117
+ * @param opt 自定义缓存配置
118
+ * @returns Promise<T>
119
+ */
120
+ getCache(axConf, opt?: CacheOpt): CacheItem {
121
+ const key = this.getCacheKey(axConf);
122
+
123
+ return this.getCacheByKey(key, opt);
124
+ }
125
+
126
+ /**
127
+ * 获取缓存
128
+ * @param key string
129
+ * @param opt 自定义缓存配置
130
+ * @returns Promise<T>
131
+ */
132
+ getCacheByKey(key, opt?: CacheOpt) {
133
+ // TODO: 过期处理? 结果和请求分开。只有请求才进行过期判断?
134
+ // 存在未过期的缓存
135
+ const cached = this._cacheMap.get(key);
136
+ if (cached && cached.expires > Date.now()) {
137
+ return cached;
138
+ }
139
+
140
+ // 过期删除
141
+ if (cached) {
142
+ this._cacheMap.delete(key);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * 添加缓存
148
+ * @param promise 要缓存的 Promise
149
+ * @param req 请求描述对象
150
+ * @param config 缓存配置
151
+ */
152
+ addCache(data: CacheItem, opt: CacheOpt = this.options): CacheItem {
153
+ const { config } = data || {};
154
+ const key = this.getCacheKey(config);
155
+ if (this._cacheMap.has(key)) {
156
+ return this._cacheMap.get(key);
157
+ }
158
+ const ttl = opt?.cacheTTL ?? this.cacheTTL;
159
+ const cache = {
160
+ ...data,
161
+ key,
162
+ expires: Date.now() + ttl,
163
+ config: config,
164
+ promise: data.promise,
165
+ };
166
+ this._cacheMap.set(key, cache);
167
+ return cache;
168
+ }
169
+
170
+ rmCache(config: Req) {
171
+ this._cacheMap.delete(this.getCacheKey(config));
172
+ }
173
+
174
+ /**
175
+ * 获取缓存的 key
176
+ * @param config 请求入参
177
+ * @returns 缓存键字符串
178
+ */
179
+ getCacheKey(config: Req): string {
180
+ const { baseURL, method, url, params, data } = config;
181
+ // 注意:params 和 data 需要序列化,并保证对象属性顺序稳定性
182
+ return `${method}:${baseURL}/${url}:${this.stableStringify(params)}:${this.stableStringify(data)}`;
183
+ }
184
+
185
+ /**
186
+ * 序列化对象,保证属性顺序一致
187
+ * @param obj 任意值
188
+ * @returns JSON 字符串
189
+ */
190
+ private stableStringify(obj: unknown): string {
191
+ if (!obj || typeof obj !== "object") return JSON.stringify(obj);
192
+ const sorted = Object.keys(obj)
193
+ .sort()
194
+ .reduce((acc, key) => {
195
+ acc[key] = (obj as Record<string, unknown>)[key];
196
+ return acc;
197
+ }, {} as Record<string, unknown>);
198
+ return JSON.stringify(sorted);
199
+ }
200
+ }
201
+
202
+ export default RequestCache;
@@ -1,4 +1,4 @@
1
- import { merge, pickBy, isNil, cloneDeep } from "lodash";
1
+ import { merge, pickBy, isNil, cloneDeep } from "lodash-es";
2
2
 
3
3
  import ArrayUtils, { STATE_CODE, FindListResult, ArrayUtilsOptions } from "./ArrayUtils";
4
4
 
package/src/axios.ts CHANGED
@@ -1,210 +1,245 @@
1
- import axiosDef, { AxiosResponse, InternalAxiosRequestConfig } from "axios";
2
- import Cookies from "js-cookie";
3
- import _ from "lodash";
4
-
5
- export const TOKEN = "token";
6
-
7
- const axios = axiosDef.create();
8
-
9
- /**
10
- * setToken 选项接口
11
- */
12
- export interface SetTokenOptions {
13
- /** 是否同时设置 cookie */
14
- hasCookie?: boolean;
15
- /** cookie 属性配置 */
16
- cookieAttrs?: Cookies.CookieAttributes;
17
- }
18
-
19
- /**
20
- * getToken 选项接口
21
- */
22
- export interface GetTokenOptions {
23
- /** 是否从 cookie 获取 */
24
- hasCookie?: boolean;
25
- }
26
-
27
- /**
28
- * 处理错误回调函数的选项接口
29
- */
30
- export interface HandleErrCbOptions {
31
- /** 直接替换回调函数 */
32
- replaceErrCb?: boolean;
33
- /** 日志前缀 */
34
- logPrefix?: string;
35
- }
36
-
37
- /**
38
- * 设置 token
39
- * @param token token 值
40
- * @param opt 选项
41
- */
42
- export function setToken(token = "", opt?: SetTokenOptions) {
43
- const { hasCookie = false } = opt || {};
44
- const _t = token || "";
45
- if (hasCookie) {
46
- const { cookieAttrs } = opt || {};
47
- const cookieOpt = _.merge(
48
- {
49
- expires: 365,
50
- },
51
- cookieAttrs,
52
- );
53
- Cookies.set(TOKEN, _t, cookieOpt);
54
- }
55
- localStorage.setItem(TOKEN, _t);
56
- setAxAuthorization(_t);
57
- }
58
-
59
- /**
60
- * 获取 token
61
- * @param opt 选项
62
- * @returns token 值
63
- */
64
- export function getToken(opt?: GetTokenOptions): string {
65
- const { hasCookie = false } = opt || {};
66
- let token = "";
67
- if (hasCookie) {
68
- token = Cookies.get(TOKEN) || "";
69
- if (token) {
70
- return token;
71
- }
72
- }
73
- token = localStorage.getItem(TOKEN);
74
- return token;
75
- }
76
-
77
- export const temp = { axiosList: [axios, axiosDef] };
78
-
79
- /**
80
- * 设置 axios 列表,用于批量设置 axios 相关配置
81
- * @param axiosList axios 实例列表
82
- * @returns 更新后的 axios 实例列表
83
- */
84
- export function setAxios(axiosList: (typeof axios | typeof axiosDef)[]) {
85
- temp.axiosList = [...temp.axiosList, ...axiosList];
86
- return temp.axiosList;
87
- }
88
-
89
- /**
90
- * axios 请求拦截器
91
- * @param cb 成功回调
92
- * @param errCb 错误回调
93
- * @param opt 选项
94
- */
95
- export function setAxRequest(
96
- cb: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>,
97
- errCb?: (error: any) => Promise<any>,
98
- opt?: HandleErrCbOptions,
99
- ) {
100
- temp.axiosList?.forEach((_ax) => {
101
- _ax.interceptors.request.use(cb, handleErrCb(errCb, { logPrefix: "Error axios response: ", ...opt }));
102
- });
103
- }
104
-
105
- /**
106
- * 响应拦截器
107
- * @param cb 成功回调
108
- * @param errCb 错误回调
109
- * @param opt 选项
110
- */
111
- export function setAxResponse(
112
- cb: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>,
113
- errCb?: (error: any) => Promise<any>,
114
- opt?: HandleErrCbOptions,
115
- ) {
116
- temp.axiosList?.forEach((_ax) => {
117
- _ax.interceptors.response.use(cb, handleErrCb(errCb, { logPrefix: "Error axios response: ", ...opt }));
118
- });
119
- }
120
-
121
- /**
122
- * 处理错误回调函数
123
- * @param errCb 错误回调
124
- * @param opt 选项
125
- * @returns 处理后的错误回调
126
- */
127
- export function handleErrCb(errCb?: (error: any) => Promise<any>, opt?: HandleErrCbOptions) {
128
- const { replaceErrCb, logPrefix } = opt || {};
129
- const isFn = typeof errCb === "function";
130
-
131
- if (replaceErrCb && isFn) {
132
- return errCb;
133
- }
134
-
135
- return (error: any) => {
136
- if (axiosDef.isCancel(error)) {
137
- console.info("axios 请求已取消:", error);
138
- return Promise.reject(error);
139
- }
140
- console.error(logPrefix, error);
141
- return isFn ? errCb(error) : Promise.reject(error);
142
- };
143
- }
144
-
145
- /**
146
- * 设置 axios.defaults
147
- * @param key 配置键
148
- * @param val 配置值
149
- */
150
- export function setAxDefaults(key: string, val: any) {
151
- temp.axiosList?.forEach((_ax) => {
152
- (_ax.defaults as any)[key] = val;
153
- });
154
- }
155
-
156
- /**
157
- * 设置 headers
158
- * @param key header 键
159
- * @param val header 值
160
- */
161
- export function setAxHeaders(key: string, val: any) {
162
- temp.axiosList?.forEach((_ax) => {
163
- (_ax.defaults.headers as any)[key] = val;
164
- });
165
- }
166
-
167
- /**
168
- * 设置 timeout
169
- * @param timeout 超时时间
170
- */
171
- export function setAxTimeout(timeout = 0) {
172
- setAxDefaults("timeout", timeout);
173
- }
174
-
175
- /**
176
- * 设置 Authorization token
177
- * @param token token 值
178
- */
179
- export function setAxAuthorization(token: string) {
180
- setAxHeaders("Authorization", token);
181
- }
182
-
183
- /**
184
- * 设置 baseUrl
185
- * @param url 基础 URL
186
- */
187
- export function setAxBaseUrl(url: string) {
188
- setAxDefaults("baseURL", url);
189
- }
190
-
191
- /**
192
- * 获取 axios.CancelToken.source,用于取消请求
193
- * @returns CancelToken source
194
- */
195
- export function getCancelTokenSource() {
196
- return axiosDef.CancelToken.source();
197
- }
198
-
199
- /**
200
- * 检查是否是 cancel 的结果
201
- * @param res
202
- * @returns
203
- */
204
- export function isCancel(res) {
205
- return axiosDef.isCancel(res);
206
- }
207
-
208
- export { axios, axiosDef };
209
-
210
- export default axios;
1
+ import axiosDef, { AxiosResponse, InternalAxiosRequestConfig } from "axios";
2
+ import Cookies from "js-cookie";
3
+ import { merge } from "lodash-es";
4
+
5
+ import { CacheItem } from "./RequestCache";
6
+
7
+ export const TOKEN = "token";
8
+
9
+ const axios = axiosDef.create();
10
+
11
+ export interface AxiosRequestConfigWithCache extends InternalAxiosRequestConfig {
12
+ cache?: CacheItem;
13
+ }
14
+
15
+ // 使用交叉类型扩展 AxiosResponse
16
+ export type AxiosResponseCus = AxiosResponse & {
17
+ config: AxiosRequestConfigWithCache;
18
+ };
19
+
20
+ /**
21
+ * setToken 选项接口
22
+ */
23
+ export interface SetTokenOptions {
24
+ /** 是否同时设置 cookie */
25
+ hasCookie?: boolean;
26
+ /** cookie 属性配置 */
27
+ cookieAttrs?: Cookies.CookieAttributes;
28
+ }
29
+
30
+ /**
31
+ * getToken 选项接口
32
+ */
33
+ export interface GetTokenOptions {
34
+ /** 是否从 cookie 获取 */
35
+ hasCookie?: boolean;
36
+ }
37
+
38
+ /**
39
+ * 处理错误回调函数的选项接口
40
+ */
41
+ export interface HandleErrCbOptions {
42
+ /** 直接替换回调函数 */
43
+ replaceErrCb?: boolean;
44
+ /** 日志前缀 */
45
+ logPrefix?: string;
46
+ }
47
+
48
+ /**
49
+ * 设置 token
50
+ * @param token token 值
51
+ * @param opt 选项
52
+ */
53
+ export function setToken(token = "", opt?: SetTokenOptions) {
54
+ const { hasCookie = false } = opt || {};
55
+ const _t = token || "";
56
+ if (hasCookie) {
57
+ const { cookieAttrs } = opt || {};
58
+ const cookieOpt = merge(
59
+ {
60
+ expires: 365,
61
+ },
62
+ cookieAttrs,
63
+ );
64
+ Cookies.set(TOKEN, _t, cookieOpt);
65
+ }
66
+ localStorage.setItem(TOKEN, _t);
67
+ setAxAuthorization(_t);
68
+ }
69
+
70
+ /**
71
+ * 获取 token
72
+ * @param opt 选项
73
+ * @returns token
74
+ */
75
+ export function getToken(opt?: GetTokenOptions): string {
76
+ const { hasCookie = false } = opt || {};
77
+ let token = "";
78
+ if (hasCookie) {
79
+ token = Cookies.get(TOKEN) || "";
80
+ if (token) {
81
+ return token;
82
+ }
83
+ }
84
+ token = localStorage.getItem(TOKEN);
85
+ return token;
86
+ }
87
+
88
+ export const temp = { axiosList: [axios, axiosDef] };
89
+
90
+ /**
91
+ * 设置 axios 列表,用于批量设置 axios 相关配置
92
+ * @param axiosList axios 实例列表
93
+ * @returns 更新后的 axios 实例列表
94
+ */
95
+ export function setAxios(axiosList: (typeof axios | typeof axiosDef)[]) {
96
+ temp.axiosList = [...temp.axiosList, ...axiosList];
97
+ return temp.axiosList;
98
+ }
99
+
100
+ /**
101
+ * axios 请求拦截器
102
+ * @param cb 成功回调
103
+ * @param errCb 错误回调
104
+ * @param opt 选项
105
+ */
106
+ export function setAxRequest(
107
+ cb: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>,
108
+ errCb?: (error: any) => Promise<any>,
109
+ opt?: HandleErrCbOptions,
110
+ ) {
111
+ temp.axiosList?.forEach((_ax) => {
112
+ _ax.interceptors.request.use(cb, handleErrCb(errCb, { logPrefix: "Error axios response: ", ...opt }));
113
+ });
114
+ }
115
+
116
+ /**
117
+ * 响应拦截器
118
+ * @param cb 成功回调
119
+ * @param errCb 错误回调
120
+ * @param opt 选项
121
+ */
122
+ export function setAxResponse(
123
+ cb: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>,
124
+ errCb?: (error) => Promise<any>,
125
+ opt?: HandleErrCbOptions,
126
+ ) {
127
+ temp.axiosList?.forEach((_ax) => {
128
+ _ax.interceptors.response.use(cb, handleErrCb(errCb, { logPrefix: "Error axios response: ", ...opt }));
129
+ });
130
+ }
131
+
132
+ /**
133
+ * 处理错误回调函数
134
+ * @param errCb 错误回调
135
+ * @param opt 选项
136
+ * @returns 处理后的错误回调
137
+ */
138
+ export function handleErrCb(errCb?: (error: any) => Promise<any>, opt?: HandleErrCbOptions) {
139
+ const { replaceErrCb, logPrefix } = opt || {};
140
+ const isFn = typeof errCb === "function";
141
+ if (replaceErrCb && isFn) {
142
+ return errCb;
143
+ }
144
+ return (error: any) => {
145
+ console.error(logPrefix, error);
146
+ return isFn ? errCb(error) : Promise.reject(error);
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 设置 axios.defaults
152
+ * @param key 配置键
153
+ * @param val 配置值
154
+ */
155
+ export function setAxDefaults(key: string, val: any) {
156
+ temp.axiosList?.forEach((_ax) => {
157
+ (_ax.defaults as any)[key] = val;
158
+ });
159
+ }
160
+
161
+ /**
162
+ * 设置 headers
163
+ * @param key header
164
+ * @param val header 值
165
+ */
166
+ export function setAxHeaders(key: string, val: any) {
167
+ temp.axiosList?.forEach((_ax) => {
168
+ (_ax.defaults.headers as any)[key] = val;
169
+ });
170
+ }
171
+
172
+ /**
173
+ * 设置 timeout
174
+ * @param timeout 超时时间
175
+ */
176
+ export function setAxTimeout(timeout = 0) {
177
+ setAxDefaults("timeout", timeout);
178
+ }
179
+
180
+ /**
181
+ * 设置 Authorization token
182
+ * @param token token 值
183
+ */
184
+ export function setAxAuthorization(token: string) {
185
+ setAxHeaders("Authorization", token);
186
+ }
187
+
188
+ /**
189
+ * 设置 baseUrl
190
+ * @param url 基础 URL
191
+ */
192
+ export function setAxBaseUrl(url: string) {
193
+ setAxDefaults("baseURL", url);
194
+ }
195
+
196
+ /**
197
+ * 获取 axios.CancelToken.source,用于取消请求
198
+ * @returns CancelToken source
199
+ */
200
+ export function getCancelTokenSource() {
201
+ return axiosDef.CancelToken.source();
202
+ }
203
+
204
+ /**
205
+ * 检查是否是 cancel 的结果
206
+ * @param res
207
+ * @returns
208
+ */
209
+ export function isCancel(res) {
210
+ return axiosDef.isCancel(res);
211
+ }
212
+
213
+ // 接口缓存处理
214
+ temp.axiosList?.forEach((_ax) => {
215
+ _ax.interceptors.response.use(
216
+ async (response: AxiosResponseCus) => {
217
+ // 存在缓存时,取缓存数据
218
+ if (response.config?.cache) {
219
+ // 有结果时结束 promise
220
+ response.config?.cache.resolve(response);
221
+ const res = await (response.config?.cache.promise as Promise<AxiosResponse>);
222
+ return res;
223
+ }
224
+ return response;
225
+ },
226
+ async (error) => {
227
+ // 取缓存数据
228
+ if (error?.cache && error?.cache.promise) {
229
+ // 有结果时结束 promise
230
+ const res = await (error?.cache.promise as Promise<AxiosResponse>);
231
+ return res;
232
+ }
233
+
234
+ // 接口取消
235
+ if (axiosDef.isCancel(error)) {
236
+ console.info("axios 请求已取消:", error);
237
+ return Promise.reject(error);
238
+ }
239
+ },
240
+ );
241
+ });
242
+
243
+ export { axios, axiosDef };
244
+
245
+ export default axios;