@jayfong/x-utils 1.0.0-202510061015 → 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/case.d.mts +184 -0
- package/lib/case.mjs +120 -0
- package/lib/chinese.d.mts +57 -0
- package/lib/chinese.mjs +79 -0
- package/lib/cli.d.mts +52 -0
- package/lib/cli.mjs +62 -0
- package/lib/clipboard.d.mts +50 -0
- package/lib/clipboard.mjs +122 -0
- package/lib/cmd.d.mts +38 -0
- package/lib/cmd.mjs +52 -0
- package/lib/crawler.d.mts +330 -0
- package/lib/crawler.mjs +313 -0
- package/lib/crypto.d.mts +158 -0
- package/lib/crypto.mjs +213 -0
- package/lib/csv.d.mts +67 -0
- package/lib/csv.mjs +76 -0
- package/lib/data_set.d.mts +76 -0
- package/lib/data_set.mjs +104 -0
- 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 +111 -0
- package/lib/fs.mjs +127 -0
- package/lib/html.d.mts +103 -0
- package/lib/html.mjs +112 -0
- package/lib/json.d.mts +50 -0
- package/lib/json.mjs +74 -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.d.mts +88 -0
- package/lib/puppeteer.mjs +219 -0
- 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 +80 -0
- package/lib/runner.mjs +66 -0
- 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 +37 -0
- package/lib/task.mjs +43 -0
- package/lib/text.d.mts +84 -0
- package/lib/text.mjs +141 -0
- package/lib/yaml.d.mts +51 -0
- package/lib/yaml.mjs +64 -0
- package/package.json +82 -164
- package/_cjs/ai.js +0 -80
- package/_cjs/alipan.js +0 -173
- package/_cjs/cli.js +0 -51
- package/_cjs/clipboard.js +0 -65
- package/_cjs/cmd.js +0 -42
- package/_cjs/compressor.js +0 -47
- package/_cjs/crawler.js +0 -341
- package/_cjs/csv.js +0 -97
- package/_cjs/data_set.js +0 -115
- package/_cjs/env.js +0 -82
- package/_cjs/fs.js +0 -86
- package/_cjs/html.js +0 -62
- package/_cjs/http_server.js +0 -79
- package/_cjs/json.js +0 -49
- package/_cjs/ksb.js +0 -218
- package/_cjs/number.js +0 -39
- package/_cjs/ocr.js +0 -74
- package/_cjs/package.json +0 -3
- package/_cjs/path.js +0 -35
- package/_cjs/pinyin.js +0 -37
- package/_cjs/puppeteer.js +0 -271
- package/_cjs/runner.js +0 -88
- package/_cjs/task.js +0 -41
- package/_cjs/text.js +0 -136
- package/_cjs/unicode/lib_unicode.js +0 -58643
- package/_cjs/unicode/unicode.js +0 -441
- package/_cjs/upload.js +0 -101
- package/_cjs/yaml.js +0 -52
- package/ai.d.ts +0 -26
- package/ai.js +0 -60
- package/alipan.d.ts +0 -209
- package/alipan.js +0 -153
- package/cli.d.ts +0 -14
- package/cli.js +0 -21
- package/clipboard.d.ts +0 -6
- package/clipboard.js +0 -35
- package/cmd.d.ts +0 -5
- package/cmd.js +0 -22
- package/compressor.d.ts +0 -5
- package/compressor.js +0 -17
- package/crawler.d.ts +0 -162
- package/crawler.js +0 -332
- package/csv.d.ts +0 -14
- package/csv.js +0 -67
- package/data_set.d.ts +0 -22
- package/data_set.js +0 -85
- package/env.d.ts +0 -6
- package/env.js +0 -52
- package/fs.d.ts +0 -23
- package/fs.js +0 -56
- package/html.d.ts +0 -7
- package/html.js +0 -45
- package/http_server.d.ts +0 -13
- package/http_server.js +0 -49
- package/json.d.ts +0 -6
- package/json.js +0 -19
- package/ksb.d.ts +0 -37
- package/ksb.js +0 -198
- package/number.d.ts +0 -3
- package/number.js +0 -9
- package/ocr.d.ts +0 -17
- package/ocr.js +0 -54
- package/path.d.ts +0 -2
- package/path.js +0 -5
- package/pinyin.d.ts +0 -8
- package/pinyin.js +0 -17
- package/puppeteer.d.ts +0 -38
- package/puppeteer.js +0 -241
- package/runner.d.ts +0 -13
- package/runner.js +0 -58
- package/task.d.ts +0 -6
- package/task.js +0 -21
- package/text.d.ts +0 -63
- package/text.js +0 -119
- package/unicode/lib_unicode.d.ts +0 -4
- package/unicode/lib_unicode.js +0 -58623
- package/unicode/unicode.d.ts +0 -347
- package/unicode/unicode.js +0 -426
- package/upload.d.ts +0 -34
- package/upload.js +0 -71
- package/yaml.d.ts +0 -6
- package/yaml.js +0 -22
|
@@ -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/case.d.mts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Options, PascalCaseOptions } from "change-case";
|
|
2
|
+
/**
|
|
3
|
+
* `XCase.camel` 的 options 参数。
|
|
4
|
+
*
|
|
5
|
+
* 继承自 `change-case` 的 `PascalCaseOptions`,支持
|
|
6
|
+
* `mergeAmbiguousCharacters` 等全部配置项,属性语义与默认行为
|
|
7
|
+
* 同 change-case,调用时原样透传。
|
|
8
|
+
*/
|
|
9
|
+
export interface XCaseCamelOptions extends PascalCaseOptions {}
|
|
10
|
+
/**
|
|
11
|
+
* `XCase.pascal` 的 options 参数。
|
|
12
|
+
*
|
|
13
|
+
* 继承自 `change-case` 的 `PascalCaseOptions`,支持
|
|
14
|
+
* `mergeAmbiguousCharacters` 等全部配置项,属性语义与默认行为
|
|
15
|
+
* 同 change-case,调用时原样透传。
|
|
16
|
+
*/
|
|
17
|
+
export interface XCasePascalOptions extends PascalCaseOptions {}
|
|
18
|
+
/**
|
|
19
|
+
* `XCase.pascalSnake` 的 options 参数。
|
|
20
|
+
*
|
|
21
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
22
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
23
|
+
* 调用时原样透传。
|
|
24
|
+
*/
|
|
25
|
+
export interface XCasePascalSnakeOptions extends Options {}
|
|
26
|
+
/**
|
|
27
|
+
* `XCase.capital` 的 options 参数。
|
|
28
|
+
*
|
|
29
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
30
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
31
|
+
* 调用时原样透传。
|
|
32
|
+
*/
|
|
33
|
+
export interface XCaseCapitalOptions extends Options {}
|
|
34
|
+
/**
|
|
35
|
+
* `XCase.constant` 的 options 参数。
|
|
36
|
+
*
|
|
37
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
38
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
39
|
+
* 调用时原样透传。
|
|
40
|
+
*/
|
|
41
|
+
export interface XCaseConstantOptions extends Options {}
|
|
42
|
+
/**
|
|
43
|
+
* `XCase.dot` 的 options 参数。
|
|
44
|
+
*
|
|
45
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
46
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
47
|
+
* 调用时原样透传。
|
|
48
|
+
*/
|
|
49
|
+
export interface XCaseDotOptions extends Options {}
|
|
50
|
+
/**
|
|
51
|
+
* `XCase.kebab` 的 options 参数。
|
|
52
|
+
*
|
|
53
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
54
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
55
|
+
* 调用时原样透传。
|
|
56
|
+
*/
|
|
57
|
+
export interface XCaseKebabOptions extends Options {}
|
|
58
|
+
/**
|
|
59
|
+
* `XCase.path` 的 options 参数。
|
|
60
|
+
*
|
|
61
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
62
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
63
|
+
* 调用时原样透传。
|
|
64
|
+
*/
|
|
65
|
+
export interface XCasePathOptions extends Options {}
|
|
66
|
+
/**
|
|
67
|
+
* `XCase.sentence` 的 options 参数。
|
|
68
|
+
*
|
|
69
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
70
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
71
|
+
* 调用时原样透传。
|
|
72
|
+
*/
|
|
73
|
+
export interface XCaseSentenceOptions extends Options {}
|
|
74
|
+
/**
|
|
75
|
+
* `XCase.snake` 的 options 参数。
|
|
76
|
+
*
|
|
77
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
78
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
79
|
+
* 调用时原样透传。
|
|
80
|
+
*/
|
|
81
|
+
export interface XCaseSnakeOptions extends Options {}
|
|
82
|
+
/**
|
|
83
|
+
* `XCase.train` 的 options 参数。
|
|
84
|
+
*
|
|
85
|
+
* 继承自 `change-case` 的 `Options`,支持 `locale`、`delimiter`、
|
|
86
|
+
* `split` 等全部配置项,属性语义与默认行为同 change-case,
|
|
87
|
+
* 调用时原样透传。
|
|
88
|
+
*/
|
|
89
|
+
export interface XCaseTrainOptions extends Options {}
|
|
90
|
+
/**
|
|
91
|
+
* 字符串大小写风格转换工具。
|
|
92
|
+
*
|
|
93
|
+
* 所有方法均委托给 `change-case` 包实现,方法名去掉了原函数名的 `Case`
|
|
94
|
+
* 后缀(如 `camelCase` → `camel`,`pascalSnakeCase` → `pascalSnake`)。
|
|
95
|
+
*
|
|
96
|
+
* 各方法的 `options` 参数会原样透传给 `change-case` 对应函数,
|
|
97
|
+
* 类型分别为导出的 `XCaseCamelOptions`、`XCasePascalOptions`、
|
|
98
|
+
* `XCasePascalSnakeOptions`、`XCaseCapitalOptions`、`XCaseConstantOptions`、
|
|
99
|
+
* `XCaseDotOptions`、`XCaseKebabOptions`、`XCasePathOptions`、
|
|
100
|
+
* `XCaseSentenceOptions`、`XCaseSnakeOptions`、`XCaseTrainOptions`,
|
|
101
|
+
* 可配置 `locale`、`delimiter`、`split` 等(`camel`/`pascal` 额外支持
|
|
102
|
+
* `mergeAmbiguousCharacters`)。
|
|
103
|
+
*
|
|
104
|
+
* 以 `Hello World` 为例,各方法的输出:
|
|
105
|
+
* - `camel` → `helloWorld`
|
|
106
|
+
* - `pascal` → `HelloWorld`
|
|
107
|
+
* - `pascalSnake` → `Hello_World`
|
|
108
|
+
* - `capital` → `Hello World`
|
|
109
|
+
* - `constant` → `HELLO_WORLD`
|
|
110
|
+
* - `dot` → `hello.world`
|
|
111
|
+
* - `kebab` → `hello-world`
|
|
112
|
+
* - `path` → `hello/world`
|
|
113
|
+
* - `sentence` → `Hello world`
|
|
114
|
+
* - `snake` → `hello_world`
|
|
115
|
+
* - `train` → `Hello-World`
|
|
116
|
+
*/
|
|
117
|
+
export declare class XCase {
|
|
118
|
+
/**
|
|
119
|
+
* 转换为 camelCase(小驼峰)命名。
|
|
120
|
+
*
|
|
121
|
+
* @example XCase.camel('Hello World') // 'helloWorld'
|
|
122
|
+
*/
|
|
123
|
+
static camel(text: string, options?: XCaseCamelOptions): string;
|
|
124
|
+
/**
|
|
125
|
+
* 转换为 PascalCase(大驼峰)命名。
|
|
126
|
+
*
|
|
127
|
+
* @example XCase.pascal('hello world') // 'HelloWorld'
|
|
128
|
+
*/
|
|
129
|
+
static pascal(text: string, options?: XCasePascalOptions): string;
|
|
130
|
+
/**
|
|
131
|
+
* 转换为 Pascal_Snake_Case(大驼峰 + 下划线)命名。
|
|
132
|
+
*
|
|
133
|
+
* @example XCase.pascalSnake('hello world') // 'Hello_World'
|
|
134
|
+
*/
|
|
135
|
+
static pascalSnake(text: string, options?: XCasePascalSnakeOptions): string;
|
|
136
|
+
/**
|
|
137
|
+
* 转换为 Capital Case(首字母大写的空格分隔)命名。
|
|
138
|
+
*
|
|
139
|
+
* @example XCase.capital('hello world') // 'Hello World'
|
|
140
|
+
*/
|
|
141
|
+
static capital(text: string, options?: XCaseCapitalOptions): string;
|
|
142
|
+
/**
|
|
143
|
+
* 转换为 CONSTANT_CASE(全大写下划线)命名。
|
|
144
|
+
*
|
|
145
|
+
* @example XCase.constant('hello world') // 'HELLO_WORLD'
|
|
146
|
+
*/
|
|
147
|
+
static constant(text: string, options?: XCaseConstantOptions): string;
|
|
148
|
+
/**
|
|
149
|
+
* 转换为 dot.case(点号分隔)命名。
|
|
150
|
+
*
|
|
151
|
+
* @example XCase.dot('hello world') // 'hello.world'
|
|
152
|
+
*/
|
|
153
|
+
static dot(text: string, options?: XCaseDotOptions): string;
|
|
154
|
+
/**
|
|
155
|
+
* 转换为 kebab-case(短横线分隔)命名。
|
|
156
|
+
*
|
|
157
|
+
* @example XCase.kebab('Hello World') // 'hello-world'
|
|
158
|
+
*/
|
|
159
|
+
static kebab(text: string, options?: XCaseKebabOptions): string;
|
|
160
|
+
/**
|
|
161
|
+
* 转换为 path/case(斜杠分隔)命名。
|
|
162
|
+
*
|
|
163
|
+
* @example XCase.path('hello world') // 'hello/world'
|
|
164
|
+
*/
|
|
165
|
+
static path(text: string, options?: XCasePathOptions): string;
|
|
166
|
+
/**
|
|
167
|
+
* 转换为 Sentence case(首字母大写的句子)命名。
|
|
168
|
+
*
|
|
169
|
+
* @example XCase.sentence('hello world') // 'Hello world'
|
|
170
|
+
*/
|
|
171
|
+
static sentence(text: string, options?: XCaseSentenceOptions): string;
|
|
172
|
+
/**
|
|
173
|
+
* 转换为 snake_case(小写下划线)命名。
|
|
174
|
+
*
|
|
175
|
+
* @example XCase.snake('Hello World') // 'hello_world'
|
|
176
|
+
*/
|
|
177
|
+
static snake(text: string, options?: XCaseSnakeOptions): string;
|
|
178
|
+
/**
|
|
179
|
+
* 转换为 Train-Case(短横线分隔、各单词首字母大写)命名。
|
|
180
|
+
*
|
|
181
|
+
* @example XCase.train('hello world') // 'Hello-World'
|
|
182
|
+
*/
|
|
183
|
+
static train(text: string, options?: XCaseTrainOptions): string;
|
|
184
|
+
}
|