@maiyunnet/kebab 5.3.1 → 6.1.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/README.md +1 -1
- package/index.d.ts +2 -3
- package/index.js +6 -2
- package/lib/ai.d.ts +4 -3
- package/lib/ai.js +19 -11
- package/lib/buffer.d.ts +8 -4
- package/lib/buffer.js +16 -4
- package/lib/core.d.ts +24 -10
- package/lib/core.js +19 -12
- package/lib/crypto.d.ts +11 -2
- package/lib/crypto.js +6 -8
- package/lib/db/conn.js +1 -1
- package/lib/db.d.ts +5 -3
- package/lib/db.js +5 -3
- package/lib/dns.d.ts +17 -17
- package/lib/dns.js +44 -42
- package/lib/fs.d.ts +2 -2
- package/lib/fs.js +2 -2
- package/lib/kv.d.ts +49 -7
- package/lib/kv.js +69 -7
- package/lib/net/request.d.ts +1 -1
- package/lib/net.d.ts +6 -6
- package/lib/net.js +3 -3
- package/lib/scan.d.ts +3 -3
- package/lib/scan.js +3 -3
- package/lib/sql.d.ts +16 -5
- package/lib/sql.js +176 -257
- package/lib/ssh.d.ts +1 -2
- package/lib/text.d.ts +4 -4
- package/lib/text.js +2 -2
- package/lib/vector.d.ts +1 -1
- package/lib/vector.js +7 -7
- package/lib/ws.d.ts +0 -1
- package/lib/ws.js +0 -1
- package/lib/zip.d.ts +0 -1
- package/lib/zip.js +0 -1
- package/lib/zlib.d.ts +1 -2
- package/lib/zlib.js +1 -2
- package/package.json +13 -8
- package/sys/cmd.js +0 -7
- package/sys/ctr.d.ts +0 -2
- package/sys/ctr.js +0 -2
- package/sys/mod.d.ts +23 -9
- package/sys/mod.js +114 -12
- package/sys/route.js +0 -1
- package/www/example/ctr/test.d.ts +2 -2
- package/www/example/ctr/test.js +170 -93
- package/lib/jwt.d.ts +0 -73
- package/lib/jwt.js +0 -226
package/lib/text.js
CHANGED
|
@@ -205,7 +205,7 @@ export function isIPv6(ip) {
|
|
|
205
205
|
export const REGEXP_DOMAIN = /^.+?\.((?![0-9]).)+$/i;
|
|
206
206
|
/**
|
|
207
207
|
* --- 判断是否是域名 ---
|
|
208
|
-
* @param
|
|
208
|
+
* @param domain 域名
|
|
209
209
|
* @return bool
|
|
210
210
|
*/
|
|
211
211
|
export function isDomain(domain) {
|
|
@@ -432,7 +432,7 @@ export function getFilename(path) {
|
|
|
432
432
|
}
|
|
433
433
|
/**
|
|
434
434
|
* --- 将普通的返回 JSON 对象序列化为字符串 ---
|
|
435
|
-
* @param
|
|
435
|
+
* @param rtn 返回 JSON 对象
|
|
436
436
|
*/
|
|
437
437
|
export function stringifyResult(rtn) {
|
|
438
438
|
if (Array.isArray(rtn)) {
|
package/lib/vector.d.ts
CHANGED
package/lib/vector.js
CHANGED
|
@@ -31,8 +31,8 @@ export class Vector {
|
|
|
31
31
|
return res;
|
|
32
32
|
}
|
|
33
33
|
catch (e) {
|
|
34
|
-
lCore.log({}, '[VECTOR][seach][error] ' + e.
|
|
35
|
-
lCore.debug('[VECTOR][seach]', e
|
|
34
|
+
lCore.log({}, '[VECTOR][seach][error] ' + (e.status?.reason ?? ''), '-error');
|
|
35
|
+
lCore.debug('[VECTOR][seach]', e);
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -50,8 +50,8 @@ export class Vector {
|
|
|
50
50
|
return res;
|
|
51
51
|
}
|
|
52
52
|
catch (e) {
|
|
53
|
-
lCore.log({}, '[VECTOR][insert][error] ' + e.
|
|
54
|
-
lCore.debug('[VECTOR][insert]', e
|
|
53
|
+
lCore.log({}, '[VECTOR][insert][error] ' + (e.status?.reason ?? ''), '-error');
|
|
54
|
+
lCore.debug('[VECTOR][insert]', e);
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -69,8 +69,8 @@ export class Vector {
|
|
|
69
69
|
return res;
|
|
70
70
|
}
|
|
71
71
|
catch (e) {
|
|
72
|
-
lCore.log({}, '[VECTOR][delete][error] ' + e.
|
|
73
|
-
lCore.debug('[VECTOR][delete]', e
|
|
72
|
+
lCore.log({}, '[VECTOR][delete][error] ' + (e.status?.reason ?? ''), '-error');
|
|
73
|
+
lCore.debug('[VECTOR][delete]', e);
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -107,7 +107,7 @@ export class Vector {
|
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
109
|
* --- 创建一个 Vector 对象 ---
|
|
110
|
-
* @param
|
|
110
|
+
* @param ctrEtc 控制器或配置信息
|
|
111
111
|
*/
|
|
112
112
|
export function get(ctrEtc) {
|
|
113
113
|
if (ctrEtc instanceof sCtr.Ctr) {
|
package/lib/ws.d.ts
CHANGED
|
@@ -166,6 +166,5 @@ export declare function rproxy(ctr: sCtr.Ctr, url: string, opt?: IRproxyOptions)
|
|
|
166
166
|
* @param ctr 当前控制器
|
|
167
167
|
* @param host 反代真实请求地址
|
|
168
168
|
* @param port 反代真实请求端口
|
|
169
|
-
* @param opt 参数
|
|
170
169
|
*/
|
|
171
170
|
export declare function rsocket(ctr: sCtr.Ctr, host: string, port: number): Promise<boolean>;
|
package/lib/ws.js
CHANGED
package/lib/zip.d.ts
CHANGED
package/lib/zip.js
CHANGED
package/lib/zlib.d.ts
CHANGED
|
@@ -49,8 +49,7 @@ export declare function createBrotliDecompress(): zlib.BrotliDecompress;
|
|
|
49
49
|
export declare function createCompress(types: string, options?: zlib.ZlibOptions): ICompress | null;
|
|
50
50
|
/**
|
|
51
51
|
* --- 根据字符串创建解压类型 ---
|
|
52
|
-
* @param types 用 , 间隔的字符串,如 gzip,deflate
|
|
53
|
-
* @param options 选项
|
|
52
|
+
* @param types 用 , 间隔的字符串,如 gzip, deflate
|
|
54
53
|
*/
|
|
55
54
|
export declare function createDecompress(types: string): ICompress | null;
|
|
56
55
|
/**
|
package/lib/zlib.js
CHANGED
|
@@ -81,8 +81,7 @@ export function createCompress(types, options = {}) {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* --- 根据字符串创建解压类型 ---
|
|
84
|
-
* @param types 用 , 间隔的字符串,如 gzip,deflate
|
|
85
|
-
* @param options 选项
|
|
84
|
+
* @param types 用 , 间隔的字符串,如 gzip, deflate
|
|
86
85
|
*/
|
|
87
86
|
export function createDecompress(types) {
|
|
88
87
|
const type = getTypeByTypes(types);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maiyunnet/kebab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
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": [
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"author": "hanguoshuai",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"types": "./index.d.ts",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build-md-doc": "bash ./utils/generate-api-doc-md.sh"
|
|
17
|
+
},
|
|
15
18
|
"bin": {
|
|
16
19
|
"kebab": "./bin/kebab.js"
|
|
17
20
|
},
|
|
@@ -19,29 +22,31 @@
|
|
|
19
22
|
"#kebab/*": "./*"
|
|
20
23
|
},
|
|
21
24
|
"dependencies": {
|
|
22
|
-
"@aws-sdk/client-s3": "^3.
|
|
23
|
-
"@aws-sdk/lib-storage": "^3.
|
|
25
|
+
"@aws-sdk/client-s3": "^3.939.0",
|
|
26
|
+
"@aws-sdk/lib-storage": "^3.939.0",
|
|
24
27
|
"@litert/http-client": "^1.1.2",
|
|
25
28
|
"@litert/mime": "^0.1.3",
|
|
26
|
-
"@litert/redis": "^3.0.
|
|
29
|
+
"@litert/redis": "^3.0.6",
|
|
27
30
|
"@litert/websocket": "^0.2.8",
|
|
28
31
|
"@types/ssh2": "^1.15.5",
|
|
29
|
-
"@zilliz/milvus2-sdk-node": "^2.6.
|
|
32
|
+
"@zilliz/milvus2-sdk-node": "^2.6.5",
|
|
30
33
|
"ejs": "^3.1.10",
|
|
31
34
|
"jszip": "^3.10.1",
|
|
32
35
|
"mysql2": "^3.15.3",
|
|
33
36
|
"node-cron": "^4.2.1",
|
|
34
|
-
"openai": "^6.
|
|
37
|
+
"openai": "^6.9.1",
|
|
35
38
|
"pg": "^8.16.3",
|
|
36
39
|
"ssh2": "^1.17.0",
|
|
37
40
|
"svg-captcha": "^1.4.0",
|
|
38
|
-
"tencentcloud-sdk-nodejs": "^4.1.
|
|
41
|
+
"tencentcloud-sdk-nodejs": "^4.1.149"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@litert/eslint-plugin-rules": "^0.3.1",
|
|
42
45
|
"@types/ejs": "^3.1.5",
|
|
43
|
-
"@types/node": "^24.10.
|
|
46
|
+
"@types/node": "^24.10.1",
|
|
44
47
|
"@types/pg": "^8.15.6",
|
|
48
|
+
"typedoc": "^0.28.14",
|
|
49
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
45
50
|
"typescript": "^5.9.3"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/sys/cmd.js
CHANGED
|
@@ -115,13 +115,6 @@ async function run() {
|
|
|
115
115
|
config.db['PGSQL'].read.name ??= 'maiyun';
|
|
116
116
|
config.db['PGSQL'].read.user ??= 'root';
|
|
117
117
|
config.db['PGSQL'].read.pwd ??= 'DashAdmin';
|
|
118
|
-
// --- config - jwt ---
|
|
119
|
-
config.jwt ??= {};
|
|
120
|
-
config.jwt.name ??= 'KE_JWT';
|
|
121
|
-
config.jwt.ttl ??= 172800;
|
|
122
|
-
config.jwt.ssl ??= false;
|
|
123
|
-
config.jwt.secret ??= 'MUSTCHANGE';
|
|
124
|
-
config.jwt.auth ??= false;
|
|
125
118
|
// --- config - dns ---
|
|
126
119
|
config.dns ??= {};
|
|
127
120
|
config.dns['DNSPOD'] ??= {};
|
package/sys/ctr.d.ts
CHANGED
|
@@ -33,8 +33,6 @@ export declare class Ctr {
|
|
|
33
33
|
protected _files: Record<string, kebab.IPostFile | kebab.IPostFile[]>;
|
|
34
34
|
/** --- Cookie 数组 --- */
|
|
35
35
|
protected _cookie: Record<string, string>;
|
|
36
|
-
/** --- Jwt 数组 --- */
|
|
37
|
-
protected _jwt: Record<string, any>;
|
|
38
36
|
/** --- Session 数组 --- */
|
|
39
37
|
protected _session: Record<string, any>;
|
|
40
38
|
/** --- Session --- 对象 */
|
package/sys/ctr.js
CHANGED
package/sys/mod.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ import * as lDb from '#kebab/lib/db.js';
|
|
|
8
8
|
import * as sCtr from '#kebab/sys/ctr.js';
|
|
9
9
|
import * as kebab from '#kebab/index.js';
|
|
10
10
|
/** --- 只获取变量 --- */
|
|
11
|
-
type TOnlyProperties<T> = {
|
|
11
|
+
export type TOnlyProperties<T> = {
|
|
12
12
|
[K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K];
|
|
13
13
|
};
|
|
14
14
|
/** --- 条数列表 --- */
|
|
15
|
-
declare class Rows<T extends Mod> implements IRows<T> {
|
|
15
|
+
export declare class Rows<T extends Mod> implements IRows<T> {
|
|
16
16
|
private readonly _items;
|
|
17
17
|
constructor(initialItems?: T[]);
|
|
18
18
|
/** --- 总行数 --- */
|
|
@@ -63,7 +63,6 @@ export default class Mod {
|
|
|
63
63
|
protected _ctr?: sCtr.Ctr;
|
|
64
64
|
/**
|
|
65
65
|
* --- 构造函数 ---
|
|
66
|
-
* @param ctr Ctr 对象
|
|
67
66
|
* @param opt 选项
|
|
68
67
|
*/
|
|
69
68
|
constructor(opt: {
|
|
@@ -170,6 +169,18 @@ export default class Mod {
|
|
|
170
169
|
'by'?: [string | string[], 'DESC' | 'ASC'];
|
|
171
170
|
'limit'?: [number, number?];
|
|
172
171
|
}): lSql.Sql;
|
|
172
|
+
/**
|
|
173
|
+
* --- 批量更新数据 ---
|
|
174
|
+
* @param db 数据库对象
|
|
175
|
+
* @param data 数据列表
|
|
176
|
+
* @param key 用于定位的主键或唯一键字段名
|
|
177
|
+
* @param opt 选项
|
|
178
|
+
*/
|
|
179
|
+
static updateList(db: lDb.Pool | lDb.Transaction, data: Array<Record<string, any>>, key: string, opt?: {
|
|
180
|
+
'ctr'?: sCtr.Ctr;
|
|
181
|
+
'pre'?: string;
|
|
182
|
+
'index'?: string;
|
|
183
|
+
}): Promise<boolean | null>;
|
|
173
184
|
/**
|
|
174
185
|
* --- select 自定字段 ---
|
|
175
186
|
* @param db 数据库对象
|
|
@@ -217,7 +228,6 @@ export default class Mod {
|
|
|
217
228
|
* --- 根据主键(或 key 字段)获取对象 ---
|
|
218
229
|
* @param db 数据库对象
|
|
219
230
|
* @param val 主键值
|
|
220
|
-
* @param lock 是否加锁
|
|
221
231
|
* @param opt 选项
|
|
222
232
|
*/
|
|
223
233
|
static find<T extends Mod>(db: lDb.Pool | lDb.Transaction, val: string | number | null, opt?: {
|
|
@@ -286,8 +296,14 @@ export default class Mod {
|
|
|
286
296
|
get(n: string): any;
|
|
287
297
|
/**
|
|
288
298
|
* --- 创建数据 ---
|
|
299
|
+
* @return true-成功,false-报错,null-唯一键非 _$key 键冲突
|
|
300
|
+
*/
|
|
301
|
+
create(): Promise<boolean | null>;
|
|
302
|
+
/**
|
|
303
|
+
* --- 插入数据,如果存在则更新(UPSERT) ---
|
|
304
|
+
* @param conflict 冲突字段,不能为 _$key 或 _$primary,应该是你要判断的唯一索引字段
|
|
289
305
|
*/
|
|
290
|
-
|
|
306
|
+
upsert(conflict: string | string[]): Promise<boolean>;
|
|
291
307
|
/**
|
|
292
308
|
* --- 刷新当前模型获取最新数据 ---
|
|
293
309
|
* @param lock 是否加锁
|
|
@@ -295,8 +311,9 @@ export default class Mod {
|
|
|
295
311
|
refresh(lock?: boolean): Promise<boolean | null>;
|
|
296
312
|
/**
|
|
297
313
|
* --- 更新 set 的数据到数据库,有未保存数据时才保存 ---
|
|
314
|
+
* @param where 自定义筛选条件,默认根据主键筛选
|
|
298
315
|
*/
|
|
299
|
-
save(): Promise<boolean>;
|
|
316
|
+
save(where?: any): Promise<boolean>;
|
|
300
317
|
/**
|
|
301
318
|
* --- 移除本条目 ---
|
|
302
319
|
*/
|
|
@@ -394,13 +411,11 @@ export default class Mod {
|
|
|
394
411
|
/**
|
|
395
412
|
* --- 筛选器 ---
|
|
396
413
|
* @param s 筛选条件数组或字符串
|
|
397
|
-
* @param raw 是否包含已被软删除的数据
|
|
398
414
|
*/
|
|
399
415
|
filter(s: kebab.Json): this;
|
|
400
416
|
/**
|
|
401
417
|
* --- 是 filter 的别名 ---
|
|
402
418
|
* @param s 筛选条件数组或字符串
|
|
403
|
-
* @param raw 是否包含已被软删除的数据
|
|
404
419
|
*/
|
|
405
420
|
where(s: kebab.Json): this;
|
|
406
421
|
/**
|
|
@@ -487,4 +502,3 @@ export interface IModUnionItem {
|
|
|
487
502
|
'field': string;
|
|
488
503
|
'where'?: any;
|
|
489
504
|
}
|
|
490
|
-
export {};
|
package/sys/mod.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as lDb from '#kebab/lib/db.js';
|
|
|
8
8
|
import * as lCore from '#kebab/lib/core.js';
|
|
9
9
|
import * as lText from '#kebab/lib/text.js';
|
|
10
10
|
/** --- 条数列表 --- */
|
|
11
|
-
class Rows {
|
|
11
|
+
export class Rows {
|
|
12
12
|
constructor(initialItems = []) {
|
|
13
13
|
this._items = initialItems;
|
|
14
14
|
}
|
|
@@ -57,7 +57,6 @@ export default class Mod {
|
|
|
57
57
|
static { this._$pre = null; }
|
|
58
58
|
/**
|
|
59
59
|
* --- 构造函数 ---
|
|
60
|
-
* @param ctr Ctr 对象
|
|
61
60
|
* @param opt 选项
|
|
62
61
|
*/
|
|
63
62
|
constructor(opt) {
|
|
@@ -274,6 +273,78 @@ export default class Mod {
|
|
|
274
273
|
}
|
|
275
274
|
return sq;
|
|
276
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* --- 批量更新数据 ---
|
|
278
|
+
* @param db 数据库对象
|
|
279
|
+
* @param data 数据列表
|
|
280
|
+
* @param key 用于定位的主键或唯一键字段名
|
|
281
|
+
* @param opt 选项
|
|
282
|
+
*/
|
|
283
|
+
static async updateList(db, data, key, opt = {}) {
|
|
284
|
+
if (!data.length) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
// --- 获取所有涉及的字段(除了 key) ---
|
|
288
|
+
const columns = new Set();
|
|
289
|
+
for (const item of data) {
|
|
290
|
+
for (const k in item) {
|
|
291
|
+
if (k !== key) {
|
|
292
|
+
columns.add(k);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (columns.size === 0) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
const cols = Array.from(columns);
|
|
300
|
+
// --- 计算分批大小 ---
|
|
301
|
+
// --- 每个字段需要 2 个占位符 (WHEN ? THEN ?),加上 WHERE IN (?) 的 1 个 ---
|
|
302
|
+
// --- Total params per row = cols.length * 2 + 1 ---
|
|
303
|
+
const paramCountPerRow = cols.length * 2 + 1;
|
|
304
|
+
const batchSize = Math.floor(60000 / paramCountPerRow);
|
|
305
|
+
const batches = [];
|
|
306
|
+
for (let i = 0; i < data.length; i += batchSize) {
|
|
307
|
+
batches.push(data.slice(i, i + batchSize));
|
|
308
|
+
}
|
|
309
|
+
for (const batch of batches) {
|
|
310
|
+
const sq = lSql.get(opt.pre ?? this._$pre ?? opt.ctr, {
|
|
311
|
+
'service': db.getService() ?? lDb.ESERVICE.PGSQL,
|
|
312
|
+
});
|
|
313
|
+
const updates = {};
|
|
314
|
+
const keys = [];
|
|
315
|
+
for (const col of cols) {
|
|
316
|
+
let caseSql = `(CASE ${sq.field(key)}`;
|
|
317
|
+
const params = [];
|
|
318
|
+
let hasUpdate = false;
|
|
319
|
+
for (const item of batch) {
|
|
320
|
+
if (item[col] !== undefined) {
|
|
321
|
+
caseSql += ` WHEN ? THEN ?`;
|
|
322
|
+
params.push(item[key], item[col]);
|
|
323
|
+
hasUpdate = true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (hasUpdate) {
|
|
327
|
+
caseSql += ` ELSE ${sq.field(col)} END)`;
|
|
328
|
+
updates[col] = [caseSql, params];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// --- 收集 keys ---
|
|
332
|
+
for (const item of batch) {
|
|
333
|
+
keys.push(item[key]);
|
|
334
|
+
}
|
|
335
|
+
if (Object.keys(updates).length === 0) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
sq.update(this._$table + (opt.index ? ('_' + opt.index) : ''), updates)
|
|
339
|
+
.where({ [key]: keys });
|
|
340
|
+
const r = await db.execute(sq.getSql(), sq.getData());
|
|
341
|
+
if (r.packet === null) {
|
|
342
|
+
lCore.log(opt.ctr ?? {}, '[MOD][updateList] ' + lText.stringifyJson(r.error?.message ?? '').slice(1, -1).replace(/"/g, '""'), '-error');
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
277
348
|
/**
|
|
278
349
|
* --- select 自定字段 ---
|
|
279
350
|
* @param db 数据库对象
|
|
@@ -326,7 +397,6 @@ export default class Mod {
|
|
|
326
397
|
* --- 根据主键(或 key 字段)获取对象 ---
|
|
327
398
|
* @param db 数据库对象
|
|
328
399
|
* @param val 主键值
|
|
329
|
-
* @param lock 是否加锁
|
|
330
400
|
* @param opt 选项
|
|
331
401
|
*/
|
|
332
402
|
static async find(db, val, opt = {}) {
|
|
@@ -436,7 +506,7 @@ export default class Mod {
|
|
|
436
506
|
return rtn;
|
|
437
507
|
}
|
|
438
508
|
/**
|
|
439
|
-
* --- 设置一个/多个属性,值为
|
|
509
|
+
* --- 设置一个/多个属性,值为 undefined 则不会被更新 ---
|
|
440
510
|
* @param n 字符串或键/值
|
|
441
511
|
* @param v 可能是数字
|
|
442
512
|
*/
|
|
@@ -476,6 +546,7 @@ export default class Mod {
|
|
|
476
546
|
}
|
|
477
547
|
/**
|
|
478
548
|
* --- 创建数据 ---
|
|
549
|
+
* @return true-成功,false-报错,null-唯一键非 _$key 键冲突
|
|
479
550
|
*/
|
|
480
551
|
async create() {
|
|
481
552
|
const cstr = this.constructor;
|
|
@@ -510,6 +581,7 @@ export default class Mod {
|
|
|
510
581
|
// --- MYSQL ---
|
|
511
582
|
const match = /for key '([\w.]+)'/.exec(r.error.message);
|
|
512
583
|
if (match?.[1].includes(cstr._$index)) {
|
|
584
|
+
// --- key 索引重复,继续生成 key ---
|
|
513
585
|
continue;
|
|
514
586
|
}
|
|
515
587
|
}
|
|
@@ -517,11 +589,12 @@ export default class Mod {
|
|
|
517
589
|
// --- PGSQL ---
|
|
518
590
|
const match = /constraint "([\w.]+)"/.exec(r.error.message);
|
|
519
591
|
if (match?.[1].includes(cstr._$index)) {
|
|
592
|
+
// --- key 索引重复,继续生成 key ---
|
|
520
593
|
continue;
|
|
521
594
|
}
|
|
522
595
|
}
|
|
523
596
|
// --- 1062 非 index 冲突,那需要用户自行处理(可能不允许重复的邮箱) ---
|
|
524
|
-
return
|
|
597
|
+
return null;
|
|
525
598
|
}
|
|
526
599
|
// --- 未处理的错误 ---
|
|
527
600
|
const service = this._db.getService();
|
|
@@ -538,8 +611,9 @@ export default class Mod {
|
|
|
538
611
|
if (r.error.errno !== 1062) {
|
|
539
612
|
lCore.debug('[MOD][create1][' + cstr._$table + ']', r);
|
|
540
613
|
lCore.log(this._ctr ?? {}, '[MOD][create1][' + cstr._$table + '] ' + lText.stringifyJson(r.error?.message ?? '').slice(1, -1).replace(/"/g, '""'), '-error');
|
|
614
|
+
return false;
|
|
541
615
|
}
|
|
542
|
-
return
|
|
616
|
+
return null;
|
|
543
617
|
}
|
|
544
618
|
}
|
|
545
619
|
if (r.packet?.affected) {
|
|
@@ -552,6 +626,35 @@ export default class Mod {
|
|
|
552
626
|
return false;
|
|
553
627
|
}
|
|
554
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* --- 插入数据,如果存在则更新(UPSERT) ---
|
|
631
|
+
* @param conflict 冲突字段,不能为 _$key 或 _$primary,应该是你要判断的唯一索引字段
|
|
632
|
+
*/
|
|
633
|
+
async upsert(conflict) {
|
|
634
|
+
// --- 这里没用 mysql 的 INSERT ... ON DUPLICATE KEY UPDATE 或 pgsql 的 ON CONFLICT DO UPDATE ---
|
|
635
|
+
// --- 因为还要处理 _$key 的自动生成 ---
|
|
636
|
+
const res = await this.create();
|
|
637
|
+
if (res) {
|
|
638
|
+
// --- 创建成功 ---
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (res === false) {
|
|
642
|
+
// --- 系统错误 ---
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
// --- res === null,唯一键冲突,执行更新 ---
|
|
646
|
+
/** --- 冲突字段列表 --- */
|
|
647
|
+
const conflicts = typeof conflict === 'string' ? [conflict] : conflict;
|
|
648
|
+
const where = {};
|
|
649
|
+
for (const field of conflicts) {
|
|
650
|
+
if (!this._updates[field]) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
where[field] = this._data[field];
|
|
654
|
+
delete this._updates[field];
|
|
655
|
+
}
|
|
656
|
+
return this.save(where);
|
|
657
|
+
}
|
|
555
658
|
/**
|
|
556
659
|
* --- 刷新当前模型获取最新数据 ---
|
|
557
660
|
* @param lock 是否加锁
|
|
@@ -581,8 +684,9 @@ export default class Mod {
|
|
|
581
684
|
}
|
|
582
685
|
/**
|
|
583
686
|
* --- 更新 set 的数据到数据库,有未保存数据时才保存 ---
|
|
687
|
+
* @param where 自定义筛选条件,默认根据主键筛选
|
|
584
688
|
*/
|
|
585
|
-
async save() {
|
|
689
|
+
async save(where) {
|
|
586
690
|
if (Object.keys(this._updates).length === 0) {
|
|
587
691
|
return true;
|
|
588
692
|
}
|
|
@@ -591,9 +695,9 @@ export default class Mod {
|
|
|
591
695
|
for (const k in this._updates) {
|
|
592
696
|
updates[k] = this._data[k];
|
|
593
697
|
}
|
|
594
|
-
this._sql.update(cstr._$table + (this._index ? ('_' + this._index[0]) : ''), [updates]).where(
|
|
595
|
-
|
|
596
|
-
|
|
698
|
+
this._sql.update(cstr._$table + (this._index ? ('_' + this._index[0]) : ''), [updates]).where(where ?? {
|
|
699
|
+
[cstr._$primary]: this._data[cstr._$primary]
|
|
700
|
+
});
|
|
597
701
|
const r = await this._db.execute(this._sql.getSql(), this._sql.getData());
|
|
598
702
|
if (r.packet === null) {
|
|
599
703
|
lCore.log(this._ctr ?? {}, '[MOD][save] ' + lText.stringifyJson(r.error?.message ?? '').slice(1, -1).replace(/"/g, '""'), '-error');
|
|
@@ -1158,7 +1262,6 @@ export default class Mod {
|
|
|
1158
1262
|
/**
|
|
1159
1263
|
* --- 筛选器 ---
|
|
1160
1264
|
* @param s 筛选条件数组或字符串
|
|
1161
|
-
* @param raw 是否包含已被软删除的数据
|
|
1162
1265
|
*/
|
|
1163
1266
|
filter(s) {
|
|
1164
1267
|
this._sql.where(s);
|
|
@@ -1167,7 +1270,6 @@ export default class Mod {
|
|
|
1167
1270
|
/**
|
|
1168
1271
|
* --- 是 filter 的别名 ---
|
|
1169
1272
|
* @param s 筛选条件数组或字符串
|
|
1170
|
-
* @param raw 是否包含已被软删除的数据
|
|
1171
1273
|
*/
|
|
1172
1274
|
where(s) {
|
|
1173
1275
|
return this.filter(s);
|
package/sys/route.js
CHANGED
|
@@ -470,7 +470,6 @@ export async function run(data) {
|
|
|
470
470
|
cctr.setPrototype('_files', middle.getPrototype('_files'));
|
|
471
471
|
cctr.setPrototype('_post', middle.getPrototype('_post'));
|
|
472
472
|
cctr.setPrototype('_cookie', cookies);
|
|
473
|
-
cctr.setPrototype('_jwt', middle.getPrototype('_jwt'));
|
|
474
473
|
if (!cctr.getPrototype('_sess') && middle.getPrototype('_sess')) {
|
|
475
474
|
cctr.setPrototype('_session', middle.getPrototype('_session'));
|
|
476
475
|
cctr.setPrototype('_sess', middle.getPrototype('_sess'));
|
|
@@ -32,6 +32,8 @@ export default class extends sCtr.Ctr {
|
|
|
32
32
|
modSplit1(): Promise<void>;
|
|
33
33
|
modSplit2(): Promise<kebab.Json[]>;
|
|
34
34
|
modInsert(): Promise<string>;
|
|
35
|
+
modUpsert(): Promise<string>;
|
|
36
|
+
modUpdateList(): Promise<string>;
|
|
35
37
|
captchaFastbuild(): string;
|
|
36
38
|
captchaBase64(): string;
|
|
37
39
|
coreRandom(): string;
|
|
@@ -83,8 +85,6 @@ export default class extends sCtr.Ctr {
|
|
|
83
85
|
scan3(): Promise<kebab.Json>;
|
|
84
86
|
private _scanLink;
|
|
85
87
|
session(): Promise<string | kebab.Json[]>;
|
|
86
|
-
jwt(): Promise<string | kebab.Json[]>;
|
|
87
|
-
jwt1(): Promise<[number, kebab.Json]>;
|
|
88
88
|
sql(): string;
|
|
89
89
|
consistentHash(): string;
|
|
90
90
|
consistentDistributed(): string;
|