midway-fatcms 0.0.5 → 0.0.6
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 +377 -134
- package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
- package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
- package/dist/libs/crud-pro/CrudPro.js +15 -0
- package/dist/libs/crud-pro/README.md +809 -0
- package/dist/libs/crud-pro/README_FUNC.md +193 -0
- package/dist/libs/crud-pro/exceptions.d.ts +2 -0
- package/dist/libs/crud-pro/exceptions.js +2 -0
- package/dist/libs/crud-pro/interfaces.d.ts +34 -1
- package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
- package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
- package/dist/libs/crud-pro/models/RequestModel.d.ts +41 -1
- package/dist/libs/crud-pro/models/RequestModel.js +103 -39
- package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
- package/dist/libs/crud-pro/models/keys.d.ts +6 -1
- package/dist/libs/crud-pro/models/keys.js +5 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
- package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
- package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
- package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
- package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
- package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
- package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
- package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
- package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
- package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
- package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
- package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
- package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
- package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
- package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
- package/dist/libs/crud-sharding/ShardingCrudPro.js +699 -0
- package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
- package/dist/libs/crud-sharding/ShardingMerger.js +280 -0
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
- package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
- package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
- package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
- package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
- package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
- package/dist/libs/crud-sharding/index.d.ts +45 -0
- package/dist/libs/crud-sharding/index.js +55 -0
- package/dist/models/StandardColumns.d.ts +71 -0
- package/dist/models/StandardColumns.js +28 -0
- package/dist/service/SysAppService.js +2 -2
- package/dist/service/SysConfigService.js +1 -1
- package/dist/service/SysDictDataService.js +2 -2
- package/dist/service/SysMenuService.js +1 -1
- package/dist/service/UserAccountService.d.ts +1 -1
- package/dist/service/crudstd/CrudStdService.d.ts +0 -1
- package/dist/service/crudstd/CrudStdService.js +0 -27
- package/dist/service/curd/CrudProQuick.d.ts +134 -4
- package/dist/service/curd/CrudProQuick.js +155 -3
- package/dist/service/curd/CurdMixService.d.ts +2 -1
- package/dist/service/curd/CurdMixService.js +5 -1
- package/dist/service/curd/CurdProService.d.ts +44 -2
- package/dist/service/curd/CurdProService.js +53 -1
- package/dist/service/curd/README.md +1001 -0
- package/dist/service/curd/fixSoftDelete.d.ts +14 -0
- package/dist/service/curd/fixSoftDelete.js +29 -11
- package/dist/service/flow/FlowConfigService.js +1 -1
- package/dist/service/flow/FlowInstanceCrudService.js +1 -1
- package/package.json +3 -1
- package/src/controller/gateway/AsyncTaskController.ts +1 -1
- package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
- package/src/index.ts +3 -0
- package/src/libs/crud-pro/CrudPro.ts +19 -1
- package/src/libs/crud-pro/README.md +809 -0
- package/src/libs/crud-pro/README_FUNC.md +193 -0
- package/src/libs/crud-pro/exceptions.ts +2 -0
- package/src/libs/crud-pro/interfaces.ts +38 -1
- package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
- package/src/libs/crud-pro/models/RequestModel.ts +108 -44
- package/src/libs/crud-pro/models/ResModel.ts +10 -4
- package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
- package/src/libs/crud-pro/models/keys.ts +5 -0
- package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
- package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
- package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
- package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
- package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
- package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
- package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
- package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
- package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
- package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
- package/src/libs/crud-sharding/ShardingCrudPro.ts +856 -0
- package/src/libs/crud-sharding/ShardingMerger.ts +382 -0
- package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
- package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
- package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
- package/src/libs/crud-sharding/index.ts +64 -0
- package/src/models/StandardColumns.ts +76 -0
- package/src/service/FileCenterService.ts +1 -1
- package/src/service/SysAppService.ts +2 -2
- package/src/service/SysConfigService.ts +1 -1
- package/src/service/SysDictDataService.ts +2 -2
- package/src/service/SysMenuService.ts +2 -2
- package/src/service/WorkbenchService.ts +1 -1
- package/src/service/anyapi/AnyApiService.ts +1 -1
- package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
- package/src/service/crudstd/CrudStdService.ts +0 -32
- package/src/service/curd/CrudProQuick.ts +164 -5
- package/src/service/curd/CurdMixService.ts +7 -2
- package/src/service/curd/CurdProService.ts +62 -3
- package/src/service/curd/README.md +1001 -0
- package/src/service/curd/fixCfgModel.ts +1 -2
- package/src/service/curd/fixSoftDelete.ts +38 -16
- package/src/service/flow/FlowConfigService.ts +1 -1
- package/src/service/flow/FlowInstanceCrudService.ts +1 -1
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShardingRouter = void 0;
|
|
4
|
+
const moment = require("moment");
|
|
5
|
+
const ShardingConfig_1 = require("./ShardingConfig");
|
|
6
|
+
const ShardingUtils_1 = require("./ShardingUtils");
|
|
7
|
+
/**
|
|
8
|
+
* 空值合并(兼容低版本 Node.js)
|
|
9
|
+
* 返回第一个非 null/undefined 的值
|
|
10
|
+
*/
|
|
11
|
+
function coalesce(a, b) {
|
|
12
|
+
return a !== null && a !== undefined ? a : b;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 分表路由器
|
|
16
|
+
*/
|
|
17
|
+
class ShardingRouter {
|
|
18
|
+
// ============ Public 方法 ============
|
|
19
|
+
/**
|
|
20
|
+
* 插入类操作专用路由(从 data 提取字段)
|
|
21
|
+
*/
|
|
22
|
+
resolveForInsert(config, context) {
|
|
23
|
+
this.validateConfig(config);
|
|
24
|
+
switch (config.type) {
|
|
25
|
+
case ShardingConfig_1.ShardingType.YEAR:
|
|
26
|
+
return this.resolveTimeForInsert(config, context, 'year');
|
|
27
|
+
case ShardingConfig_1.ShardingType.MONTH:
|
|
28
|
+
return this.resolveTimeForInsert(config, context, 'month');
|
|
29
|
+
case ShardingConfig_1.ShardingType.DAY:
|
|
30
|
+
return this.resolveTimeForInsert(config, context, 'day');
|
|
31
|
+
case ShardingConfig_1.ShardingType.RANGE:
|
|
32
|
+
return this.resolveRangeForInsert(config, context);
|
|
33
|
+
case ShardingConfig_1.ShardingType.HASH:
|
|
34
|
+
return this.resolveHashForInsert(config, context);
|
|
35
|
+
case ShardingConfig_1.ShardingType.CUSTOM:
|
|
36
|
+
return this.resolveCustomForInsert(config, context);
|
|
37
|
+
default:
|
|
38
|
+
return config.baseTable;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 条件类操作专用路由(从 condition 提取字段)
|
|
43
|
+
*/
|
|
44
|
+
resolveForCondition(config, context) {
|
|
45
|
+
this.validateConfig(config);
|
|
46
|
+
switch (config.type) {
|
|
47
|
+
case ShardingConfig_1.ShardingType.YEAR:
|
|
48
|
+
return this.resolveTimeForCondition(config, context, 'year');
|
|
49
|
+
case ShardingConfig_1.ShardingType.MONTH:
|
|
50
|
+
return this.resolveTimeForCondition(config, context, 'month');
|
|
51
|
+
case ShardingConfig_1.ShardingType.DAY:
|
|
52
|
+
return this.resolveTimeForCondition(config, context, 'day');
|
|
53
|
+
case ShardingConfig_1.ShardingType.RANGE:
|
|
54
|
+
return this.resolveRangeForCondition(config, context);
|
|
55
|
+
case ShardingConfig_1.ShardingType.HASH:
|
|
56
|
+
return this.resolveHashForCondition(config, context);
|
|
57
|
+
case ShardingConfig_1.ShardingType.CUSTOM:
|
|
58
|
+
return this.resolveCustomForCondition(config, context);
|
|
59
|
+
default:
|
|
60
|
+
return config.baseTable;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 查询专用路由(带表存在性过滤)
|
|
65
|
+
*/
|
|
66
|
+
async resolveQuery(config, context, tableInfoProvider) {
|
|
67
|
+
const existingTables = await tableInfoProvider.getExistingTables();
|
|
68
|
+
// 时间分表:从真实存在的表中取最近N张,再与条件取交集
|
|
69
|
+
if (this.isTimeSharding(config)) {
|
|
70
|
+
return this.getRecentExistingTablesFromAll(existingTables, config, context.condition);
|
|
71
|
+
}
|
|
72
|
+
// 非时间分表:先计算候选表,再过滤存在的表
|
|
73
|
+
const candidateResult = this.resolveForCondition(config, context);
|
|
74
|
+
const candidateTables = Array.isArray(candidateResult)
|
|
75
|
+
? candidateResult
|
|
76
|
+
: [candidateResult];
|
|
77
|
+
return this.filterExistingTables(candidateTables, existingTables);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 过滤出真实存在的分表
|
|
81
|
+
*/
|
|
82
|
+
filterExistingTables(candidateTables, existingTables) {
|
|
83
|
+
const candidates = Array.isArray(candidateTables) ? candidateTables : [candidateTables];
|
|
84
|
+
return candidates.filter(table => existingTables.has(table));
|
|
85
|
+
}
|
|
86
|
+
// ============ 配置校验 ============
|
|
87
|
+
validateConfig(config) {
|
|
88
|
+
if (!config.baseTable) {
|
|
89
|
+
throw new Error('[ShardingRouter] baseTable 不能为空');
|
|
90
|
+
}
|
|
91
|
+
if ((config.type === ShardingConfig_1.ShardingType.RANGE || config.type === ShardingConfig_1.ShardingType.HASH) && !config.shardingColumn) {
|
|
92
|
+
throw new Error(`[ShardingRouter] ${config.type} 分表必须指定 shardingColumn`);
|
|
93
|
+
}
|
|
94
|
+
if (config.type === ShardingConfig_1.ShardingType.CUSTOM && !config.customRouter) {
|
|
95
|
+
throw new Error('[ShardingRouter] CUSTOM 分表必须提供 customRouter 函数');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ============ 时间分表路由 ============
|
|
99
|
+
resolveTimeForInsert(config, context, granularity) {
|
|
100
|
+
const { baseTable, timeColumn } = config;
|
|
101
|
+
const timeValue = this.extractTimeFromData(context.data, timeColumn);
|
|
102
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(timeValue, granularity);
|
|
103
|
+
return this.formatTableName(baseTable, suffix, config);
|
|
104
|
+
}
|
|
105
|
+
resolveTimeForCondition(config, context, granularity) {
|
|
106
|
+
const { baseTable, timeColumn } = config;
|
|
107
|
+
const condition = context.condition || {};
|
|
108
|
+
const timeRange = this.extractTimeRange(condition, timeColumn);
|
|
109
|
+
const singleTimeValue = this.tryExtractTimeFromCondition(condition, timeColumn);
|
|
110
|
+
if (timeRange) {
|
|
111
|
+
return this.generateTablesInRange(baseTable, timeRange.start, timeRange.end, granularity, config);
|
|
112
|
+
}
|
|
113
|
+
if (singleTimeValue) {
|
|
114
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(singleTimeValue, granularity);
|
|
115
|
+
return this.formatTableName(baseTable, suffix, config);
|
|
116
|
+
}
|
|
117
|
+
const recentCount = this.getRecentCountByGranularity(granularity, config);
|
|
118
|
+
return this.getRecentTables(baseTable, granularity, recentCount, config);
|
|
119
|
+
}
|
|
120
|
+
// ============ 范围分表路由 ============
|
|
121
|
+
resolveRangeForInsert(config, context) {
|
|
122
|
+
const { baseTable, tableCount = 10, shardingColumn } = config;
|
|
123
|
+
const value = this.extractColumnFromData(context.data, shardingColumn);
|
|
124
|
+
const index = this.calculateRangeIndex(value, tableCount);
|
|
125
|
+
return this.formatTableName(baseTable, String(index), config);
|
|
126
|
+
}
|
|
127
|
+
resolveRangeForCondition(config, context) {
|
|
128
|
+
const { baseTable, tableCount = 10, shardingColumn } = config;
|
|
129
|
+
const value = this.extractColumnFromCondition(context.condition, shardingColumn);
|
|
130
|
+
const index = this.calculateRangeIndex(value, tableCount);
|
|
131
|
+
return this.formatTableName(baseTable, String(index), config);
|
|
132
|
+
}
|
|
133
|
+
// ============ 哈希分表路由 ============
|
|
134
|
+
resolveHashForInsert(config, context) {
|
|
135
|
+
const { baseTable, tableCount = 16, shardingColumn } = config;
|
|
136
|
+
const value = this.extractColumnFromData(context.data, shardingColumn);
|
|
137
|
+
const index = this.calculateHash(value, tableCount);
|
|
138
|
+
return this.formatTableName(baseTable, String(index).padStart(2, '0'), config);
|
|
139
|
+
}
|
|
140
|
+
resolveHashForCondition(config, context) {
|
|
141
|
+
const { baseTable, tableCount = 16, shardingColumn } = config;
|
|
142
|
+
const value = this.extractColumnFromCondition(context.condition, shardingColumn);
|
|
143
|
+
const index = this.calculateHash(value, tableCount);
|
|
144
|
+
return this.formatTableName(baseTable, String(index).padStart(2, '0'), config);
|
|
145
|
+
}
|
|
146
|
+
// ============ 自定义分表路由 ============
|
|
147
|
+
resolveCustomForInsert(config, context) {
|
|
148
|
+
const result = config.customRouter(context);
|
|
149
|
+
return Array.isArray(result) ? result[0] : result;
|
|
150
|
+
}
|
|
151
|
+
resolveCustomForCondition(config, context) {
|
|
152
|
+
return config.customRouter(context);
|
|
153
|
+
}
|
|
154
|
+
// ============ 查询相关私有方法 ============
|
|
155
|
+
isTimeSharding(config) {
|
|
156
|
+
return [ShardingConfig_1.ShardingType.YEAR, ShardingConfig_1.ShardingType.MONTH, ShardingConfig_1.ShardingType.DAY].includes(config.type);
|
|
157
|
+
}
|
|
158
|
+
getRecentExistingTablesFromAll(existingTables, config, condition) {
|
|
159
|
+
const baseTablePrefix = `${config.baseTable}_`;
|
|
160
|
+
const shardingTables = Array.from(existingTables).filter(t => t.startsWith(baseTablePrefix));
|
|
161
|
+
if (shardingTables.length === 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
shardingTables.sort();
|
|
165
|
+
// 配置了 recentTableCount:取最近N张,再与条件取交集
|
|
166
|
+
if (config.recentTableCount !== undefined) {
|
|
167
|
+
const recentTables = shardingTables.slice(-config.recentTableCount);
|
|
168
|
+
return this.intersectWithCondition(recentTables, config, condition);
|
|
169
|
+
}
|
|
170
|
+
// 未配置 recentTableCount:所有表与条件取交集,最多取3张
|
|
171
|
+
const intersected = this.intersectWithCondition(shardingTables, config, condition);
|
|
172
|
+
return intersected.slice(0, 3);
|
|
173
|
+
}
|
|
174
|
+
intersectWithCondition(tables, config, condition) {
|
|
175
|
+
if (!condition) {
|
|
176
|
+
return this.sortTablesByTimeDesc(tables);
|
|
177
|
+
}
|
|
178
|
+
const timeColumn = config.timeColumn;
|
|
179
|
+
const timeRange = this.extractTimeRange(condition, timeColumn);
|
|
180
|
+
if (timeRange) {
|
|
181
|
+
return this.intersectTablesByTimeRange(tables, timeRange, config);
|
|
182
|
+
}
|
|
183
|
+
const singleTimeValue = this.tryExtractTimeFromCondition(condition, timeColumn);
|
|
184
|
+
if (singleTimeValue) {
|
|
185
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(singleTimeValue, config.type);
|
|
186
|
+
const targetTable = `${config.baseTable}_${suffix}`;
|
|
187
|
+
return tables.includes(targetTable) ? [targetTable] : [];
|
|
188
|
+
}
|
|
189
|
+
return this.sortTablesByTimeDesc(tables);
|
|
190
|
+
}
|
|
191
|
+
intersectTablesByTimeRange(tables, timeRange, config) {
|
|
192
|
+
const startSuffix = (0, ShardingUtils_1.getTimeSuffix)(timeRange.start, config.type);
|
|
193
|
+
const endSuffix = (0, ShardingUtils_1.getTimeSuffix)(timeRange.end, config.type);
|
|
194
|
+
const baseTablePrefix = `${config.baseTable}_`;
|
|
195
|
+
const prefixLen = baseTablePrefix.length;
|
|
196
|
+
return this.sortTablesByTimeDesc(tables.filter(table => {
|
|
197
|
+
const suffix = table.substring(prefixLen);
|
|
198
|
+
return suffix >= startSuffix && suffix <= endSuffix;
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
// ============ 排序 ============
|
|
202
|
+
/**
|
|
203
|
+
* 将分表按时间后缀降序排列(新→旧)
|
|
204
|
+
*
|
|
205
|
+
* 分表名格式为 baseTable_suffix,后缀为时间格式(如 202403、20240101),
|
|
206
|
+
* 字典序即时间序,降序即新→旧。
|
|
207
|
+
*/
|
|
208
|
+
sortTablesByTimeDesc(tables) {
|
|
209
|
+
return [...tables].sort((a, b) => b.localeCompare(a));
|
|
210
|
+
}
|
|
211
|
+
// ============ 值提取:基础方法 ============
|
|
212
|
+
extractFromData(data, column) {
|
|
213
|
+
return data === null || data === void 0 ? void 0 : data[column];
|
|
214
|
+
}
|
|
215
|
+
extractFromCondition(condition, column) {
|
|
216
|
+
return condition === null || condition === void 0 ? void 0 : condition[column];
|
|
217
|
+
}
|
|
218
|
+
parseTimeValue(value, source, operation) {
|
|
219
|
+
if (value === undefined || value === null) {
|
|
220
|
+
throw new Error(`[ShardingRouter] ${operation} 操作无法从 ${source} 中获取时间分表字段的值`);
|
|
221
|
+
}
|
|
222
|
+
const m = moment(value);
|
|
223
|
+
if (!m.isValid()) {
|
|
224
|
+
throw new Error(`[ShardingRouter] 无法解析时间分表字段的值:${value}`);
|
|
225
|
+
}
|
|
226
|
+
return m.toDate();
|
|
227
|
+
}
|
|
228
|
+
parseColumnValue(value, source, operation) {
|
|
229
|
+
if (value === undefined || value === null) {
|
|
230
|
+
throw new Error(`[ShardingRouter] ${operation} 操作无法从 ${source} 中获取分表字段的值`);
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
// ============ 值提取:时间字段 ============
|
|
235
|
+
extractTimeFromData(data, column) {
|
|
236
|
+
const value = this.extractFromData(data, column);
|
|
237
|
+
return this.parseTimeValue(value, 'data', 'insert');
|
|
238
|
+
}
|
|
239
|
+
tryExtractTimeFromCondition(condition, column) {
|
|
240
|
+
const value = this.extractFromCondition(condition, column);
|
|
241
|
+
if (value === undefined || value === null) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const m = moment(value);
|
|
245
|
+
if (!m.isValid()) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return m.toDate();
|
|
249
|
+
}
|
|
250
|
+
extractTimeRange(condition, column) {
|
|
251
|
+
const colCond = condition[column];
|
|
252
|
+
if (!colCond || typeof colCond !== 'object')
|
|
253
|
+
return null;
|
|
254
|
+
const start = coalesce(colCond.$gte, colCond.$gt);
|
|
255
|
+
const end = coalesce(colCond.$lte, colCond.$lt);
|
|
256
|
+
if (start && end) {
|
|
257
|
+
const startMoment = moment(start);
|
|
258
|
+
const endMoment = moment(end);
|
|
259
|
+
if (!startMoment.isValid() || !endMoment.isValid()) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
start: startMoment.toDate(),
|
|
264
|
+
end: endMoment.toDate(),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (start) {
|
|
268
|
+
const startMoment = moment(start);
|
|
269
|
+
if (!startMoment.isValid()) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
start: startMoment.toDate(),
|
|
274
|
+
end: new Date(),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (end) {
|
|
278
|
+
const endMoment = moment(end);
|
|
279
|
+
if (!endMoment.isValid()) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
start: new Date(0),
|
|
284
|
+
end: endMoment.toDate(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
// ============ 值提取:分表字段 ============
|
|
290
|
+
extractColumnFromData(data, column) {
|
|
291
|
+
const value = this.extractFromData(data, column);
|
|
292
|
+
return this.parseColumnValue(value, 'data', 'insert');
|
|
293
|
+
}
|
|
294
|
+
extractColumnFromCondition(condition, column) {
|
|
295
|
+
const value = this.extractFromCondition(condition, column);
|
|
296
|
+
return this.parseColumnValue(value, 'condition', '查询/更新/删除');
|
|
297
|
+
}
|
|
298
|
+
// ============ 时间处理 ============
|
|
299
|
+
generateTablesInRange(baseTable, start, end, granularity, config) {
|
|
300
|
+
const tables = [];
|
|
301
|
+
const current = moment(start);
|
|
302
|
+
const maxTables = 100;
|
|
303
|
+
while (current.toDate() <= end && tables.length < maxTables) {
|
|
304
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(current.toDate(), granularity);
|
|
305
|
+
tables.push(this.formatTableName(baseTable, suffix, config));
|
|
306
|
+
switch (granularity) {
|
|
307
|
+
case 'year':
|
|
308
|
+
current.add(1, 'year');
|
|
309
|
+
break;
|
|
310
|
+
case 'month':
|
|
311
|
+
current.add(1, 'month');
|
|
312
|
+
break;
|
|
313
|
+
case 'day':
|
|
314
|
+
current.add(1, 'day');
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return tables;
|
|
319
|
+
}
|
|
320
|
+
getRecentTables(baseTable, granularity, count, config) {
|
|
321
|
+
const tables = [];
|
|
322
|
+
const current = moment();
|
|
323
|
+
for (let i = 0; i < count; i++) {
|
|
324
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(current.toDate(), granularity);
|
|
325
|
+
tables.unshift(this.formatTableName(baseTable, suffix, config));
|
|
326
|
+
switch (granularity) {
|
|
327
|
+
case 'year':
|
|
328
|
+
current.subtract(1, 'year');
|
|
329
|
+
break;
|
|
330
|
+
case 'month':
|
|
331
|
+
current.subtract(1, 'month');
|
|
332
|
+
break;
|
|
333
|
+
case 'day':
|
|
334
|
+
current.subtract(1, 'day');
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return tables;
|
|
339
|
+
}
|
|
340
|
+
getRecentCountByGranularity(granularity, config) {
|
|
341
|
+
if (config.recentTableCount !== undefined && config.recentTableCount > 0) {
|
|
342
|
+
return config.recentTableCount;
|
|
343
|
+
}
|
|
344
|
+
switch (granularity) {
|
|
345
|
+
case 'year':
|
|
346
|
+
return 3;
|
|
347
|
+
case 'month':
|
|
348
|
+
return 12;
|
|
349
|
+
case 'day':
|
|
350
|
+
return 7;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// ============ 计算相关 ============
|
|
354
|
+
calculateRangeIndex(value, count) {
|
|
355
|
+
if (typeof value === 'number') {
|
|
356
|
+
return Math.abs(value) % count;
|
|
357
|
+
}
|
|
358
|
+
return Math.abs(this.simpleHash(String(value))) % count;
|
|
359
|
+
}
|
|
360
|
+
calculateHash(value, count) {
|
|
361
|
+
return Math.abs(this.simpleHash(String(value))) % count;
|
|
362
|
+
}
|
|
363
|
+
simpleHash(str) {
|
|
364
|
+
let hash = 5381;
|
|
365
|
+
for (let i = 0; i < str.length; i++) {
|
|
366
|
+
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
367
|
+
hash = hash & hash;
|
|
368
|
+
}
|
|
369
|
+
return hash;
|
|
370
|
+
}
|
|
371
|
+
// ============ 格式化 ============
|
|
372
|
+
formatTableName(baseTable, suffix, config) {
|
|
373
|
+
const formattedSuffix = config.suffixFormatter ? config.suffixFormatter(suffix) : suffix;
|
|
374
|
+
return `${baseTable}_${formattedSuffix}`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.ShardingRouter = ShardingRouter;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { CrudPro } from '../../libs/crud-pro/CrudPro';
|
|
2
|
+
import { SqlDbType } from '../../libs/crud-pro/models/keys';
|
|
3
|
+
import { IShardingConfig, IShardingTableCreateOptions, IShardingTableCreateResult } from './ShardingConfig';
|
|
4
|
+
/**
|
|
5
|
+
* 分表自动创建器
|
|
6
|
+
*
|
|
7
|
+
* 根据基表结构自动创建分表,支持 MySQL、PostgreSQL、SQL Server。
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const creator = new ShardingTableCreator(crudPro, config);
|
|
11
|
+
* await creator.createTableIfNeeded('t_order_202403', {
|
|
12
|
+
* sqlDatabase: 'mydb',
|
|
13
|
+
* sqlDbType: SqlDbType.mysql,
|
|
14
|
+
* });
|
|
15
|
+
*/
|
|
16
|
+
export declare class ShardingTableCreator {
|
|
17
|
+
private readonly crudPro;
|
|
18
|
+
private readonly config;
|
|
19
|
+
constructor(crudPro: CrudPro, config: IShardingConfig);
|
|
20
|
+
/**
|
|
21
|
+
* 校验表名格式,防止 SQL 注入
|
|
22
|
+
* 只允许合法的数据库标识符:以字母或下划线开头,后续只能包含字母数字下划线
|
|
23
|
+
*/
|
|
24
|
+
private validateIdentifier;
|
|
25
|
+
/**
|
|
26
|
+
* 创建分表(如果不存在)
|
|
27
|
+
*
|
|
28
|
+
* @param tableName 目标分表名
|
|
29
|
+
* @param baseCfg 基础配置(sqlDatabase, sqlDbType)
|
|
30
|
+
* @param options 创建选项
|
|
31
|
+
* @returns 创建结果
|
|
32
|
+
*/
|
|
33
|
+
createTableIfNeeded(tableName: string, baseCfg: {
|
|
34
|
+
sqlDatabase: string;
|
|
35
|
+
sqlDbType: SqlDbType;
|
|
36
|
+
}, options?: IShardingTableCreateOptions): Promise<IShardingTableCreateResult>;
|
|
37
|
+
/**
|
|
38
|
+
* 获取基表列信息
|
|
39
|
+
*/
|
|
40
|
+
private getBaseTableMeta;
|
|
41
|
+
/**
|
|
42
|
+
* 根据数据库类型加载表列信息
|
|
43
|
+
*/
|
|
44
|
+
private loadTableColumns;
|
|
45
|
+
/**
|
|
46
|
+
* 加载 MySQL 表列信息
|
|
47
|
+
*/
|
|
48
|
+
private loadMySQLColumns;
|
|
49
|
+
/**
|
|
50
|
+
* 加载 PostgreSQL 表列信息
|
|
51
|
+
*/
|
|
52
|
+
private loadPostgreSQLColumns;
|
|
53
|
+
/**
|
|
54
|
+
* 获取 PostgreSQL 表主键列
|
|
55
|
+
*/
|
|
56
|
+
private getPostgreSQLPrimaryKeys;
|
|
57
|
+
/**
|
|
58
|
+
* 加载 SQL Server 表列信息
|
|
59
|
+
*/
|
|
60
|
+
private loadSQLServerColumns;
|
|
61
|
+
/**
|
|
62
|
+
* 获取 SQL Server 表主键列
|
|
63
|
+
*/
|
|
64
|
+
private getSQLServerPrimaryKeys;
|
|
65
|
+
/**
|
|
66
|
+
* 生成建表 SQL
|
|
67
|
+
*/
|
|
68
|
+
private generateCreateTableSql;
|
|
69
|
+
private generateMySQLCreateSql;
|
|
70
|
+
private formatMySQLColumn;
|
|
71
|
+
private generatePostgreSQLCreateSql;
|
|
72
|
+
private formatPostgreSQLColumn;
|
|
73
|
+
private mapToPostgreSQLType;
|
|
74
|
+
private generateSQLServerCreateSql;
|
|
75
|
+
private formatSQLServerColumn;
|
|
76
|
+
private mapToSQLServerType;
|
|
77
|
+
/**
|
|
78
|
+
* 清理 SQL Server 系统目录中的默认值表达式
|
|
79
|
+
*
|
|
80
|
+
* sys.default_constraints.definition 返回格式如 ((1)), ('abc'), (getdate())
|
|
81
|
+
* 需要去除最外层括号,还原为可用的默认值
|
|
82
|
+
*/
|
|
83
|
+
private normalizeSQLServerDefaultValue;
|
|
84
|
+
/**
|
|
85
|
+
* 清理 PostgreSQL 系统目录中的默认值表达式
|
|
86
|
+
*
|
|
87
|
+
* column_default 返回值如:
|
|
88
|
+
* - nextval('xxx_seq'::regclass) → 序列自增,不应作为 DEFAULT
|
|
89
|
+
* - 'hello'::character varying → 带类型后缀的字符串默认值
|
|
90
|
+
* - 1, true, now() 等
|
|
91
|
+
*/
|
|
92
|
+
private normalizePostgreSQLDefaultValue;
|
|
93
|
+
/**
|
|
94
|
+
* 格式化默认值
|
|
95
|
+
*/
|
|
96
|
+
private formatDefaultValue;
|
|
97
|
+
/**
|
|
98
|
+
* 转义字符串
|
|
99
|
+
*/
|
|
100
|
+
private escapeString;
|
|
101
|
+
/**
|
|
102
|
+
* 执行建表 SQL
|
|
103
|
+
*/
|
|
104
|
+
private executeCreateSql;
|
|
105
|
+
/**
|
|
106
|
+
* 复制索引(可选功能)
|
|
107
|
+
*
|
|
108
|
+
* 从基表复制索引到分表。
|
|
109
|
+
* 注意:主键索引已在建表时创建,此处只复制普通索引。
|
|
110
|
+
*/
|
|
111
|
+
private copyIndexes;
|
|
112
|
+
/**
|
|
113
|
+
* 在索引 SQL 中替换表名
|
|
114
|
+
*
|
|
115
|
+
* 支持 MySQL(反引号)、PostgreSQL(双引号)、SQL Server(方括号) 的标识符语法
|
|
116
|
+
*/
|
|
117
|
+
private replaceTableNameInIndexSql;
|
|
118
|
+
/**
|
|
119
|
+
* 获取基表的索引定义
|
|
120
|
+
*
|
|
121
|
+
* 支持 MySQL、PostgreSQL、SQL Server,返回可直接执行的 CREATE INDEX 语句
|
|
122
|
+
*/
|
|
123
|
+
private getIndexDefinitions;
|
|
124
|
+
/**
|
|
125
|
+
* 获取 MySQL 索引定义
|
|
126
|
+
*/
|
|
127
|
+
private getMySQLIndexDefinitions;
|
|
128
|
+
/**
|
|
129
|
+
* 获取 PostgreSQL 索引定义
|
|
130
|
+
*/
|
|
131
|
+
private getPostgreSQLIndexDefinitions;
|
|
132
|
+
/**
|
|
133
|
+
* 获取 SQL Server 索引定义
|
|
134
|
+
*/
|
|
135
|
+
private getSQLServerIndexDefinitions;
|
|
136
|
+
/**
|
|
137
|
+
* 重命名索引(避免不同分表索引名冲突)
|
|
138
|
+
*
|
|
139
|
+
* 支持 MySQL、PostgreSQL、SQL Server 的标识符语法
|
|
140
|
+
*/
|
|
141
|
+
private renameIndex;
|
|
142
|
+
/**
|
|
143
|
+
* 转义正则特殊字符
|
|
144
|
+
*/
|
|
145
|
+
private escapeRegExp;
|
|
146
|
+
}
|