@jayfong/x-utils 3.3.0 → 3.4.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/ai.d.mts +103 -0
- package/lib/ai.mjs +75 -0
- package/lib/cache.d.mts +464 -0
- package/lib/cache.mjs +531 -0
- package/lib/captcha.d.mts +259 -0
- package/lib/captcha.mjs +174 -0
- package/lib/chinese.d.mts +19 -1
- package/lib/chinese.mjs +28 -1
- package/lib/cli.d.mts +25 -37
- package/lib/cli.mjs +25 -22
- package/lib/crawler.d.mts +1 -1
- package/lib/crawler.mjs +1 -1
- package/lib/crypto.d.mts +158 -0
- package/lib/crypto.mjs +213 -0
- package/lib/data_set.mjs +3 -3
- package/lib/db_value.d.mts +213 -0
- package/lib/db_value.mjs +208 -0
- package/lib/dispose.d.mts +98 -0
- package/lib/dispose.mjs +107 -0
- package/lib/file_store.d.mts +167 -0
- package/lib/file_store.mjs +120 -0
- package/lib/fs.d.mts +64 -2
- package/lib/fs.mjs +65 -3
- package/lib/html.d.mts +103 -0
- package/lib/html.mjs +112 -0
- package/lib/jwt.d.mts +128 -0
- package/lib/jwt.mjs +104 -0
- package/lib/mail.d.mts +125 -0
- package/lib/mail.mjs +108 -0
- package/lib/pay.d.mts +394 -0
- package/lib/pay.mjs +343 -0
- package/lib/puppeteer.mjs +3 -3
- package/lib/random.d.mts +71 -0
- package/lib/random.mjs +92 -0
- package/lib/redis.d.mts +32 -0
- package/lib/redis.mjs +24 -0
- package/lib/runner.d.mts +52 -24
- package/lib/runner.mjs +31 -15
- package/lib/sensitive_words.d.mts +82 -0
- package/lib/sensitive_words.mjs +73 -0
- package/lib/sms.d.mts +133 -0
- package/lib/sms.mjs +100 -0
- package/lib/task.d.mts +1 -1
- package/lib/task.mjs +4 -4
- package/package.json +35 -3
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { XCache } from "./cache.mjs";
|
|
2
|
+
import { MsValue } from "vtils";
|
|
3
|
+
/**
|
|
4
|
+
* {@link XCaptcha} 的配置项。
|
|
5
|
+
*/
|
|
6
|
+
export interface XCaptchaOptions {
|
|
7
|
+
/**
|
|
8
|
+
* 缓存服务实例。
|
|
9
|
+
*
|
|
10
|
+
* 用于保存与读取验证码数据:生成时写入,校验时按 token 读取。建议为验证码
|
|
11
|
+
* 单独使用一个 {@link XCache} 实例,便于统一管理前缀、TTL 与序列化方式,
|
|
12
|
+
* 避免与其他业务缓存相互干扰。
|
|
13
|
+
*/
|
|
14
|
+
cache: XCache;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 验证码字符集。
|
|
18
|
+
*
|
|
19
|
+
* 由若干字符段组合而成,各字符段含义:
|
|
20
|
+
* - `0`:数字 0-9;
|
|
21
|
+
* - `1`:数字 1-9(不含 0);
|
|
22
|
+
* - `a` / `z`:小写字母 a-z;
|
|
23
|
+
* - `A` / `Z`:大写字母 A-Z。
|
|
24
|
+
*
|
|
25
|
+
* 完整取值:
|
|
26
|
+
* - `09`:0-9;
|
|
27
|
+
* - `19`:1-9;
|
|
28
|
+
* - `az`:小写字母;
|
|
29
|
+
* - `AZ`:大写字母;
|
|
30
|
+
* - `aZ`:小写 + 大写字母;
|
|
31
|
+
* - `0z`:0-9 + 小写字母;
|
|
32
|
+
* - `0Z`:0-9 + 小写 + 大写字母;
|
|
33
|
+
* - `1z`:1-9 + 小写字母;
|
|
34
|
+
* - `1Z`:1-9 + 小写 + 大写字母。
|
|
35
|
+
*/
|
|
36
|
+
export type XCaptchaGenerateCharset = '09' | '19' | 'az' | 'AZ' | 'aZ' | '0z' | '0Z' | '1z' | '1Z';
|
|
37
|
+
/**
|
|
38
|
+
* {@link XCaptcha.generate} 与 {@link XCaptcha.generateDataUrl} 的入参。
|
|
39
|
+
*/
|
|
40
|
+
export interface XCaptchaGeneratePayload<T> {
|
|
41
|
+
/**
|
|
42
|
+
* 场景值。
|
|
43
|
+
*
|
|
44
|
+
* 用于区分验证码用途,防止后台验证码图片登录时误用其他场景(如短信)
|
|
45
|
+
* 下生成的验证码。校验时场景值不一致会直接判定失败。
|
|
46
|
+
*/
|
|
47
|
+
scene: string;
|
|
48
|
+
/**
|
|
49
|
+
* 验证码长度,即生成的字符个数,应为正整数。
|
|
50
|
+
*/
|
|
51
|
+
size: number;
|
|
52
|
+
/**
|
|
53
|
+
* 验证码存活时长。
|
|
54
|
+
*
|
|
55
|
+
* 与 `vtils` 的 `ms` 解析规则一致,可为毫秒数或时间字符串
|
|
56
|
+
* (如 `'5m'`、`'10s'`)。到期后验证码自动失效。
|
|
57
|
+
*/
|
|
58
|
+
ttl: MsValue;
|
|
59
|
+
/**
|
|
60
|
+
* 字符集,决定验证码由哪些字符组成。
|
|
61
|
+
*/
|
|
62
|
+
charset: XCaptchaGenerateCharset;
|
|
63
|
+
/**
|
|
64
|
+
* 可重试次数。
|
|
65
|
+
*
|
|
66
|
+
* - 大于 0:每次校验(无论成败)扣减一次,扣到 0 后验证码失效;
|
|
67
|
+
* - 等于 0:首次校验(无论成败)后即失效;
|
|
68
|
+
* - 缺省为 0。
|
|
69
|
+
*/
|
|
70
|
+
retryCount?: number;
|
|
71
|
+
/**
|
|
72
|
+
* 其他附加信息。
|
|
73
|
+
*
|
|
74
|
+
* 随验证码一起写入缓存,校验时可通过
|
|
75
|
+
* {@link XCaptchaVerifyOptions.extra} 自定义比对逻辑。
|
|
76
|
+
*/
|
|
77
|
+
extra?: T;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* {@link XCaptcha.generate} 的返回结果。
|
|
81
|
+
*/
|
|
82
|
+
export interface XCaptchaGenerateResult {
|
|
83
|
+
/**
|
|
84
|
+
* 校验凭证。
|
|
85
|
+
*
|
|
86
|
+
* 随机生成且 URL 友好,用于在缓存中定位验证码;校验时需原样传回。
|
|
87
|
+
*/
|
|
88
|
+
token: string;
|
|
89
|
+
/**
|
|
90
|
+
* 生成的验证码明文。
|
|
91
|
+
*
|
|
92
|
+
* 主要用于调试或日志;生产环境通常只向客户端返回图片
|
|
93
|
+
* (见 {@link XCaptchaGenerateDataUrlResult.dataUrl}),不应回传明文。
|
|
94
|
+
*/
|
|
95
|
+
code: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* {@link XCaptcha.generateDataUrl} 的返回结果。
|
|
99
|
+
*/
|
|
100
|
+
export interface XCaptchaGenerateDataUrlResult {
|
|
101
|
+
/**
|
|
102
|
+
* 校验凭证,语义同 {@link XCaptchaGenerateResult.token}。
|
|
103
|
+
*/
|
|
104
|
+
token: string;
|
|
105
|
+
/**
|
|
106
|
+
* 生成的验证码明文,语义同 {@link XCaptchaGenerateResult.code}。
|
|
107
|
+
*/
|
|
108
|
+
code: string;
|
|
109
|
+
/**
|
|
110
|
+
* 验证码图片的 Data URL。
|
|
111
|
+
*
|
|
112
|
+
* 形如 `data:image/svg+xml,...`,可直接作为 `<img>` 的 `src` 展示。
|
|
113
|
+
*/
|
|
114
|
+
dataUrl: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* {@link XCaptcha.verify} 的入参。
|
|
118
|
+
*/
|
|
119
|
+
export interface XCaptchaVerifyOptions<T> {
|
|
120
|
+
/**
|
|
121
|
+
* 校验凭证,来自生成结果中的 `token`。
|
|
122
|
+
*/
|
|
123
|
+
token: string;
|
|
124
|
+
/**
|
|
125
|
+
* 待校验的验证码明文。
|
|
126
|
+
*/
|
|
127
|
+
code: string;
|
|
128
|
+
/**
|
|
129
|
+
* 场景值,须与生成时一致。
|
|
130
|
+
*/
|
|
131
|
+
scene: string;
|
|
132
|
+
/**
|
|
133
|
+
* 附加信息比对函数。
|
|
134
|
+
*
|
|
135
|
+
* 接收生成时存入缓存的 `extra`,返回是否通过。仅在场景值与验证码均通过
|
|
136
|
+
* 后才会执行;缺省时不校验附加信息。若生成时未提供 `extra`,回调入参为
|
|
137
|
+
* `undefined`。
|
|
138
|
+
*/
|
|
139
|
+
extra?: (extra: T) => boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 验证码生成与校验工具。
|
|
143
|
+
*
|
|
144
|
+
* 提供两种生成方式:
|
|
145
|
+
* - {@link XCaptcha.generate}:仅生成验证码文本,适合短信、邮件等无需图片的
|
|
146
|
+
* 场景;
|
|
147
|
+
* - {@link XCaptcha.generateDataUrl}:生成验证码文本与 SVG 图片 Data URL,
|
|
148
|
+
* 适合 Web 页面图片验证码。
|
|
149
|
+
*
|
|
150
|
+
* 生成时按指定字符集与长度随机拼接验证码,并将验证码明文、场景值、可重试
|
|
151
|
+
* 次数与附加信息一并写入缓存;校验时依次检查:
|
|
152
|
+
* 1. token 是否存在;
|
|
153
|
+
* 2. 场景值是否一致;
|
|
154
|
+
* 3. 验证码是否一致(大小写不敏感);
|
|
155
|
+
* 4. 附加信息是否通过(可选)。
|
|
156
|
+
*
|
|
157
|
+
* 任意一项不通过即返回 `false`,且每次校验都会消耗一次重试次数或使验证码
|
|
158
|
+
* 失效(见 {@link XCaptchaGeneratePayload.retryCount})。
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* const captcha = new XCaptcha({ cache });
|
|
162
|
+
*
|
|
163
|
+
* const { token, code, dataUrl } = await captcha.generateDataUrl({
|
|
164
|
+
* scene: 'login',
|
|
165
|
+
* size: 4,
|
|
166
|
+
* ttl: '5m',
|
|
167
|
+
* charset: '09',
|
|
168
|
+
* retryCount: 3,
|
|
169
|
+
* });
|
|
170
|
+
* // dataUrl 形如 'data:image/svg+xml,...'
|
|
171
|
+
*
|
|
172
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
173
|
+
* // true
|
|
174
|
+
*/
|
|
175
|
+
export declare class XCaptcha {
|
|
176
|
+
private readonly cache;
|
|
177
|
+
/**
|
|
178
|
+
* 创建验证码工具实例。
|
|
179
|
+
*
|
|
180
|
+
* @param options 配置项,包含缓存服务实例。
|
|
181
|
+
*/
|
|
182
|
+
constructor(options: XCaptchaOptions);
|
|
183
|
+
/**
|
|
184
|
+
* 各字符集对应的字符数组。
|
|
185
|
+
*
|
|
186
|
+
* 键为 {@link XCaptchaGenerateCharset} 取值,值为可从中随机取样的字符列表;
|
|
187
|
+
* `generate` 从中逐个 `sample`,`generateDataUrl` 则将其拼接为 svg-captcha
|
|
188
|
+
* 的 `charPreset`。
|
|
189
|
+
*/
|
|
190
|
+
private charsetMap;
|
|
191
|
+
/**
|
|
192
|
+
* 生成验证码文本。
|
|
193
|
+
*
|
|
194
|
+
* 从 {@link XCaptchaGeneratePayload.charset} 对应的字符集中随机抽取
|
|
195
|
+
* `size` 个字符组成验证码,与场景值、可重试次数、附加信息一起写入缓存,
|
|
196
|
+
* 并返回缓存 token 与验证码明文。
|
|
197
|
+
*
|
|
198
|
+
* 适用于短信、邮件等不需要图片的验证码场景;若需要图片请使用
|
|
199
|
+
* {@link XCaptcha.generateDataUrl}。
|
|
200
|
+
*
|
|
201
|
+
* @param payload 生成参数,包含场景值、长度、存活时长、字符集等。
|
|
202
|
+
* @returns 包含 `token` 与验证码明文 `code` 的结果对象。
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const { token, code } = await captcha.generate({
|
|
206
|
+
* scene: 'sms-login',
|
|
207
|
+
* size: 6,
|
|
208
|
+
* ttl: '5m',
|
|
209
|
+
* charset: '09',
|
|
210
|
+
* });
|
|
211
|
+
* // token 形如 'X__xxxx',code 为 6 位数字
|
|
212
|
+
*/
|
|
213
|
+
generate<T extends Record<string, any>>(payload: XCaptchaGeneratePayload<T>): Promise<XCaptchaGenerateResult>;
|
|
214
|
+
/**
|
|
215
|
+
* 生成验证码文本与 SVG 图片 Data URL。
|
|
216
|
+
*
|
|
217
|
+
* 使用 svg-captcha 依据 {@link XCaptchaGeneratePayload.charset} 与 `size`
|
|
218
|
+
* 生成图片,并将验证码文本按与 {@link XCaptcha.generate} 相同的规则写入
|
|
219
|
+
* 缓存;随后将 SVG 转为 Data URL 一并返回。
|
|
220
|
+
*
|
|
221
|
+
* 适用于 Web 页面图片验证码,`dataUrl` 可直接作为 `<img>` 的 `src`。
|
|
222
|
+
*
|
|
223
|
+
* @param payload 生成参数,包含场景值、长度、存活时长、字符集等。
|
|
224
|
+
* @returns 包含 `token`、验证码明文 `code` 与图片 `dataUrl` 的结果对象。
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* const { token, dataUrl } = await captcha.generateDataUrl({
|
|
228
|
+
* scene: 'login',
|
|
229
|
+
* size: 4,
|
|
230
|
+
* ttl: '5m',
|
|
231
|
+
* charset: 'aZ',
|
|
232
|
+
* });
|
|
233
|
+
* // dataUrl 形如 'data:image/svg+xml,...'
|
|
234
|
+
*/
|
|
235
|
+
generateDataUrl<T extends Record<string, any>>(payload: XCaptchaGeneratePayload<T>): Promise<XCaptchaGenerateDataUrlResult>;
|
|
236
|
+
/**
|
|
237
|
+
* 校验验证码。
|
|
238
|
+
*
|
|
239
|
+
* 按顺序执行以下检查,任意一项不通过即返回 `false`:
|
|
240
|
+
* 1. token 存在:缓存中找不到 token 对应数据时直接失败;
|
|
241
|
+
* 2. 场景一致:`scene` 与生成时不一致时失败,防止跨场景误用;
|
|
242
|
+
* 3. 验证码一致:`code` 与生成时一致(大小写不敏感);
|
|
243
|
+
* 4. 附加信息:提供了 `extra` 时执行其回调,返回 `false` 则失败。
|
|
244
|
+
*
|
|
245
|
+
* 注意:每次校验(无论最终是否通过)都会消耗一次重试次数——可重试次数
|
|
246
|
+
* 大于 0 时扣减并写回缓存,等于 0 时直接删除缓存,此后该 token 不可再用。
|
|
247
|
+
*
|
|
248
|
+
* @param options 校验参数,包含 token、验证码明文、场景值与可选的附加信息
|
|
249
|
+
* 比对函数。
|
|
250
|
+
* @returns 校验是否通过。
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
254
|
+
* // true
|
|
255
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
256
|
+
* // false:该 token 已失效
|
|
257
|
+
*/
|
|
258
|
+
verify<T extends Record<string, any>>(options: XCaptchaVerifyOptions<T>): Promise<boolean>;
|
|
259
|
+
}
|
package/lib/captcha.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { sample } from "vtils";
|
|
2
|
+
import svgToDataUrl from "mini-svg-data-uri";
|
|
3
|
+
import svgCaptcha from "svg-captcha";
|
|
4
|
+
// src/captcha.ts
|
|
5
|
+
/**
|
|
6
|
+
* 验证码生成与校验工具。
|
|
7
|
+
*
|
|
8
|
+
* 提供两种生成方式:
|
|
9
|
+
* - {@link XCaptcha.generate}:仅生成验证码文本,适合短信、邮件等无需图片的
|
|
10
|
+
* 场景;
|
|
11
|
+
* - {@link XCaptcha.generateDataUrl}:生成验证码文本与 SVG 图片 Data URL,
|
|
12
|
+
* 适合 Web 页面图片验证码。
|
|
13
|
+
*
|
|
14
|
+
* 生成时按指定字符集与长度随机拼接验证码,并将验证码明文、场景值、可重试
|
|
15
|
+
* 次数与附加信息一并写入缓存;校验时依次检查:
|
|
16
|
+
* 1. token 是否存在;
|
|
17
|
+
* 2. 场景值是否一致;
|
|
18
|
+
* 3. 验证码是否一致(大小写不敏感);
|
|
19
|
+
* 4. 附加信息是否通过(可选)。
|
|
20
|
+
*
|
|
21
|
+
* 任意一项不通过即返回 `false`,且每次校验都会消耗一次重试次数或使验证码
|
|
22
|
+
* 失效(见 {@link XCaptchaGeneratePayload.retryCount})。
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const captcha = new XCaptcha({ cache });
|
|
26
|
+
*
|
|
27
|
+
* const { token, code, dataUrl } = await captcha.generateDataUrl({
|
|
28
|
+
* scene: 'login',
|
|
29
|
+
* size: 4,
|
|
30
|
+
* ttl: '5m',
|
|
31
|
+
* charset: '09',
|
|
32
|
+
* retryCount: 3,
|
|
33
|
+
* });
|
|
34
|
+
* // dataUrl 形如 'data:image/svg+xml,...'
|
|
35
|
+
*
|
|
36
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
37
|
+
* // true
|
|
38
|
+
*/
|
|
39
|
+
var XCaptcha = class {
|
|
40
|
+
cache;
|
|
41
|
+
/**
|
|
42
|
+
* 创建验证码工具实例。
|
|
43
|
+
*
|
|
44
|
+
* @param options 配置项,包含缓存服务实例。
|
|
45
|
+
*/
|
|
46
|
+
constructor(options) {
|
|
47
|
+
this.cache = options.cache;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 各字符集对应的字符数组。
|
|
51
|
+
*
|
|
52
|
+
* 键为 {@link XCaptchaGenerateCharset} 取值,值为可从中随机取样的字符列表;
|
|
53
|
+
* `generate` 从中逐个 `sample`,`generateDataUrl` 则将其拼接为 svg-captcha
|
|
54
|
+
* 的 `charPreset`。
|
|
55
|
+
*/
|
|
56
|
+
charsetMap = {
|
|
57
|
+
"09": "0123456789".split(""),
|
|
58
|
+
"19": "123456789".split(""),
|
|
59
|
+
az: "abcdefghijklmnopqrstuvwxyz".split(""),
|
|
60
|
+
AZ: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""),
|
|
61
|
+
aZ: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""),
|
|
62
|
+
"0z": "0123456789abcdefghijklmnopqrstuvwxyz".split(""),
|
|
63
|
+
"0Z": "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""),
|
|
64
|
+
"1z": "123456789abcdefghijklmnopqrstuvwxyz".split(""),
|
|
65
|
+
"1Z": "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 生成验证码文本。
|
|
69
|
+
*
|
|
70
|
+
* 从 {@link XCaptchaGeneratePayload.charset} 对应的字符集中随机抽取
|
|
71
|
+
* `size` 个字符组成验证码,与场景值、可重试次数、附加信息一起写入缓存,
|
|
72
|
+
* 并返回缓存 token 与验证码明文。
|
|
73
|
+
*
|
|
74
|
+
* 适用于短信、邮件等不需要图片的验证码场景;若需要图片请使用
|
|
75
|
+
* {@link XCaptcha.generateDataUrl}。
|
|
76
|
+
*
|
|
77
|
+
* @param payload 生成参数,包含场景值、长度、存活时长、字符集等。
|
|
78
|
+
* @returns 包含 `token` 与验证码明文 `code` 的结果对象。
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* const { token, code } = await captcha.generate({
|
|
82
|
+
* scene: 'sms-login',
|
|
83
|
+
* size: 6,
|
|
84
|
+
* ttl: '5m',
|
|
85
|
+
* charset: '09',
|
|
86
|
+
* });
|
|
87
|
+
* // token 形如 'X__xxxx',code 为 6 位数字
|
|
88
|
+
*/
|
|
89
|
+
async generate(payload) {
|
|
90
|
+
const chars = this.charsetMap[payload.charset];
|
|
91
|
+
let code = "";
|
|
92
|
+
for (let i = 0; i < payload.size; i++) code += sample(chars);
|
|
93
|
+
return {
|
|
94
|
+
token: await this.cache.save({
|
|
95
|
+
code,
|
|
96
|
+
scene: payload.scene,
|
|
97
|
+
retryCount: payload.retryCount ?? 0,
|
|
98
|
+
extra: payload.extra
|
|
99
|
+
}, payload.ttl),
|
|
100
|
+
code
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 生成验证码文本与 SVG 图片 Data URL。
|
|
105
|
+
*
|
|
106
|
+
* 使用 svg-captcha 依据 {@link XCaptchaGeneratePayload.charset} 与 `size`
|
|
107
|
+
* 生成图片,并将验证码文本按与 {@link XCaptcha.generate} 相同的规则写入
|
|
108
|
+
* 缓存;随后将 SVG 转为 Data URL 一并返回。
|
|
109
|
+
*
|
|
110
|
+
* 适用于 Web 页面图片验证码,`dataUrl` 可直接作为 `<img>` 的 `src`。
|
|
111
|
+
*
|
|
112
|
+
* @param payload 生成参数,包含场景值、长度、存活时长、字符集等。
|
|
113
|
+
* @returns 包含 `token`、验证码明文 `code` 与图片 `dataUrl` 的结果对象。
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const { token, dataUrl } = await captcha.generateDataUrl({
|
|
117
|
+
* scene: 'login',
|
|
118
|
+
* size: 4,
|
|
119
|
+
* ttl: '5m',
|
|
120
|
+
* charset: 'aZ',
|
|
121
|
+
* });
|
|
122
|
+
* // dataUrl 形如 'data:image/svg+xml,...'
|
|
123
|
+
*/
|
|
124
|
+
async generateDataUrl(payload) {
|
|
125
|
+
const { text: code, data: svg } = svgCaptcha.create({
|
|
126
|
+
size: payload.size,
|
|
127
|
+
charPreset: this.charsetMap[payload.charset].join("")
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
token: await this.cache.save({
|
|
131
|
+
code,
|
|
132
|
+
scene: payload.scene,
|
|
133
|
+
retryCount: payload.retryCount ?? 0,
|
|
134
|
+
extra: payload.extra
|
|
135
|
+
}, payload.ttl),
|
|
136
|
+
code,
|
|
137
|
+
dataUrl: svgToDataUrl(svg)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 校验验证码。
|
|
142
|
+
*
|
|
143
|
+
* 按顺序执行以下检查,任意一项不通过即返回 `false`:
|
|
144
|
+
* 1. token 存在:缓存中找不到 token 对应数据时直接失败;
|
|
145
|
+
* 2. 场景一致:`scene` 与生成时不一致时失败,防止跨场景误用;
|
|
146
|
+
* 3. 验证码一致:`code` 与生成时一致(大小写不敏感);
|
|
147
|
+
* 4. 附加信息:提供了 `extra` 时执行其回调,返回 `false` 则失败。
|
|
148
|
+
*
|
|
149
|
+
* 注意:每次校验(无论最终是否通过)都会消耗一次重试次数——可重试次数
|
|
150
|
+
* 大于 0 时扣减并写回缓存,等于 0 时直接删除缓存,此后该 token 不可再用。
|
|
151
|
+
*
|
|
152
|
+
* @param options 校验参数,包含 token、验证码明文、场景值与可选的附加信息
|
|
153
|
+
* 比对函数。
|
|
154
|
+
* @returns 校验是否通过。
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
158
|
+
* // true
|
|
159
|
+
* await captcha.verify({ token, code, scene: 'login' });
|
|
160
|
+
* // false:该 token 已失效
|
|
161
|
+
*/
|
|
162
|
+
async verify(options) {
|
|
163
|
+
const expected = await this.cache.get(options.token);
|
|
164
|
+
if (expected == null) return false;
|
|
165
|
+
if (expected.retryCount === 0) await this.cache.remove(options.token);
|
|
166
|
+
else {
|
|
167
|
+
expected.retryCount--;
|
|
168
|
+
const ttl = await this.cache.ttl(options.token);
|
|
169
|
+
await this.cache.set(options.token, expected, ttl);
|
|
170
|
+
}
|
|
171
|
+
return expected.scene === options.scene && options.code.toLowerCase() === expected.code.toLowerCase() && (options.extra ? options.extra(expected.extra) : true);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
export { XCaptcha };
|
package/lib/chinese.d.mts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* 基于 `pinyin-pro` 实现,返回逐字的拼音数组:
|
|
5
5
|
* - `pinyin`:无声调拼音,如 `XChinese.pinyin('我们')` → `['wo', 'men']`;
|
|
6
6
|
* - `pinyinWithTone`:带声调拼音(声调标在韵母上),
|
|
7
|
-
* 如 `XChinese.pinyinWithTone('我们')` → `['wǒ', 'men']
|
|
7
|
+
* 如 `XChinese.pinyinWithTone('我们')` → `['wǒ', 'men']`;
|
|
8
|
+
* - `pinyinInitials`:拼音首字母缩写(小写字符串),
|
|
9
|
+
* 如 `XChinese.pinyinInitials('我们')` → `'wm'`。
|
|
8
10
|
*
|
|
9
11
|
* 处理规则:
|
|
10
12
|
* - 每个汉字对应数组中的一个拼音元素;
|
|
@@ -36,4 +38,20 @@ export declare class XChinese {
|
|
|
36
38
|
* XChinese.pinyinWithTone('我爱你') // ['wǒ', 'ài', 'nǐ']
|
|
37
39
|
*/
|
|
38
40
|
static pinyinWithTone(text: string): string[];
|
|
41
|
+
/**
|
|
42
|
+
* 获取拼音首字母缩写(小写字符串)。
|
|
43
|
+
*
|
|
44
|
+
* 每个汉字取对应拼音的首字母,并统一转为小写;非汉字字符原样保留
|
|
45
|
+
* (其中的英文字母也会被转为小写,如 `ABC` → `abc`)。
|
|
46
|
+
*
|
|
47
|
+
* @param text 要转换的汉语字符串,可混含非汉字字符。
|
|
48
|
+
* @returns 小写拼音首字母字符串,例如 `'我们'` → `'wm'`、`'我爱你'` → `'wan'`。
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* XChinese.pinyinInitials('我们') // 'wm'
|
|
52
|
+
* XChinese.pinyinInitials('我爱你') // 'wan'
|
|
53
|
+
* XChinese.pinyinInitials('我爱ABC') // 'waabc'
|
|
54
|
+
* XChinese.pinyinInitials('重庆') // 'cq'
|
|
55
|
+
*/
|
|
56
|
+
static pinyinInitials(text: string): string;
|
|
39
57
|
}
|
package/lib/chinese.mjs
CHANGED
|
@@ -6,7 +6,9 @@ import { pinyin } from "pinyin-pro";
|
|
|
6
6
|
* 基于 `pinyin-pro` 实现,返回逐字的拼音数组:
|
|
7
7
|
* - `pinyin`:无声调拼音,如 `XChinese.pinyin('我们')` → `['wo', 'men']`;
|
|
8
8
|
* - `pinyinWithTone`:带声调拼音(声调标在韵母上),
|
|
9
|
-
* 如 `XChinese.pinyinWithTone('我们')` → `['wǒ', 'men']
|
|
9
|
+
* 如 `XChinese.pinyinWithTone('我们')` → `['wǒ', 'men']`;
|
|
10
|
+
* - `pinyinInitials`:拼音首字母缩写(小写字符串),
|
|
11
|
+
* 如 `XChinese.pinyinInitials('我们')` → `'wm'`。
|
|
10
12
|
*
|
|
11
13
|
* 处理规则:
|
|
12
14
|
* - 每个汉字对应数组中的一个拼音元素;
|
|
@@ -48,5 +50,30 @@ var XChinese = class {
|
|
|
48
50
|
toneType: "symbol"
|
|
49
51
|
});
|
|
50
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 获取拼音首字母缩写(小写字符串)。
|
|
55
|
+
*
|
|
56
|
+
* 每个汉字取对应拼音的首字母,并统一转为小写;非汉字字符原样保留
|
|
57
|
+
* (其中的英文字母也会被转为小写,如 `ABC` → `abc`)。
|
|
58
|
+
*
|
|
59
|
+
* @param text 要转换的汉语字符串,可混含非汉字字符。
|
|
60
|
+
* @returns 小写拼音首字母字符串,例如 `'我们'` → `'wm'`、`'我爱你'` → `'wan'`。
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* XChinese.pinyinInitials('我们') // 'wm'
|
|
64
|
+
* XChinese.pinyinInitials('我爱你') // 'wan'
|
|
65
|
+
* XChinese.pinyinInitials('我爱ABC') // 'waabc'
|
|
66
|
+
* XChinese.pinyinInitials('重庆') // 'cq'
|
|
67
|
+
*/
|
|
68
|
+
static pinyinInitials(text) {
|
|
69
|
+
return pinyin(text, {
|
|
70
|
+
type: "string",
|
|
71
|
+
pattern: "first",
|
|
72
|
+
toneType: "none",
|
|
73
|
+
separator: "",
|
|
74
|
+
removeNonZh: false,
|
|
75
|
+
nonZh: "consecutive"
|
|
76
|
+
}).toLowerCase();
|
|
77
|
+
}
|
|
51
78
|
};
|
|
52
79
|
export { XChinese };
|
package/lib/cli.d.mts
CHANGED
|
@@ -1,46 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
* 支持的交互提问类型:
|
|
3
|
-
* - `confirm`:是/否确认,结果为布尔值;
|
|
4
|
-
* - `input`:文本输入,结果为字符串。
|
|
5
|
-
*/
|
|
6
|
-
export type XCliPromptType = 'confirm' | 'input';
|
|
7
|
-
/**
|
|
8
|
-
* 提问类型与返回值类型的映射。
|
|
9
|
-
*/
|
|
10
|
-
export interface XCliPromptResult {
|
|
11
|
-
confirm: boolean;
|
|
12
|
-
input: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* 单次交互提问的配置。
|
|
16
|
-
*/
|
|
17
|
-
export interface XCliPromptOptions<T extends XCliPromptType> {
|
|
18
|
-
/** 展示给用户的提示文案。 */
|
|
19
|
-
message: string;
|
|
20
|
-
/** 提问类型,决定返回值类型。 */
|
|
21
|
-
type: T;
|
|
22
|
-
/** 可选的默认值,类型随 `type` 联动。 */
|
|
23
|
-
default?: XCliPromptResult[T];
|
|
24
|
-
}
|
|
1
|
+
import { checkbox, confirm, editor, expand, input, number, password, rawlist, search, select } from "@inquirer/prompts";
|
|
25
2
|
/**
|
|
26
3
|
* 命令行交互与表格输出工具。
|
|
27
4
|
*
|
|
28
|
-
* 基于
|
|
29
|
-
* - `
|
|
5
|
+
* 基于 `@inquirer/prompts` 与 `cli-table3` 封装:
|
|
6
|
+
* - 以静态属性的形式直接透出 `input`、`select`、`checkbox`、`confirm`、
|
|
7
|
+
* `search`、`password`、`expand`、`editor`、`number`、`rawlist` 等交互提问函数,
|
|
8
|
+
* 签名与返回类型与 `@inquirer/prompts` 保持一致;
|
|
30
9
|
* - `table`:将二维数组渲染为对齐的文本表格。
|
|
31
10
|
*/
|
|
32
11
|
export declare class XCli {
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
12
|
+
/** 发起一次文本输入交互,返回 `Promise<string>`。 */
|
|
13
|
+
static input: typeof input;
|
|
14
|
+
/** 发起一次单选交互,返回选中项的值。 */
|
|
15
|
+
static select: typeof select;
|
|
16
|
+
/** 发起一次多选交互,返回选中项的数组。 */
|
|
17
|
+
static checkbox: typeof checkbox;
|
|
18
|
+
/** 发起一次是/否确认交互,返回 `Promise<boolean>`。 */
|
|
19
|
+
static confirm: typeof confirm;
|
|
20
|
+
/** 发起一次带搜索过滤的单选交互,返回选中项的值。 */
|
|
21
|
+
static search: typeof search;
|
|
22
|
+
/** 发起一次隐藏输入的密码交互,返回 `Promise<string>`。 */
|
|
23
|
+
static password: typeof password;
|
|
24
|
+
/** 发起一次可展开选项的交互,返回选中项的值。 */
|
|
25
|
+
static expand: typeof expand;
|
|
26
|
+
/** 打开外部编辑器进行文本输入,返回 `Promise<string>`。 */
|
|
27
|
+
static editor: typeof editor;
|
|
28
|
+
/** 发起一次数字输入交互,返回数字结果(未强制必填时可能为 `undefined`)。 */
|
|
29
|
+
static number: typeof number;
|
|
30
|
+
/** 发起一次原始列表单选交互,返回选中项的值。 */
|
|
31
|
+
static rawlist: typeof rawlist;
|
|
44
32
|
/**
|
|
45
33
|
* 将二维数组渲染为对齐的文本表格。
|
|
46
34
|
*
|
package/lib/cli.mjs
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
+
import { checkbox, confirm, editor, expand, input, number, password, rawlist, search, select } from "@inquirer/prompts";
|
|
1
2
|
import Table from "cli-table3";
|
|
2
|
-
import inquirer from "inquirer";
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
/**
|
|
5
5
|
* 命令行交互与表格输出工具。
|
|
6
6
|
*
|
|
7
|
-
* 基于
|
|
8
|
-
* - `
|
|
7
|
+
* 基于 `@inquirer/prompts` 与 `cli-table3` 封装:
|
|
8
|
+
* - 以静态属性的形式直接透出 `input`、`select`、`checkbox`、`confirm`、
|
|
9
|
+
* `search`、`password`、`expand`、`editor`、`number`、`rawlist` 等交互提问函数,
|
|
10
|
+
* 签名与返回类型与 `@inquirer/prompts` 保持一致;
|
|
9
11
|
* - `table`:将二维数组渲染为对齐的文本表格。
|
|
10
12
|
*/
|
|
11
13
|
var XCli = class {
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
/** 发起一次文本输入交互,返回 `Promise<string>`。 */
|
|
15
|
+
static input = input;
|
|
16
|
+
/** 发起一次单选交互,返回选中项的值。 */
|
|
17
|
+
static select = select;
|
|
18
|
+
/** 发起一次多选交互,返回选中项的数组。 */
|
|
19
|
+
static checkbox = checkbox;
|
|
20
|
+
/** 发起一次是/否确认交互,返回 `Promise<boolean>`。 */
|
|
21
|
+
static confirm = confirm;
|
|
22
|
+
/** 发起一次带搜索过滤的单选交互,返回选中项的值。 */
|
|
23
|
+
static search = search;
|
|
24
|
+
/** 发起一次隐藏输入的密码交互,返回 `Promise<string>`。 */
|
|
25
|
+
static password = password;
|
|
26
|
+
/** 发起一次可展开选项的交互,返回选中项的值。 */
|
|
27
|
+
static expand = expand;
|
|
28
|
+
/** 打开外部编辑器进行文本输入,返回 `Promise<string>`。 */
|
|
29
|
+
static editor = editor;
|
|
30
|
+
/** 发起一次数字输入交互,返回数字结果(未强制必填时可能为 `undefined`)。 */
|
|
31
|
+
static number = number;
|
|
32
|
+
/** 发起一次原始列表单选交互,返回选中项的值。 */
|
|
33
|
+
static rawlist = rawlist;
|
|
31
34
|
/**
|
|
32
35
|
* 将二维数组渲染为对齐的文本表格。
|
|
33
36
|
*
|
package/lib/crawler.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XPuppeteerGetPageHtmlOptions } from "./puppeteer.mjs";
|
|
2
|
-
import { HTMLElement } from "node-html-parser";
|
|
3
2
|
import { EventBus } from "vtils";
|
|
3
|
+
import { HTMLElement } from "node-html-parser";
|
|
4
4
|
import { VaeObjectSchemaShapeOf, v } from "vtils/vae";
|
|
5
5
|
import { AsyncOrSync, OneOrMore } from "vtils/types";
|
|
6
6
|
export type XCrawlerData = Record<any, any>;
|
package/lib/crawler.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { XPuppeteer } from "./puppeteer.mjs";
|
|
2
2
|
import { xr } from "@jayfong/x-request";
|
|
3
|
-
import { parse } from "node-html-parser";
|
|
4
3
|
import { EventBus, castArray, cloneDeepFast, escape, get, isRegExp, isUrl, keysStrict, memoize, omitStrict } from "vtils";
|
|
4
|
+
import { parse } from "node-html-parser";
|
|
5
5
|
import { v } from "vtils/vae";
|
|
6
6
|
// src/crawler.ts
|
|
7
7
|
/**
|