midway-fatcms 0.0.6 → 0.0.8
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/01-quick-start.md +231 -0
- package/.qoder/skills/midway-fatcms/02-crud-quick.md +337 -0
- package/.qoder/skills/midway-fatcms/03-crud-sharding.md +488 -0
- package/.qoder/skills/midway-fatcms/04-condition-operators.md +93 -0
- package/.qoder/skills/midway-fatcms/05-configuration.md +290 -0
- package/.qoder/skills/midway-fatcms/06-builtin-functions.md +241 -0
- package/.qoder/skills/midway-fatcms/07-examples.md +500 -0
- package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
- package/README.md +9 -9
- package/dist/controller/base/BaseApiController.d.ts +1 -2
- package/dist/controller/base/BaseApiController.js +0 -4
- package/dist/controller/gateway/DocGatewayController.js +1 -1
- package/dist/controller/manage/FlowConfigManageApi.js +4 -2
- package/dist/controller/manage/SysConfigMangeApi.js +6 -1
- package/dist/controller/manage/UserAccountManageApi.js +7 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/libs/crud-pro/CrudPro.d.ts +23 -2
- package/dist/libs/crud-pro/CrudPro.js +53 -2
- package/dist/libs/crud-pro/interfaces.d.ts +82 -12
- package/dist/libs/crud-pro/models/CrudResult.d.ts +115 -0
- package/dist/libs/crud-pro/models/CrudResult.js +126 -0
- package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -38
- package/dist/libs/crud-pro/models/RequestModel.js +2 -99
- package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
- package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +1 -2
- package/dist/libs/crud-pro/utils/OrderByUtils.d.ts +70 -0
- package/dist/libs/crud-pro/utils/OrderByUtils.js +158 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +295 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.js +529 -0
- package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
- package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
- package/dist/libs/crud-pro-quick/index.d.ts +36 -0
- package/dist/libs/crud-pro-quick/index.js +49 -0
- package/dist/libs/crud-pro-quick/models.d.ts +33 -0
- package/dist/libs/crud-pro-quick/models.js +2 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +15 -2
- package/dist/libs/crud-sharding/ShardingConfig.js +2 -2
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +119 -274
- package/dist/libs/crud-sharding/ShardingCrudPro.js +559 -379
- package/dist/libs/crud-sharding/ShardingMerger.d.ts +12 -20
- package/dist/libs/crud-sharding/ShardingMerger.js +36 -51
- package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
- package/dist/libs/crud-sharding/ShardingResult.js +16 -0
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +1 -0
- package/dist/libs/crud-sharding/ShardingRouter.js +25 -6
- package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
- package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
- package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
- package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
- package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
- package/dist/libs/crud-sharding/index.d.ts +4 -3
- package/dist/libs/crud-sharding/index.js +14 -2
- package/dist/models/bizmodels.d.ts +2 -6
- package/dist/service/SysAppService.d.ts +2 -2
- package/dist/service/SysAppService.js +16 -5
- package/dist/service/SysConfigService.d.ts +1 -1
- package/dist/service/SysConfigService.js +7 -2
- package/dist/service/SysDictDataService.js +14 -4
- package/dist/service/SysMenuService.js +7 -2
- package/dist/service/curd/CurdMixService.d.ts +6 -4
- package/dist/service/curd/CurdMixService.js +16 -2
- package/dist/service/curd/CurdProService.d.ts +43 -27
- package/dist/service/curd/CurdProService.js +32 -33
- package/dist/service/flow/FlowConfigService.js +7 -2
- package/dist/service/flow/FlowInstanceCrudService.js +22 -19
- package/package.json +2 -1
- package/src/controller/base/BaseApiController.ts +0 -5
- package/src/controller/gateway/DocGatewayController.ts +1 -1
- package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
- package/src/controller/manage/FlowConfigManageApi.ts +4 -2
- package/src/controller/manage/SysConfigMangeApi.ts +6 -1
- package/src/controller/manage/UserAccountManageApi.ts +7 -2
- package/src/index.ts +2 -2
- package/src/libs/crud-pro/CrudPro.ts +62 -4
- package/src/libs/crud-pro/interfaces.ts +110 -15
- package/src/libs/crud-pro/models/CrudResult.ts +178 -0
- package/src/libs/crud-pro/models/RequestModel.ts +4 -110
- package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
- package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +1 -2
- package/src/libs/crud-pro/utils/OrderByUtils.ts +169 -0
- package/src/libs/crud-pro-quick/CrudProQuick.ts +594 -0
- package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
- package/src/libs/crud-pro-quick/index.ts +52 -0
- package/src/libs/crud-pro-quick/models.ts +35 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +18 -2
- package/src/libs/crud-sharding/ShardingCrudPro.ts +689 -440
- package/src/libs/crud-sharding/ShardingMerger.ts +47 -73
- package/src/libs/crud-sharding/ShardingResult.ts +29 -0
- package/src/libs/crud-sharding/ShardingRouter.ts +27 -6
- package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
- package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
- package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
- package/src/libs/crud-sharding/index.ts +14 -3
- package/src/models/bizmodels.ts +4 -7
- package/src/service/SysAppService.ts +18 -7
- package/src/service/SysConfigService.ts +8 -3
- package/src/service/SysDictDataService.ts +14 -4
- package/src/service/SysMenuService.ts +7 -2
- package/src/service/crudstd/CrudStdService.ts +2 -2
- package/src/service/curd/CurdMixService.ts +26 -5
- package/src/service/curd/CurdProService.ts +58 -39
- package/src/service/flow/FlowConfigService.ts +7 -2
- package/src/service/flow/FlowInstanceCrudService.ts +23 -20
- package/dist/libs/crud-pro/README.md +0 -809
- package/dist/libs/crud-pro/README_FUNC.md +0 -193
- package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
- package/dist/models/StandardColumns.d.ts +0 -71
- package/dist/models/StandardColumns.js +0 -28
- package/dist/service/curd/CrudProQuick.d.ts +0 -190
- package/dist/service/curd/CrudProQuick.js +0 -319
- package/dist/service/curd/README.md +0 -1001
- package/dist/service/curd/fixSoftDelete.d.ts +0 -20
- package/src/libs/crud-pro/README.md +0 -809
- package/src/libs/crud-pro/README_FUNC.md +0 -193
- package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
- package/src/models/StandardColumns.ts +0 -76
- package/src/service/curd/CrudProQuick.ts +0 -360
- package/src/service/curd/README.md +0 -1001
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ITableColumn } from '@/libs/crud-pro/interfaces';
|
|
1
|
+
import { ExecuteSQLRowsResult, ITableColumn } from '@/libs/crud-pro/interfaces';
|
|
3
2
|
import { SqlDbType } from '@/libs/crud-pro/models/keys';
|
|
4
|
-
import { IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResult } from './ShardingConfig';
|
|
3
|
+
import { CrudProFactory, IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResult } from './ShardingConfig';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* 扩展列信息(在 ITableColumn 基础上增加分表建表所需的内部标记)
|
|
@@ -25,7 +24,7 @@ interface IShardingTableColumn extends ITableColumn {
|
|
|
25
24
|
*/
|
|
26
25
|
export class ShardingTableCreator {
|
|
27
26
|
constructor(
|
|
28
|
-
private readonly
|
|
27
|
+
private readonly crudProFactory: CrudProFactory,
|
|
29
28
|
private readonly config: IShardingConfig
|
|
30
29
|
) {
|
|
31
30
|
this.validateIdentifier(this.config.baseTable, 'baseTable');
|
|
@@ -68,36 +67,65 @@ export class ShardingTableCreator {
|
|
|
68
67
|
|
|
69
68
|
const { copyIndexes = false } = options;
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
// 0. 先检查表是否已存在
|
|
71
|
+
const tableExists = await this.checkTableExists(tableName, baseCfg);
|
|
72
|
+
if (tableExists) {
|
|
73
|
+
// 表已存在,跳过创建和索引复制
|
|
74
|
+
return { success: true, tableName, createSql: '' };
|
|
75
|
+
}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
options
|
|
84
|
-
);
|
|
77
|
+
// 1. 获取基表元信息
|
|
78
|
+
const tableMeta = await this.getBaseTableMeta(baseCfg);
|
|
79
|
+
if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
|
|
80
|
+
throw new Error(`[ShardingTableCreator] 无法获取基表 ${this.config.baseTable} 的结构信息`);
|
|
81
|
+
}
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
// 2. 生成建表 SQL
|
|
84
|
+
const createSql = this.generateCreateTableSql(
|
|
85
|
+
tableName,
|
|
86
|
+
tableMeta.columnDetails,
|
|
87
|
+
baseCfg.sqlDbType,
|
|
88
|
+
options
|
|
89
|
+
);
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
// 3. 执行建表
|
|
92
|
+
await this.executeCreateSql(baseCfg, createSql);
|
|
93
|
+
|
|
94
|
+
// 4. 可选:复制索引(只在新建表时执行)
|
|
95
|
+
if (copyIndexes) {
|
|
96
|
+
try {
|
|
91
97
|
await this.copyIndexes(tableName, baseCfg);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error("copyIndexes", e);
|
|
92
100
|
}
|
|
101
|
+
}
|
|
93
102
|
|
|
94
|
-
|
|
103
|
+
return { success: true, tableName, createSql };
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 检查表是否已存在
|
|
109
|
+
* 复用 CrudPro.getAllTableInfos 接口,使用缓存提升性能
|
|
110
|
+
* DDL 操作本身具有幂等性(CREATE TABLE IF NOT EXISTS),缓存短暂延迟可接受
|
|
111
|
+
*/
|
|
112
|
+
private async checkTableExists(
|
|
113
|
+
tableName: string,
|
|
114
|
+
baseCfg: { sqlDatabase: string; sqlDbType: SqlDbType }
|
|
115
|
+
): Promise<boolean> {
|
|
116
|
+
try {
|
|
117
|
+
const { tables } = await this.crudProFactory().getAllTableInfos(
|
|
118
|
+
{
|
|
119
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
120
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
121
|
+
},
|
|
122
|
+
{ skipCache: false } // 使用缓存,避免每次建表都直查数据库
|
|
123
|
+
);
|
|
124
|
+
return tables.some(t => t.name === tableName);
|
|
95
125
|
} catch (error) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
100
|
-
};
|
|
126
|
+
console.warn('[ShardingTableCreator] 检查表存在性失败:', error);
|
|
127
|
+
// 检查失败时,假设表不存在,继续尝试创建
|
|
128
|
+
return false;
|
|
101
129
|
}
|
|
102
130
|
}
|
|
103
131
|
|
|
@@ -145,22 +173,21 @@ export class ShardingTableCreator {
|
|
|
145
173
|
baseCfg: { sqlDatabase: string; sqlDbType: SqlDbType }
|
|
146
174
|
): Promise<ITableColumn[]> {
|
|
147
175
|
const sql = `
|
|
148
|
-
SELECT
|
|
149
|
-
COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
176
|
+
SELECT
|
|
177
|
+
COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY,
|
|
150
178
|
COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA
|
|
151
179
|
FROM information_schema.COLUMNS
|
|
152
180
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '${this.config.baseTable}'
|
|
153
181
|
ORDER BY ORDINAL_POSITION
|
|
154
182
|
`.trim();
|
|
155
183
|
|
|
156
|
-
const
|
|
184
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
157
185
|
isNativeSQL: true,
|
|
158
186
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
159
187
|
sqlDbType: baseCfg.sqlDbType,
|
|
160
188
|
executeSql: sql,
|
|
161
|
-
});
|
|
189
|
+
}) as ExecuteSQLRowsResult || [];
|
|
162
190
|
|
|
163
|
-
const rows = result?.rows || [];
|
|
164
191
|
return rows.map((row: any) => ({
|
|
165
192
|
name: row.COLUMN_NAME,
|
|
166
193
|
type: row.COLUMN_TYPE,
|
|
@@ -191,14 +218,12 @@ export class ShardingTableCreator {
|
|
|
191
218
|
ORDER BY ordinal_position
|
|
192
219
|
`.trim();
|
|
193
220
|
|
|
194
|
-
const
|
|
221
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
195
222
|
isNativeSQL: true,
|
|
196
223
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
197
224
|
sqlDbType: baseCfg.sqlDbType,
|
|
198
225
|
executeSql: sql,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const rows = result?.rows || [];
|
|
226
|
+
}) as ExecuteSQLRowsResult || [];
|
|
202
227
|
|
|
203
228
|
// 获取主键列
|
|
204
229
|
const pkColumns = await this.getPostgreSQLPrimaryKeys(baseCfg, schema);
|
|
@@ -235,14 +260,14 @@ export class ShardingTableCreator {
|
|
|
235
260
|
AND i.indisprimary
|
|
236
261
|
`.trim();
|
|
237
262
|
|
|
238
|
-
const result = await this.
|
|
263
|
+
const result = await this.crudProFactory().executeSQL({
|
|
239
264
|
isNativeSQL: true,
|
|
240
265
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
241
266
|
sqlDbType: baseCfg.sqlDbType,
|
|
242
267
|
executeSql: sql,
|
|
243
|
-
});
|
|
268
|
+
}) as ExecuteSQLRowsResult || [];
|
|
244
269
|
|
|
245
|
-
return
|
|
270
|
+
return result.map((row: any) => row.attname);
|
|
246
271
|
}
|
|
247
272
|
|
|
248
273
|
/**
|
|
@@ -266,14 +291,12 @@ export class ShardingTableCreator {
|
|
|
266
291
|
ORDER BY c.column_id
|
|
267
292
|
`.trim();
|
|
268
293
|
|
|
269
|
-
const
|
|
294
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
270
295
|
isNativeSQL: true,
|
|
271
296
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
272
297
|
sqlDbType: baseCfg.sqlDbType,
|
|
273
298
|
executeSql: sql,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const rows = result?.rows || [];
|
|
299
|
+
}) as ExecuteSQLRowsResult || [];
|
|
277
300
|
|
|
278
301
|
// 获取主键列
|
|
279
302
|
const pkColumns = await this.getSQLServerPrimaryKeys(baseCfg);
|
|
@@ -310,14 +333,14 @@ export class ShardingTableCreator {
|
|
|
310
333
|
AND i.is_primary_key = 1
|
|
311
334
|
`.trim();
|
|
312
335
|
|
|
313
|
-
const result = await this.
|
|
336
|
+
const result = await this.crudProFactory().executeSQL({
|
|
314
337
|
isNativeSQL: true,
|
|
315
338
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
316
339
|
sqlDbType: baseCfg.sqlDbType,
|
|
317
340
|
executeSql: sql,
|
|
318
|
-
});
|
|
341
|
+
}) as ExecuteSQLRowsResult || [];
|
|
319
342
|
|
|
320
|
-
return
|
|
343
|
+
return result.map((row: any) => row.name);
|
|
321
344
|
}
|
|
322
345
|
|
|
323
346
|
/**
|
|
@@ -509,7 +532,7 @@ export class ShardingTableCreator {
|
|
|
509
532
|
const tableOptions = options.tableOptions ? ` ${options.tableOptions}` : '';
|
|
510
533
|
|
|
511
534
|
return `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = '${tableName}')\n` +
|
|
512
|
-
|
|
535
|
+
`CREATE TABLE [${tableName}] (\n ${columnDefs.join(',\n ')}\n)${tableOptions}`;
|
|
513
536
|
}
|
|
514
537
|
|
|
515
538
|
private formatSQLServerColumn(col: ITableColumn): string {
|
|
@@ -702,7 +725,8 @@ export class ShardingTableCreator {
|
|
|
702
725
|
*/
|
|
703
726
|
private async executeCreateSql(
|
|
704
727
|
baseCfg: { sqlDatabase: string; sqlDbType: SqlDbType },
|
|
705
|
-
createSql: string
|
|
728
|
+
createSql: string,
|
|
729
|
+
options: { silentDuplicate?: boolean } = {}
|
|
706
730
|
): Promise<void> {
|
|
707
731
|
const sqlCfgModel = {
|
|
708
732
|
isNativeSQL: true,
|
|
@@ -711,7 +735,36 @@ export class ShardingTableCreator {
|
|
|
711
735
|
executeSql: createSql,
|
|
712
736
|
};
|
|
713
737
|
|
|
714
|
-
|
|
738
|
+
try {
|
|
739
|
+
await this.crudProFactory().executeSQL(sqlCfgModel);
|
|
740
|
+
} catch (error: any) {
|
|
741
|
+
// 静默处理 DDL "已存在"类错误(表已存在、索引已存在等)
|
|
742
|
+
if (options.silentDuplicate && this.isDuplicateError(error)) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* 判断是否为"已存在"类错误(表已存在、索引已存在等)
|
|
751
|
+
*/
|
|
752
|
+
private isDuplicateError(error: any): boolean {
|
|
753
|
+
if (!error) return false;
|
|
754
|
+
const code = error.code || error.errno;
|
|
755
|
+
const message = error.message || '';
|
|
756
|
+
// MySQL: ER_TABLE_EXISTS_ERROR (1050), ER_DUP_KEYNAME (1061)
|
|
757
|
+
// PostgreSQL: 42P07 (duplicate_table), 42P06 (duplicate_schema)
|
|
758
|
+
// SQL Server: 错误消息包含 "already exists"
|
|
759
|
+
return code === 'ER_TABLE_EXISTS_ERROR' ||
|
|
760
|
+
code === 'ER_DUP_KEYNAME' ||
|
|
761
|
+
code === 1050 ||
|
|
762
|
+
code === 1061 ||
|
|
763
|
+
code === '42P07' ||
|
|
764
|
+
code === '42P06' ||
|
|
765
|
+
message.includes('already exists') ||
|
|
766
|
+
message.includes('Duplicate') ||
|
|
767
|
+
message.includes('Table') && message.includes('already exists');
|
|
715
768
|
}
|
|
716
769
|
|
|
717
770
|
/**
|
|
@@ -724,29 +777,125 @@ export class ShardingTableCreator {
|
|
|
724
777
|
tableName: string,
|
|
725
778
|
baseCfg: { sqlDatabase: string; sqlDbType: SqlDbType }
|
|
726
779
|
): Promise<void> {
|
|
727
|
-
|
|
728
|
-
|
|
780
|
+
const indexDefs = await this.getIndexDefinitions(baseCfg);
|
|
781
|
+
|
|
782
|
+
// 过滤掉主键索引和空定义
|
|
783
|
+
const validIndexDefs = indexDefs.filter(def => !def.isPrimary && def.createSql);
|
|
784
|
+
if (validIndexDefs.length === 0) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
729
787
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (indexDef.isPrimary || !indexDef.createSql) continue;
|
|
788
|
+
// 一次性获取目标表已存在的所有索引名
|
|
789
|
+
const existingIndexNames = await this.getExistingIndexNames(tableName, baseCfg);
|
|
733
790
|
|
|
734
|
-
|
|
791
|
+
// 用于去重的 Set,防止同一请求中重复创建同名索引
|
|
792
|
+
const createdIndexNames = new Set<string>();
|
|
793
|
+
|
|
794
|
+
for (const indexDef of validIndexDefs) {
|
|
795
|
+
try {
|
|
796
|
+
// 替换表名
|
|
735
797
|
const indexSql = this.replaceTableNameInIndexSql(
|
|
736
798
|
indexDef.createSql,
|
|
737
799
|
tableName,
|
|
738
800
|
baseCfg.sqlDbType
|
|
739
801
|
);
|
|
740
802
|
|
|
741
|
-
//
|
|
803
|
+
// 重命名索引
|
|
742
804
|
const renamedSql = this.renameIndex(indexSql, tableName, baseCfg.sqlDbType);
|
|
743
805
|
|
|
744
|
-
|
|
806
|
+
// 提取新索引名
|
|
807
|
+
const newIndexName = this.extractIndexName(renamedSql, baseCfg.sqlDbType);
|
|
808
|
+
|
|
809
|
+
// 如果索引已存在(数据库中或本次请求已创建),跳过创建
|
|
810
|
+
if (existingIndexNames.has(newIndexName) || createdIndexNames.has(newIndexName)) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
console.info('[ShardingTableCreator] 准备创建索引 =>' + newIndexName + " ===> renamedSql" + renamedSql);
|
|
815
|
+
|
|
816
|
+
// 使用 silentDuplicate 静默处理"索引已存在"错误
|
|
817
|
+
await this.executeCreateSql(baseCfg, renamedSql, { silentDuplicate: true });
|
|
818
|
+
// 创建成功后添加到已创建列表,防止重复创建
|
|
819
|
+
createdIndexNames.add(newIndexName);
|
|
820
|
+
} catch (error: any) {
|
|
821
|
+
// 非预期错误,记录日志
|
|
822
|
+
console.warn('[ShardingTableCreator] 创建索引失败:', error?.message || error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* 获取目标表已存在的所有索引名
|
|
829
|
+
*/
|
|
830
|
+
private async getExistingIndexNames(
|
|
831
|
+
tableName: string,
|
|
832
|
+
baseCfg: { sqlDatabase: string; sqlDbType: SqlDbType }
|
|
833
|
+
): Promise<Set<string>> {
|
|
834
|
+
const indexNames = new Set<string>();
|
|
835
|
+
|
|
836
|
+
try {
|
|
837
|
+
if (baseCfg.sqlDbType === SqlDbType.mysql) {
|
|
838
|
+
const sql = `
|
|
839
|
+
SELECT INDEX_NAME
|
|
840
|
+
FROM information_schema.STATISTICS
|
|
841
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '${tableName}'
|
|
842
|
+
`;
|
|
843
|
+
const result = await this.crudProFactory().executeSQL({
|
|
844
|
+
isNativeSQL: true,
|
|
845
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
846
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
847
|
+
executeSql: sql,
|
|
848
|
+
}) as Array<{ INDEX_NAME: string }>;
|
|
849
|
+
result.forEach(row => indexNames.add(row.INDEX_NAME));
|
|
850
|
+
} else if (baseCfg.sqlDbType === SqlDbType.postgres) {
|
|
851
|
+
const sql = `
|
|
852
|
+
SELECT indexname
|
|
853
|
+
FROM pg_indexes
|
|
854
|
+
WHERE schemaname = 'public' AND tablename = '${tableName}'
|
|
855
|
+
`;
|
|
856
|
+
const result = await this.crudProFactory().executeSQL({
|
|
857
|
+
isNativeSQL: true,
|
|
858
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
859
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
860
|
+
executeSql: sql,
|
|
861
|
+
}) as Array<{ indexname: string }>;
|
|
862
|
+
result.forEach(row => indexNames.add(row.indexname));
|
|
863
|
+
} else if (baseCfg.sqlDbType === SqlDbType.sqlserver) {
|
|
864
|
+
const sql = `
|
|
865
|
+
SELECT name
|
|
866
|
+
FROM sys.indexes
|
|
867
|
+
WHERE object_id = OBJECT_ID('${tableName}')
|
|
868
|
+
`;
|
|
869
|
+
const result = await this.crudProFactory().executeSQL({
|
|
870
|
+
isNativeSQL: true,
|
|
871
|
+
sqlDatabase: baseCfg.sqlDatabase,
|
|
872
|
+
sqlDbType: baseCfg.sqlDbType,
|
|
873
|
+
executeSql: sql,
|
|
874
|
+
}) as Array<{ name: string }>;
|
|
875
|
+
result.forEach(row => indexNames.add(row.name));
|
|
745
876
|
}
|
|
746
877
|
} catch (error) {
|
|
747
|
-
console.warn('[ShardingTableCreator]
|
|
748
|
-
|
|
878
|
+
console.warn('[ShardingTableCreator] 获取已存在索引列表失败:', error);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return indexNames;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* 从 CREATE INDEX 语句中提取索引名
|
|
886
|
+
*/
|
|
887
|
+
private extractIndexName(indexSql: string, sqlDbType: SqlDbType): string {
|
|
888
|
+
if (sqlDbType === SqlDbType.mysql) {
|
|
889
|
+
const match = indexSql.match(/INDEX `([^`]+)`/i);
|
|
890
|
+
return match ? match[1] : '';
|
|
891
|
+
} else if (sqlDbType === SqlDbType.postgres) {
|
|
892
|
+
const match = indexSql.match(/INDEX "([^"]+)"/i);
|
|
893
|
+
return match ? match[1] : '';
|
|
894
|
+
} else if (sqlDbType === SqlDbType.sqlserver) {
|
|
895
|
+
const match = indexSql.match(/INDEX \[([^\]]+)\]/i);
|
|
896
|
+
return match ? match[1] : '';
|
|
749
897
|
}
|
|
898
|
+
return '';
|
|
750
899
|
}
|
|
751
900
|
|
|
752
901
|
/**
|
|
@@ -828,14 +977,12 @@ export class ShardingTableCreator {
|
|
|
828
977
|
ORDER BY INDEX_NAME, SEQ_IN_INDEX
|
|
829
978
|
`;
|
|
830
979
|
|
|
831
|
-
const
|
|
980
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
832
981
|
isNativeSQL: true,
|
|
833
982
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
834
983
|
sqlDbType: baseCfg.sqlDbType,
|
|
835
984
|
executeSql: sql,
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
const rows = result?.rows || [];
|
|
985
|
+
}) as ExecuteSQLRowsResult || [];
|
|
839
986
|
|
|
840
987
|
// 按索引名分组
|
|
841
988
|
const indexMap = new Map<string, { columns: string[]; isUnique: boolean; isPrimary: boolean }>();
|
|
@@ -898,14 +1045,12 @@ export class ShardingTableCreator {
|
|
|
898
1045
|
AND t.relname = '${this.config.baseTable}'
|
|
899
1046
|
`;
|
|
900
1047
|
|
|
901
|
-
const
|
|
1048
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
902
1049
|
isNativeSQL: true,
|
|
903
1050
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
904
1051
|
sqlDbType: baseCfg.sqlDbType,
|
|
905
1052
|
executeSql: sql,
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
const rows = result?.rows || [];
|
|
1053
|
+
}) as ExecuteSQLRowsResult || [];
|
|
909
1054
|
|
|
910
1055
|
return rows.map((row: any) => ({
|
|
911
1056
|
isPrimary: row.is_primary,
|
|
@@ -938,14 +1083,12 @@ export class ShardingTableCreator {
|
|
|
938
1083
|
AND i.name IS NOT NULL
|
|
939
1084
|
`;
|
|
940
1085
|
|
|
941
|
-
const
|
|
1086
|
+
const rows = await this.crudProFactory().executeSQL({
|
|
942
1087
|
isNativeSQL: true,
|
|
943
1088
|
sqlDatabase: baseCfg.sqlDatabase,
|
|
944
1089
|
sqlDbType: baseCfg.sqlDbType,
|
|
945
1090
|
executeSql: sql,
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
const rows = result?.rows || [];
|
|
1091
|
+
}) as ExecuteSQLRowsResult || [];
|
|
949
1092
|
|
|
950
1093
|
return rows.map((row: any) => {
|
|
951
1094
|
const isPrimary = row.is_primary;
|
|
@@ -82,3 +82,140 @@ export function isCurrentTable(
|
|
|
82
82
|
const expectedTableName = `${baseTable}_${currentSuffix}`;
|
|
83
83
|
return tableName === expectedTableName;
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
// ============ 时间字段清理与转换工具函数 ============
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 日期字符串的粒度类型
|
|
90
|
+
*/
|
|
91
|
+
export type DateStringGranularity = 'year' | 'month' | 'day' | 'datetime' | null;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 判断日期字符串的粒度
|
|
95
|
+
*
|
|
96
|
+
* 根据字符串格式推断精度:
|
|
97
|
+
* - 4位纯年:'2024' → 'year'
|
|
98
|
+
* - 7位年-月:'2024-01' → 'month'
|
|
99
|
+
* - 10位年-月-日:'2024-01-15' → 'day'
|
|
100
|
+
* - 带时分秒:'2024-01-15 10:30:00' → 'datetime'(精度已足够,不需要转换)
|
|
101
|
+
*
|
|
102
|
+
* @param value 待判断的值
|
|
103
|
+
* @returns 粒度类型,非日期字符串返回 null
|
|
104
|
+
*/
|
|
105
|
+
export function detectDateStringGranularity(value: string): DateStringGranularity {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
|
|
108
|
+
// 4位纯年:'2024'
|
|
109
|
+
if (/^\d{4}$/.test(trimmed)) {
|
|
110
|
+
return 'year';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 7位年-月:'2024-01'
|
|
114
|
+
if (/^\d{4}-\d{2}$/.test(trimmed)) {
|
|
115
|
+
return 'month';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 10位年-月-日:'2024-01-15'
|
|
119
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
|
|
120
|
+
return 'day';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 带时分秒的完整时间字符串(精度已足够,不需要转换)
|
|
124
|
+
if (/^\d{4}-\d{2}-\d{2}[T ]\d{1,2}/.test(trimmed)) {
|
|
125
|
+
return 'datetime';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 判断值是否为时间操作符表达式(ICompareCondition / ILogicalCondition)
|
|
133
|
+
*
|
|
134
|
+
* 排除 Date 对象后,typeof === 'object' 且非 null 即为操作符表达式。
|
|
135
|
+
* 判断优先级:`value instanceof Date` → 先于 `typeof === 'object'` 判断。
|
|
136
|
+
*
|
|
137
|
+
* @param value 待判断的值
|
|
138
|
+
* @returns 是否为操作符表达式
|
|
139
|
+
*/
|
|
140
|
+
export function isOperatorExpression(value: unknown): boolean {
|
|
141
|
+
// Date 对象是精确值,不是操作符
|
|
142
|
+
if (value instanceof Date) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
// 非-null 对象视为操作符表达式(如 { $gte: ... }, { $or: ... })
|
|
146
|
+
return typeof value === 'object' && value !== null && Object.keys(value).length > 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取指定年月的最后一天日期
|
|
151
|
+
*
|
|
152
|
+
* @param year 年份
|
|
153
|
+
* @param month 月份(1-12)
|
|
154
|
+
* @returns 最后一天的日号(28/29/30/31)
|
|
155
|
+
*/
|
|
156
|
+
function getLastDayOfMonth(year: number, month: number): number {
|
|
157
|
+
// new Date(year, month, 0) 返回上个月最后一天
|
|
158
|
+
return new Date(year, month, 0).getDate();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 将日期字符串按粒度转换为 $gte/$lte 范围表达式
|
|
163
|
+
*
|
|
164
|
+
* 仅对 year/month/day 粒度的字符串进行转换,
|
|
165
|
+
* datetime 粒度和非日期字符串不转换返回 null。
|
|
166
|
+
* 闰年2月自动处理(28/29天)。
|
|
167
|
+
*
|
|
168
|
+
* @param value 日期字符串
|
|
169
|
+
* @returns 范围表达式对象,不需要转换时返回 null
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* expandDateToRange('2024')
|
|
173
|
+
* // → { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' }
|
|
174
|
+
*
|
|
175
|
+
* expandDateToRange('2024-01')
|
|
176
|
+
* // → { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' }
|
|
177
|
+
*
|
|
178
|
+
* expandDateToRange('2024-01-15')
|
|
179
|
+
* // → { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' }
|
|
180
|
+
*/
|
|
181
|
+
export function expandDateToRange(value: string): Record<string, string> | null {
|
|
182
|
+
const granularity = detectDateStringGranularity(value);
|
|
183
|
+
|
|
184
|
+
if (!granularity || granularity === 'datetime') {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const trimmed = value.trim();
|
|
189
|
+
|
|
190
|
+
switch (granularity) {
|
|
191
|
+
case 'year': {
|
|
192
|
+
// '2024' → 2024-01-01 ~ 2024-12-31
|
|
193
|
+
const year = parseInt(trimmed, 10);
|
|
194
|
+
return {
|
|
195
|
+
$gte: `${year}-01-01 00:00:00`,
|
|
196
|
+
$lte: `${year}-12-31 23:59:59`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case 'month': {
|
|
200
|
+
// '2024-01' → 2024-01-01 ~ 2024-01-31
|
|
201
|
+
const [yearStr, monthStr] = trimmed.split('-');
|
|
202
|
+
const year = parseInt(yearStr, 10);
|
|
203
|
+
const month = parseInt(monthStr, 10);
|
|
204
|
+
const lastDay = getLastDayOfMonth(year, month);
|
|
205
|
+
const monthPadded = monthStr.padStart(2, '0');
|
|
206
|
+
return {
|
|
207
|
+
$gte: `${year}-${monthPadded}-01 00:00:00`,
|
|
208
|
+
$lte: `${year}-${monthPadded}-${String(lastDay).padStart(2, '0')} 23:59:59`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
case 'day': {
|
|
212
|
+
// '2024-01-15' → 2024-01-15 00:00:00 ~ 2024-01-15 23:59:59
|
|
213
|
+
return {
|
|
214
|
+
$gte: `${trimmed} 00:00:00`,
|
|
215
|
+
$lte: `${trimmed} 23:59:59`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|