ph-utils 0.20.0 → 1.0.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/lib/browser.d.ts +416 -0
- package/lib/browser.js +858 -0
- package/lib/color-CUZrwrjD.js +206 -0
- package/lib/index.d.ts +615 -61
- package/lib/index.js +1539 -235
- package/lib/node.d.ts +115 -0
- package/lib/node.js +213 -0
- package/package.json +10 -64
- package/lib/array.d.ts +0 -79
- package/lib/array.js +0 -212
- package/lib/color.d.ts +0 -55
- package/lib/color.js +0 -294
- package/lib/config.d.ts +0 -33
- package/lib/config.js +0 -99
- package/lib/copy.d.ts +0 -11
- package/lib/copy.js +0 -101
- package/lib/crypto.d.ts +0 -74
- package/lib/crypto.js +0 -261
- package/lib/crypto_node.d.ts +0 -61
- package/lib/crypto_node.js +0 -133
- package/lib/date.d.ts +0 -66
- package/lib/date.js +0 -202
- package/lib/dom.d.ts +0 -265
- package/lib/dom.js +0 -635
- package/lib/file.d.ts +0 -29
- package/lib/file.js +0 -54
- package/lib/id.d.ts +0 -68
- package/lib/id.js +0 -170
- package/lib/logger.d.ts +0 -62
- package/lib/logger.js +0 -122
- package/lib/server.d.ts +0 -33
- package/lib/server.js +0 -65
- package/lib/storage.d.ts +0 -51
- package/lib/storage.js +0 -73
- package/lib/theme.d.ts +0 -44
- package/lib/theme.js +0 -156
- package/lib/validator.d.ts +0 -71
- package/lib/validator.js +0 -248
- package/lib/web.d.ts +0 -30
- package/lib/web.js +0 -100
package/lib/index.js
CHANGED
|
@@ -1,239 +1,1543 @@
|
|
|
1
|
+
import { a as toRgb, i as toHsv, n as rgbToHex, r as toHex, t as adjust } from "./color-CUZrwrjD.js";
|
|
2
|
+
//#region src/common.ts
|
|
1
3
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
+
* node 和 web 通用的基础工具函数
|
|
5
|
+
*/
|
|
4
6
|
/** 包含字母+数字的随机数字符 */
|
|
5
7
|
const RANDOM_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
6
|
-
/**
|
|
7
|
-
const
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
8
|
+
/** 只包含数字的随机数字符 */
|
|
9
|
+
const NUMBER_RANDOM_CHARS = "0123456789";
|
|
10
|
+
/**
|
|
11
|
+
* 验证参数是否为空
|
|
12
|
+
* @param str 待验证的参数
|
|
13
|
+
* @param ignoreWhitespace 如果是字符串是否忽略空格(包括空白字符串以及[\r\t\n]之类的制表符),默认为true
|
|
14
|
+
*/
|
|
15
|
+
function isBlank(str, ignoreWhitespace = true) {
|
|
16
|
+
if (str == null) return true;
|
|
17
|
+
if (typeof str !== "string") return str.length === 0;
|
|
18
|
+
return ignoreWhitespace ? str.trim().length === 0 : str.length === 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 屏蔽手机号,中间部分用 * 展示(保持向后兼容)
|
|
22
|
+
* @param mobile 待屏蔽的手机号
|
|
23
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
24
|
+
* @returns 屏蔽后的手机号,例如:138****5678
|
|
25
|
+
*/
|
|
26
|
+
function shieldMobile(mobile, placeholder = "*") {
|
|
27
|
+
return shieldString({
|
|
28
|
+
str: mobile,
|
|
29
|
+
startKeep: 3,
|
|
30
|
+
endKeep: 4,
|
|
31
|
+
placeholder,
|
|
32
|
+
addSpaces: false
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 通用字符串遮掩函数
|
|
37
|
+
* @param options 配置参数
|
|
38
|
+
* @returns 遮掩后的字符串
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // 基本遮掩: 13812345678 -> 138****5678
|
|
42
|
+
* shieldString({ str: '13812345678' })
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // 限制长度: 64位字符串 -> 只显示前20位
|
|
46
|
+
* shieldString({
|
|
47
|
+
* str: '1234567890123456789012345678901234567890123456789012345678901234',
|
|
48
|
+
* startKeep: 8,
|
|
49
|
+
* endKeep: 8,
|
|
50
|
+
* maxLength: 20
|
|
51
|
+
* })
|
|
52
|
+
* // 结果: 12345678****87654321
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // 带省略号截断
|
|
56
|
+
* shieldString({
|
|
57
|
+
* str: '1234567890123456789012345678901234567890123456789012345678901234',
|
|
58
|
+
* startKeep: 8,
|
|
59
|
+
* endKeep: 8,
|
|
60
|
+
* maxLength: 15,
|
|
61
|
+
* showEllipsis: true
|
|
62
|
+
* })
|
|
63
|
+
* // 结果: 12345678...8765
|
|
64
|
+
*/
|
|
65
|
+
function shieldString(options) {
|
|
66
|
+
const { str, startKeep = 3, endKeep = 4, placeholder = "*", addSpaces = false, customShield, maxLength, showEllipsis = true, ellipsis = "..." } = options;
|
|
67
|
+
let result;
|
|
68
|
+
if (customShield) result = customShield(str);
|
|
69
|
+
else if (str.length <= startKeep + endKeep) result = placeholder.repeat(str.length);
|
|
70
|
+
else {
|
|
71
|
+
const shieldCount = str.length - startKeep - endKeep;
|
|
72
|
+
const shieldStr = placeholder.repeat(shieldCount);
|
|
73
|
+
const startPart = str.slice(0, startKeep);
|
|
74
|
+
const endPart = str.slice(-endKeep);
|
|
75
|
+
result = addSpaces ? `${startPart} ${shieldStr} ${endPart}`.trim() : startPart + shieldStr + endPart;
|
|
76
|
+
}
|
|
77
|
+
if (maxLength != null && result.length > maxLength) {
|
|
78
|
+
if (!showEllipsis) return result.slice(0, maxLength);
|
|
79
|
+
const ellipsisLen = ellipsis.length;
|
|
80
|
+
if (maxLength <= ellipsisLen) return ellipsis.slice(0, maxLength);
|
|
81
|
+
const keepLen = maxLength - ellipsisLen;
|
|
82
|
+
const startKeepLen = Math.min(Math.floor(keepLen * .7), keepLen);
|
|
83
|
+
const endKeepLen = keepLen - startKeepLen;
|
|
84
|
+
if (startKeepLen < 3 && endKeepLen > 0) {
|
|
85
|
+
const startPart = result.slice(0, Math.min(startKeepLen, result.length));
|
|
86
|
+
const endPart = result.slice(-endKeepLen);
|
|
87
|
+
return startPart + ellipsis + endPart;
|
|
88
|
+
}
|
|
89
|
+
const startPart = result.slice(0, startKeepLen);
|
|
90
|
+
const endPart = result.slice(-endKeepLen);
|
|
91
|
+
return startPart + ellipsis + endPart;
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 简化版:遮掩并限制长度
|
|
97
|
+
* @param str 待遮掩的字符串
|
|
98
|
+
* @param options 配置选项
|
|
99
|
+
* @returns 遮掩并截断后的字符串
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // 遮掩手机号并限制显示长度
|
|
103
|
+
* shieldWithLimit('13812345678', { maxLength: 8 }) // 138****78
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // 遮掩长字符串
|
|
107
|
+
* shieldWithLimit('12345678901234567890', {
|
|
108
|
+
* startKeep: 4,
|
|
109
|
+
* endKeep: 4,
|
|
110
|
+
* maxLength: 12
|
|
111
|
+
* }) // 1234****7890
|
|
112
|
+
*/
|
|
113
|
+
function shieldWithLimit(str, options) {
|
|
114
|
+
return shieldString({
|
|
115
|
+
str,
|
|
116
|
+
...options
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 智能摘要:根据重要性自动调整保留位数
|
|
121
|
+
* @param str 待处理的字符串
|
|
122
|
+
* @param maxLength 最大输出长度
|
|
123
|
+
* @param placeholder 遮掩字符
|
|
124
|
+
* @returns 摘要后的字符串
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // 长字符串自动摘要
|
|
128
|
+
* smartSummary('12345678901234567890', 10) // 1234****90
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // 短字符串不变
|
|
132
|
+
* smartSummary('12345', 10) // 12345
|
|
133
|
+
*/
|
|
134
|
+
function smartSummary(str, maxLength, placeholder = "*") {
|
|
135
|
+
if (str.length <= maxLength) return str;
|
|
136
|
+
if (maxLength < 3) return str.slice(0, maxLength);
|
|
137
|
+
const totalKeep = maxLength - placeholder.length;
|
|
138
|
+
const startKeep = Math.floor(totalKeep / 2);
|
|
139
|
+
const endKeep = totalKeep - startKeep;
|
|
140
|
+
return shieldString({
|
|
141
|
+
str,
|
|
142
|
+
startKeep: Math.max(1, startKeep),
|
|
143
|
+
endKeep: Math.max(1, endKeep),
|
|
144
|
+
placeholder,
|
|
145
|
+
maxLength,
|
|
146
|
+
showEllipsis: false
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 屏蔽邮箱
|
|
151
|
+
* @param email 待屏蔽的邮箱
|
|
152
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
153
|
+
* @returns 屏蔽后的邮箱,例如:exa****@email.com
|
|
154
|
+
*/
|
|
155
|
+
function shieldEmail(email, placeholder = "*") {
|
|
156
|
+
const [name, domain] = email.split("@");
|
|
157
|
+
if (!domain) return email;
|
|
158
|
+
const keepLength = Math.min(3, Math.floor(name.length / 2));
|
|
159
|
+
const shieldCount = name.length - keepLength;
|
|
160
|
+
const shieldStr = placeholder.repeat(Math.min(shieldCount, 4));
|
|
161
|
+
return `${name.slice(0, keepLength)}${shieldStr}@${domain}`;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 屏蔽身份证号
|
|
165
|
+
* @param idCard 待屏蔽的身份证号
|
|
166
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
167
|
+
* @returns 屏蔽后的身份证号,例如:110101********1234
|
|
168
|
+
*/
|
|
169
|
+
function shieldIdCard(idCard, placeholder = "*") {
|
|
170
|
+
return shieldString({
|
|
171
|
+
str: idCard,
|
|
172
|
+
startKeep: 6,
|
|
173
|
+
endKeep: 4,
|
|
174
|
+
placeholder,
|
|
175
|
+
addSpaces: false
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 屏蔽银行卡号(带空格格式化)
|
|
180
|
+
* @param bankCard 待屏蔽的银行卡号
|
|
181
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
182
|
+
* @returns 屏蔽后的银行卡号,例如:622848 **** **** 9018
|
|
183
|
+
*/
|
|
184
|
+
function shieldBankCard(bankCard, placeholder = "*") {
|
|
185
|
+
return shieldString({
|
|
186
|
+
str: bankCard,
|
|
187
|
+
startKeep: 6,
|
|
188
|
+
endKeep: 4,
|
|
189
|
+
placeholder,
|
|
190
|
+
addSpaces: true
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 屏蔽姓名
|
|
195
|
+
* @param name 待屏蔽的姓名
|
|
196
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
197
|
+
* @returns 屏蔽后的姓名,例如:张** 或 张*三
|
|
198
|
+
*/
|
|
199
|
+
function shieldName(name, placeholder = "*") {
|
|
200
|
+
if (name.length <= 2) return name.charAt(0) + placeholder.repeat(name.length - 1);
|
|
201
|
+
return shieldString({
|
|
202
|
+
str: name,
|
|
203
|
+
startKeep: 1,
|
|
204
|
+
endKeep: 1,
|
|
205
|
+
placeholder,
|
|
206
|
+
addSpaces: false
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 检测敏感信息并智能遮掩(支持长度控制)
|
|
211
|
+
* @param str 待检测的字符串
|
|
212
|
+
* @param placeholder 遮掩字符,默认: '*'
|
|
213
|
+
* @param maxLength 最大输出长度,默认: 不限制
|
|
214
|
+
* @returns 遮掩后的字符串,自动识别类型
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* // 手机号:138****5678
|
|
218
|
+
* smartShield('13812345678')
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* // 邮箱:exa****@email.com
|
|
222
|
+
* smartShield('example@email.com')
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* // 身份证:110101********1234
|
|
226
|
+
* smartShield('110101199001011234')
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // 限制长度
|
|
230
|
+
* smartShield('13812345678', '*', 8) // 138****78
|
|
231
|
+
*/
|
|
232
|
+
function smartShield(str, placeholder = "*", maxLength) {
|
|
233
|
+
const cleanStr = str.replace(/\s/g, "");
|
|
234
|
+
let result;
|
|
235
|
+
if (/^1\d{10}$/.test(cleanStr)) result = shieldMobile(cleanStr, placeholder);
|
|
236
|
+
else if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(cleanStr)) result = shieldEmail(cleanStr, placeholder);
|
|
237
|
+
else if (/^[1-9]\d{14}(\d{2}[\dXx])?$/.test(cleanStr)) result = shieldIdCard(cleanStr, placeholder);
|
|
238
|
+
else if (/^\d{16,19}$/.test(cleanStr)) result = shieldBankCard(cleanStr, placeholder);
|
|
239
|
+
else {
|
|
240
|
+
const keepLength = Math.floor(cleanStr.length / 3);
|
|
241
|
+
result = shieldString({
|
|
242
|
+
str: cleanStr,
|
|
243
|
+
startKeep: keepLength,
|
|
244
|
+
endKeep: keepLength,
|
|
245
|
+
placeholder,
|
|
246
|
+
addSpaces: false
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (maxLength != null && result.length > maxLength) return result.slice(0, maxLength);
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 验证参数是否是数字
|
|
254
|
+
* @param str 待验证的字符串
|
|
255
|
+
* @param numericParam 通过参数标记是否包含小数、正数
|
|
256
|
+
* @param numericParam.isPositive 是否是正数, 默认: false
|
|
257
|
+
* @param numericParam.isFloat 是否是小数, 默认: true
|
|
258
|
+
* @returns true 是数字, false 不是数字
|
|
259
|
+
*/
|
|
260
|
+
function isNumeric(str, numericParam) {
|
|
261
|
+
const { isPositive = false, isFloat = true } = numericParam ?? {};
|
|
262
|
+
return new RegExp(`^${isPositive ? "[+]?" : "[+-]?"}${isFloat ? "([0-9]*[.])?[0-9]+" : "[0-9]+"}$`).test(str);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 验证参数是否是Boolean 类型
|
|
266
|
+
* @param str 待验证的字符串
|
|
267
|
+
* @returns
|
|
268
|
+
*/
|
|
269
|
+
function isBoolean(str) {
|
|
270
|
+
return ["true", "false"].includes(str);
|
|
271
|
+
}
|
|
272
|
+
function random(opts) {
|
|
273
|
+
if (typeof opts === "object" && opts !== null && "min" in opts && "max" in opts) {
|
|
274
|
+
const { min, max, hasEnd = false, isInteger = true } = opts;
|
|
275
|
+
const end = hasEnd ? 1 : 0;
|
|
276
|
+
const resRandom = Math.random() * (max - min + end) + min;
|
|
277
|
+
return isInteger ? Math.floor(resRandom) : resRandom;
|
|
278
|
+
}
|
|
279
|
+
const len = typeof opts === "object" ? opts.length : opts;
|
|
280
|
+
if (len == null) throw new Error("random_length_cannot_null");
|
|
281
|
+
const chars = typeof opts === "object" && opts.hasLetter === false ? NUMBER_RANDOM_CHARS : RANDOM_CHARS;
|
|
282
|
+
const charsLen = chars.length;
|
|
283
|
+
const generate = () => {
|
|
284
|
+
const result = [];
|
|
285
|
+
let i = 0;
|
|
286
|
+
while (i < len) {
|
|
287
|
+
result.push(chars[Math.floor(Math.random() * charsLen)]);
|
|
288
|
+
i++;
|
|
289
|
+
}
|
|
290
|
+
return result.join("");
|
|
291
|
+
};
|
|
292
|
+
const result = generate();
|
|
293
|
+
if (typeof opts === "object" && opts.firstIsZero === false && result.at(0) === "0") return random(opts);
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 将金额数字格式化为金额格式显示并且会保留两位小数[去除多余的位数,不是四舍五入,而是直接舍去] 1234523432.23 => 123,123,123.23
|
|
298
|
+
* @param {number} number 待转换的金额数字
|
|
299
|
+
* @return string
|
|
300
|
+
*/
|
|
301
|
+
function formatMoney(number) {
|
|
302
|
+
if (!Number.isFinite(number)) return "0.00";
|
|
303
|
+
return new Intl.NumberFormat("zh-CN", {
|
|
304
|
+
style: "decimal",
|
|
305
|
+
maximumFractionDigits: 2,
|
|
306
|
+
minimumFractionDigits: 2
|
|
307
|
+
}).format(number);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 将风格由大写风格转换为下划线风格: HelloWorld -> hello-world
|
|
311
|
+
* @param name 命名, 例如: HelloWorld
|
|
312
|
+
* @param connector 连接符, 默认为: _
|
|
313
|
+
*/
|
|
314
|
+
function snakeCaseStyle(name, connector = "-") {
|
|
315
|
+
return name.replace(/([A-Z])/g, (match, p1, offset) => offset > 0 ? connector + match.toLowerCase() : match.toLowerCase());
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 对数字进行四舍五入处理
|
|
319
|
+
* @param num 需要进行四舍五入的数字
|
|
320
|
+
* @param precision 精度,默认为2,即保留小数点后两位
|
|
321
|
+
* @param roundType 舍入类型,默认为0,提供三种取值:
|
|
322
|
+
* 0: 标准四舍五入
|
|
323
|
+
* 1: 向上取整
|
|
324
|
+
* 2: 向下取整
|
|
325
|
+
* @returns 返回经过指定方式舍入后的数字
|
|
326
|
+
*/
|
|
327
|
+
function round(num, precision = 2, roundType = 0) {
|
|
328
|
+
if (!Number.isFinite(num)) return num;
|
|
329
|
+
const factor = 10 ** precision;
|
|
330
|
+
const roundFn = {
|
|
331
|
+
0: Math.round,
|
|
332
|
+
1: Math.ceil,
|
|
333
|
+
2: Math.floor
|
|
334
|
+
}[roundType];
|
|
335
|
+
if (!roundFn) return num;
|
|
336
|
+
return roundFn(num * factor) / factor;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 反转字符串
|
|
340
|
+
*/
|
|
341
|
+
function reverseStr(str) {
|
|
342
|
+
return str.split("").reverse().join("");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* 嵌套的 json 指定 key 数据
|
|
346
|
+
* @param data JSON格式数据
|
|
347
|
+
* @param keys 待获取的数据 key, 可以通过 [.] 获取嵌套数据, 例如: a.b.c
|
|
348
|
+
* @returns
|
|
349
|
+
*/
|
|
350
|
+
function getJSONValue(data, keystr) {
|
|
351
|
+
if (data == null) return null;
|
|
352
|
+
return keystr.split(".").reduce((acc, key) => acc?.[key] ?? null, data);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* 数据格式化主要用于数据类型转换
|
|
356
|
+
* @param data 待转换数据类型的数据
|
|
357
|
+
* @param config 转换配置
|
|
358
|
+
* @returns
|
|
359
|
+
*/
|
|
360
|
+
function formatData(data, config) {
|
|
361
|
+
if (data == null) return data;
|
|
362
|
+
const { numberFields = [], stringFields = [], formatter = {} } = config ?? {};
|
|
363
|
+
return Object.fromEntries(Object.entries(data).map(([key, value]) => {
|
|
364
|
+
let formater = formatter[key];
|
|
365
|
+
if (formater == null) {
|
|
366
|
+
if (numberFields.includes(key)) formater = "number";
|
|
367
|
+
else if (stringFields.includes(key)) formater = "string";
|
|
368
|
+
}
|
|
369
|
+
let newValue = value;
|
|
370
|
+
if (formater != null) {
|
|
371
|
+
if (typeof formater === "function") newValue = formater(value);
|
|
372
|
+
else if (formater === "number") newValue = Number(value);
|
|
373
|
+
else if (formater === "string") newValue = String(value);
|
|
374
|
+
}
|
|
375
|
+
return [key, newValue];
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/date.ts
|
|
380
|
+
/**
|
|
381
|
+
* node 和 web 端日期处理工具类
|
|
382
|
+
*/
|
|
383
|
+
const REGEX_FORMAT = /yy(?:yy)?|([HMmds])\1?|(S)?/g;
|
|
384
|
+
const REGEX_PARSE = /^(\d{4})-?(\d{1,2})-?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d{1,3})?$/;
|
|
385
|
+
const ofArgs = {
|
|
386
|
+
start: [
|
|
387
|
+
0,
|
|
388
|
+
0,
|
|
389
|
+
1,
|
|
390
|
+
0,
|
|
391
|
+
0,
|
|
392
|
+
0,
|
|
393
|
+
0
|
|
394
|
+
],
|
|
395
|
+
end: [
|
|
396
|
+
0,
|
|
397
|
+
11,
|
|
398
|
+
-2,
|
|
399
|
+
23,
|
|
400
|
+
59,
|
|
401
|
+
59,
|
|
402
|
+
999
|
|
403
|
+
]
|
|
404
|
+
};
|
|
405
|
+
const units = {
|
|
406
|
+
Date: [
|
|
407
|
+
"date",
|
|
408
|
+
"Date",
|
|
409
|
+
"day",
|
|
410
|
+
"Day",
|
|
411
|
+
"D",
|
|
412
|
+
"d"
|
|
413
|
+
],
|
|
414
|
+
Month: [
|
|
415
|
+
"Month",
|
|
416
|
+
"month",
|
|
417
|
+
"m"
|
|
418
|
+
],
|
|
419
|
+
FullYear: [
|
|
420
|
+
"Year",
|
|
421
|
+
"year",
|
|
422
|
+
"y"
|
|
423
|
+
],
|
|
424
|
+
Hours: [
|
|
425
|
+
"Hours",
|
|
426
|
+
"hours",
|
|
427
|
+
"H"
|
|
428
|
+
],
|
|
429
|
+
Minutes: [
|
|
430
|
+
"Minutes",
|
|
431
|
+
"Minute",
|
|
432
|
+
"minute",
|
|
433
|
+
"minutes",
|
|
434
|
+
"M"
|
|
435
|
+
],
|
|
436
|
+
Seconds: [
|
|
437
|
+
"Seconds",
|
|
438
|
+
"seconds",
|
|
439
|
+
"Second",
|
|
440
|
+
"second",
|
|
441
|
+
"s"
|
|
442
|
+
],
|
|
443
|
+
Milliseconds: [
|
|
444
|
+
"Milliseconds",
|
|
445
|
+
"Millisecond",
|
|
446
|
+
"milliseconds",
|
|
447
|
+
"illisecond",
|
|
448
|
+
"S"
|
|
449
|
+
]
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* 不足位数, 前位补 0
|
|
453
|
+
* @param s 日期数字
|
|
454
|
+
* @param l 截取位数
|
|
455
|
+
* @returns {string} 补0后的日期数字
|
|
456
|
+
*/
|
|
457
|
+
function p(s, l = 2) {
|
|
458
|
+
return `000${s}`.slice(l * -1);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 将单位转换为首字母大写, 例如:hours -> Hours
|
|
462
|
+
* @param unit hours
|
|
463
|
+
* @returns
|
|
464
|
+
*/
|
|
465
|
+
function getUnit(unit) {
|
|
466
|
+
let period = null;
|
|
467
|
+
for (const [key, value] of Object.entries(units)) if (value.includes(unit)) {
|
|
468
|
+
period = key;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
if (period == null) throw new Error(`Invalid unit: ${unit}`);
|
|
472
|
+
return period;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* 获取指定日期某个月的最后一天
|
|
476
|
+
* @param date 日期
|
|
477
|
+
* @param month 月份, 如果不传, 则当前月的最后一天
|
|
478
|
+
* @returns
|
|
479
|
+
*/
|
|
480
|
+
function getLastDayOfMonth(date, month) {
|
|
481
|
+
const lastDate = new Date(date.getFullYear(), (month || date.getMonth()) + 1, 1);
|
|
482
|
+
lastDate.setDate(lastDate.getDate() - 1);
|
|
483
|
+
return lastDate.getDate();
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* 将日期格式化为指定形式的字符串
|
|
487
|
+
* @param date 日期
|
|
488
|
+
* @param pattern 格式化字符串 yyyy - 年, mm - 月, dd - 日, HH - 小时(24时制), MM - 分钟, ss - 秒, S - 毫秒, 默认: yyyy-mm-dd HH:MM:ss
|
|
489
|
+
*/
|
|
490
|
+
function format(date, pattern = "yyyy-mm-dd HH:MM:ss") {
|
|
491
|
+
date = parse(date);
|
|
492
|
+
const d = date.getDate();
|
|
493
|
+
const y = date.getFullYear();
|
|
494
|
+
const m = date.getMonth();
|
|
495
|
+
const H = date.getHours();
|
|
496
|
+
const M = date.getMinutes();
|
|
497
|
+
const s = date.getSeconds();
|
|
498
|
+
const flags = {
|
|
499
|
+
yy: p(y),
|
|
500
|
+
yyyy: y,
|
|
501
|
+
m: m + 1,
|
|
502
|
+
mm: p(m + 1),
|
|
503
|
+
d,
|
|
504
|
+
dd: p(d),
|
|
505
|
+
H,
|
|
506
|
+
HH: p(H),
|
|
507
|
+
M,
|
|
508
|
+
MM: p(M),
|
|
509
|
+
s,
|
|
510
|
+
ss: p(s),
|
|
511
|
+
S: p(date.getMilliseconds(), 3)
|
|
512
|
+
};
|
|
513
|
+
if (pattern != null) return pattern.replace(REGEX_FORMAT, (flag) => {
|
|
514
|
+
if (flag in flags) return flags[flag];
|
|
515
|
+
return flag;
|
|
516
|
+
});
|
|
517
|
+
return String(date.getTime());
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* 将指定的参数解析为日期对象(Date)
|
|
521
|
+
* 参考 dayjs 实现, 也可以参考 https://github.com/nomiddlename/date-format
|
|
522
|
+
* @param date 待解析的日期参数
|
|
523
|
+
*/
|
|
524
|
+
function parse(date) {
|
|
525
|
+
if (date == null) return /* @__PURE__ */ new Date();
|
|
526
|
+
if (typeof date === "string" && !/Z$/i.test(date)) {
|
|
527
|
+
const d = date.match(REGEX_PARSE);
|
|
528
|
+
if (d) return new Date(d[1], d[2] - 1, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, d[7] || 0);
|
|
529
|
+
}
|
|
530
|
+
if (typeof date === "number") return new Date(date <= 9999999999 ? date * 1e3 : date);
|
|
531
|
+
return new Date(date);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* 设置日期的开始或者结束的点
|
|
535
|
+
* @param date 日期,能够被 parse 解析的日期
|
|
536
|
+
* @param unit 单位,Date[D]、Minute[M], 默认为 Date
|
|
537
|
+
* @param isEnd true则为 endOf
|
|
538
|
+
*/
|
|
539
|
+
function dateOf(date, unit, isEnd = false) {
|
|
540
|
+
const periods = [
|
|
541
|
+
"Year",
|
|
542
|
+
"Month",
|
|
543
|
+
"Date",
|
|
544
|
+
"Hours",
|
|
545
|
+
"Minutes",
|
|
546
|
+
"Seconds",
|
|
547
|
+
"Milliseconds"
|
|
548
|
+
];
|
|
549
|
+
let index = periods.indexOf(getUnit(unit || "Date"));
|
|
550
|
+
const clone = parse(date);
|
|
551
|
+
index++;
|
|
552
|
+
const setValues = ofArgs[isEnd === true ? "end" : "start"];
|
|
553
|
+
for (const len = periods.length; index < len; index++) {
|
|
554
|
+
let value = setValues[index];
|
|
555
|
+
if (value === -2) value = getLastDayOfMonth(clone);
|
|
556
|
+
Date.prototype["set" + periods[index]].apply(clone, [setValues[index]]);
|
|
557
|
+
}
|
|
558
|
+
return clone;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* 设置日期的开始的点
|
|
562
|
+
* @param date 日期,能够被 parse 解析的日期
|
|
563
|
+
* @param unit 单位,Date[D]、Minute[M], 默认为 Date
|
|
564
|
+
* @returns
|
|
565
|
+
*/
|
|
566
|
+
function startOf(date, unit) {
|
|
567
|
+
return dateOf(date, unit);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 设置日期的结束点
|
|
571
|
+
* @param date 日期,能够被 parse 解析的日期
|
|
572
|
+
* @param unit 单位,Date[D]、Minute[M], 默认为 Date
|
|
573
|
+
* @returns
|
|
574
|
+
*/
|
|
575
|
+
function endOf(date, unit) {
|
|
576
|
+
return dateOf(date, unit, true);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* 获取时间戳
|
|
580
|
+
* @param ctime 时间
|
|
581
|
+
* @param pre 精度, s - 精确到秒, ms - 精确到毫秒, 默认: s
|
|
582
|
+
* @returns
|
|
583
|
+
*/
|
|
584
|
+
function timestamp(ctime, pre = "s") {
|
|
585
|
+
let tm = parse(ctime).getTime();
|
|
586
|
+
return pre === "s" ? Math.floor(tm / 1e3) : tm;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* 日期加上指定时间后的日期
|
|
590
|
+
* @param date 指定的日期
|
|
591
|
+
* @param num 需要添加的数字, 如果这个参数传递一个小于0的数字,则就是日期减去相应的数字
|
|
592
|
+
* @param unit 需要添加的单位,date - 加减天数
|
|
593
|
+
* @param fmt 可选参数,如果传递了格式化的单位,则返回格式化后的日期, 格式化字符串 yyyy - 年, mm - 月, dd - 日, HH - 小时, MM - 分钟, ss - 秒
|
|
594
|
+
* @returns {Date | string} 如果传递了 fmt 参数,则返回 string,否则返回 Date
|
|
595
|
+
*/
|
|
596
|
+
function add(date, num, unit, fmt) {
|
|
597
|
+
let sdate = /* @__PURE__ */ new Date();
|
|
598
|
+
if (date != null) sdate = parse(date);
|
|
599
|
+
unit = getUnit(unit);
|
|
600
|
+
let fn = "set" + unit;
|
|
601
|
+
let gn = "get" + unit;
|
|
602
|
+
let oldValue = Date.prototype[gn].apply(sdate);
|
|
603
|
+
Date.prototype[fn].apply(sdate, [oldValue + num]);
|
|
604
|
+
if (typeof fmt === "string") return format(sdate, fmt);
|
|
605
|
+
else return sdate;
|
|
606
|
+
}
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/array.ts
|
|
609
|
+
/**
|
|
610
|
+
* 全局 Collator 单例缓存,避免重复创建
|
|
611
|
+
*/
|
|
612
|
+
let _collator;
|
|
613
|
+
/**
|
|
614
|
+
* 获取 Intl.Collator 单例(支持数字感知的字符串比较,如 "a2" < "a10")
|
|
615
|
+
* 使用 ??= 运算符实现惰性初始化
|
|
616
|
+
*/
|
|
617
|
+
function _getCollator() {
|
|
618
|
+
return _collator ?? (_collator = new Intl.Collator(void 0, { numeric: true }));
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* 数组排序(不修改原数组,返回新数组)
|
|
622
|
+
* @param arr 待排序数组
|
|
623
|
+
* @param order 排序方向: "asc" - 升序(默认), "desc" - 降序
|
|
624
|
+
* @param orderKey 如果数组元素是对象,指定按哪个字段排序;为 null 时按元素本身排序
|
|
625
|
+
* @returns 排序后的新数组
|
|
626
|
+
*/
|
|
627
|
+
function order(arr, order = "asc", orderKey = null) {
|
|
628
|
+
const collator = _getCollator();
|
|
629
|
+
const sign = order === "asc" ? 1 : -1;
|
|
630
|
+
const get = orderKey == null ? (v) => v : (v) => v[orderKey];
|
|
631
|
+
return [...arr].sort((a, b) => {
|
|
632
|
+
const av = get(a);
|
|
633
|
+
const bv = get(b);
|
|
634
|
+
return (typeof av === "string" ? collator.compare(av, bv) : av < bv ? -1 : av > bv ? 1 : 0) * sign;
|
|
635
|
+
});
|
|
239
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* 获取集合大小(兼容 Set 和 Array)
|
|
639
|
+
* @param v Set 或 Array
|
|
640
|
+
* @returns 元素个数
|
|
641
|
+
*/
|
|
642
|
+
function _size(v) {
|
|
643
|
+
return v instanceof Set ? v.size : v.length;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* 将 Set 或 Array 统一转为 Set(如果已经是 Set 则直接返回,避免重复创建)
|
|
647
|
+
* @param v Set 或 Array
|
|
648
|
+
* @returns Set 实例
|
|
649
|
+
*/
|
|
650
|
+
function _toSet(v) {
|
|
651
|
+
return v instanceof Set ? v : new Set(v);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* 检测对象上是否存在可用的原生方法(ES2025 Set 新方法检测)
|
|
655
|
+
* @param obj 目标对象
|
|
656
|
+
* @param name 方法名
|
|
657
|
+
* @returns 是否为可调用的函数
|
|
658
|
+
*/
|
|
659
|
+
function _hasNative(obj, name) {
|
|
660
|
+
return typeof obj[name] === "function";
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* 返回所有集合交集的元素组成的新集合
|
|
664
|
+
* - 以第一个参数的类型决定返回 Set 还是 Array
|
|
665
|
+
* - 支持混合传入 Set 和 Array
|
|
666
|
+
* - 优先使用原生 ES2025 Set.prototype.intersection
|
|
667
|
+
* @param arrs 多个集合/数组
|
|
668
|
+
* @returns 交集结果(Set 或 Array)
|
|
669
|
+
*/
|
|
670
|
+
function intersection(...arrs) {
|
|
671
|
+
const isSet = arrs[0] instanceof Set;
|
|
672
|
+
let acc = isSet ? new Set(arrs[0]) : [...arrs[0]];
|
|
673
|
+
for (let i = 1; i < arrs.length && _size(acc) > 0; i++) {
|
|
674
|
+
const cur = arrs[i];
|
|
675
|
+
if (_size(cur) === 0) return isSet ? /* @__PURE__ */ new Set() : [];
|
|
676
|
+
if (isSet) {
|
|
677
|
+
const accSet = acc;
|
|
678
|
+
const curSet = _toSet(cur);
|
|
679
|
+
acc = _hasNative(accSet, "intersection") ? accSet.intersection(curSet) : _intersectFallback(accSet, curSet);
|
|
680
|
+
} else {
|
|
681
|
+
const curSet = _toSet(cur);
|
|
682
|
+
acc = acc.filter((x) => curSet.has(x));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return acc;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* 交集回退实现(不支持原生 intersection 时使用)
|
|
689
|
+
* 性能优化:始终遍历较小的集合,减少迭代次数
|
|
690
|
+
* @param a 集合 A
|
|
691
|
+
* @param b 集合 B
|
|
692
|
+
* @returns 交集 Set
|
|
693
|
+
*/
|
|
694
|
+
function _intersectFallback(a, b) {
|
|
695
|
+
const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
|
|
696
|
+
const res = /* @__PURE__ */ new Set();
|
|
697
|
+
for (const v of smaller) if (larger.has(v)) res.add(v);
|
|
698
|
+
return res;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* 返回第一个集合中有但后续所有集合中没有的元素组成的新集合
|
|
702
|
+
* - 优先使用原生 ES2025 Set.prototype.difference
|
|
703
|
+
* - 数组模式使用 Set.has 替代 includes,性能从 O(n×m) 降至 O(n+m)
|
|
704
|
+
* @param arrs 多个集合/数组
|
|
705
|
+
* @returns 差集结果(Set 或 Array)
|
|
706
|
+
*/
|
|
707
|
+
function difference(...arrs) {
|
|
708
|
+
const isSet = arrs[0] instanceof Set;
|
|
709
|
+
let acc = isSet ? new Set(arrs[0]) : [...arrs[0]];
|
|
710
|
+
for (let i = 1; i < arrs.length && _size(acc) > 0; i++) {
|
|
711
|
+
const curSet = _toSet(arrs[i]);
|
|
712
|
+
if (isSet) {
|
|
713
|
+
const accSet = acc;
|
|
714
|
+
acc = _hasNative(accSet, "difference") ? accSet.difference(curSet) : _filterSet(accSet, (v) => !curSet.has(v));
|
|
715
|
+
} else acc = acc.filter((v) => !curSet.has(v));
|
|
716
|
+
}
|
|
717
|
+
return acc;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Set 过滤辅助函数:返回满足条件的新 Set
|
|
721
|
+
* @param set 源集合
|
|
722
|
+
* @param predicate 过滤条件函数
|
|
723
|
+
* @returns 过滤后的新 Set
|
|
724
|
+
*/
|
|
725
|
+
function _filterSet(set, predicate) {
|
|
726
|
+
const res = /* @__PURE__ */ new Set();
|
|
727
|
+
for (const v of set) if (predicate(v)) res.add(v);
|
|
728
|
+
return res;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* 返回多个集合的并集
|
|
732
|
+
* - 优先使用原生 ES2025 Set.prototype.union
|
|
733
|
+
* - Set 模式自动去重;Array 模式保持原有行为(拼接,不去重)
|
|
734
|
+
* @param arrs 多个集合/数组
|
|
735
|
+
* @returns 并集结果(Set 或 Array)
|
|
736
|
+
*/
|
|
737
|
+
function union(...arrs) {
|
|
738
|
+
const isSet = arrs[0] instanceof Set;
|
|
739
|
+
let acc = isSet ? new Set(arrs[0]) : [...arrs[0]];
|
|
740
|
+
for (let i = 1; i < arrs.length; i++) {
|
|
741
|
+
const cur = arrs[i];
|
|
742
|
+
if (isSet) {
|
|
743
|
+
const accSet = acc;
|
|
744
|
+
const curSet = _toSet(cur);
|
|
745
|
+
acc = _hasNative(accSet, "union") ? accSet.union(curSet) : /* @__PURE__ */ new Set([...accSet, ...curSet]);
|
|
746
|
+
} else acc = [...acc, ...cur];
|
|
747
|
+
}
|
|
748
|
+
return acc;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* 对称差:返回只存在于其中一个集合中的元素(不同时存在于两个集合中的元素)
|
|
752
|
+
* - 优先使用原生 ES2025 Set.prototype.symmetricDifference
|
|
753
|
+
* - 数组模式使用 Set.has 替代 includes,性能大幅提升
|
|
754
|
+
* @param arrs 多个集合/数组
|
|
755
|
+
* @returns 对称差结果(Set 或 Array)
|
|
756
|
+
*/
|
|
757
|
+
function symmetricDifference(...arrs) {
|
|
758
|
+
const isSet = arrs[0] instanceof Set;
|
|
759
|
+
let acc = isSet ? new Set(arrs[0]) : [...arrs[0]];
|
|
760
|
+
for (let i = 1; i < arrs.length; i++) {
|
|
761
|
+
const cur = arrs[i];
|
|
762
|
+
if (isSet) {
|
|
763
|
+
const accSet = acc;
|
|
764
|
+
const curSet = _toSet(cur);
|
|
765
|
+
acc = _hasNative(accSet, "symmetricDifference") ? accSet.symmetricDifference(curSet) : _symDiffSetFallback(accSet, curSet);
|
|
766
|
+
} else acc = _symDiffArrayFallback(acc, cur);
|
|
767
|
+
}
|
|
768
|
+
return acc;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Set 对称差回退实现
|
|
772
|
+
* 逻辑:A△B = (A-B) ∪ (B-A)
|
|
773
|
+
* @param a 集合 A
|
|
774
|
+
* @param b 集合 B
|
|
775
|
+
* @returns 对称差 Set
|
|
776
|
+
*/
|
|
777
|
+
function _symDiffSetFallback(a, b) {
|
|
778
|
+
const res = /* @__PURE__ */ new Set();
|
|
779
|
+
for (const v of a) if (!b.has(v)) res.add(v);
|
|
780
|
+
for (const v of b) if (!a.has(v)) res.add(v);
|
|
781
|
+
return res;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* 数组对称差回退实现
|
|
785
|
+
* 性能优化:将两个数组都转为 Set,查找从 O(n) 降至 O(1)
|
|
786
|
+
* @param a 数组 A
|
|
787
|
+
* @param b 数组 B
|
|
788
|
+
* @returns 对称差数组
|
|
789
|
+
*/
|
|
790
|
+
function _symDiffArrayFallback(a, b) {
|
|
791
|
+
const aSet = new Set(a);
|
|
792
|
+
const bSet = new Set(b);
|
|
793
|
+
const res = [];
|
|
794
|
+
for (const v of a) if (!bSet.has(v)) res.push(v);
|
|
795
|
+
for (const v of b) if (!aSet.has(v)) res.push(v);
|
|
796
|
+
return res;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 判断 a1 是否是 a2 的子集(a1 的所有元素是否都在 a2 中)
|
|
800
|
+
* - 优先使用原生 ES2025 Set.prototype.isSubsetOf
|
|
801
|
+
* - 数组模式使用 Set.has 替代 includes,性能优化
|
|
802
|
+
* @param a1 待判断集合(可能是子集)
|
|
803
|
+
* @param a2 目标集合(可能是超集)
|
|
804
|
+
* @returns a1 ⊆ a2 时返回 true
|
|
805
|
+
*/
|
|
806
|
+
function isSubsetOf(a1, a2) {
|
|
807
|
+
if (a1 instanceof Set && _hasNative(a1, "isSubsetOf")) return a1.isSubsetOf(_toSet(a2));
|
|
808
|
+
const set2 = _toSet(a2);
|
|
809
|
+
for (const item of a1) if (!set2.has(item)) return false;
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* 判断 a1 是否是 a2 的超集(a2 的所有元素是否都在 a1 中)
|
|
814
|
+
* 逻辑等价于:a2 是 a1 的子集
|
|
815
|
+
* @param a1 待判断集合(可能是超集)
|
|
816
|
+
* @param a2 目标集合(可能是子集)
|
|
817
|
+
* @returns a1 ⊇ a2 时返回 true
|
|
818
|
+
*/
|
|
819
|
+
function isSupersetOf(a1, a2) {
|
|
820
|
+
return isSubsetOf(a2, a1);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* 判断两个集合是否没有公共元素(不相交)
|
|
824
|
+
* - 优先使用原生 ES2025 Set.prototype.isDisjointFrom
|
|
825
|
+
* - 数组模式使用 Set.has 替代 includes,性能优化
|
|
826
|
+
* @param a1 集合 1
|
|
827
|
+
* @param a2 集合 2
|
|
828
|
+
* @returns 两集合无交集时返回 true
|
|
829
|
+
*/
|
|
830
|
+
function isDisjointFrom(a1, a2) {
|
|
831
|
+
if (a1 instanceof Set && _hasNative(a1, "isDisjointFrom")) return a1.isDisjointFrom(_toSet(a2));
|
|
832
|
+
const set2 = _toSet(a2);
|
|
833
|
+
for (const item of a1) if (set2.has(item)) return false;
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
//#endregion
|
|
837
|
+
//#region src/id.ts
|
|
838
|
+
/** 雪花ID, 推荐在全局构造一个对象用于生成id */
|
|
839
|
+
var SnowflakeID = class SnowflakeID {
|
|
840
|
+
/**
|
|
841
|
+
* 构造函数
|
|
842
|
+
*
|
|
843
|
+
* @param machineId 机器标识,默认为1
|
|
844
|
+
* @param epoch 时间戳起始值,默认为1288834974657
|
|
845
|
+
* @param maxClockTolerance 允许的最大时钟回拨毫秒数,默认为5
|
|
846
|
+
*/
|
|
847
|
+
constructor(machineId = 1, epoch = 1288834974657, maxClockTolerance = SnowflakeID.DEFAULT_MAX_CLOCK_TOLERANCE) {
|
|
848
|
+
this._version = 0;
|
|
849
|
+
if (!Number.isSafeInteger(machineId) || machineId < 0 || machineId > SnowflakeID.MAX_MACHINE_ID) throw new Error(`machineId must be a safe integer between 0 and ${SnowflakeID.MAX_MACHINE_ID}, got ${machineId}`);
|
|
850
|
+
if (!Number.isSafeInteger(epoch) || epoch < 0 || epoch > Date.now()) throw new Error(`epoch must be a non-negative safe integer not later than the current time, got ${epoch}`);
|
|
851
|
+
if (!Number.isSafeInteger(maxClockTolerance) || maxClockTolerance < 0) throw new Error(`maxClockTolerance must be a non-negative safe integer, got ${maxClockTolerance}`);
|
|
852
|
+
this.machineId = BigInt(machineId);
|
|
853
|
+
this.epoch = BigInt(epoch);
|
|
854
|
+
this.maxClockTolerance = maxClockTolerance;
|
|
855
|
+
const stateKey = `${this.epoch}:${this.machineId}`;
|
|
856
|
+
let state = SnowflakeID.STATES.get(stateKey);
|
|
857
|
+
if (state == null) {
|
|
858
|
+
state = {
|
|
859
|
+
lastTimestamp: 0n,
|
|
860
|
+
sequence: 0n
|
|
861
|
+
};
|
|
862
|
+
SnowflakeID.STATES.set(stateKey, state);
|
|
863
|
+
}
|
|
864
|
+
this._state = state;
|
|
865
|
+
}
|
|
866
|
+
get version() {
|
|
867
|
+
return this._version;
|
|
868
|
+
}
|
|
869
|
+
set version(version) {
|
|
870
|
+
if (!Number.isInteger(version) || version < 0 || version > 9) throw new Error(`version must be an integer between 0 and 9, got ${version}`);
|
|
871
|
+
this._version = version;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* 生成雪花ID
|
|
875
|
+
*
|
|
876
|
+
* @returns 返回生成的唯一ID字符串
|
|
877
|
+
* @throws 如果时钟回拨超过容差阈值,抛出错误
|
|
878
|
+
*/
|
|
879
|
+
generate() {
|
|
880
|
+
let cTimestamp = BigInt(Date.now());
|
|
881
|
+
if (cTimestamp < this._state.lastTimestamp) {
|
|
882
|
+
const drift = Number(this._state.lastTimestamp - cTimestamp);
|
|
883
|
+
if (drift > this.maxClockTolerance) throw new Error(`Clock moved backwards by ${drift}ms (tolerance: ${this.maxClockTolerance}ms)`);
|
|
884
|
+
cTimestamp = this._state.lastTimestamp;
|
|
885
|
+
}
|
|
886
|
+
let sequence;
|
|
887
|
+
if (cTimestamp === this._state.lastTimestamp) {
|
|
888
|
+
sequence = this._state.sequence + 1n & SnowflakeID.SEQUENCE_MASK;
|
|
889
|
+
if (sequence === 0n) cTimestamp += 1n;
|
|
890
|
+
} else sequence = 0n;
|
|
891
|
+
const timeDiff = cTimestamp - this.epoch;
|
|
892
|
+
if (timeDiff < 0n || timeDiff > SnowflakeID.TIMESTAMP_MASK) throw new Error(`Timestamp offset must be between 0 and ${SnowflakeID.TIMESTAMP_MASK}, got ${timeDiff}`);
|
|
893
|
+
this._state.lastTimestamp = cTimestamp;
|
|
894
|
+
this._state.sequence = sequence;
|
|
895
|
+
const idstr = (timeDiff << SnowflakeID.SHIFT_TIMESTAMP | this.machineId << SnowflakeID.SHIFT_MACHINE | sequence).toString().padStart(19, "0");
|
|
896
|
+
return `${this.version}${idstr}`;
|
|
897
|
+
}
|
|
898
|
+
parse(snowflakeID, epoch, includeVersion = true) {
|
|
899
|
+
if (typeof snowflakeID !== "string" || !/^\d+$/.test(snowflakeID)) throw new Error("snowflakeID must be a non-empty decimal string");
|
|
900
|
+
let version;
|
|
901
|
+
if (includeVersion) {
|
|
902
|
+
if (snowflakeID.length < 2) throw new Error("snowflakeID must contain a version and an ID value");
|
|
903
|
+
version = snowflakeID.substring(0, 1);
|
|
904
|
+
snowflakeID = snowflakeID.substring(1);
|
|
905
|
+
}
|
|
906
|
+
const epochTime = epoch ?? Number(this.epoch);
|
|
907
|
+
if (!Number.isSafeInteger(epochTime) || epochTime < 0) throw new Error(`epoch must be a non-negative safe integer, got ${epochTime}`);
|
|
908
|
+
const id = BigInt(snowflakeID);
|
|
909
|
+
if (id > SnowflakeID.MAX_ID) throw new Error(`snowflakeID value exceeds the 63-bit Snowflake range`);
|
|
910
|
+
const timeDiff = id >> SnowflakeID.SHIFT_TIMESTAMP & SnowflakeID.TIMESTAMP_MASK;
|
|
911
|
+
const flowTime = Number(timeDiff) + epochTime;
|
|
912
|
+
if (!Number.isSafeInteger(flowTime)) throw new Error("Parsed timestamp exceeds the JavaScript safe integer range");
|
|
913
|
+
const machineId = id >> SnowflakeID.SHIFT_MACHINE & SnowflakeID.MACHINE_MASK;
|
|
914
|
+
const sequence = id & SnowflakeID.SEQUENCE_MASK;
|
|
915
|
+
return {
|
|
916
|
+
value: snowflakeID,
|
|
917
|
+
timeOffset: timeDiff,
|
|
918
|
+
timestamp: flowTime,
|
|
919
|
+
machineId,
|
|
920
|
+
sequence,
|
|
921
|
+
epoch: epochTime,
|
|
922
|
+
version
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
SnowflakeID.SEQUENCE_MASK = 4095n;
|
|
927
|
+
SnowflakeID.TIMESTAMP_MASK = 2199023255551n;
|
|
928
|
+
SnowflakeID.MACHINE_MASK = 1023n;
|
|
929
|
+
SnowflakeID.SHIFT_TIMESTAMP = 22n;
|
|
930
|
+
SnowflakeID.SHIFT_MACHINE = 12n;
|
|
931
|
+
SnowflakeID.MAX_MACHINE_ID = 1023;
|
|
932
|
+
SnowflakeID.DEFAULT_MAX_CLOCK_TOLERANCE = 5;
|
|
933
|
+
SnowflakeID.MAX_ID = (1n << 63n) - 1n;
|
|
934
|
+
SnowflakeID.STATES = /* @__PURE__ */ new Map();
|
|
935
|
+
/** 将uuid转换为更简单的唯一标记id */
|
|
936
|
+
var ShortUUID = class {
|
|
937
|
+
/**
|
|
938
|
+
* 构造函数,用于初始化字母表
|
|
939
|
+
* @param {string} [alphabet] - 可选参数,用于指定自定义字母表
|
|
940
|
+
* 如果提供了alphabet参数,则将其设置为实例的字母表属性
|
|
941
|
+
* 如果未提供alphabet参数,则使用默认值
|
|
942
|
+
*/
|
|
943
|
+
constructor(alphabet) {
|
|
944
|
+
this.alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
945
|
+
if (alphabet) this.alphabet = alphabet;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* 将UUID字符串进行编码处理
|
|
949
|
+
* @param uuid - 需要编码的UUID字符串
|
|
950
|
+
* @returns 编码后的字符串
|
|
951
|
+
*/
|
|
952
|
+
encode(uuid, alphabet) {
|
|
953
|
+
const uuidInt = this._uuidHexToInt(uuid);
|
|
954
|
+
return this._intToString(uuidInt, alphabet);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* 解码短UUID字符串,将其转换为UUID整数和十六进制格式
|
|
958
|
+
* @param shortUUID - 需要解码的短UUID字符串
|
|
959
|
+
* @returns 返回包含UUID整数和十六进制格式的对象
|
|
960
|
+
*/
|
|
961
|
+
decode(shortUUID, alphabet) {
|
|
962
|
+
const uuidInt = this._stringToInt(shortUUID, alphabet);
|
|
963
|
+
return {
|
|
964
|
+
uuidInt,
|
|
965
|
+
uuid: this._uuidIntToHex(uuidInt)
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
_stringToInt(str, alphabet) {
|
|
969
|
+
if (!alphabet) alphabet = this.alphabet;
|
|
970
|
+
const alphabetlen = BigInt(alphabet.length);
|
|
971
|
+
let result = BigInt(0);
|
|
972
|
+
let multiplier = BigInt(1);
|
|
973
|
+
const strlen = str.length;
|
|
974
|
+
for (let i = strlen - 1; i >= 0; i--) {
|
|
975
|
+
const char = str[i];
|
|
976
|
+
const index = alphabet.indexOf(char);
|
|
977
|
+
if (index === -1) throw new Error(`Character "${char}" not found in alphabet`);
|
|
978
|
+
result += BigInt(index) * multiplier;
|
|
979
|
+
multiplier *= alphabetlen;
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
_intToString(uuidInt, alphabet) {
|
|
984
|
+
if (!alphabet) alphabet = this.alphabet;
|
|
985
|
+
const alphabetlen = BigInt(alphabet.length);
|
|
986
|
+
let num = uuidInt;
|
|
987
|
+
const result = [];
|
|
988
|
+
while (num) {
|
|
989
|
+
const index = Number(num % alphabetlen);
|
|
990
|
+
num = num / alphabetlen;
|
|
991
|
+
result.push(alphabet.charAt(index));
|
|
992
|
+
}
|
|
993
|
+
return result.reverse().join("");
|
|
994
|
+
}
|
|
995
|
+
_uuidHexToInt(uuid) {
|
|
996
|
+
const uuidHex = uuid.replaceAll("-", "");
|
|
997
|
+
return BigInt(`0x${uuidHex}`);
|
|
998
|
+
}
|
|
999
|
+
_uuidIntToHex(uuidInt) {
|
|
1000
|
+
const uuidHexStr = uuidInt.toString(16);
|
|
1001
|
+
if (!/^[0-9a-fA-F]{32}$/.test(uuidHexStr)) throw new Error("Invalid UUID string (must be 32 hex characters)");
|
|
1002
|
+
return [
|
|
1003
|
+
uuidHexStr.slice(0, 8),
|
|
1004
|
+
uuidHexStr.slice(8, 12),
|
|
1005
|
+
uuidHexStr.slice(12, 16),
|
|
1006
|
+
uuidHexStr.slice(16, 20),
|
|
1007
|
+
uuidHexStr.slice(20)
|
|
1008
|
+
].join("-");
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/crypto.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* 将原始的二进制数据转换为 Hex String
|
|
1015
|
+
* @param bf 待转换的原始数据
|
|
1016
|
+
* @param upper 是否需要转换为大写
|
|
1017
|
+
* @returns
|
|
1018
|
+
*/
|
|
1019
|
+
function bufferToHex(bf, upper = false) {
|
|
1020
|
+
const u8Array = bf instanceof Uint8Array ? bf : new Uint8Array(bf);
|
|
1021
|
+
return Array.from(u8Array).map((b) => {
|
|
1022
|
+
let hx = b.toString(16).padStart(2, "0");
|
|
1023
|
+
return upper === true ? hx.toUpperCase() : hx;
|
|
1024
|
+
}).join("");
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* 将16进制的数据转换为 UInt8Array
|
|
1028
|
+
* @param data 16进制的数据
|
|
1029
|
+
* @returns
|
|
1030
|
+
*/
|
|
1031
|
+
function hexToBuffer(data) {
|
|
1032
|
+
const len = data.length / 2;
|
|
1033
|
+
const uint8Array = new Uint8Array(len);
|
|
1034
|
+
for (let i = 0; i < len; i++) {
|
|
1035
|
+
const byteHex = data.substring(i * 2, i * 2 + 2);
|
|
1036
|
+
const byte = parseInt(byteHex.toLocaleLowerCase(), 16);
|
|
1037
|
+
uint8Array[i] = byte;
|
|
1038
|
+
}
|
|
1039
|
+
return uint8Array;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* 将原始数据转换为 Base64 编码
|
|
1043
|
+
* @param bf 待转换的原始数据
|
|
1044
|
+
* @returns
|
|
1045
|
+
*/
|
|
1046
|
+
function bufferToBase64(bf) {
|
|
1047
|
+
const u8Array = bf instanceof Uint8Array ? bf : new Uint8Array(bf);
|
|
1048
|
+
const hashArray = Array.from(u8Array);
|
|
1049
|
+
return globalThis.btoa(String.fromCharCode.apply(null, hashArray));
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* 将 Base64 转换为 UInt8Array 数据
|
|
1053
|
+
* @param data
|
|
1054
|
+
* @returns
|
|
1055
|
+
*/
|
|
1056
|
+
function base64ToBuffer(data) {
|
|
1057
|
+
return new Uint8Array(globalThis.atob(data).split("").map((char) => char.charCodeAt(0)));
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* SHA 哈希算法
|
|
1061
|
+
* @param message 待进行 hash 的数据
|
|
1062
|
+
* @param upper 是否转换为大写, 默认为: false
|
|
1063
|
+
* @param algorithm hash算法, 支持: SHA-1、SHA-256、SHA-384、SHA-512; 默认为: SHA-256
|
|
1064
|
+
* @returns
|
|
1065
|
+
*/
|
|
1066
|
+
async function sha(message, upper = false, algorithm = "SHA-256") {
|
|
1067
|
+
let msgBuffer = message;
|
|
1068
|
+
if (typeof message === "string") msgBuffer = new TextEncoder().encode(message);
|
|
1069
|
+
return bufferToHex(await globalThis.crypto.subtle.digest(algorithm || "SHA-256", msgBuffer), upper);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* 哈希算法
|
|
1073
|
+
* @param message 待进行 hash 的数据
|
|
1074
|
+
* @param upper 是否转换为大写, 默认为: false
|
|
1075
|
+
* @param algorithm hash算法, 支持: SHA-1、SHA-256、SHA-384、SHA-512; 默认为: SHA-256
|
|
1076
|
+
* @returns
|
|
1077
|
+
*/
|
|
1078
|
+
async function hash(message, upper = false, algorithm = "SHA-256") {
|
|
1079
|
+
return sha(message, upper, algorithm);
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* 使用 HMAC 算法计算消息的哈希值
|
|
1083
|
+
* @param message - 需要计算哈希的消息字符串
|
|
1084
|
+
* @param secret - 用于生成 HMAC 的密钥
|
|
1085
|
+
* @param algorithm - HMAC 使用的哈希算法,默认为 "SHA-256"
|
|
1086
|
+
* @param upper - 是否将结果转换为大写,默认为 false
|
|
1087
|
+
* @returns 返回十六进制格式的 HMAC 哈希值
|
|
1088
|
+
*/
|
|
1089
|
+
async function hmacHash(message, secret, algorithm = "SHA-256", upper = false) {
|
|
1090
|
+
const encoder = new TextEncoder();
|
|
1091
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
|
|
1092
|
+
name: "HMAC",
|
|
1093
|
+
hash: { name: algorithm }
|
|
1094
|
+
}, false, ["sign"]);
|
|
1095
|
+
return bufferToHex(await crypto.subtle.sign("HMAC", key, encoder.encode(message)), upper);
|
|
1096
|
+
}
|
|
1097
|
+
function parseRsaKey(pem) {
|
|
1098
|
+
const pemHeader = "-----BEGIN PUBLIC KEY-----";
|
|
1099
|
+
const pemFooter = "-----END PUBLIC KEY-----";
|
|
1100
|
+
if (pem.indexOf(pemHeader) !== -1 && pem.indexOf(pemFooter) !== -1) pem = pem.substring(26, pem.indexOf(pemFooter)).trim();
|
|
1101
|
+
return pem;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* 导入上下文密钥
|
|
1105
|
+
* @param key 导入的密钥
|
|
1106
|
+
* @param algorithmName 算法名称
|
|
1107
|
+
* @param usages 该密钥可以用于哪些函数使用
|
|
1108
|
+
* @returns
|
|
1109
|
+
*/
|
|
1110
|
+
async function importKey(key, algorithmName, usages, encoding = "hex") {
|
|
1111
|
+
let name = "AES-CBC";
|
|
1112
|
+
if (algorithmName == null) name = "AES-CBC";
|
|
1113
|
+
else if (algorithmName.toUpperCase() === "AES") name = "AES-CBC";
|
|
1114
|
+
else if (algorithmName.toUpperCase() === "RSA") name = "RSA-OAEP";
|
|
1115
|
+
name = name.toUpperCase();
|
|
1116
|
+
if (usages == null || usages.length === 0) usages = ["encrypt"];
|
|
1117
|
+
let format = "raw";
|
|
1118
|
+
let algorithm = { name };
|
|
1119
|
+
if (name.includes("RSA")) {
|
|
1120
|
+
format = "spki";
|
|
1121
|
+
algorithm.hash = { name: "SHA-256" };
|
|
1122
|
+
key = parseRsaKey(key);
|
|
1123
|
+
}
|
|
1124
|
+
const keyBuf = encoding === "base64" ? base64ToBuffer(key) : hexToBuffer(key);
|
|
1125
|
+
return Promise.all([globalThis.crypto.subtle.importKey(format, keyBuf, algorithm, false, usages), Promise.resolve({ name })]);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* 加密
|
|
1129
|
+
* @param algorithm 算法参数, 算法名称、向量等
|
|
1130
|
+
* @param key 算法密钥
|
|
1131
|
+
* @param message 待加密的数据
|
|
1132
|
+
* @param encode 解密后返回数据格式
|
|
1133
|
+
* @returns
|
|
1134
|
+
*/
|
|
1135
|
+
async function encrypt(algorithm, key, message, encode = "hex") {
|
|
1136
|
+
if (typeof message === "string") message = new TextEncoder().encode(message);
|
|
1137
|
+
const encrypted = await globalThis.crypto.subtle.encrypt(algorithm, key, message);
|
|
1138
|
+
if (encode === "hex") return bufferToHex(encrypted);
|
|
1139
|
+
else if (encode === "hexUpper") return bufferToHex(encrypted, true);
|
|
1140
|
+
else if (encode === "base64") return bufferToBase64(encrypted);
|
|
1141
|
+
else return encrypted;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* 数据解密
|
|
1145
|
+
* @param algorithm 解密算法
|
|
1146
|
+
* @param key 解密密钥
|
|
1147
|
+
* @param message 加密后的数据
|
|
1148
|
+
* @returns
|
|
1149
|
+
*/
|
|
1150
|
+
async function decrypt(algorithm, key, message) {
|
|
1151
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(algorithm, key, message);
|
|
1152
|
+
return new TextDecoder("utf-8").decode(decrypted);
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* AES 加密
|
|
1156
|
+
* @param message 待加密的数据
|
|
1157
|
+
* @param key 加解密密钥
|
|
1158
|
+
* @param encode 加密后的数据转换的形式, hex - 转换为16进制字符串, hexUpper - 转换为16进制且大写, base64 - 转换为 base64 形式
|
|
1159
|
+
* @param iv 加解密向量
|
|
1160
|
+
* @returns [加密数据,向量]
|
|
1161
|
+
*/
|
|
1162
|
+
async function aesEncrypt(message, key, encode = "hex", iv = null) {
|
|
1163
|
+
let ciphertext = "";
|
|
1164
|
+
let resIv = "";
|
|
1165
|
+
const [cryptoKey, algorithm] = await importKey(key, "aes", ["encrypt"]);
|
|
1166
|
+
if (iv == null) iv = globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
|
|
1167
|
+
else if (typeof iv === "string") iv = hexToBuffer(iv);
|
|
1168
|
+
ciphertext = await encrypt({
|
|
1169
|
+
...algorithm,
|
|
1170
|
+
iv
|
|
1171
|
+
}, cryptoKey, message, encode);
|
|
1172
|
+
resIv = bufferToHex(iv);
|
|
1173
|
+
return {
|
|
1174
|
+
ciphertext,
|
|
1175
|
+
iv: resIv,
|
|
1176
|
+
key
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* 根据加密后的数据类型使用对应函数最终转换为 UInt8Array
|
|
1181
|
+
* @param message 原始加密后的数据
|
|
1182
|
+
* @param type 类型: hex | base64
|
|
1183
|
+
* @returns
|
|
1184
|
+
*/
|
|
1185
|
+
function parseEncryptData(message, type) {
|
|
1186
|
+
let input;
|
|
1187
|
+
if (typeof message === "string") {
|
|
1188
|
+
if (type === "hex" || type === "hexUpper") input = hexToBuffer(message);
|
|
1189
|
+
else input = base64ToBuffer(message);
|
|
1190
|
+
} else input = message;
|
|
1191
|
+
return input;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* AES 解密
|
|
1195
|
+
* @param message 加密后的数据
|
|
1196
|
+
* @param key 解密密钥
|
|
1197
|
+
* @param iv 向量
|
|
1198
|
+
* @param encode 加密后数据的形式: hex | base64
|
|
1199
|
+
* @returns
|
|
1200
|
+
*/
|
|
1201
|
+
async function aesDecrypt(message, key, iv, encode = "hex") {
|
|
1202
|
+
const [cryptoKey, algorithm] = await importKey(key, "aes", ["decrypt"]);
|
|
1203
|
+
const input = parseEncryptData(message, encode);
|
|
1204
|
+
return await decrypt({
|
|
1205
|
+
...algorithm,
|
|
1206
|
+
iv: hexToBuffer(iv)
|
|
1207
|
+
}, cryptoKey, input);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* RSA 加密
|
|
1211
|
+
* @param key 公钥
|
|
1212
|
+
* @param message 待加密数据
|
|
1213
|
+
* @param encode 返回类型
|
|
1214
|
+
* @returns
|
|
1215
|
+
*/
|
|
1216
|
+
async function rsaEncrypt(message, publicKey, encode = "hex") {
|
|
1217
|
+
const [cryptoKey, algorithm] = await importKey(publicKey, "rsa", ["encrypt"], "base64");
|
|
1218
|
+
return await encrypt(algorithm, cryptoKey, message, encode);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* RSA 解密
|
|
1222
|
+
* @param key 私钥, 根据私钥解密
|
|
1223
|
+
* @param message 加密后的数据
|
|
1224
|
+
* @param encode 加密后的数据形式
|
|
1225
|
+
* @returns
|
|
1226
|
+
*/
|
|
1227
|
+
async function rsaDecrypt(privateKey, message, encode = "hex") {
|
|
1228
|
+
const [cryptoKey, algorithm] = await importKey(privateKey, "rsa", ["decrypt"]);
|
|
1229
|
+
return await decrypt({ ...algorithm }, cryptoKey, parseEncryptData(message, encode));
|
|
1230
|
+
}
|
|
1231
|
+
//#endregion
|
|
1232
|
+
//#region src/base-codec.ts
|
|
1233
|
+
/** 通用进制编码解码器,兼容 Node.js 和浏览器环境 */
|
|
1234
|
+
const BASE62_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
1235
|
+
const BASE32_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
1236
|
+
var BaseCodec = class {
|
|
1237
|
+
constructor(name, alphabet) {
|
|
1238
|
+
this.alphabet = alphabet;
|
|
1239
|
+
this.base = BigInt(alphabet.length);
|
|
1240
|
+
this.name = name;
|
|
1241
|
+
}
|
|
1242
|
+
intToBytes(value) {
|
|
1243
|
+
if (value < 0n) throw new RangeError("Only supports non-negative integers");
|
|
1244
|
+
if (value === 0n) return new Uint8Array([0]);
|
|
1245
|
+
const bytes = [];
|
|
1246
|
+
while (value > 0n) {
|
|
1247
|
+
bytes.unshift(Number(value & 255n));
|
|
1248
|
+
value >>= 8n;
|
|
1249
|
+
}
|
|
1250
|
+
return new Uint8Array(bytes);
|
|
1251
|
+
}
|
|
1252
|
+
bytesToInt(bytes) {
|
|
1253
|
+
let value = 0n;
|
|
1254
|
+
for (const byte of bytes) value = value << 8n | BigInt(byte);
|
|
1255
|
+
return value;
|
|
1256
|
+
}
|
|
1257
|
+
concatBytes(...arrays) {
|
|
1258
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
1259
|
+
const result = new Uint8Array(totalLength);
|
|
1260
|
+
let offset = 0;
|
|
1261
|
+
for (const arr of arrays) {
|
|
1262
|
+
result.set(arr, offset);
|
|
1263
|
+
offset += arr.length;
|
|
1264
|
+
}
|
|
1265
|
+
return result;
|
|
1266
|
+
}
|
|
1267
|
+
bytesToUtf8(bytes) {
|
|
1268
|
+
return new TextDecoder().decode(bytes);
|
|
1269
|
+
}
|
|
1270
|
+
utf8ToBytes(str) {
|
|
1271
|
+
return new TextEncoder().encode(str);
|
|
1272
|
+
}
|
|
1273
|
+
bytesToHex(bytes) {
|
|
1274
|
+
let hex = "";
|
|
1275
|
+
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
1276
|
+
return hex;
|
|
1277
|
+
}
|
|
1278
|
+
encodeFromBytes(input) {
|
|
1279
|
+
if (input.length === 0) return "";
|
|
1280
|
+
let leadingZeros = 0;
|
|
1281
|
+
while (leadingZeros < input.length && input[leadingZeros] === 0) leadingZeros++;
|
|
1282
|
+
let value = this.bytesToInt(input);
|
|
1283
|
+
let encoded = "";
|
|
1284
|
+
while (value > 0n) {
|
|
1285
|
+
encoded = this.alphabet.charAt(Number(value % this.base)) + encoded;
|
|
1286
|
+
value /= this.base;
|
|
1287
|
+
}
|
|
1288
|
+
return this.alphabet.charAt(0).repeat(leadingZeros) + encoded;
|
|
1289
|
+
}
|
|
1290
|
+
decodeToBytes(input) {
|
|
1291
|
+
if (input.length === 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
1292
|
+
let leadingZeros = 0;
|
|
1293
|
+
while (leadingZeros < input.length && input.charAt(leadingZeros) === this.alphabet.charAt(0)) leadingZeros++;
|
|
1294
|
+
let value = 0n;
|
|
1295
|
+
for (const char of input.slice(leadingZeros)) {
|
|
1296
|
+
const index = this.alphabet.indexOf(char);
|
|
1297
|
+
if (index === -1) throw new TypeError(`Invalid ${this.name} character: ${char}`);
|
|
1298
|
+
value = value * this.base + BigInt(index);
|
|
1299
|
+
}
|
|
1300
|
+
const valueBytes = value === 0n ? /* @__PURE__ */ new Uint8Array(0) : this.intToBytes(value);
|
|
1301
|
+
return this.concatBytes(new Uint8Array(leadingZeros), valueBytes);
|
|
1302
|
+
}
|
|
1303
|
+
/** 将非负 bigint 编码为字符串 */
|
|
1304
|
+
encodeFromInt(value) {
|
|
1305
|
+
return this.encodeFromBytes(this.intToBytes(value));
|
|
1306
|
+
}
|
|
1307
|
+
/** 先将字符串转换为 UTF-8 字节,再编码 */
|
|
1308
|
+
encodeFromStr(value) {
|
|
1309
|
+
return this.encodeFromBytes(this.utf8ToBytes(value));
|
|
1310
|
+
}
|
|
1311
|
+
/** 根据参数类型将 bigint 或字符串编码 */
|
|
1312
|
+
encode(value) {
|
|
1313
|
+
return typeof value === "bigint" ? this.encodeFromInt(value) : this.encodeFromStr(value);
|
|
1314
|
+
}
|
|
1315
|
+
/** 解码为非负 bigint */
|
|
1316
|
+
decodeToInt(value) {
|
|
1317
|
+
return this.bytesToInt(this.decodeToBytes(value));
|
|
1318
|
+
}
|
|
1319
|
+
/** 解码为 UTF-8 字符串 */
|
|
1320
|
+
decodeToStr(value) {
|
|
1321
|
+
return this.bytesToUtf8(this.decodeToBytes(value));
|
|
1322
|
+
}
|
|
1323
|
+
/** 默认解码为 UTF-8 字符串 */
|
|
1324
|
+
decode(value) {
|
|
1325
|
+
return this.decodeToStr(value);
|
|
1326
|
+
}
|
|
1327
|
+
/** 将字节数组转换为十六进制字符串 */
|
|
1328
|
+
bufferToHex(value) {
|
|
1329
|
+
return this.bytesToHex(value);
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
var Base32 = class extends BaseCodec {
|
|
1333
|
+
constructor() {
|
|
1334
|
+
super("Base32", BASE32_ALPHABET);
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
var Base62 = class extends BaseCodec {
|
|
1338
|
+
constructor() {
|
|
1339
|
+
super("Base62", BASE62_ALPHABET);
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
//#endregion
|
|
1343
|
+
//#region src/validator.ts
|
|
1344
|
+
/**
|
|
1345
|
+
* 数据验证器
|
|
1346
|
+
*/
|
|
1347
|
+
const defaultMsgs = {
|
|
1348
|
+
mobile: "请输入正确的手机号",
|
|
1349
|
+
same: "两次输入不一致",
|
|
1350
|
+
required: "%s为必填字段",
|
|
1351
|
+
default: "请输入正确的数据"
|
|
1352
|
+
};
|
|
1353
|
+
const ruleRegexs = {
|
|
1354
|
+
/** 验证跟其余数据相等的正则,一般用于验证再次输入密码 */
|
|
1355
|
+
same: /^same:(.+)$/i,
|
|
1356
|
+
/** 验证手机号的正则表达式 */
|
|
1357
|
+
mobile: /^1[3456789]\d{9}$/,
|
|
1358
|
+
/** 非空验证的正则表达式 */
|
|
1359
|
+
required: /\S/
|
|
1360
|
+
};
|
|
1361
|
+
const ruleFns = {
|
|
1362
|
+
/** 验证相等 */
|
|
1363
|
+
same(val1, val2) {
|
|
1364
|
+
return val2 === val1;
|
|
1365
|
+
},
|
|
1366
|
+
/** 正则匹配 */
|
|
1367
|
+
pattern(regex, val) {
|
|
1368
|
+
if (val == null) return false;
|
|
1369
|
+
return regex.test(String(val));
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
var ValidateError = class extends Error {
|
|
1373
|
+
constructor(detail, key, message) {
|
|
1374
|
+
super(message);
|
|
1375
|
+
this.name = "ValidateError";
|
|
1376
|
+
this.key = key;
|
|
1377
|
+
this.detail = detail;
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
/**
|
|
1381
|
+
* 数据验证器
|
|
1382
|
+
*/
|
|
1383
|
+
var Validator = class {
|
|
1384
|
+
/**
|
|
1385
|
+
* 构造数据验证转换器
|
|
1386
|
+
*
|
|
1387
|
+
* See {@link https://gitee.com/towardly/ph/wikis/utils/validator|Validator文档}.
|
|
1388
|
+
*
|
|
1389
|
+
* @param schemas 配置验证转换规则
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
*
|
|
1393
|
+
* const validator = new Validator([
|
|
1394
|
+
* { key: 'mobile', rules: ['required', 'mobile'] },
|
|
1395
|
+
* { key: 'code': rules: /^\d{6}$/, message: '请输入正确的验证码' },
|
|
1396
|
+
* { key: 'confirmPassword', rules: ['required', 'same:password'] }
|
|
1397
|
+
* ])
|
|
1398
|
+
* // 验证某一个字段
|
|
1399
|
+
* validator.validateKey().then(res => {})
|
|
1400
|
+
*/
|
|
1401
|
+
constructor(schemas) {
|
|
1402
|
+
this.setSchemas(schemas);
|
|
1403
|
+
}
|
|
1404
|
+
addSchemas(schemas) {
|
|
1405
|
+
for (let schema of schemas) this.addSchema(schema);
|
|
1406
|
+
}
|
|
1407
|
+
addSchema(schema) {
|
|
1408
|
+
if (schema.key in this.rules) this.rules[schema.key] = [...this.rules[schema.key], ...this._parseSchemaRules(schema)];
|
|
1409
|
+
else this.rules[schema.key] = this._parseSchemaRules(schema);
|
|
1410
|
+
}
|
|
1411
|
+
setSchema(schema) {
|
|
1412
|
+
this.rules[schema.key] = this._parseSchemaRules(schema);
|
|
1413
|
+
}
|
|
1414
|
+
setSchemas(schemas) {
|
|
1415
|
+
this.rules = {};
|
|
1416
|
+
for (let schema of schemas) this.rules[schema.key] = this._parseSchemaRules(schema);
|
|
1417
|
+
}
|
|
1418
|
+
removeSchema(key) {
|
|
1419
|
+
delete this.rules[key];
|
|
1420
|
+
}
|
|
1421
|
+
hasSchema(key) {
|
|
1422
|
+
return key in this.rules;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* 进行数据验证
|
|
1426
|
+
* @param data 待验证的数据
|
|
1427
|
+
* @param all 是否全部验证, false - 只要验证错误一个则停止验证
|
|
1428
|
+
* @returns
|
|
1429
|
+
*/
|
|
1430
|
+
validate(data, all = false) {
|
|
1431
|
+
const detail = {};
|
|
1432
|
+
let currentKey = void 0;
|
|
1433
|
+
let currentMessage = void 0;
|
|
1434
|
+
for (let key in this.rules) {
|
|
1435
|
+
let errMsg = this._validateRule(this.rules[key], data[key], data);
|
|
1436
|
+
if (errMsg !== "") {
|
|
1437
|
+
errMsg = errMsg.replace("%s", key);
|
|
1438
|
+
detail[key] = errMsg;
|
|
1439
|
+
currentKey = key;
|
|
1440
|
+
currentMessage = errMsg;
|
|
1441
|
+
if (!all) break;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (currentKey == null) return true;
|
|
1445
|
+
throw new ValidateError(detail, currentKey, currentMessage);
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* 只验证指定 key 的数据格式
|
|
1449
|
+
* @param key 指定待验证的 key
|
|
1450
|
+
* @param value 待验证的数据
|
|
1451
|
+
* @param data 原始数据,当验证确认密码时需要使用
|
|
1452
|
+
*/
|
|
1453
|
+
validateKey(key, value, data) {
|
|
1454
|
+
let keyRules = this.rules[key];
|
|
1455
|
+
if (keyRules == null) return {
|
|
1456
|
+
key,
|
|
1457
|
+
value
|
|
1458
|
+
};
|
|
1459
|
+
let errMsg = this._validateRule(keyRules, value, data);
|
|
1460
|
+
if (errMsg !== "") {
|
|
1461
|
+
errMsg = errMsg.replace("%s", key);
|
|
1462
|
+
throw new ValidateError({ [key]: errMsg }, key, errMsg);
|
|
1463
|
+
}
|
|
1464
|
+
return {
|
|
1465
|
+
key,
|
|
1466
|
+
value
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
_validateRule(rules, value, data) {
|
|
1470
|
+
let errMsg = "";
|
|
1471
|
+
for (let rule of rules) {
|
|
1472
|
+
if (!rule) continue;
|
|
1473
|
+
if (rule.rule === "required") {
|
|
1474
|
+
if (value == null || !ruleFns.pattern(ruleRegexs.required, value)) errMsg = rule.message;
|
|
1475
|
+
} else if (typeof rule.rule === "function") {
|
|
1476
|
+
if (!rule.rule(value)) errMsg = rule.message;
|
|
1477
|
+
} else if (rule.sameKey != null) {
|
|
1478
|
+
if (data != null) {
|
|
1479
|
+
if (!ruleFns.same(value, data[rule.sameKey])) errMsg = rule.message;
|
|
1480
|
+
}
|
|
1481
|
+
} else if (rule && !ruleFns.pattern(rule.rule, value)) errMsg = rule.message;
|
|
1482
|
+
if (errMsg !== "") break;
|
|
1483
|
+
}
|
|
1484
|
+
return errMsg;
|
|
1485
|
+
}
|
|
1486
|
+
_parseSchemaRules(schema) {
|
|
1487
|
+
let rules = [];
|
|
1488
|
+
let rule = schema.rules;
|
|
1489
|
+
if (schema.required === true) rules.push(...this._parseStringRule("required", schema.message));
|
|
1490
|
+
if (rule != null) {
|
|
1491
|
+
if (typeof rule === "string") rules.push(...this._parseStringRule(rule, schema.message));
|
|
1492
|
+
else if (rule instanceof Array) for (let ruleItem of rule) if (typeof ruleItem === "string") rules.push(...this._parseStringRule(ruleItem, schema.message));
|
|
1493
|
+
else if (ruleItem instanceof RegExp || typeof ruleItem === "function") rules.push({
|
|
1494
|
+
rule: ruleItem,
|
|
1495
|
+
message: schema.message || defaultMsgs["default"]
|
|
1496
|
+
});
|
|
1497
|
+
else {
|
|
1498
|
+
const emessage = ruleItem.message || schema.message || defaultMsgs["default"];
|
|
1499
|
+
if (typeof ruleItem.rule === "string") rules.push(...this._parseStringRule(ruleItem.rule, emessage));
|
|
1500
|
+
else rules.push({
|
|
1501
|
+
rule: ruleItem.rule,
|
|
1502
|
+
message: emessage
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
else rules.push({
|
|
1506
|
+
rule,
|
|
1507
|
+
message: defaultMsgs["default"]
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
return rules;
|
|
1511
|
+
}
|
|
1512
|
+
_parseStringRule(rule, ruleErrMsg) {
|
|
1513
|
+
let rules = [];
|
|
1514
|
+
let trule = rule.split("|");
|
|
1515
|
+
for (let r of trule) {
|
|
1516
|
+
let message = ruleErrMsg;
|
|
1517
|
+
let rrule = null;
|
|
1518
|
+
let sameKey;
|
|
1519
|
+
if (r === "required") {
|
|
1520
|
+
rrule = "required";
|
|
1521
|
+
message = message || ruleErrMsg || defaultMsgs.required;
|
|
1522
|
+
} else if (ruleRegexs.same.test(r)) {
|
|
1523
|
+
let m = r.match(ruleRegexs.same);
|
|
1524
|
+
if (m != null) {
|
|
1525
|
+
rrule = ruleRegexs.same;
|
|
1526
|
+
sameKey = m[1];
|
|
1527
|
+
message = message || defaultMsgs["same"];
|
|
1528
|
+
}
|
|
1529
|
+
} else if (Object.hasOwn(ruleRegexs, r)) {
|
|
1530
|
+
rrule = ruleRegexs[r];
|
|
1531
|
+
message = message || defaultMsgs[r];
|
|
1532
|
+
}
|
|
1533
|
+
if (rrule) rules.push({
|
|
1534
|
+
rule: rrule,
|
|
1535
|
+
message,
|
|
1536
|
+
sameKey
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
return rules;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
//#endregion
|
|
1543
|
+
export { Base32, Base62, ShortUUID, SnowflakeID, Validator, add, adjust, aesDecrypt, aesEncrypt, bufferToHex, dateOf, difference, endOf, format, formatData, formatMoney, getJSONValue, hash, hmacHash, intersection, isBlank, isBoolean, isDisjointFrom, isNumeric, isSubsetOf, isSupersetOf, order, parse, random, reverseStr, rgbToHex, round, rsaDecrypt, rsaEncrypt, sha, shieldBankCard, shieldEmail, shieldIdCard, shieldMobile, shieldName, shieldString, shieldWithLimit, smartShield, smartSummary, snakeCaseStyle, startOf, symmetricDifference, timestamp, toHex, toHsv, toRgb, union };
|