@maiyunnet/kebab 9.13.8 → 9.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/doc/kebab-rag.md +372 -180
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/core.js +24 -7
- package/lib/crypto.d.ts +26 -26
- package/lib/crypto.js +1 -1
- package/lib/sql.d.ts +27 -1
- package/lib/sql.js +34 -1
- package/lib/undici/response.d.ts +1 -1
- package/package.json +23 -23
- package/sys/child.js +15 -0
- package/sys/ctr.d.ts +4 -0
- package/sys/master.js +57 -31
- package/sys/mod.d.ts +33 -1
- package/sys/mod.js +37 -1
- package/sys/route.d.ts +4 -0
- package/sys/route.js +63 -0
- package/www/example/ctr/test.js +16 -0
- package/www/example/stc/chunk-FG5CKGYR.js +81 -0
- package/www/example/stc/lib/ui/checkbox.d.ts +1 -1
- package/www/example/stc/lib/ui/input.d.ts +1 -1
- package/www/example/stc/lib/ui/label.d.ts +1 -1
- package/www/example/stc/lib/ui/switch.d.ts +1 -1
- package/www/example/stc/view/hello.page.bundle.js +1 -1
- package/www/example/stc/view/hello.page.css +2 -2
- package/www/example/stc/view/react-router.page.bundle.js +1 -1
- package/www/example/stc/view/react-router.page.css +2 -2
- package/www/example/stc/view/react-router.page.d.ts +1 -1
- package/www/example/stc/view/react-router.page.js +1 -1
- package/www/example/stc/view/react-router.page.tsx +1 -1
- package/www/example/stc/view/react.page.bundle.js +3 -3
- package/www/example/stc/view/react.page.css +2 -2
- package/www/example/stc/view/react.page.d.ts +1 -1
- package/www/example/stc/chunk-YJ3GYATF.js +0 -81
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* --- 本文件用来定义每个目录实体地址的常量 ---
|
|
7
7
|
*/
|
|
8
8
|
/** --- 当前系统版本号 --- */
|
|
9
|
-
export const VER = '9.
|
|
9
|
+
export const VER = '9.14.1';
|
|
10
10
|
// --- 服务端用的路径 ---
|
|
11
11
|
const imu = decodeURIComponent(import.meta.url).replace('file://', '').replace(/^\/(\w:)/, '$1');
|
|
12
12
|
/** --- /xxx/xxx --- */
|
package/lib/core.js
CHANGED
|
@@ -307,10 +307,11 @@ export function muid(ctr, opt = {}) {
|
|
|
307
307
|
export function ip(ctr, req) {
|
|
308
308
|
const headers = ctr instanceof sCtr.Ctr ? ctr.getPrototype('_headers') : ctr;
|
|
309
309
|
if (typeof headers['cf-connecting-ip'] === 'string') {
|
|
310
|
-
return headers['cf-connecting-ip'];
|
|
310
|
+
return normalizeIP(headers['cf-connecting-ip']);
|
|
311
311
|
}
|
|
312
312
|
else if (typeof headers['x-forwarded-for'] === 'string') {
|
|
313
|
-
|
|
313
|
+
// --- X-Forwarded-For 可能为逗号分隔的多个 IP,取第一个即客户端 IP ---
|
|
314
|
+
return normalizeIP(headers['x-forwarded-for'].split(',')[0].trim());
|
|
314
315
|
}
|
|
315
316
|
else {
|
|
316
317
|
if (!req) {
|
|
@@ -321,21 +322,37 @@ export function ip(ctr, req) {
|
|
|
321
322
|
return '';
|
|
322
323
|
}
|
|
323
324
|
}
|
|
324
|
-
return req.socket.remoteAddress ?? '';
|
|
325
|
+
return normalizeIP(req.socket.remoteAddress ?? '');
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
/** --- 获取 CF 和 X 的 IP --- */
|
|
328
329
|
export function ips(ctr) {
|
|
329
330
|
const headers = ctr instanceof sCtr.Ctr ? ctr.getPrototype('_headers') : ctr;
|
|
330
331
|
return {
|
|
331
|
-
'cf': typeof headers['cf-connecting-ip'] === 'string' ? headers['cf-connecting-ip'] : '',
|
|
332
|
-
'x': typeof headers['x-forwarded-for'] === 'string' ? headers['x-forwarded-for'] : ''
|
|
332
|
+
'cf': typeof headers['cf-connecting-ip'] === 'string' ? normalizeIP(headers['cf-connecting-ip']) : '',
|
|
333
|
+
'x': typeof headers['x-forwarded-for'] === 'string' ? normalizeIP(headers['x-forwarded-for'].split(',')[0].trim()) : ''
|
|
333
334
|
};
|
|
334
335
|
}
|
|
335
336
|
/** --- 使用 X-Forwarded-For 的 CDN 厂商 --- */
|
|
336
337
|
export const REAL_IP_X = 'x-forwarded-for';
|
|
337
338
|
/** --- 使用的是 Cloudflare --- */
|
|
338
339
|
export const REAL_IP_CF = 'cf-connecting-ip';
|
|
340
|
+
/**
|
|
341
|
+
* --- 规范化 IP 地址 ---
|
|
342
|
+
* --- 将 IPv4-mapped IPv6(如 ::ffff:127.0.0.1)转换为纯 IPv4,原生 IPv6 保持不变 ---
|
|
343
|
+
* @param ip 原始 IP 地址
|
|
344
|
+
*/
|
|
345
|
+
function normalizeIP(ip) {
|
|
346
|
+
if (!ip) {
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
const value = ip.trim();
|
|
350
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(value);
|
|
351
|
+
if (mapped) {
|
|
352
|
+
return mapped[1];
|
|
353
|
+
}
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
339
356
|
/**
|
|
340
357
|
* --- 获取直连 IP(安全 IP) ---
|
|
341
358
|
* @param ctr
|
|
@@ -346,11 +363,11 @@ export function realIP(ctr, name = '') {
|
|
|
346
363
|
if (name !== '') {
|
|
347
364
|
const value = headers[name];
|
|
348
365
|
if (typeof value === 'string') {
|
|
349
|
-
return value;
|
|
366
|
+
return normalizeIP(value);
|
|
350
367
|
}
|
|
351
368
|
}
|
|
352
369
|
const req = ctr.getPrototype('_req');
|
|
353
|
-
return req.socket.remoteAddress ?? '';
|
|
370
|
+
return normalizeIP(req.socket.remoteAddress ?? '');
|
|
354
371
|
}
|
|
355
372
|
/**
|
|
356
373
|
* --- 间隔一段时间 ---
|
package/lib/crypto.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project: Kebab, User: JianSuoQiYue
|
|
3
3
|
* Date: 2019-4-2 14:01:06
|
|
4
|
-
* Last: 2020-3-12 14:05:24, 2022-09-12 11:52:35, 2024-9-8 17:09:39, 2024-11-11 00:21:58, 2025-6-18 20:27:47
|
|
4
|
+
* Last: 2020-3-12 14:05:24, 2022-09-12 11:52:35, 2024-9-8 17:09:39, 2024-11-11 00:21:58, 2025-6-18 20:27:47, 2026-07-29 11:51
|
|
5
5
|
*/
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
7
|
/**
|
|
@@ -31,8 +31,8 @@ export declare function generateKeyPair(type: string, options?: {
|
|
|
31
31
|
* @param format 输出格式
|
|
32
32
|
* @param algorithm 哈希方式
|
|
33
33
|
*/
|
|
34
|
-
export declare function sign(data:
|
|
35
|
-
export declare function sign(data:
|
|
34
|
+
export declare function sign(data: string | NodeJS.ArrayBufferView, privateKey: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput | crypto.SignJsonWebKeyInput, format: 'hex' | 'base64' | 'binary', algorithm?: string): string;
|
|
35
|
+
export declare function sign(data: string | NodeJS.ArrayBufferView, privateKey: crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput | crypto.SignJsonWebKeyInput, format?: 'buffer', algorithm?: string): Buffer;
|
|
36
36
|
/**
|
|
37
37
|
* --- 非对称验签 ---
|
|
38
38
|
* @param data 数据
|
|
@@ -40,31 +40,31 @@ export declare function sign(data: crypto.BinaryLike, privateKey: crypto.KeyLike
|
|
|
40
40
|
* @param signature 签名
|
|
41
41
|
* @param algorithm 哈希方式
|
|
42
42
|
*/
|
|
43
|
-
export declare function verify(data:
|
|
43
|
+
export declare function verify(data: string | NodeJS.ArrayBufferView, object: crypto.KeyLike | crypto.VerifyKeyObjectInput | crypto.VerifyPublicKeyInput | crypto.VerifyJsonWebKeyInput, signature: NodeJS.ArrayBufferView, algorithm?: string): boolean;
|
|
44
44
|
/**
|
|
45
45
|
* --- 非对称公钥加密 ---
|
|
46
46
|
* @param key 公钥
|
|
47
47
|
* @param buffer 数据
|
|
48
48
|
*/
|
|
49
|
-
export declare function publicEncrypt(key: crypto.
|
|
49
|
+
export declare function publicEncrypt(key: crypto.KeyLike | crypto.PublicKeyInput | crypto.PrivateKeyInput, buffer: NodeJS.ArrayBufferView | string): Buffer;
|
|
50
50
|
/**
|
|
51
51
|
* --- 非对称私钥加密 ---
|
|
52
52
|
* @param key 私钥
|
|
53
53
|
* @param buffer 数据
|
|
54
54
|
*/
|
|
55
|
-
export declare function privateEncrypt(key: crypto.
|
|
55
|
+
export declare function privateEncrypt(key: crypto.KeyLike | crypto.PrivateKeyInput, buffer: NodeJS.ArrayBufferView | string): Buffer;
|
|
56
56
|
/**
|
|
57
57
|
* --- 非对称公钥解密 ---
|
|
58
58
|
* @param key 公钥
|
|
59
59
|
* @param buffer 数据
|
|
60
60
|
*/
|
|
61
|
-
export declare function publicDecrypt(key: crypto.
|
|
61
|
+
export declare function publicDecrypt(key: crypto.KeyLike | crypto.PublicKeyInput | crypto.PrivateKeyInput, buffer: NodeJS.ArrayBufferView | string): Buffer;
|
|
62
62
|
/**
|
|
63
63
|
* --- 非对称私钥解密 ---
|
|
64
64
|
* @param key 私钥
|
|
65
65
|
* @param buffer 数据
|
|
66
66
|
*/
|
|
67
|
-
export declare function privateDecrypt(key: crypto.
|
|
67
|
+
export declare function privateDecrypt(key: crypto.KeyLike | crypto.PrivateKeyInput, buffer: NodeJS.ArrayBufferView | string): Buffer;
|
|
68
68
|
/** --- 勿使用,无 iv 默认,但勿使用 --- */
|
|
69
69
|
export declare const AES_256_ECB = "aes-256-ecb";
|
|
70
70
|
/** --- 一般不用,兼容性场景下用 --- */
|
|
@@ -86,7 +86,7 @@ export declare const SM4_CFB = "sm4-cfb";
|
|
|
86
86
|
* @param method 加密方法
|
|
87
87
|
* @param output 输出类型
|
|
88
88
|
*/
|
|
89
|
-
export declare function cipherEncrypt(original: string | Buffer, key: crypto.
|
|
89
|
+
export declare function cipherEncrypt(original: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'base64' | 'buffer'): string | Buffer | false;
|
|
90
90
|
/**
|
|
91
91
|
* --- AES 加密 ---
|
|
92
92
|
* @param original 原始字符串
|
|
@@ -95,16 +95,16 @@ export declare function cipherEncrypt(original: string | Buffer, key: crypto.Cip
|
|
|
95
95
|
* @param method 加密方法
|
|
96
96
|
* @param output 输出类型
|
|
97
97
|
*/
|
|
98
|
-
export declare function aesEncrypt(original: string | Buffer, key: crypto.
|
|
99
|
-
export declare function aesEncrypt(original: string | Buffer, key: crypto.
|
|
98
|
+
export declare function aesEncrypt(original: string | Buffer, key: crypto.KeyLike, iv: string, method: string, output: 'buffer'): Buffer | false;
|
|
99
|
+
export declare function aesEncrypt(original: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'base64'): string | false;
|
|
100
100
|
/**
|
|
101
101
|
* --- AES GCM 托管加密 ---
|
|
102
102
|
* @param original 原始字符串
|
|
103
103
|
* @param key 密钥尽量 32 个英文字母和数字,不是 32 个系统会自动处理
|
|
104
104
|
* @param output 输出类型
|
|
105
105
|
*/
|
|
106
|
-
export declare function gcmEncrypt(original: string | Buffer, key: crypto.
|
|
107
|
-
export declare function gcmEncrypt(original: string | Buffer, key: crypto.
|
|
106
|
+
export declare function gcmEncrypt(original: string | Buffer, key: crypto.KeyLike, output: 'buffer'): Buffer | false;
|
|
107
|
+
export declare function gcmEncrypt(original: string | Buffer, key: crypto.KeyLike, output?: 'base64'): string | false;
|
|
108
108
|
/**
|
|
109
109
|
* --- SM4 加密 ---
|
|
110
110
|
* @param original 原始字符串
|
|
@@ -112,8 +112,8 @@ export declare function gcmEncrypt(original: string | Buffer, key: crypto.Cipher
|
|
|
112
112
|
* @param iv 向量 16 个英文字母和数字
|
|
113
113
|
* @param method 加密方法
|
|
114
114
|
*/
|
|
115
|
-
export declare function sm4Encrypt(original: string | Buffer, key: crypto.
|
|
116
|
-
export declare function sm4Encrypt(original: string | Buffer, key: crypto.
|
|
115
|
+
export declare function sm4Encrypt(original: string | Buffer, key: crypto.KeyLike, iv: string, method: string, output: 'buffer'): Buffer | false;
|
|
116
|
+
export declare function sm4Encrypt(original: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'base64'): string | false;
|
|
117
117
|
/**
|
|
118
118
|
* --- cipher 解密 ---
|
|
119
119
|
* @param encrypt 需解密的字符串
|
|
@@ -122,7 +122,7 @@ export declare function sm4Encrypt(original: string | Buffer, key: crypto.Cipher
|
|
|
122
122
|
* @param method 加密方法
|
|
123
123
|
* @param output 输出类型
|
|
124
124
|
*/
|
|
125
|
-
export declare function cipherDecrypt(encrypt: string | Buffer, key: crypto.
|
|
125
|
+
export declare function cipherDecrypt(encrypt: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'binary' | 'buffer'): string | Buffer | false;
|
|
126
126
|
/**
|
|
127
127
|
* --- AES 解密 ---
|
|
128
128
|
* @param encrypt 需解密的字符串
|
|
@@ -130,16 +130,16 @@ export declare function cipherDecrypt(encrypt: string | Buffer, key: crypto.Ciph
|
|
|
130
130
|
* @param iv 向量 16(CTR) 或 12(GCM) 个英文字母和数字
|
|
131
131
|
* @param method 加密方法
|
|
132
132
|
*/
|
|
133
|
-
export declare function aesDecrypt(encrypt: string | Buffer, key: crypto.
|
|
134
|
-
export declare function aesDecrypt(encrypt: string | Buffer, key: crypto.
|
|
133
|
+
export declare function aesDecrypt(encrypt: string | Buffer, key: crypto.KeyLike, iv: string, method: string, output: 'buffer'): Buffer | false;
|
|
134
|
+
export declare function aesDecrypt(encrypt: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'binary'): string | false;
|
|
135
135
|
/**
|
|
136
136
|
* --- AES 解密 ---
|
|
137
137
|
* @param encrypt 需解密的字符串
|
|
138
138
|
* @param key 密钥 32 个英文字母和数字
|
|
139
139
|
* @param output 输出类型
|
|
140
140
|
*/
|
|
141
|
-
export declare function gcmDecrypt(encrypt: string | Buffer, key: crypto.
|
|
142
|
-
export declare function gcmDecrypt(encrypt: string | Buffer, key: crypto.
|
|
141
|
+
export declare function gcmDecrypt(encrypt: string | Buffer, key: crypto.KeyLike, output: 'buffer'): Buffer | false;
|
|
142
|
+
export declare function gcmDecrypt(encrypt: string | Buffer, key: crypto.KeyLike, output?: 'binary'): string | false;
|
|
143
143
|
/**
|
|
144
144
|
* --- SM4 解密 ---
|
|
145
145
|
* @param encrypt 需解密的字符串
|
|
@@ -147,24 +147,24 @@ export declare function gcmDecrypt(encrypt: string | Buffer, key: crypto.CipherK
|
|
|
147
147
|
* @param iv 向量 16 个英文字母和数字
|
|
148
148
|
* @param method 加密方法
|
|
149
149
|
*/
|
|
150
|
-
export declare function sm4Decrypt(encrypt: string | Buffer, key: crypto.
|
|
151
|
-
export declare function sm4Decrypt(encrypt: string | Buffer, key: crypto.
|
|
150
|
+
export declare function sm4Decrypt(encrypt: string | Buffer, key: crypto.KeyLike, iv: string, method: string, output: 'buffer'): Buffer | false;
|
|
151
|
+
export declare function sm4Decrypt(encrypt: string | Buffer, key: crypto.KeyLike, iv?: string, method?: string, output?: 'binary'): string | false;
|
|
152
152
|
/**
|
|
153
153
|
* --- hash 或 hmac 加密 ---
|
|
154
154
|
* @param algorithm 哈希方式
|
|
155
155
|
* @param data 源数据
|
|
156
156
|
* @param key 设置则采用 hmac 加密
|
|
157
157
|
*/
|
|
158
|
-
export declare function hashHmac(algorithm: string, data: Buffer | string, key?: crypto.
|
|
159
|
-
export declare function hashHmac(algorithm: string, data: Buffer | string, key: crypto.
|
|
158
|
+
export declare function hashHmac(algorithm: string, data: Buffer | string, key?: crypto.KeyLike, format?: 'hex' | 'base64'): string;
|
|
159
|
+
export declare function hashHmac(algorithm: string, data: Buffer | string, key: crypto.KeyLike | undefined, format: 'buffer'): Buffer;
|
|
160
160
|
/**
|
|
161
161
|
* --- hash 或 hmac 加密文件 ---
|
|
162
162
|
* @param algorithm 加密方式,如 md5、sha256、sm3 等
|
|
163
163
|
* @param path 文件路径
|
|
164
164
|
* @param key 设置则采用 hmac 加密
|
|
165
165
|
*/
|
|
166
|
-
export declare function hashHmacFile(algorithm: string, path: string, key?: crypto.
|
|
167
|
-
export declare function hashHmacFile(algorithm: string, path: string, key: crypto.
|
|
166
|
+
export declare function hashHmacFile(algorithm: string, path: string, key?: crypto.KeyLike, encoding?: 'hex' | 'base64' | 'base64url'): Promise<string | false>;
|
|
167
|
+
export declare function hashHmacFile(algorithm: string, path: string, key: crypto.KeyLike, encoding: 'buffer'): Promise<Buffer | false>;
|
|
168
168
|
/**
|
|
169
169
|
* --- base64 编码 ---
|
|
170
170
|
* @param data 字符串或 Buffer
|
package/lib/crypto.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Project: Kebab, User: JianSuoQiYue
|
|
3
3
|
* Date: 2019-4-2 14:01:06
|
|
4
|
-
* Last: 2020-3-12 14:05:24, 2022-09-12 11:52:35, 2024-9-8 17:09:39, 2024-11-11 00:21:58, 2025-6-18 20:27:47
|
|
4
|
+
* Last: 2020-3-12 14:05:24, 2022-09-12 11:52:35, 2024-9-8 17:09:39, 2024-11-11 00:21:58, 2025-6-18 20:27:47, 2026-07-29 11:51
|
|
5
5
|
*/
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
7
|
// --- 库和定义 ---
|
package/lib/sql.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export declare class Sql {
|
|
|
42
42
|
private _placeholderCounter;
|
|
43
43
|
/** --- 是否忽略错误 --- */
|
|
44
44
|
private _ignore;
|
|
45
|
+
/** --- MySQL 优化器 Hint,如 INDEX(table idx) --- */
|
|
46
|
+
private _hint;
|
|
45
47
|
constructor(opt: {
|
|
46
48
|
'service': ESERVICE;
|
|
47
49
|
'ctr'?: ctr.Ctr;
|
|
@@ -51,6 +53,30 @@ export declare class Sql {
|
|
|
51
53
|
'sql'?: string[];
|
|
52
54
|
'alias'?: string[];
|
|
53
55
|
});
|
|
56
|
+
/**
|
|
57
|
+
* --- 设置 MySQL 优化器 Hint ---
|
|
58
|
+
* --- 会以 Optimizer Hint 注释语法注入到 SELECT 关键字后 ---
|
|
59
|
+
* --- 传入 hint 内容即可,无需包裹注释符号 ---
|
|
60
|
+
* @param h Hint 内容
|
|
61
|
+
* @example
|
|
62
|
+
* // --- 强制走指定索引(最常用) ---
|
|
63
|
+
* INDEX(`supply_date_0_0` `idx_supply_date_query`)
|
|
64
|
+
*
|
|
65
|
+
* // --- 指定某表不用某索引 ---
|
|
66
|
+
* NO_INDEX(`supply_date_0_0` `idx_old`)
|
|
67
|
+
*
|
|
68
|
+
* // --- 指定 JOIN 顺序和索引 ---
|
|
69
|
+
* JOIN_ORDER(`a` `b`) INDEX(`a` `idx_a`) INDEX(`b` `idx_b`)
|
|
70
|
+
*
|
|
71
|
+
* // --- 指定 JOIN 中某表使用的索引 ---
|
|
72
|
+
* JOIN_INDEX(`supply_date_0_0` `idx_supply_date_query`)
|
|
73
|
+
*
|
|
74
|
+
* // --- 多表多索引组合 ---
|
|
75
|
+
* INDEX(`t1` `idx_a`) JOIN_INDEX(`t2` `idx_b`)
|
|
76
|
+
*
|
|
77
|
+
* // --- 官方文档: https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html ---
|
|
78
|
+
*/
|
|
79
|
+
hint(h: string): this;
|
|
54
80
|
/**
|
|
55
81
|
* --- 插入数据前导 ---
|
|
56
82
|
* @param table 表名
|
|
@@ -303,4 +329,4 @@ export declare function value(val: kebab.DbValue): {
|
|
|
303
329
|
* --- 将对象转换为 JSON 字符串并避开类型检查,用于适配 PostgreSQL 的 jsonb 字段 ---
|
|
304
330
|
* @param obj 要转换的 JSON 对象
|
|
305
331
|
*/
|
|
306
|
-
export declare function json(obj:
|
|
332
|
+
export declare function json<T>(obj: T): T;
|
package/lib/sql.js
CHANGED
|
@@ -45,6 +45,8 @@ export class Sql {
|
|
|
45
45
|
_placeholderCounter = 1;
|
|
46
46
|
/** --- 是否忽略错误 --- */
|
|
47
47
|
_ignore = false;
|
|
48
|
+
/** --- MySQL 优化器 Hint,如 INDEX(table idx) --- */
|
|
49
|
+
_hint = '';
|
|
48
50
|
// --- 实例化 ---
|
|
49
51
|
constructor(opt) {
|
|
50
52
|
this._ctr = opt.ctr;
|
|
@@ -60,6 +62,33 @@ export class Sql {
|
|
|
60
62
|
this._alias = opt.alias;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* --- 设置 MySQL 优化器 Hint ---
|
|
67
|
+
* --- 会以 Optimizer Hint 注释语法注入到 SELECT 关键字后 ---
|
|
68
|
+
* --- 传入 hint 内容即可,无需包裹注释符号 ---
|
|
69
|
+
* @param h Hint 内容
|
|
70
|
+
* @example
|
|
71
|
+
* // --- 强制走指定索引(最常用) ---
|
|
72
|
+
* INDEX(`supply_date_0_0` `idx_supply_date_query`)
|
|
73
|
+
*
|
|
74
|
+
* // --- 指定某表不用某索引 ---
|
|
75
|
+
* NO_INDEX(`supply_date_0_0` `idx_old`)
|
|
76
|
+
*
|
|
77
|
+
* // --- 指定 JOIN 顺序和索引 ---
|
|
78
|
+
* JOIN_ORDER(`a` `b`) INDEX(`a` `idx_a`) INDEX(`b` `idx_b`)
|
|
79
|
+
*
|
|
80
|
+
* // --- 指定 JOIN 中某表使用的索引 ---
|
|
81
|
+
* JOIN_INDEX(`supply_date_0_0` `idx_supply_date_query`)
|
|
82
|
+
*
|
|
83
|
+
* // --- 多表多索引组合 ---
|
|
84
|
+
* INDEX(`t1` `idx_a`) JOIN_INDEX(`t2` `idx_b`)
|
|
85
|
+
*
|
|
86
|
+
* // --- 官方文档: https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html ---
|
|
87
|
+
*/
|
|
88
|
+
hint(h) {
|
|
89
|
+
this._hint = h;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
63
92
|
// --- 前导 ---
|
|
64
93
|
/**
|
|
65
94
|
* --- 插入数据前导 ---
|
|
@@ -880,7 +909,7 @@ export class Sql {
|
|
|
880
909
|
'data': data,
|
|
881
910
|
'sql': sql,
|
|
882
911
|
'alias': lCore.clone(this._alias),
|
|
883
|
-
});
|
|
912
|
+
}).hint(this._hint);
|
|
884
913
|
}
|
|
885
914
|
// --- 操作 ---
|
|
886
915
|
/**
|
|
@@ -888,6 +917,10 @@ export class Sql {
|
|
|
888
917
|
*/
|
|
889
918
|
getSql() {
|
|
890
919
|
let sql = this._sql.join('');
|
|
920
|
+
// --- 注入优化器 Hint 到 SELECT 关键字后 ---
|
|
921
|
+
if (this._hint && !sql.includes('/*+')) {
|
|
922
|
+
sql = sql.replace(/^SELECT /i, `SELECT /*+ ${this._hint} */ `);
|
|
923
|
+
}
|
|
891
924
|
if (this._pre) {
|
|
892
925
|
return this._alias.reduce((result, item) => {
|
|
893
926
|
if (this._service === ESERVICE.MYSQL) {
|
package/lib/undici/response.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare class Response {
|
|
|
31
31
|
/**
|
|
32
32
|
* --- 获取响应读取流对象 ---
|
|
33
33
|
*/
|
|
34
|
-
getStream():
|
|
34
|
+
getStream(): (import("undici/types/readable").default & undici.Dispatcher.BodyMixin) | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress | null;
|
|
35
35
|
/**
|
|
36
36
|
* --- 获取原生响应读取流对象 ---
|
|
37
37
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maiyunnet/kebab",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.14.1",
|
|
4
4
|
"description": "Simple, easy-to-use, and fully-featured Node.js framework that is ready-to-use out of the box.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -22,47 +22,47 @@
|
|
|
22
22
|
"#kebab/*": "./*"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@aws-sdk/client-s3": "^3.
|
|
26
|
-
"@aws-sdk/lib-storage": "^3.
|
|
25
|
+
"@aws-sdk/client-s3": "^3.1096.0",
|
|
26
|
+
"@aws-sdk/lib-storage": "^3.1096.0",
|
|
27
27
|
"@litert/http-client": "^1.1.2",
|
|
28
28
|
"@litert/mime": "^0.1.3",
|
|
29
29
|
"@litert/redis": "^3.2.1",
|
|
30
30
|
"@litert/websocket": "^0.2.8",
|
|
31
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
32
|
-
"@radix-ui/react-label": "^2.1.
|
|
33
|
-
"@radix-ui/react-slot": "^1.
|
|
34
|
-
"@radix-ui/react-switch": "^1.
|
|
35
|
-
"@tailwindcss/cli": "^4.3.
|
|
31
|
+
"@radix-ui/react-checkbox": "^1.3.11",
|
|
32
|
+
"@radix-ui/react-label": "^2.1.15",
|
|
33
|
+
"@radix-ui/react-slot": "^1.3.3",
|
|
34
|
+
"@radix-ui/react-switch": "^1.3.7",
|
|
35
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
36
36
|
"@types/ssh2": "^1.15.5",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"ajv-formats": "^3.0.1",
|
|
39
39
|
"class-variance-authority": "^0.7.1",
|
|
40
40
|
"clsx": "^2.1.1",
|
|
41
|
-
"ejs": "^
|
|
42
|
-
"esbuild": "^0.28.
|
|
41
|
+
"ejs": "^6.0.1",
|
|
42
|
+
"esbuild": "^0.28.1",
|
|
43
43
|
"jszip": "^3.10.1",
|
|
44
|
-
"mysql2": "^3.
|
|
45
|
-
"node-cron": "^4.
|
|
46
|
-
"openai": "^
|
|
47
|
-
"pg": "^8.
|
|
48
|
-
"react": "^19.2.
|
|
49
|
-
"react-dom": "^19.2.
|
|
50
|
-
"react-router-dom": "^7.
|
|
44
|
+
"mysql2": "^3.23.2",
|
|
45
|
+
"node-cron": "^4.6.0",
|
|
46
|
+
"openai": "^7.0.0",
|
|
47
|
+
"pg": "^8.22.0",
|
|
48
|
+
"react": "^19.2.8",
|
|
49
|
+
"react-dom": "^19.2.8",
|
|
50
|
+
"react-router-dom": "^7.18.1",
|
|
51
51
|
"ssh2": "^1.17.0",
|
|
52
52
|
"svg-captcha": "^1.4.0",
|
|
53
53
|
"tailwind-merge": "^3.6.0",
|
|
54
|
-
"tencentcloud-sdk-nodejs": "^4.1.
|
|
55
|
-
"undici": "^8.
|
|
54
|
+
"tencentcloud-sdk-nodejs": "^4.1.277",
|
|
55
|
+
"undici": "^8.9.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@litert/eslint-plugin-rules": "^0.3.1",
|
|
59
59
|
"@types/ejs": "^3.1.5",
|
|
60
|
-
"@types/node": "^
|
|
60
|
+
"@types/node": "^26.1.2",
|
|
61
61
|
"@types/pg": "^8.20.0",
|
|
62
|
-
"@types/react": "^19.2.
|
|
62
|
+
"@types/react": "^19.2.17",
|
|
63
63
|
"@types/react-dom": "^19.2.3",
|
|
64
|
-
"typedoc": "^0.28.
|
|
65
|
-
"typedoc-plugin-markdown": "^4.
|
|
64
|
+
"typedoc": "^0.28.20",
|
|
65
|
+
"typedoc-plugin-markdown": "^4.12.0",
|
|
66
66
|
"typescript": "^6.0.3"
|
|
67
67
|
}
|
|
68
68
|
}
|
package/sys/child.js
CHANGED
|
@@ -47,6 +47,8 @@ let http2Server;
|
|
|
47
47
|
const linkCount = {};
|
|
48
48
|
/** --- 是否正在停止,停止时对 HTTP/1.1 响应追加 Connection: close,避免请求完成后连接回到保活池 --- */
|
|
49
49
|
let stopping = false;
|
|
50
|
+
/** --- 当前活跃的 HTTP/2 会话集合,用于 graceful shutdown 时发送 GOAWAY 帧 --- */
|
|
51
|
+
const http2Sessions = new Set();
|
|
50
52
|
/**
|
|
51
53
|
* --- 包装请求处理函数,统一管理 linkCount 计数和错误处理 ---
|
|
52
54
|
* @param key 连接标识
|
|
@@ -144,6 +146,12 @@ async function run() {
|
|
|
144
146
|
}
|
|
145
147
|
wrapWithLinkCount(host + (req.url ?? ''), () => upgradeHandler(req, socket, true, head), '[CHILD][http2][upgrade]', 'UPGRADE');
|
|
146
148
|
}).listen(lCore.globalConfig.httpsPort);
|
|
149
|
+
// --- 追踪 HTTP/2 会话,用于 graceful shutdown 时发送 GOAWAY 帧通知 CDN 切换连接 ---
|
|
150
|
+
http2Server.on('session', (session) => {
|
|
151
|
+
http2Sessions.add(session);
|
|
152
|
+
session.on('close', () => { http2Sessions.delete(session); });
|
|
153
|
+
session.on('error', () => { http2Sessions.delete(session); });
|
|
154
|
+
});
|
|
147
155
|
// --- 增大 HTTP/2 超时,防止 CDN 预建连接被 Node.js 过早关闭 ---
|
|
148
156
|
http2Server.setTimeout(120_000);
|
|
149
157
|
httpServer = http.createServer(function (req, res) {
|
|
@@ -562,6 +570,13 @@ process.on('message', function (msg) {
|
|
|
562
570
|
case 'stop': {
|
|
563
571
|
// --- 需要停止监听,等待已有连接全部断开,然后关闭线程 ---
|
|
564
572
|
stopping = true;
|
|
573
|
+
// --- 向所有 HTTP/2 会话发送 GOAWAY 帧,通知 CDN 客户端停止在该连接上发送新请求并切换到新连接 ---
|
|
574
|
+
for (const session of http2Sessions) {
|
|
575
|
+
try {
|
|
576
|
+
session.goaway(http2.constants.NGHTTP2_NO_ERROR);
|
|
577
|
+
}
|
|
578
|
+
catch { }
|
|
579
|
+
}
|
|
565
580
|
httpServer?.close();
|
|
566
581
|
http2Server?.close();
|
|
567
582
|
// --- 立即关闭空闲保活连接(无活跃请求的 keep-alive socket),避免进程长时间等待 ---
|
package/sys/ctr.d.ts
CHANGED
|
@@ -342,5 +342,9 @@ export declare class Ctr {
|
|
|
342
342
|
'maxFileSize'?: number;
|
|
343
343
|
/** --- 允许的文件扩展名(含点号),如 ['.jpg', '.png', '.pdf'] --- */
|
|
344
344
|
'allowedExts'?: string[];
|
|
345
|
+
/** --- 单个字段(非文件)最大字节数,默认 1 MB --- */
|
|
346
|
+
'maxFieldSize'?: number;
|
|
347
|
+
/** --- 整体请求超时时间(毫秒),默认 5 分钟,设为 0 禁用超时 --- */
|
|
348
|
+
'timeout'?: number;
|
|
345
349
|
}): Promise<boolean>;
|
|
346
350
|
}
|
package/sys/master.js
CHANGED
|
@@ -18,6 +18,29 @@ import * as lTime from '#kebab/lib/time.js';
|
|
|
18
18
|
import * as lZip from '#kebab/lib/zip.js';
|
|
19
19
|
/** --- 当前运行中的子进程列表 --- */
|
|
20
20
|
const workerList = {};
|
|
21
|
+
/**
|
|
22
|
+
* --- 等待指定 worker 开始监听端口就绪 ---
|
|
23
|
+
* @param worker 要等待的 worker 对象
|
|
24
|
+
* @param timeout 超时时间(毫秒),默认 30 秒
|
|
25
|
+
*/
|
|
26
|
+
function waitForWorkerListening(worker, timeout = 30_000) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let timer;
|
|
29
|
+
const onListening = (w) => {
|
|
30
|
+
if (w.id === worker.id) {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
cluster.removeListener('listening', onListening);
|
|
33
|
+
resolve();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
timer = setTimeout(() => {
|
|
37
|
+
lCore.display(`[master] Worker ${worker.process.pid ?? 'undefined'} timed out waiting for listening, proceeding anyway.`);
|
|
38
|
+
cluster.removeListener('listening', onListening);
|
|
39
|
+
resolve();
|
|
40
|
+
}, timeout);
|
|
41
|
+
cluster.on('listening', onListening);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
21
44
|
/**
|
|
22
45
|
* --- 最终调用执行的函数块 ---
|
|
23
46
|
*/
|
|
@@ -124,16 +147,7 @@ function createRpcListener() {
|
|
|
124
147
|
break;
|
|
125
148
|
}
|
|
126
149
|
case 'restart': {
|
|
127
|
-
|
|
128
|
-
for (const pid in workerList) {
|
|
129
|
-
workerList[pid].worker.send({
|
|
130
|
-
'action': 'stop'
|
|
131
|
-
});
|
|
132
|
-
// --- 开启新线程 ---
|
|
133
|
-
await createChildProcess(workerList[pid].cpu);
|
|
134
|
-
// --- 删除记录 ---
|
|
135
|
-
delete workerList[pid];
|
|
136
|
-
}
|
|
150
|
+
await restartWorkers();
|
|
137
151
|
break;
|
|
138
152
|
}
|
|
139
153
|
case 'global': {
|
|
@@ -768,6 +782,34 @@ function createRpcListener() {
|
|
|
768
782
|
});
|
|
769
783
|
}).listen(lCore.globalConfig.rpcPort);
|
|
770
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* --- 零宕机重启所有 worker 进程:先创建新进程并等待监听就绪,再停止旧进程 ---
|
|
787
|
+
*/
|
|
788
|
+
async function restartWorkers() {
|
|
789
|
+
// --- 收集旧进程信息(先快照,避免迭代过程中修改 workerList) ---
|
|
790
|
+
const oldEntries = [];
|
|
791
|
+
for (const pid in workerList) {
|
|
792
|
+
oldEntries.push({
|
|
793
|
+
'pid': pid,
|
|
794
|
+
'cpu': workerList[pid].cpu,
|
|
795
|
+
'worker': workerList[pid].worker
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
// --- 阶段 1:创建新进程并等待其监听端口就绪 ---
|
|
799
|
+
for (const entry of oldEntries) {
|
|
800
|
+
const newWorker = await createChildProcess(entry.cpu);
|
|
801
|
+
if (newWorker) {
|
|
802
|
+
await waitForWorkerListening(newWorker);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
// --- 阶段 2:停止旧进程 ---
|
|
806
|
+
for (const entry of oldEntries) {
|
|
807
|
+
entry.worker.send({
|
|
808
|
+
'action': 'stop'
|
|
809
|
+
});
|
|
810
|
+
delete workerList[entry.pid];
|
|
811
|
+
}
|
|
812
|
+
}
|
|
771
813
|
/**
|
|
772
814
|
* --- 检测是否有死掉丢失的子进程,复活之 ---
|
|
773
815
|
*/
|
|
@@ -795,7 +837,7 @@ async function checkWorkerLost() {
|
|
|
795
837
|
async function createChildProcess(cpu) {
|
|
796
838
|
const worker = cluster.fork();
|
|
797
839
|
if (!worker.process.pid) {
|
|
798
|
-
return;
|
|
840
|
+
return null;
|
|
799
841
|
}
|
|
800
842
|
workerList[worker.process.pid] = {
|
|
801
843
|
'worker': worker,
|
|
@@ -823,16 +865,7 @@ async function createChildProcess(cpu) {
|
|
|
823
865
|
break;
|
|
824
866
|
}
|
|
825
867
|
case 'restart': {
|
|
826
|
-
|
|
827
|
-
for (const pid in workerList) {
|
|
828
|
-
workerList[pid].worker.send({
|
|
829
|
-
'action': 'stop'
|
|
830
|
-
});
|
|
831
|
-
// --- 开启新线程 ---
|
|
832
|
-
await createChildProcess(workerList[pid].cpu);
|
|
833
|
-
// --- 删除记录 ---
|
|
834
|
-
delete workerList[pid];
|
|
835
|
-
}
|
|
868
|
+
await restartWorkers();
|
|
836
869
|
break;
|
|
837
870
|
}
|
|
838
871
|
case 'global': {
|
|
@@ -889,6 +922,7 @@ async function createChildProcess(cpu) {
|
|
|
889
922
|
'data': lCore.global
|
|
890
923
|
});
|
|
891
924
|
}
|
|
925
|
+
return worker;
|
|
892
926
|
}
|
|
893
927
|
/**
|
|
894
928
|
* --- 开发模式下的文件监听自动重载(HMR),仅在 debug: true 时启用 ---
|
|
@@ -919,18 +953,10 @@ function startFileWatcher() {
|
|
|
919
953
|
}
|
|
920
954
|
restarting = true;
|
|
921
955
|
lCore.display(`[HMR] File changed: ${formatName}, reloading workers...`);
|
|
922
|
-
(
|
|
923
|
-
// --- 为所有子进程发送 stop 信息并重启 ---
|
|
924
|
-
for (const pid in workerList) {
|
|
925
|
-
workerList[pid].worker.send({
|
|
926
|
-
'action': 'stop'
|
|
927
|
-
});
|
|
928
|
-
await createChildProcess(workerList[pid].cpu);
|
|
929
|
-
delete workerList[pid];
|
|
930
|
-
}
|
|
956
|
+
restartWorkers().then(() => {
|
|
931
957
|
restarting = false;
|
|
932
958
|
lCore.display('[HMR] All workers reloaded.');
|
|
933
|
-
})
|
|
959
|
+
}).catch((e) => {
|
|
934
960
|
restarting = false;
|
|
935
961
|
lCore.display('[HMR] Reload error:', e);
|
|
936
962
|
});
|