midway-fatcms 0.0.8 → 0.0.9
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/.qoder/skills/midway-fatcms/02-crud-quick.md +38 -0
- package/.qoder/skills/midway-fatcms/03-crud-sharding.md +37 -36
- package/.qoder/skills/midway-fatcms/07-examples.md +4 -0
- package/dist/configuration.d.ts +10 -0
- package/dist/configuration.js +26 -0
- package/dist/controller/helpers.controller.d.ts +6 -0
- package/dist/controller/helpers.controller.js +19 -0
- package/dist/libs/crud-pro/CrudPro.d.ts +29 -2
- package/dist/libs/crud-pro/CrudPro.js +58 -2
- package/dist/libs/crud-pro/exceptions.d.ts +7 -0
- package/dist/libs/crud-pro/exceptions.js +7 -0
- package/dist/libs/crud-pro/interfaces.d.ts +1 -0
- package/dist/libs/crud-pro/models/CrudResult.d.ts +3 -2
- package/dist/libs/crud-pro/models/CrudResult.js +1 -1
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -3
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +93 -6
- package/dist/libs/crud-pro-quick/CrudProQuick.js +192 -32
- package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
- package/dist/libs/crud-sharding/ShardingBase.js +179 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +10 -8
- package/dist/libs/crud-sharding/ShardingConfig.js +3 -3
- package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
- package/dist/libs/crud-sharding/index.d.ts +10 -13
- package/dist/libs/crud-sharding/index.js +21 -17
- package/dist/models/RedisKeys.d.ts +1 -0
- package/dist/models/RedisKeys.js +1 -0
- package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
- package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
- package/dist/service/curd/CurdMixService.d.ts +2 -2
- package/dist/service/curd/CurdProService.d.ts +109 -5
- package/dist/service/curd/CurdProService.js +127 -7
- package/package.json +1 -1
- package/src/configuration.ts +27 -0
- package/src/controller/helpers.controller.ts +15 -0
- package/src/libs/crud-pro/CrudPro.ts +73 -4
- package/src/libs/crud-pro/exceptions.ts +8 -0
- package/src/libs/crud-pro/interfaces.ts +1 -0
- package/src/libs/crud-pro/models/CrudResult.ts +5 -5
- package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
- package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -2
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
- package/src/libs/crud-pro-quick/CrudProQuick.ts +234 -46
- package/src/libs/crud-sharding/ShardingBase.ts +256 -0
- package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
- package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
- package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
- package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +10 -8
- package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
- package/src/libs/crud-sharding/index.ts +17 -14
- package/src/models/RedisKeys.ts +1 -0
- package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
- package/src/service/curd/CurdMixService.ts +2 -2
- package/src/service/curd/CurdProService.ts +131 -9
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -208
- package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -879
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -70
- package/dist/libs/crud-sharding/ShardingRouter.js +0 -396
- package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -1105
- package/src/libs/crud-sharding/ShardingRouter.ts +0 -533
|
@@ -87,10 +87,6 @@ export declare class CrudProQuick {
|
|
|
87
87
|
* 构建辅助定位信息
|
|
88
88
|
*/
|
|
89
89
|
private buildDebugInfo;
|
|
90
|
-
/**
|
|
91
|
-
* 格式化唯一性错误消息
|
|
92
|
-
*/
|
|
93
|
-
private formatUniqueError;
|
|
94
90
|
/**
|
|
95
91
|
* 查询数据列表
|
|
96
92
|
* @param reqJson 请求参数
|
|
@@ -156,6 +152,9 @@ export declare class CrudProQuick {
|
|
|
156
152
|
update(reqJson: IRequestModel, sqlTable?: string): Promise<CrudWriteResult>;
|
|
157
153
|
/**
|
|
158
154
|
* 插入或更新数据记录(upsert)
|
|
155
|
+
* 限制:
|
|
156
|
+
* - 必须有condition字段,用于判断记录是否存在
|
|
157
|
+
* - 必须有data字段,用于指定插入/更新的内容
|
|
159
158
|
* 内部使用 SIMPLE_INSERT_OR_UPDATE 模式,若记录已存在则更新,不存在则插入
|
|
160
159
|
* @param reqJson 请求参数,data 为要插入/更新的内容,condition 为判断条件
|
|
161
160
|
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
@@ -205,6 +204,41 @@ export declare class CrudProQuick {
|
|
|
205
204
|
* await quick.restore({ condition: { status: 'deleted' } });
|
|
206
205
|
*/
|
|
207
206
|
restore(reqJson: IRequestModel, sqlTable?: string): Promise<CrudWriteResult>;
|
|
207
|
+
/**
|
|
208
|
+
* 保存数据记录(自动判断 INSERT 或 UPDATE)
|
|
209
|
+
*
|
|
210
|
+
* 只需传入 data,无需手动指定 condition。方法会自动从表结构中获取主键列,
|
|
211
|
+
* 根据 data 中是否包含主键值来决定操作类型:
|
|
212
|
+
* - data 包含主键且主键值不为 undefined/null → 调用 insertOrUpdate(先查后写,存在则更新,不存在则插入)
|
|
213
|
+
* - data 不包含主键或主键值为 undefined/null → 调用 insert(直接插入)
|
|
214
|
+
*
|
|
215
|
+
* **前置条件**:data 不能为空
|
|
216
|
+
*
|
|
217
|
+
* @param reqJson 请求参数,只需 data 字段
|
|
218
|
+
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
219
|
+
* @returns CrudUpsertResult 包含 affected、insertAffected、updateAffected、isExist 和 getRawContext()
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // 有主键 → 先查后写(存在则 UPDATE,不存在则 INSERT)
|
|
223
|
+
* await quick.save({ data: { id: 1, name: '张三' } });
|
|
224
|
+
*
|
|
225
|
+
* // 无主键 → 直接 INSERT
|
|
226
|
+
* await quick.save({ data: { name: '李四' } });
|
|
227
|
+
*
|
|
228
|
+
* // 主键值为 null → 视为无主键,直接 INSERT
|
|
229
|
+
* await quick.save({ data: { id: null, name: '王五' } });
|
|
230
|
+
*/
|
|
231
|
+
save(reqJson: IRequestModel, sqlTable?: string): Promise<CrudUpsertResult>;
|
|
232
|
+
/**
|
|
233
|
+
* 获取表的主键列名列表
|
|
234
|
+
*
|
|
235
|
+
* 委托给 CrudPro.getPrimaryKeyColumns,复用 CrudProTableMetaService 的 getTableMeta 缓存,
|
|
236
|
+
* 避免重复查询数据库。
|
|
237
|
+
*
|
|
238
|
+
* @param sqlTable 表名
|
|
239
|
+
* @returns 主键列名数组,如果查询失败返回 ['id'] 作为保守默认值
|
|
240
|
+
*/
|
|
241
|
+
private getPrimaryKeyColumns;
|
|
208
242
|
/**
|
|
209
243
|
* 根据主键 ID 查询单条记录
|
|
210
244
|
*
|
|
@@ -229,9 +263,14 @@ export declare class CrudProQuick {
|
|
|
229
263
|
/**
|
|
230
264
|
* 根据主键 ID 列表查询多条记录
|
|
231
265
|
*
|
|
232
|
-
*
|
|
266
|
+
* 实现逻辑:
|
|
267
|
+
* 1. 先对 ids 去重,避免重复 ID 导致无效查询和重复结果
|
|
268
|
+
* 2. 若去重后为空,直接返回空结果
|
|
269
|
+
* 3. 若 ID 数量不超过批次大小,单次 findList 查询完成
|
|
270
|
+
* 4. 若 ID 数量超过批次大小,自动分批查询(每批默认 200 个 ID),
|
|
271
|
+
* 避免单次 IN 列表过大导致执行计划退化和内存尖峰,最后合并结果返回
|
|
233
272
|
*
|
|
234
|
-
* @param ids
|
|
273
|
+
* @param ids 主键值数组(内部会自动去重)
|
|
235
274
|
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
236
275
|
* @returns CrudQueryListResult 包含 rows、count 和 getRawContext()
|
|
237
276
|
*
|
|
@@ -292,4 +331,52 @@ export declare class CrudProQuick {
|
|
|
292
331
|
* // result 类型为 ExecuteSQLResult,实际运行时返回 ExecuteSQLUpdateResult
|
|
293
332
|
*/
|
|
294
333
|
executeNativeSQL<T = Record<string, any>>(executeNativeSql: string, args?: any[]): Promise<ExecuteSQLResult<T>>;
|
|
334
|
+
/**
|
|
335
|
+
* findOne 的快捷方式,直接返回 row
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* const row = await quick.getOne({ condition: { id: 1 } });
|
|
339
|
+
* // 等价于 (await quick.findOne({ condition: { id: 1 } })).row
|
|
340
|
+
*/
|
|
341
|
+
getOne(reqJson: IRequestModel, sqlTable?: string): Promise<Record<string, any>>;
|
|
342
|
+
/**
|
|
343
|
+
* findUniqueOne 的快捷方式,直接返回 row
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* const row = await quick.getUniqueOne({ condition: { order_id: 'ORD001' } });
|
|
347
|
+
* // 等价于 (await quick.findUniqueOne({ condition: { order_id: 'ORD001' } })).row
|
|
348
|
+
*/
|
|
349
|
+
getUniqueOne(reqJson: IRequestModel, sqlTable?: string): Promise<Record<string, any>>;
|
|
350
|
+
/**
|
|
351
|
+
* findList 的快捷方式,直接返回 rows
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* const rows = await quick.getList({ condition: { status: 'active' } });
|
|
355
|
+
* // 等价于 (await quick.findList({ condition: { status: 'active' } })).rows
|
|
356
|
+
*/
|
|
357
|
+
getList(reqJson: IRequestModel, sqlTable?: string): Promise<Record<string, any>[]>;
|
|
358
|
+
/**
|
|
359
|
+
* findCount 的快捷方式,直接返回 count
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* const count = await quick.getCount({ condition: { status: 'active' } });
|
|
363
|
+
* // 等价于 (await quick.findCount({ condition: { status: 'active' } })).count
|
|
364
|
+
*/
|
|
365
|
+
getCount(reqJson: IRequestModel, sqlTable?: string): Promise<number>;
|
|
366
|
+
/**
|
|
367
|
+
* isExist 的快捷方式,直接返回 exists
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* const exists = await quick.getIsExist({ condition: { id: 1 } });
|
|
371
|
+
* // 等价于 (await quick.isExist({ condition: { id: 1 } })).exists
|
|
372
|
+
*/
|
|
373
|
+
getIsExist(reqJson: IRequestModel, sqlTable?: string): Promise<boolean>;
|
|
374
|
+
/**
|
|
375
|
+
* findOneById 的快捷方式,直接返回 row
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* const row = await quick.getOneById(1);
|
|
379
|
+
* // 等价于 (await quick.findOneById(1)).row
|
|
380
|
+
*/
|
|
381
|
+
getOneById(id: number | string, sqlTable?: string): Promise<Record<string, any>>;
|
|
295
382
|
}
|
|
@@ -4,7 +4,9 @@ exports.CrudProQuick = void 0;
|
|
|
4
4
|
const keys_1 = require("../../libs/crud-pro/models/keys");
|
|
5
5
|
const fixSoftDelete_1 = require("./fixSoftDelete");
|
|
6
6
|
const CrudResult_1 = require("../../libs/crud-pro/models/CrudResult");
|
|
7
|
+
const exceptions_1 = require("../../libs/crud-pro/exceptions");
|
|
7
8
|
const DEFAULT_MAX_LIMIT = 10 * 10000;
|
|
9
|
+
const DEFAULT_BATCH_SIZE_FOR_IDS_QUERY = 200;
|
|
8
10
|
/**
|
|
9
11
|
* 快捷 CRUD 操作封装器
|
|
10
12
|
*
|
|
@@ -39,6 +41,7 @@ class CrudProQuick {
|
|
|
39
41
|
constructor(args) {
|
|
40
42
|
this.baseCfgModel = {
|
|
41
43
|
maxLimit: DEFAULT_MAX_LIMIT,
|
|
44
|
+
batchSizeForIdsQuery: DEFAULT_BATCH_SIZE_FOR_IDS_QUERY,
|
|
42
45
|
};
|
|
43
46
|
this.sqlDatabase = args.sqlDatabase;
|
|
44
47
|
this.sqlDbType = args.sqlDbType;
|
|
@@ -79,13 +82,19 @@ class CrudProQuick {
|
|
|
79
82
|
* @returns CrudQueryOneResult 包含 row、found 和 getRawContext()
|
|
80
83
|
*/
|
|
81
84
|
async findOne(reqJson, sqlTable) {
|
|
85
|
+
const effectiveTable = sqlTable || this.sqlTable;
|
|
82
86
|
const cfgModel = {
|
|
83
|
-
sqlTable,
|
|
87
|
+
sqlTable: effectiveTable,
|
|
84
88
|
sqlSimpleName: keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_ONE,
|
|
85
89
|
};
|
|
86
90
|
const ctx = await this.executeCrudByCfg(reqJson, cfgModel);
|
|
87
91
|
const row = ctx.getOneObj();
|
|
88
|
-
return new CrudResult_1.CrudQueryOneResult({
|
|
92
|
+
return new CrudResult_1.CrudQueryOneResult({
|
|
93
|
+
row,
|
|
94
|
+
rawContext: ctx,
|
|
95
|
+
debugInfo: this.buildDebugInfo(effectiveTable, reqJson.condition),
|
|
96
|
+
fromTable: effectiveTable
|
|
97
|
+
});
|
|
89
98
|
}
|
|
90
99
|
/**
|
|
91
100
|
* 查询唯一单条记录(期望结果为 0 条或 1 条)
|
|
@@ -118,14 +127,13 @@ class CrudProQuick {
|
|
|
118
127
|
const ctx = await this.executeCrudByCfg(reqJson, cfgModel);
|
|
119
128
|
const rows = ctx.getResRows();
|
|
120
129
|
if (rows.length > 1) {
|
|
121
|
-
|
|
122
|
-
const errorMsg = this.formatUniqueError(rows.length, debugInfo);
|
|
123
|
-
throw new Error(errorMsg);
|
|
130
|
+
throw new exceptions_1.CommonException(exceptions_1.Exceptions.MORE_THAN_ONE_RECORDS_FOUND, '发现了不止一条数据');
|
|
124
131
|
}
|
|
125
132
|
return new CrudResult_1.CrudQueryOneResult({
|
|
126
133
|
row: rows[0] || null,
|
|
127
134
|
rawContext: ctx,
|
|
128
135
|
debugInfo: this.buildDebugInfo(effectiveTable, reqJson.condition),
|
|
136
|
+
fromTable: effectiveTable,
|
|
129
137
|
});
|
|
130
138
|
}
|
|
131
139
|
/**
|
|
@@ -141,29 +149,6 @@ class CrudProQuick {
|
|
|
141
149
|
}
|
|
142
150
|
return info;
|
|
143
151
|
}
|
|
144
|
-
/**
|
|
145
|
-
* 格式化唯一性错误消息
|
|
146
|
-
*/
|
|
147
|
-
formatUniqueError(foundCount, debugInfo) {
|
|
148
|
-
const parts = [
|
|
149
|
-
`[CrudProQuick] findUniqueOne 期望唯一一条记录,但查询到 ${foundCount} 条`,
|
|
150
|
-
];
|
|
151
|
-
if (debugInfo.sqlDatabase) {
|
|
152
|
-
parts.push(`数据库: ${debugInfo.sqlDatabase}`);
|
|
153
|
-
}
|
|
154
|
-
if (debugInfo.sqlTable) {
|
|
155
|
-
parts.push(`表: ${debugInfo.sqlTable}`);
|
|
156
|
-
}
|
|
157
|
-
if (debugInfo.condition) {
|
|
158
|
-
try {
|
|
159
|
-
parts.push(`条件: ${JSON.stringify(debugInfo.condition)}`);
|
|
160
|
-
}
|
|
161
|
-
catch (_a) {
|
|
162
|
-
parts.push(`条件: [无法序列化]`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return parts.join(' | ');
|
|
166
|
-
}
|
|
167
152
|
/**
|
|
168
153
|
* 查询数据列表
|
|
169
154
|
* @param reqJson 请求参数
|
|
@@ -301,6 +286,9 @@ class CrudProQuick {
|
|
|
301
286
|
}
|
|
302
287
|
/**
|
|
303
288
|
* 插入或更新数据记录(upsert)
|
|
289
|
+
* 限制:
|
|
290
|
+
* - 必须有condition字段,用于判断记录是否存在
|
|
291
|
+
* - 必须有data字段,用于指定插入/更新的内容
|
|
304
292
|
* 内部使用 SIMPLE_INSERT_OR_UPDATE 模式,若记录已存在则更新,不存在则插入
|
|
305
293
|
* @param reqJson 请求参数,data 为要插入/更新的内容,condition 为判断条件
|
|
306
294
|
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
@@ -315,7 +303,6 @@ class CrudProQuick {
|
|
|
315
303
|
const ctx = await this.executeCrudByCfg(reqJson, cfgModel);
|
|
316
304
|
const resModel = ctx.getResModel();
|
|
317
305
|
return new CrudResult_1.CrudUpsertResult({
|
|
318
|
-
affected: resModel.affected,
|
|
319
306
|
insertAffected: resModel.insert_affected,
|
|
320
307
|
updateAffected: resModel.update_affected,
|
|
321
308
|
isExist: (_a = resModel.is_exist) !== null && _a !== void 0 ? _a : false,
|
|
@@ -415,6 +402,91 @@ class CrudProQuick {
|
|
|
415
402
|
rawContext: ctx,
|
|
416
403
|
});
|
|
417
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* 保存数据记录(自动判断 INSERT 或 UPDATE)
|
|
407
|
+
*
|
|
408
|
+
* 只需传入 data,无需手动指定 condition。方法会自动从表结构中获取主键列,
|
|
409
|
+
* 根据 data 中是否包含主键值来决定操作类型:
|
|
410
|
+
* - data 包含主键且主键值不为 undefined/null → 调用 insertOrUpdate(先查后写,存在则更新,不存在则插入)
|
|
411
|
+
* - data 不包含主键或主键值为 undefined/null → 调用 insert(直接插入)
|
|
412
|
+
*
|
|
413
|
+
* **前置条件**:data 不能为空
|
|
414
|
+
*
|
|
415
|
+
* @param reqJson 请求参数,只需 data 字段
|
|
416
|
+
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
417
|
+
* @returns CrudUpsertResult 包含 affected、insertAffected、updateAffected、isExist 和 getRawContext()
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* // 有主键 → 先查后写(存在则 UPDATE,不存在则 INSERT)
|
|
421
|
+
* await quick.save({ data: { id: 1, name: '张三' } });
|
|
422
|
+
*
|
|
423
|
+
* // 无主键 → 直接 INSERT
|
|
424
|
+
* await quick.save({ data: { name: '李四' } });
|
|
425
|
+
*
|
|
426
|
+
* // 主键值为 null → 视为无主键,直接 INSERT
|
|
427
|
+
* await quick.save({ data: { id: null, name: '王五' } });
|
|
428
|
+
*/
|
|
429
|
+
async save(reqJson, sqlTable) {
|
|
430
|
+
if (!reqJson.data || Object.keys(reqJson.data).length === 0) {
|
|
431
|
+
throw new Error('[CrudProQuick] save 操作的 data 不能为空。data 用于指定保存的内容。');
|
|
432
|
+
}
|
|
433
|
+
const effectiveTable = sqlTable || this.sqlTable;
|
|
434
|
+
if (!effectiveTable) {
|
|
435
|
+
throw new Error('[CrudProQuick] sqlTable not found');
|
|
436
|
+
}
|
|
437
|
+
// 获取表的主键列
|
|
438
|
+
const primaryKeys = await this.getPrimaryKeyColumns(effectiveTable);
|
|
439
|
+
const data = reqJson.data;
|
|
440
|
+
// 检查 data 中是否包含所有主键且值有效
|
|
441
|
+
const hasPrimaryKeyValue = primaryKeys.length > 0 &&
|
|
442
|
+
primaryKeys.every(pk => pk in data && data[pk] !== undefined && data[pk] !== null);
|
|
443
|
+
if (hasPrimaryKeyValue) {
|
|
444
|
+
// 从 data 中提取主键作为 condition
|
|
445
|
+
const condition = {};
|
|
446
|
+
for (const pk of primaryKeys) {
|
|
447
|
+
condition[pk] = data[pk];
|
|
448
|
+
}
|
|
449
|
+
// 调用 insertOrUpdate(先查后写)
|
|
450
|
+
return this.insertOrUpdate({ ...reqJson, condition }, sqlTable);
|
|
451
|
+
}
|
|
452
|
+
const condition = reqJson.condition;
|
|
453
|
+
if (condition && Object.keys(condition).length > 0) {
|
|
454
|
+
return this.insertOrUpdate(reqJson, sqlTable);
|
|
455
|
+
}
|
|
456
|
+
// 无主键值 → 直接 INSERT
|
|
457
|
+
const insertResult = await this.insert(reqJson, sqlTable);
|
|
458
|
+
// 将 CrudWriteResult 转换为 CrudUpsertResult
|
|
459
|
+
return new CrudResult_1.CrudUpsertResult({
|
|
460
|
+
insertAffected: { affectedRows: insertResult.affectedRows, insertId: insertResult.insertId },
|
|
461
|
+
updateAffected: { affectedRows: 0, insertId: null },
|
|
462
|
+
isExist: false,
|
|
463
|
+
rawContext: insertResult.getRawContext(),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* 获取表的主键列名列表
|
|
468
|
+
*
|
|
469
|
+
* 委托给 CrudPro.getPrimaryKeyColumns,复用 CrudProTableMetaService 的 getTableMeta 缓存,
|
|
470
|
+
* 避免重复查询数据库。
|
|
471
|
+
*
|
|
472
|
+
* @param sqlTable 表名
|
|
473
|
+
* @returns 主键列名数组,如果查询失败返回 ['id'] 作为保守默认值
|
|
474
|
+
*/
|
|
475
|
+
async getPrimaryKeyColumns(sqlTable) {
|
|
476
|
+
try {
|
|
477
|
+
const crudPro = this.crudProFactory();
|
|
478
|
+
return await crudPro.getPrimaryKeyColumns({
|
|
479
|
+
sqlTable,
|
|
480
|
+
sqlDatabase: this.sqlDatabase,
|
|
481
|
+
sqlDbType: this.sqlDbType,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
// 查询失败时保守返回 ['id'],避免阻断正常流程
|
|
486
|
+
console.warn('[CrudProQuick] 获取主键列失败,使用默认值 ["id"]:', error);
|
|
487
|
+
return ['id'];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
418
490
|
/**
|
|
419
491
|
* 根据主键 ID 查询单条记录
|
|
420
492
|
*
|
|
@@ -441,9 +513,14 @@ class CrudProQuick {
|
|
|
441
513
|
/**
|
|
442
514
|
* 根据主键 ID 列表查询多条记录
|
|
443
515
|
*
|
|
444
|
-
*
|
|
516
|
+
* 实现逻辑:
|
|
517
|
+
* 1. 先对 ids 去重,避免重复 ID 导致无效查询和重复结果
|
|
518
|
+
* 2. 若去重后为空,直接返回空结果
|
|
519
|
+
* 3. 若 ID 数量不超过批次大小,单次 findList 查询完成
|
|
520
|
+
* 4. 若 ID 数量超过批次大小,自动分批查询(每批默认 200 个 ID),
|
|
521
|
+
* 避免单次 IN 列表过大导致执行计划退化和内存尖峰,最后合并结果返回
|
|
445
522
|
*
|
|
446
|
-
* @param ids
|
|
523
|
+
* @param ids 主键值数组(内部会自动去重)
|
|
447
524
|
* @param sqlTable 数据库表名,若构造实例时已设置全局 sqlTable 则可省略
|
|
448
525
|
* @returns CrudQueryListResult 包含 rows、count 和 getRawContext()
|
|
449
526
|
*
|
|
@@ -456,7 +533,24 @@ class CrudProQuick {
|
|
|
456
533
|
* const result = await quick.findListByIds(['ORD001', 'ORD002'], 't_order');
|
|
457
534
|
*/
|
|
458
535
|
async findListByIds(ids, sqlTable) {
|
|
459
|
-
|
|
536
|
+
// 去重:避免重复 ID 导致无效查询和重复结果
|
|
537
|
+
const uniqueIds = [...new Set(ids)];
|
|
538
|
+
const batchSize = this.baseCfgModel.batchSizeForIdsQuery || DEFAULT_BATCH_SIZE_FOR_IDS_QUERY;
|
|
539
|
+
if (uniqueIds.length === 0) {
|
|
540
|
+
return new CrudResult_1.CrudQueryListResult({ rows: [], rawContext: null });
|
|
541
|
+
}
|
|
542
|
+
if (uniqueIds.length <= batchSize) {
|
|
543
|
+
return this.findList({ condition: { id: { $in: uniqueIds } } }, sqlTable);
|
|
544
|
+
}
|
|
545
|
+
const allRows = [];
|
|
546
|
+
let lastRawContext = null;
|
|
547
|
+
for (let i = 0; i < uniqueIds.length; i += batchSize) {
|
|
548
|
+
const batchIds = uniqueIds.slice(i, i + batchSize);
|
|
549
|
+
const batchResult = await this.findList({ condition: { id: { $in: batchIds } } }, sqlTable);
|
|
550
|
+
allRows.push(...batchResult.rows);
|
|
551
|
+
lastRawContext = batchResult.getRawContext();
|
|
552
|
+
}
|
|
553
|
+
return new CrudResult_1.CrudQueryListResult({ rows: allRows, rawContext: lastRawContext });
|
|
460
554
|
}
|
|
461
555
|
/**
|
|
462
556
|
* 执行自定义 SQL 语句(经 CrudPro 框架封装处理)
|
|
@@ -525,5 +619,71 @@ class CrudProQuick {
|
|
|
525
619
|
};
|
|
526
620
|
return await this.crudProFactory().executeSQL(sqlCfgModel);
|
|
527
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* findOne 的快捷方式,直接返回 row
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* const row = await quick.getOne({ condition: { id: 1 } });
|
|
627
|
+
* // 等价于 (await quick.findOne({ condition: { id: 1 } })).row
|
|
628
|
+
*/
|
|
629
|
+
async getOne(reqJson, sqlTable) {
|
|
630
|
+
const res = await this.findOne(reqJson, sqlTable);
|
|
631
|
+
return res.row;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* findUniqueOne 的快捷方式,直接返回 row
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* const row = await quick.getUniqueOne({ condition: { order_id: 'ORD001' } });
|
|
638
|
+
* // 等价于 (await quick.findUniqueOne({ condition: { order_id: 'ORD001' } })).row
|
|
639
|
+
*/
|
|
640
|
+
async getUniqueOne(reqJson, sqlTable) {
|
|
641
|
+
const res = await this.findUniqueOne(reqJson, sqlTable);
|
|
642
|
+
return res.row;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* findList 的快捷方式,直接返回 rows
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* const rows = await quick.getList({ condition: { status: 'active' } });
|
|
649
|
+
* // 等价于 (await quick.findList({ condition: { status: 'active' } })).rows
|
|
650
|
+
*/
|
|
651
|
+
async getList(reqJson, sqlTable) {
|
|
652
|
+
const res = await this.findList(reqJson, sqlTable);
|
|
653
|
+
return res.rows;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* findCount 的快捷方式,直接返回 count
|
|
657
|
+
*
|
|
658
|
+
* @example
|
|
659
|
+
* const count = await quick.getCount({ condition: { status: 'active' } });
|
|
660
|
+
* // 等价于 (await quick.findCount({ condition: { status: 'active' } })).count
|
|
661
|
+
*/
|
|
662
|
+
async getCount(reqJson, sqlTable) {
|
|
663
|
+
const res = await this.findCount(reqJson, sqlTable);
|
|
664
|
+
return res.count;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* isExist 的快捷方式,直接返回 exists
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* const exists = await quick.getIsExist({ condition: { id: 1 } });
|
|
671
|
+
* // 等价于 (await quick.isExist({ condition: { id: 1 } })).exists
|
|
672
|
+
*/
|
|
673
|
+
async getIsExist(reqJson, sqlTable) {
|
|
674
|
+
const res = await this.isExist(reqJson, sqlTable);
|
|
675
|
+
return res.exists;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* findOneById 的快捷方式,直接返回 row
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* const row = await quick.getOneById(1);
|
|
682
|
+
* // 等价于 (await quick.findOneById(1)).row
|
|
683
|
+
*/
|
|
684
|
+
async getOneById(id, sqlTable) {
|
|
685
|
+
const res = await this.findOneById(id, sqlTable);
|
|
686
|
+
return res.row;
|
|
687
|
+
}
|
|
528
688
|
}
|
|
529
689
|
exports.CrudProQuick = CrudProQuick;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { IRequestModel, IRequestCfgModel } from '../../libs/crud-pro/interfaces';
|
|
2
|
+
import { ExecuteContext } from '../../libs/crud-pro/models/ExecuteContext';
|
|
3
|
+
import { KeysOfSimpleSQL } from '../../libs/crud-pro/models/keys';
|
|
4
|
+
import { IShardingConfig, IShardingRouterContext, CrudProFactory } from './ShardingConfig';
|
|
5
|
+
import { ShardingTableCreator } from './ShardingTableCreator';
|
|
6
|
+
import { CrudWriteResult, CrudQueryOneResult, CrudQueryListResult, CrudQueryPageResult, CrudExistResult, CrudCountResult, CrudUpsertResult } from '../../libs/crud-pro/models/CrudResult';
|
|
7
|
+
import { ShardingBatchInsertResult } from './ShardingResult';
|
|
8
|
+
/**
|
|
9
|
+
* 分表 CRUD 基类
|
|
10
|
+
*
|
|
11
|
+
* 提供所有分表子类共用的基础设施方法,不包含任何路由逻辑。
|
|
12
|
+
* 路由逻辑由各子类自行实现。
|
|
13
|
+
*
|
|
14
|
+
* 子类:
|
|
15
|
+
* - ShardingByTimeCrud: 时间分表(YEAR/MONTH/DAY)
|
|
16
|
+
* - ShardingByHashCrud: 哈希分表
|
|
17
|
+
* - ShardingByKeyCrud: 键值分表
|
|
18
|
+
* - ShardingByCustomCrud: 自定义分表
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class ShardingBase {
|
|
21
|
+
protected readonly crudProFactory: CrudProFactory;
|
|
22
|
+
protected readonly config: IShardingConfig;
|
|
23
|
+
protected readonly tableCreator: ShardingTableCreator;
|
|
24
|
+
protected baseCfg: Partial<IRequestCfgModel>;
|
|
25
|
+
protected enableSoftDelete: boolean;
|
|
26
|
+
constructor(crudProFactory: CrudProFactory, config: IShardingConfig);
|
|
27
|
+
setBaseCfg(cfg: Partial<IRequestCfgModel>): this;
|
|
28
|
+
getConfig(): IShardingConfig;
|
|
29
|
+
setEnableSoftDelete(enable: boolean): this;
|
|
30
|
+
abstract insert(reqJson: IRequestModel): Promise<CrudWriteResult>;
|
|
31
|
+
abstract batchInsert(reqJson: IRequestModel): Promise<ShardingBatchInsertResult>;
|
|
32
|
+
abstract update(reqJson: IRequestModel): Promise<CrudWriteResult>;
|
|
33
|
+
abstract delete(reqJson: IRequestModel): Promise<CrudWriteResult>;
|
|
34
|
+
abstract restore(reqJson: IRequestModel): Promise<CrudWriteResult>;
|
|
35
|
+
abstract insertOrUpdate(reqJson: IRequestModel): Promise<CrudUpsertResult>;
|
|
36
|
+
abstract findOne<T = Record<string, any>>(reqJson: IRequestModel): Promise<CrudQueryOneResult<T>>;
|
|
37
|
+
abstract findUniqueOne<T = Record<string, any>>(reqJson: IRequestModel): Promise<CrudQueryOneResult<T>>;
|
|
38
|
+
abstract findList<T = Record<string, any>>(reqJson: IRequestModel): Promise<CrudQueryListResult<T>>;
|
|
39
|
+
abstract findPage<T = Record<string, any>>(reqJson: IRequestModel): Promise<CrudQueryPageResult<T>>;
|
|
40
|
+
abstract findCount(reqJson: IRequestModel): Promise<CrudCountResult>;
|
|
41
|
+
abstract isExist(reqJson: IRequestModel): Promise<CrudExistResult>;
|
|
42
|
+
/**
|
|
43
|
+
* 判断当前路由上下文是否处于「插入阶段」(应从 data 提取分表字段)
|
|
44
|
+
*
|
|
45
|
+
* - INSERT / BATCH_INSERT → 始终从 data
|
|
46
|
+
* - 其余操作(QUERY / UPDATE / DELETE / INSERT_OR_UPDATE)→ 从 condition
|
|
47
|
+
*/
|
|
48
|
+
protected static isInsertPhase(ctx: IShardingRouterContext): boolean;
|
|
49
|
+
protected buildCfg(sqlSimpleName: KeysOfSimpleSQL): IRequestCfgModel & {
|
|
50
|
+
enableSoftDelete?: boolean;
|
|
51
|
+
};
|
|
52
|
+
protected executeOnTable(table: string, reqJson: IRequestModel, sqlSimpleName: KeysOfSimpleSQL, extraCfg?: Partial<IRequestCfgModel>): Promise<ExecuteContext>;
|
|
53
|
+
protected findCountFromTable(table: string, reqJson: IRequestModel): Promise<{
|
|
54
|
+
count: number;
|
|
55
|
+
ctx: ExecuteContext | null;
|
|
56
|
+
}>;
|
|
57
|
+
protected isExistInTable(table: string, reqJson: IRequestModel): Promise<{
|
|
58
|
+
exists: boolean;
|
|
59
|
+
ctx: ExecuteContext | null;
|
|
60
|
+
}>;
|
|
61
|
+
protected getExistingTablesSet(skipCache?: boolean): Promise<Set<string>>;
|
|
62
|
+
protected isTableExists(tableName: string): Promise<boolean>;
|
|
63
|
+
protected createShardingTableIfNeeded(tableName: string): Promise<void>;
|
|
64
|
+
protected buildDebugInfo(sqlTable: string | undefined, condition: Record<string, any> | undefined): {
|
|
65
|
+
sqlDatabase: string;
|
|
66
|
+
sqlTable: string;
|
|
67
|
+
condition?: Record<string, any>;
|
|
68
|
+
};
|
|
69
|
+
protected formatUniqueError(foundCount: number, tables: string | string[], condition: Record<string, any> | undefined, isMultiTable?: boolean): string;
|
|
70
|
+
/**
|
|
71
|
+
* 获取所有以 baseTable_ 开头的已存在分表(按字典序排序)
|
|
72
|
+
*/
|
|
73
|
+
protected getAllExistingShardingTables(): Promise<string[]>;
|
|
74
|
+
/**
|
|
75
|
+
* 过滤出真实存在的分表
|
|
76
|
+
*/
|
|
77
|
+
protected filterExistingTables(candidateTables: string[]): Promise<string[]>;
|
|
78
|
+
}
|