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.
Files changed (75) hide show
  1. package/.qoder/skills/midway-fatcms/02-crud-quick.md +38 -0
  2. package/.qoder/skills/midway-fatcms/03-crud-sharding.md +37 -36
  3. package/.qoder/skills/midway-fatcms/07-examples.md +4 -0
  4. package/dist/configuration.d.ts +10 -0
  5. package/dist/configuration.js +26 -0
  6. package/dist/controller/helpers.controller.d.ts +6 -0
  7. package/dist/controller/helpers.controller.js +19 -0
  8. package/dist/libs/crud-pro/CrudPro.d.ts +29 -2
  9. package/dist/libs/crud-pro/CrudPro.js +58 -2
  10. package/dist/libs/crud-pro/exceptions.d.ts +7 -0
  11. package/dist/libs/crud-pro/exceptions.js +7 -0
  12. package/dist/libs/crud-pro/interfaces.d.ts +1 -0
  13. package/dist/libs/crud-pro/models/CrudResult.d.ts +3 -2
  14. package/dist/libs/crud-pro/models/CrudResult.js +1 -1
  15. package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
  16. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
  17. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
  18. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
  19. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -3
  20. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
  21. package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
  22. package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +93 -6
  23. package/dist/libs/crud-pro-quick/CrudProQuick.js +192 -32
  24. package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
  25. package/dist/libs/crud-sharding/ShardingBase.js +179 -0
  26. package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
  27. package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
  28. package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
  29. package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
  30. package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
  31. package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
  32. package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
  33. package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
  34. package/dist/libs/crud-sharding/ShardingConfig.d.ts +10 -8
  35. package/dist/libs/crud-sharding/ShardingConfig.js +3 -3
  36. package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
  37. package/dist/libs/crud-sharding/index.d.ts +10 -13
  38. package/dist/libs/crud-sharding/index.js +21 -17
  39. package/dist/models/RedisKeys.d.ts +1 -0
  40. package/dist/models/RedisKeys.js +1 -0
  41. package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
  42. package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
  43. package/dist/service/curd/CurdMixService.d.ts +2 -2
  44. package/dist/service/curd/CurdProService.d.ts +109 -5
  45. package/dist/service/curd/CurdProService.js +127 -7
  46. package/package.json +1 -1
  47. package/src/configuration.ts +27 -0
  48. package/src/controller/helpers.controller.ts +15 -0
  49. package/src/libs/crud-pro/CrudPro.ts +73 -4
  50. package/src/libs/crud-pro/exceptions.ts +8 -0
  51. package/src/libs/crud-pro/interfaces.ts +1 -0
  52. package/src/libs/crud-pro/models/CrudResult.ts +5 -5
  53. package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
  54. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
  55. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -2
  56. package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
  57. package/src/libs/crud-pro-quick/CrudProQuick.ts +234 -46
  58. package/src/libs/crud-sharding/ShardingBase.ts +256 -0
  59. package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
  60. package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
  61. package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
  62. package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
  63. package/src/libs/crud-sharding/ShardingConfig.ts +10 -8
  64. package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
  65. package/src/libs/crud-sharding/index.ts +17 -14
  66. package/src/models/RedisKeys.ts +1 -0
  67. package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
  68. package/src/service/curd/CurdMixService.ts +2 -2
  69. package/src/service/curd/CurdProService.ts +131 -9
  70. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -208
  71. package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -879
  72. package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -70
  73. package/dist/libs/crud-sharding/ShardingRouter.js +0 -396
  74. package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -1105
  75. 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
- * 等价于 `findList({ condition: { id: { $in: ids } } })`,但更简洁。
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({ row, rawContext: ctx, debugInfo: this.buildDebugInfo(sqlTable, reqJson.condition) });
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
- const debugInfo = this.buildDebugInfo(effectiveTable, reqJson.condition);
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
- * 等价于 `findList({ condition: { id: { $in: ids } } })`,但更简洁。
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
- return this.findList({ condition: { id: { $in: ids } } }, sqlTable);
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
+ }