midway-fatcms 0.0.7 → 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/01-quick-start.md +231 -0
- package/.qoder/skills/midway-fatcms/02-crud-quick.md +375 -0
- package/.qoder/skills/midway-fatcms/03-crud-sharding.md +489 -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 +504 -0
- package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
- package/README.md +9 -9
- package/dist/configuration.d.ts +10 -0
- package/dist/configuration.js +26 -0
- 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/helpers.controller.d.ts +6 -0
- package/dist/controller/helpers.controller.js +19 -0
- 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 +51 -3
- package/dist/libs/crud-pro/CrudPro.js +111 -4
- 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 +83 -12
- package/dist/libs/crud-pro/models/CrudResult.d.ts +116 -0
- package/dist/libs/crud-pro/models/CrudResult.js +126 -0
- package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -2
- 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/CrudProExecuteSqlService.js +36 -2
- package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -4
- 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 +382 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.js +689 -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/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 +25 -10
- package/dist/libs/crud-sharding/ShardingConfig.js +5 -5
- package/dist/libs/crud-sharding/ShardingMerger.d.ts +10 -18
- package/dist/libs/crud-sharding/ShardingMerger.js +27 -44
- package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
- package/dist/libs/crud-sharding/ShardingResult.js +16 -0
- 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 +13 -15
- package/dist/libs/crud-sharding/index.js +33 -17
- package/dist/models/RedisKeys.d.ts +1 -0
- package/dist/models/RedisKeys.js +1 -0
- 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/TableMetaCacheRedisSubscriber.d.ts +31 -0
- package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
- package/dist/service/curd/CurdMixService.d.ts +6 -4
- package/dist/service/curd/CurdMixService.js +16 -2
- package/dist/service/curd/CurdProService.d.ts +149 -29
- package/dist/service/curd/CurdProService.js +157 -38
- package/dist/service/flow/FlowConfigService.js +7 -2
- package/dist/service/flow/FlowInstanceCrudService.js +22 -19
- package/package.json +1 -1
- package/src/configuration.ts +27 -0
- package/src/controller/base/BaseApiController.ts +0 -5
- package/src/controller/gateway/DocGatewayController.ts +1 -1
- package/src/controller/helpers.controller.ts +15 -0
- 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 +134 -7
- package/src/libs/crud-pro/exceptions.ts +8 -0
- package/src/libs/crud-pro/interfaces.ts +111 -15
- package/src/libs/crud-pro/models/CrudResult.ts +178 -0
- package/src/libs/crud-pro/models/RequestModel.ts +2 -2
- 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/CrudProExecuteSqlService.ts +41 -2
- package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -3
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
- package/src/libs/crud-pro-quick/CrudProQuick.ts +782 -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/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 +28 -10
- package/src/libs/crud-sharding/ShardingMerger.ts +35 -63
- package/src/libs/crud-sharding/ShardingResult.ts +29 -0
- 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 +30 -16
- package/src/models/RedisKeys.ts +1 -0
- 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/TableMetaCacheRedisSubscriber.ts +105 -0
- package/src/service/crudstd/CrudStdService.ts +2 -2
- package/src/service/curd/CurdMixService.ts +26 -5
- package/src/service/curd/CurdProService.ts +186 -45
- package/src/service/flow/FlowConfigService.ts +7 -2
- package/src/service/flow/FlowInstanceCrudService.ts +23 -20
- package/.qoder/skills/midway-fatcms-crud/SKILL.md +0 -375
- package/.qoder/skills/midway-fatcms-crud/examples.md +0 -990
- package/.qoder/skills/midway-fatcms-crud/reference.md +0 -568
- 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/libs/crud-sharding/ShardingCrudPro.d.ts +0 -363
- package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -675
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -69
- package/dist/libs/crud-sharding/ShardingRouter.js +0 -377
- 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 -1100
- 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/libs/crud-sharding/ShardingCrudPro.ts +0 -835
- package/src/libs/crud-sharding/ShardingRouter.ts +0 -512
- package/src/models/StandardColumns.ts +0 -76
- package/src/service/curd/CrudProQuick.ts +0 -360
- package/src/service/curd/README.md +0 -1100
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShardingByTimeCrud = void 0;
|
|
4
|
+
const moment = require("moment");
|
|
5
|
+
const OrderByUtils_1 = require("../../libs/crud-pro/utils/OrderByUtils");
|
|
6
|
+
const keys_1 = require("../../libs/crud-pro/models/keys");
|
|
7
|
+
const ShardingConfig_1 = require("./ShardingConfig");
|
|
8
|
+
const ShardingBase_1 = require("./ShardingBase");
|
|
9
|
+
const ShardingMerger_1 = require("./ShardingMerger");
|
|
10
|
+
const ShardingCountCache_1 = require("./ShardingCountCache");
|
|
11
|
+
const CrudResult_1 = require("../../libs/crud-pro/models/CrudResult");
|
|
12
|
+
const ShardingResult_1 = require("./ShardingResult");
|
|
13
|
+
const ShardingUtils_1 = require("./ShardingUtils");
|
|
14
|
+
/**
|
|
15
|
+
* 时间分表 CRUD
|
|
16
|
+
*
|
|
17
|
+
* 支持 YEAR / MONTH / DAY 三种时间粒度分表。
|
|
18
|
+
* 路由规则:根据 timeColumn 的值计算时间后缀,定位到对应分表。
|
|
19
|
+
*
|
|
20
|
+
* 特性:
|
|
21
|
+
* - 插入:data 中的 timeColumn 决定目标分表
|
|
22
|
+
* - 更新/删除:condition 中的 timeColumn 定位分表
|
|
23
|
+
* - 查询:时间范围条件交叉过滤候选表,无条件时回退到 recentTableCount
|
|
24
|
+
* - insertOrUpdate:condition 路由查找,data 路由插入(双路由)
|
|
25
|
+
* - 智能清理:timeColumn 在有主键时仅用于路由,不参与 WHERE 条件
|
|
26
|
+
* - COUNT 缓存:历史表缓存,当前表实时查
|
|
27
|
+
* - orderBy 约束:多表查询必须以 timeColumn 排序
|
|
28
|
+
*/
|
|
29
|
+
class ShardingByTimeCrud extends ShardingBase_1.ShardingBase {
|
|
30
|
+
constructor(crudProFactory, config) {
|
|
31
|
+
super(crudProFactory, config);
|
|
32
|
+
// 校验
|
|
33
|
+
if (!config.timeColumn) {
|
|
34
|
+
throw new Error('[ShardingByTimeCrud] 时间分表必须显式指定 timeColumn 字段');
|
|
35
|
+
}
|
|
36
|
+
this.granularity = this.typeToGranularity(config.type);
|
|
37
|
+
this.merger = new ShardingMerger_1.ShardingMerger();
|
|
38
|
+
// 初始化 COUNT 缓存
|
|
39
|
+
if (config.countCache) {
|
|
40
|
+
const countCache = new ShardingCountCache_1.ShardingCountCache(config.type, config.baseTable, config.countCache.ttlSeconds, config.countCache.maxSize);
|
|
41
|
+
this.merger.setCountCache(countCache);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ============ 路由 ============
|
|
45
|
+
typeToGranularity(type) {
|
|
46
|
+
switch (type) {
|
|
47
|
+
case ShardingConfig_1.ShardingType.YEAR: return 'year';
|
|
48
|
+
case ShardingConfig_1.ShardingType.MONTH: return 'month';
|
|
49
|
+
case ShardingConfig_1.ShardingType.DAY: return 'day';
|
|
50
|
+
default: throw new Error(`[ShardingByTimeCrud] 不支持的分表类型: ${type}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** 从 data 中提取时间字段,计算目标分表名 */
|
|
54
|
+
resolveTableForInsert(data) {
|
|
55
|
+
const timeValue = data[this.config.timeColumn];
|
|
56
|
+
if (timeValue === undefined || timeValue === null) {
|
|
57
|
+
throw new Error(`[ShardingByTimeCrud] insert 操作的 data 必须包含时间字段 '${this.config.timeColumn}'`);
|
|
58
|
+
}
|
|
59
|
+
const m = moment(timeValue);
|
|
60
|
+
if (!m.isValid()) {
|
|
61
|
+
throw new Error(`[ShardingByTimeCrud] 无法解析时间值: ${timeValue}`);
|
|
62
|
+
}
|
|
63
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(m.toDate(), this.granularity);
|
|
64
|
+
return this.formatTableName(suffix);
|
|
65
|
+
}
|
|
66
|
+
/** 从 condition 中提取时间字段,计算目标分表名(单表) */
|
|
67
|
+
resolveTableForCondition(condition) {
|
|
68
|
+
const timeColumn = this.config.timeColumn;
|
|
69
|
+
const timeValue = condition[timeColumn];
|
|
70
|
+
if (timeValue === undefined || timeValue === null) {
|
|
71
|
+
throw new Error(`[ShardingByTimeCrud] 写操作的 condition 必须包含时间字段 '${timeColumn}',` +
|
|
72
|
+
`用于路由到正确的分表。`);
|
|
73
|
+
}
|
|
74
|
+
// 如果是操作符表达式($gte/$lte),尝试提取范围的 start
|
|
75
|
+
if ((0, ShardingUtils_1.isOperatorExpression)(timeValue)) {
|
|
76
|
+
const range = this.extractTimeRange(condition);
|
|
77
|
+
if (range) {
|
|
78
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(range.start, this.granularity);
|
|
79
|
+
return this.formatTableName(suffix);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const m = moment(timeValue);
|
|
83
|
+
if (!m.isValid()) {
|
|
84
|
+
throw new Error(`[ShardingByTimeCrud] 无法解析时间值: ${timeValue}`);
|
|
85
|
+
}
|
|
86
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(m.toDate(), this.granularity);
|
|
87
|
+
return this.formatTableName(suffix);
|
|
88
|
+
}
|
|
89
|
+
/** 解析查询目标表列表 */
|
|
90
|
+
async resolveFindTables(reqJson) {
|
|
91
|
+
const existingTables = await this.getExistingTablesSet();
|
|
92
|
+
const baseTablePrefix = `${this.config.baseTable}_`;
|
|
93
|
+
const shardingTables = Array.from(existingTables).filter(t => t.startsWith(baseTablePrefix));
|
|
94
|
+
if (shardingTables.length === 0)
|
|
95
|
+
return [];
|
|
96
|
+
shardingTables.sort();
|
|
97
|
+
const condition = reqJson.condition;
|
|
98
|
+
// 有 recentTableCount:取最近N张,再与条件取交集
|
|
99
|
+
if (this.config.recentTableCount !== undefined) {
|
|
100
|
+
const recentTables = shardingTables.slice(-this.config.recentTableCount);
|
|
101
|
+
return this.intersectWithCondition(recentTables, condition);
|
|
102
|
+
}
|
|
103
|
+
// 无 recentTableCount:所有表与条件取交集,最多取默认数量
|
|
104
|
+
const intersected = this.intersectWithCondition(shardingTables, condition);
|
|
105
|
+
const defaultCount = this.getDefaultRecentCount();
|
|
106
|
+
return intersected.slice(0, defaultCount);
|
|
107
|
+
}
|
|
108
|
+
intersectWithCondition(tables, condition) {
|
|
109
|
+
if (!condition)
|
|
110
|
+
return this.sortTablesByTimeDesc(tables);
|
|
111
|
+
const timeRange = this.extractTimeRange(condition);
|
|
112
|
+
if (timeRange) {
|
|
113
|
+
return this.intersectTablesByTimeRange(tables, timeRange);
|
|
114
|
+
}
|
|
115
|
+
const singleTimeValue = this.tryExtractTimeFromCondition(condition);
|
|
116
|
+
if (singleTimeValue) {
|
|
117
|
+
const suffix = (0, ShardingUtils_1.getTimeSuffix)(singleTimeValue, this.granularity);
|
|
118
|
+
const targetTable = this.formatTableName(suffix);
|
|
119
|
+
return tables.includes(targetTable) ? [targetTable] : [];
|
|
120
|
+
}
|
|
121
|
+
// condition 存在但不包含时间字段,返回所有表(降序)
|
|
122
|
+
return this.sortTablesByTimeDesc(tables);
|
|
123
|
+
}
|
|
124
|
+
intersectTablesByTimeRange(tables, timeRange) {
|
|
125
|
+
const startSuffix = (0, ShardingUtils_1.getTimeSuffix)(timeRange.start, this.granularity);
|
|
126
|
+
const endSuffix = (0, ShardingUtils_1.getTimeSuffix)(timeRange.end, this.granularity);
|
|
127
|
+
const prefix = `${this.config.baseTable}_`;
|
|
128
|
+
const prefixLen = prefix.length;
|
|
129
|
+
return this.sortTablesByTimeDesc(tables.filter(table => {
|
|
130
|
+
const suffix = table.substring(prefixLen);
|
|
131
|
+
return suffix >= startSuffix && suffix <= endSuffix;
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
extractTimeRange(condition) {
|
|
135
|
+
var _a, _b;
|
|
136
|
+
const colCond = condition[this.config.timeColumn];
|
|
137
|
+
if (!colCond || typeof colCond !== 'object')
|
|
138
|
+
return null;
|
|
139
|
+
const start = (_a = colCond.$gte) !== null && _a !== void 0 ? _a : colCond.$gt;
|
|
140
|
+
const end = (_b = colCond.$lte) !== null && _b !== void 0 ? _b : colCond.$lt;
|
|
141
|
+
if (start && end) {
|
|
142
|
+
const sm = moment(start), em = moment(end);
|
|
143
|
+
if (!sm.isValid() || !em.isValid())
|
|
144
|
+
return null;
|
|
145
|
+
return { start: sm.toDate(), end: em.toDate() };
|
|
146
|
+
}
|
|
147
|
+
if (start) {
|
|
148
|
+
const sm = moment(start);
|
|
149
|
+
return sm.isValid() ? { start: sm.toDate(), end: new Date() } : null;
|
|
150
|
+
}
|
|
151
|
+
if (end) {
|
|
152
|
+
const em = moment(end);
|
|
153
|
+
return em.isValid() ? { start: new Date(0), end: em.toDate() } : null;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
tryExtractTimeFromCondition(condition) {
|
|
158
|
+
const value = condition[this.config.timeColumn];
|
|
159
|
+
if (value === undefined || value === null)
|
|
160
|
+
return null;
|
|
161
|
+
const m = moment(value);
|
|
162
|
+
return m.isValid() ? m.toDate() : null;
|
|
163
|
+
}
|
|
164
|
+
sortTablesByTimeDesc(tables) {
|
|
165
|
+
return [...tables].sort((a, b) => b.localeCompare(a));
|
|
166
|
+
}
|
|
167
|
+
getDefaultRecentCount() {
|
|
168
|
+
if (this.config.recentTableCount !== undefined && this.config.recentTableCount > 0) {
|
|
169
|
+
return this.config.recentTableCount;
|
|
170
|
+
}
|
|
171
|
+
switch (this.granularity) {
|
|
172
|
+
case 'year': return 2;
|
|
173
|
+
case 'month': return 3;
|
|
174
|
+
case 'day': return 7;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
formatTableName(suffix) {
|
|
178
|
+
const formattedSuffix = this.config.suffixFormatter ? this.config.suffixFormatter(suffix) : suffix;
|
|
179
|
+
return `${this.config.baseTable}_${formattedSuffix}`;
|
|
180
|
+
}
|
|
181
|
+
// ============ 写操作 ============
|
|
182
|
+
async insert(reqJson) {
|
|
183
|
+
this.validateTimeColumnForData(reqJson, 'insert');
|
|
184
|
+
const targetTable = this.resolveTableForInsert(reqJson.data);
|
|
185
|
+
await this.createShardingTableIfNeeded(targetTable);
|
|
186
|
+
const ctx = await this.executeOnTable(targetTable, reqJson, keys_1.KeysOfSimpleSQL.SIMPLE_INSERT);
|
|
187
|
+
const affected = ctx.getResModel().affected || { affectedRows: 0 };
|
|
188
|
+
return new CrudResult_1.CrudWriteResult({ affectedRows: affected.affectedRows, insertId: affected.insertId, rawContext: ctx });
|
|
189
|
+
}
|
|
190
|
+
async batchInsert(reqJson) {
|
|
191
|
+
var _a, _b;
|
|
192
|
+
const dataArray = reqJson.data;
|
|
193
|
+
if (!Array.isArray(dataArray) || dataArray.length === 0) {
|
|
194
|
+
throw new Error('[ShardingByTimeCrud] batchInsert requires non-empty data array');
|
|
195
|
+
}
|
|
196
|
+
this.validateTimeColumnForBatchData(dataArray, 'batchInsert');
|
|
197
|
+
// 按分表分组
|
|
198
|
+
const groupedData = new Map();
|
|
199
|
+
for (const item of dataArray) {
|
|
200
|
+
const table = this.resolveTableForInsert(item);
|
|
201
|
+
if (!groupedData.has(table))
|
|
202
|
+
groupedData.set(table, []);
|
|
203
|
+
groupedData.get(table).push(item);
|
|
204
|
+
}
|
|
205
|
+
// 确保所有目标分表存在
|
|
206
|
+
for (const tableName of groupedData.keys()) {
|
|
207
|
+
await this.createShardingTableIfNeeded(tableName);
|
|
208
|
+
}
|
|
209
|
+
// 串行执行各分表的批量插入
|
|
210
|
+
const results = [];
|
|
211
|
+
let lastContext = null;
|
|
212
|
+
for (const [table, items] of groupedData.entries()) {
|
|
213
|
+
try {
|
|
214
|
+
const ctx = await this.executeOnTable(table, { data: items }, keys_1.KeysOfSimpleSQL.SIMPLE_BATCH_INSERT);
|
|
215
|
+
lastContext = ctx;
|
|
216
|
+
results.push({ table, affected: ((_a = ctx.getResModelItem('affected')) === null || _a === void 0 ? void 0 : _a.affectedRows) || items.length, rowCount: items.length, error: null, context: ctx });
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
results.push({ table, affected: 0, rowCount: items.length, error: e, context: null });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const tableResults = results.map(r => ({ table: r.table, affected: r.affected, rowCount: r.rowCount }));
|
|
223
|
+
const errors = results.filter(r => r.error !== null).map(r => ({ table: r.table, error: r.error }));
|
|
224
|
+
const totalAffected = results.reduce((sum, r) => sum + r.affected, 0);
|
|
225
|
+
const finalContext = lastContext || ((_b = results.find(r => r.context)) === null || _b === void 0 ? void 0 : _b.context);
|
|
226
|
+
if (!finalContext)
|
|
227
|
+
throw new Error('[ShardingByTimeCrud] batchInsert failed - no successful inserts');
|
|
228
|
+
return new ShardingResult_1.ShardingBatchInsertResult({ totalAffected, tableResults, tableCount: groupedData.size, success: errors.length === 0, errors, lastContext: finalContext });
|
|
229
|
+
}
|
|
230
|
+
async update(reqJson) {
|
|
231
|
+
this.validateRoutingFieldForCondition(reqJson, 'update');
|
|
232
|
+
const targetTable = this.resolveTableForCondition(reqJson.condition);
|
|
233
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
234
|
+
const ctx = await this.executeOnTable(targetTable, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE);
|
|
235
|
+
const affected = ctx.getResModel().affected || { affectedRows: 0 };
|
|
236
|
+
return new CrudResult_1.CrudWriteResult({ affectedRows: affected.affectedRows, insertId: affected.insertId, rawContext: ctx });
|
|
237
|
+
}
|
|
238
|
+
async delete(reqJson) {
|
|
239
|
+
this.validateRoutingFieldForCondition(reqJson, 'delete');
|
|
240
|
+
const targetTable = this.resolveTableForCondition(reqJson.condition);
|
|
241
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
242
|
+
const ctx = await this.executeOnTable(targetTable, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_DELETE);
|
|
243
|
+
const affected = ctx.getResModel().affected || { affectedRows: 0 };
|
|
244
|
+
return new CrudResult_1.CrudWriteResult({ affectedRows: affected.affectedRows, insertId: affected.insertId, rawContext: ctx });
|
|
245
|
+
}
|
|
246
|
+
async restore(reqJson) {
|
|
247
|
+
this.validateRoutingFieldForCondition(reqJson, 'restore');
|
|
248
|
+
const targetTable = this.resolveTableForCondition(reqJson.condition);
|
|
249
|
+
if (!reqJson.condition || Object.keys(reqJson.condition).length === 0) {
|
|
250
|
+
throw new Error('[ShardingByTimeCrud] restore 操作必须指定恢复条件');
|
|
251
|
+
}
|
|
252
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
253
|
+
const restoreReqJson = {
|
|
254
|
+
...cleanedReqJson,
|
|
255
|
+
data: { ...cleanedReqJson.data, deleted_at: 0, deleted_by: '' },
|
|
256
|
+
};
|
|
257
|
+
const ctx = await this.executeOnTable(targetTable, restoreReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE);
|
|
258
|
+
const affected = ctx.getResModel().affected || { affectedRows: 0 };
|
|
259
|
+
return new CrudResult_1.CrudWriteResult({ affectedRows: affected.affectedRows, insertId: affected.insertId, rawContext: ctx });
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 插入或更新(双路由)
|
|
263
|
+
*
|
|
264
|
+
* 1. condition 路由定位查找分表
|
|
265
|
+
* 2. 检查记录是否存在
|
|
266
|
+
* 3. 存在 → UPDATE(conditionTable)
|
|
267
|
+
* 4. 不存在 → data 路由定位插入分表,INSERT(dataTable)
|
|
268
|
+
*/
|
|
269
|
+
async insertOrUpdate(reqJson) {
|
|
270
|
+
this.validateTimeColumnForData(reqJson, 'insertOrUpdate');
|
|
271
|
+
this.validateRoutingFieldForCondition(reqJson, 'insertOrUpdate');
|
|
272
|
+
const conditionTable = this.resolveTableForCondition(reqJson.condition);
|
|
273
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
274
|
+
const existCtx = await this.executeOnTable(conditionTable, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_EXIST);
|
|
275
|
+
const isExist = existCtx.getResModelItem('is_exist') === true;
|
|
276
|
+
if (isExist) {
|
|
277
|
+
const updateCtx = await this.executeOnTable(conditionTable, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE);
|
|
278
|
+
const updateAffected = updateCtx.getResModel().affected;
|
|
279
|
+
return new CrudResult_1.CrudUpsertResult({ insertAffected: undefined, updateAffected, isExist: true, rawContext: updateCtx });
|
|
280
|
+
}
|
|
281
|
+
const dataTable = this.resolveTableForInsert(reqJson.data);
|
|
282
|
+
await this.createShardingTableIfNeeded(dataTable);
|
|
283
|
+
const insertCtx = await this.executeOnTable(dataTable, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_INSERT);
|
|
284
|
+
const insertAffected = insertCtx.getResModel().affected;
|
|
285
|
+
return new CrudResult_1.CrudUpsertResult({ insertAffected, updateAffected: undefined, isExist: false, rawContext: insertCtx });
|
|
286
|
+
}
|
|
287
|
+
// ============ 查询操作 ============
|
|
288
|
+
async findOne(reqJson) {
|
|
289
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
290
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
291
|
+
if (targetTables.length === 0) {
|
|
292
|
+
throw new Error('[ShardingByTimeCrud] findOne: no matching tables found');
|
|
293
|
+
}
|
|
294
|
+
let lastCtx = null;
|
|
295
|
+
let lastTable;
|
|
296
|
+
for (const table of targetTables) {
|
|
297
|
+
try {
|
|
298
|
+
const ctx = await this.executeOnTable(table, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_ONE);
|
|
299
|
+
lastCtx = ctx;
|
|
300
|
+
lastTable = table;
|
|
301
|
+
const row = ctx.getOneObj();
|
|
302
|
+
if (row) {
|
|
303
|
+
return new CrudResult_1.CrudQueryOneResult({ row: row, rawContext: ctx, debugInfo: this.buildDebugInfo(table, reqJson.condition), fromTable: table });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
// 表不存在时跳过
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return new CrudResult_1.CrudQueryOneResult({ row: null, rawContext: lastCtx, debugInfo: this.buildDebugInfo(targetTables[0], reqJson.condition), fromTable: lastTable });
|
|
311
|
+
}
|
|
312
|
+
async findUniqueOne(reqJson) {
|
|
313
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
314
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
315
|
+
if (targetTables.length === 0) {
|
|
316
|
+
return new CrudResult_1.CrudQueryOneResult({ row: null, rawContext: null, debugInfo: this.buildDebugInfo(undefined, reqJson.condition), fromTable: null });
|
|
317
|
+
}
|
|
318
|
+
const allRows = [];
|
|
319
|
+
const foundTables = [];
|
|
320
|
+
let firstCtx = null;
|
|
321
|
+
for (const table of targetTables) {
|
|
322
|
+
try {
|
|
323
|
+
const ctx = await this.executeOnTable(table, cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY, { maxLimit: 2 });
|
|
324
|
+
if (!firstCtx)
|
|
325
|
+
firstCtx = ctx;
|
|
326
|
+
const rows = ctx.getResRows();
|
|
327
|
+
if (rows.length > 0) {
|
|
328
|
+
allRows.push(...rows);
|
|
329
|
+
foundTables.push(table);
|
|
330
|
+
if (allRows.length > 1) {
|
|
331
|
+
throw new Error(this.formatUniqueError(allRows.length, foundTables, reqJson.condition, foundTables.length > 1));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
if (e instanceof Error && e.message.startsWith('[ShardingBase] findUniqueOne'))
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const fromTable = foundTables[0] || null;
|
|
341
|
+
return new CrudResult_1.CrudQueryOneResult({
|
|
342
|
+
row: allRows[0] || null, rawContext: firstCtx,
|
|
343
|
+
debugInfo: this.buildDebugInfo(fromTable || targetTables[0], reqJson.condition),
|
|
344
|
+
fromTable: fromTable || targetTables[0],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async findList(reqJson) {
|
|
348
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
349
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
350
|
+
if (targetTables.length === 0) {
|
|
351
|
+
return new CrudResult_1.CrudQueryListResult({ rows: [], rawContext: null });
|
|
352
|
+
}
|
|
353
|
+
if (targetTables.length === 1) {
|
|
354
|
+
const ctx = await this.executeOnTable(targetTables[0], cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY);
|
|
355
|
+
return new CrudResult_1.CrudQueryListResult({ rows: ctx.getResRows(), rawContext: ctx });
|
|
356
|
+
}
|
|
357
|
+
this.validateFindOrderBy(reqJson);
|
|
358
|
+
const tablesForMerge = this.sortTablesForOrderBy(targetTables, reqJson);
|
|
359
|
+
const mergeResult = await this.merger.mergeQuery(this.crudProFactory(), tablesForMerge, cleanedReqJson, this.buildCfg(keys_1.KeysOfSimpleSQL.SIMPLE_QUERY));
|
|
360
|
+
return new CrudResult_1.CrudQueryListResult({ rows: mergeResult.rows, rawContext: mergeResult.lastCtx });
|
|
361
|
+
}
|
|
362
|
+
async findPage(reqJson) {
|
|
363
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
364
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
365
|
+
if (targetTables.length === 0) {
|
|
366
|
+
throw new Error('[ShardingByTimeCrud] findPage: no matching tables found');
|
|
367
|
+
}
|
|
368
|
+
if (targetTables.length === 1) {
|
|
369
|
+
const ctx = await this.executeOnTable(targetTables[0], cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_PAGE);
|
|
370
|
+
const pageResult = ctx.getResModelForQueryPage();
|
|
371
|
+
return new CrudResult_1.CrudQueryPageResult({ rows: pageResult.rows, totalCount: pageResult.total_count, rawContext: ctx });
|
|
372
|
+
}
|
|
373
|
+
this.validateFindOrderBy(reqJson);
|
|
374
|
+
const tablesForMerge = this.sortTablesForOrderBy(targetTables, reqJson);
|
|
375
|
+
const pageResult = await this.merger.mergePageQuery(this.crudProFactory(), tablesForMerge, cleanedReqJson, this.buildCfg(keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_PAGE));
|
|
376
|
+
return new CrudResult_1.CrudQueryPageResult({ rows: pageResult.rows, totalCount: pageResult.total_count, rawContext: pageResult.lastCtx });
|
|
377
|
+
}
|
|
378
|
+
async findCount(reqJson) {
|
|
379
|
+
var _a;
|
|
380
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
381
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
382
|
+
if (targetTables.length === 0) {
|
|
383
|
+
throw new Error('[ShardingByTimeCrud] findCount: no matching tables found');
|
|
384
|
+
}
|
|
385
|
+
if (targetTables.length === 1) {
|
|
386
|
+
const ctx = await this.executeOnTable(targetTables[0], cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_COUNT);
|
|
387
|
+
return new CrudResult_1.CrudCountResult({ count: ctx.getResModelItem('total_count') || 0, rawContext: ctx });
|
|
388
|
+
}
|
|
389
|
+
const countResults = [];
|
|
390
|
+
for (const table of targetTables) {
|
|
391
|
+
countResults.push(await this.findCountFromTable(table, cleanedReqJson));
|
|
392
|
+
}
|
|
393
|
+
const totalCount = countResults.reduce((sum, r) => sum + r.count, 0);
|
|
394
|
+
const firstCtx = ((_a = countResults.find(r => r.ctx !== null)) === null || _a === void 0 ? void 0 : _a.ctx) || null;
|
|
395
|
+
return new CrudResult_1.CrudCountResult({ count: totalCount, rawContext: firstCtx });
|
|
396
|
+
}
|
|
397
|
+
async isExist(reqJson) {
|
|
398
|
+
const cleanedReqJson = this.cleanTimeColumnForSingleTableQuery(reqJson);
|
|
399
|
+
const targetTables = await this.resolveFindTables(reqJson);
|
|
400
|
+
if (targetTables.length === 0) {
|
|
401
|
+
throw new Error('[ShardingByTimeCrud] isExist: no matching tables found');
|
|
402
|
+
}
|
|
403
|
+
if (targetTables.length === 1) {
|
|
404
|
+
const ctx = await this.executeOnTable(targetTables[0], cleanedReqJson, keys_1.KeysOfSimpleSQL.SIMPLE_QUERY_EXIST);
|
|
405
|
+
return new CrudResult_1.CrudExistResult({ exists: ctx.getResModelItem('is_exist') === true, rawContext: ctx });
|
|
406
|
+
}
|
|
407
|
+
let exists = false;
|
|
408
|
+
let firstCtx = null;
|
|
409
|
+
for (const table of targetTables) {
|
|
410
|
+
const result = await this.isExistInTable(table, cleanedReqJson);
|
|
411
|
+
if (!firstCtx && result.ctx)
|
|
412
|
+
firstCtx = result.ctx;
|
|
413
|
+
if (result.exists) {
|
|
414
|
+
exists = true;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return new CrudResult_1.CrudExistResult({ exists, rawContext: firstCtx });
|
|
419
|
+
}
|
|
420
|
+
// ============ 校验 ============
|
|
421
|
+
validateTimeColumnForData(reqJson, operation) {
|
|
422
|
+
const timeColumn = this.config.timeColumn;
|
|
423
|
+
const data = reqJson.data;
|
|
424
|
+
if (!data || data[timeColumn] === undefined) {
|
|
425
|
+
throw new Error(`[ShardingByTimeCrud] ${operation} 操作的 data 必须包含时间字段 '${timeColumn}'`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
validateTimeColumnForBatchData(dataArray, operation) {
|
|
429
|
+
const timeColumn = this.config.timeColumn;
|
|
430
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
431
|
+
if (!dataArray[i] || dataArray[i][timeColumn] === undefined) {
|
|
432
|
+
throw new Error(`[ShardingByTimeCrud] ${operation} 操作的 data[${i}] 必须包含时间字段 '${timeColumn}'`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
validateRoutingFieldForCondition(reqJson, operation) {
|
|
437
|
+
const timeColumn = this.config.timeColumn;
|
|
438
|
+
const condition = reqJson.condition;
|
|
439
|
+
if (!condition || !condition[timeColumn]) {
|
|
440
|
+
throw new Error(`[ShardingByTimeCrud] ${operation} 操作的 condition 必须包含时间字段 '${timeColumn}',` +
|
|
441
|
+
`用于路由到正确的分表。`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
validateFindOrderBy(reqJson) {
|
|
445
|
+
const timeColumn = this.config.timeColumn;
|
|
446
|
+
const orderBy = reqJson.orderBy;
|
|
447
|
+
if (!orderBy) {
|
|
448
|
+
throw new Error(`[ShardingByTimeCrud] 多表查询必须传 orderBy 参数。期望: '${timeColumn} DESC' 或 '${timeColumn} ASC'`);
|
|
449
|
+
}
|
|
450
|
+
const firstOrderBy = OrderByUtils_1.OrderByUtils.getFirstOrderBy(orderBy);
|
|
451
|
+
if (!firstOrderBy) {
|
|
452
|
+
throw new Error(`[ShardingByTimeCrud] orderBy 参数格式错误,无法解析`);
|
|
453
|
+
}
|
|
454
|
+
if (firstOrderBy.fieldName !== timeColumn) {
|
|
455
|
+
throw new Error(`[ShardingByTimeCrud] orderBy 首个排序字段必须为 '${timeColumn}',当前: '${firstOrderBy.fieldName}'`);
|
|
456
|
+
}
|
|
457
|
+
const orderType = firstOrderBy.orderType.toUpperCase();
|
|
458
|
+
if (orderType !== 'ASC' && orderType !== 'DESC') {
|
|
459
|
+
throw new Error(`[ShardingByTimeCrud] orderBy 排序方向必须是 'ASC' 或 'DESC',当前: '${firstOrderBy.orderType}'`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
sortTablesForOrderBy(tables, reqJson) {
|
|
463
|
+
const isAsc = OrderByUtils_1.OrderByUtils.isFirstOrderByAsc(reqJson.orderBy);
|
|
464
|
+
return [...tables].sort((a, b) => isAsc ? a.localeCompare(b) : b.localeCompare(a));
|
|
465
|
+
}
|
|
466
|
+
// ============ 时间字段智能清理(详见 TIME_COLUMN_CLEAN_SPEC.md) ============
|
|
467
|
+
cleanTimeColumnForSingleTableQuery(reqJson) {
|
|
468
|
+
const { primaryKey, timeColumn, type } = this.config;
|
|
469
|
+
if (!timeColumn || ![ShardingConfig_1.ShardingType.YEAR, ShardingConfig_1.ShardingType.MONTH, ShardingConfig_1.ShardingType.DAY].includes(type))
|
|
470
|
+
return reqJson;
|
|
471
|
+
const condition = reqJson.condition;
|
|
472
|
+
if (!condition || typeof condition !== 'object')
|
|
473
|
+
return reqJson;
|
|
474
|
+
const timeValue = condition[timeColumn];
|
|
475
|
+
if (timeValue === undefined)
|
|
476
|
+
return reqJson;
|
|
477
|
+
// 操作符表达式 → 保留,但校验精度
|
|
478
|
+
if ((0, ShardingUtils_1.isOperatorExpression)(timeValue)) {
|
|
479
|
+
this.validateTimeOperatorPrecision(timeValue, timeColumn);
|
|
480
|
+
return reqJson;
|
|
481
|
+
}
|
|
482
|
+
const hasPrimaryKey = primaryKey && condition[primaryKey] !== undefined;
|
|
483
|
+
if (typeof timeValue === 'string') {
|
|
484
|
+
const range = (0, ShardingUtils_1.expandDateToRange)(timeValue);
|
|
485
|
+
if (hasPrimaryKey) {
|
|
486
|
+
const { [timeColumn]: _, ...cleanedCondition } = condition;
|
|
487
|
+
return { ...reqJson, condition: cleanedCondition };
|
|
488
|
+
}
|
|
489
|
+
if (range) {
|
|
490
|
+
return { ...reqJson, condition: { ...condition, [timeColumn]: range } };
|
|
491
|
+
}
|
|
492
|
+
return reqJson;
|
|
493
|
+
}
|
|
494
|
+
if (hasPrimaryKey) {
|
|
495
|
+
const { [timeColumn]: _, ...cleanedCondition } = condition;
|
|
496
|
+
return { ...reqJson, condition: cleanedCondition };
|
|
497
|
+
}
|
|
498
|
+
return reqJson;
|
|
499
|
+
}
|
|
500
|
+
validateTimeOperatorPrecision(operatorValue, timeColumn) {
|
|
501
|
+
const PRECISION_OPERATORS = ['$gte', '$lte', '$gt', '$lt'];
|
|
502
|
+
const dateOnlyPattern = /^\d{4}-\d{1,2}-\d{1,2}$/;
|
|
503
|
+
for (const op of PRECISION_OPERATORS) {
|
|
504
|
+
const value = operatorValue[op];
|
|
505
|
+
if (value !== undefined && typeof value === 'string' && dateOnlyPattern.test(value)) {
|
|
506
|
+
throw new Error(`[ShardingByTimeCrud] 时间字段 '${timeColumn}' 的 ${op} 值必须精确到秒,` +
|
|
507
|
+
`当前值: '${value}'。` +
|
|
508
|
+
`请使用 '${value} 23:59:59'($lte/$lt)或 '${value} 00:00:00'($gte/$gt),` +
|
|
509
|
+
`或直接传粒度字符串由系统自动转换范围。`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const rangeValue = operatorValue.$range;
|
|
513
|
+
if (Array.isArray(rangeValue) && rangeValue.length >= 2) {
|
|
514
|
+
for (let i = 0; i < rangeValue.length; i++) {
|
|
515
|
+
const val = rangeValue[i];
|
|
516
|
+
if (typeof val === 'string' && dateOnlyPattern.test(val)) {
|
|
517
|
+
throw new Error(`[ShardingByTimeCrud] 时间字段 '${timeColumn}' 的 $range[${i}] 值必须精确到秒,` +
|
|
518
|
+
`当前值: '${val}'。请使用 '${val} ${i === 0 ? '00:00:00' : '23:59:59'}'`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
exports.ShardingByTimeCrud = ShardingByTimeCrud;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { CrudPro } from '../../libs/crud-pro/CrudPro';
|
|
2
|
+
import { KeysOfSimpleSQL } from '../crud-pro/models/keys';
|
|
3
|
+
/**
|
|
4
|
+
* CrudPro 工厂函数类型
|
|
5
|
+
* 每次调用返回新的 CrudPro 实例,避免状态污染
|
|
6
|
+
*/
|
|
7
|
+
export declare type CrudProFactory = () => CrudPro;
|
|
1
8
|
/**
|
|
2
9
|
* 分表配置模型
|
|
3
10
|
*
|
|
@@ -5,8 +12,8 @@
|
|
|
5
12
|
* - YEAR: 按年分表,如 order_2024, order_2025
|
|
6
13
|
* - MONTH: 按月分表,如 order_202401, order_202402
|
|
7
14
|
* - DAY: 按日分表,如 order_20240101
|
|
8
|
-
* - RANGE: 按范围分表,如 user_0 ~ user_99
|
|
9
15
|
* - HASH: 按哈希分表,如 order_01 ~ order_16
|
|
16
|
+
* - KEY: 按键值分表,字段原值=表后缀,如 order_east, order_west
|
|
10
17
|
* - CUSTOM: 自定义分表规则
|
|
11
18
|
*/
|
|
12
19
|
/**
|
|
@@ -19,10 +26,10 @@ export declare enum ShardingType {
|
|
|
19
26
|
MONTH = "month",
|
|
20
27
|
/** 按日分表:table_20240101 */
|
|
21
28
|
DAY = "day",
|
|
22
|
-
/** 按范围分表:table_0 ~ table_99 */
|
|
23
|
-
RANGE = "range",
|
|
24
29
|
/** 按哈希分表:table_01 ~ table_16 */
|
|
25
30
|
HASH = "hash",
|
|
31
|
+
/** 按键值分表:字段原值=表后缀,如 table_east, table_west */
|
|
32
|
+
KEY = "key",
|
|
26
33
|
/** 自定义分表规则 */
|
|
27
34
|
CUSTOM = "custom"
|
|
28
35
|
}
|
|
@@ -41,14 +48,15 @@ export interface IShardingConfig {
|
|
|
41
48
|
baseTable: string;
|
|
42
49
|
/**
|
|
43
50
|
* 分表数量
|
|
44
|
-
* 用于
|
|
45
|
-
* 默认值:RANGE 为 10,HASH 为 16
|
|
51
|
+
* 用于 HASH 类型,指定哈希取模的分表总数
|
|
46
52
|
*/
|
|
47
53
|
tableCount?: number;
|
|
48
54
|
/**
|
|
49
55
|
* 分表字段
|
|
50
|
-
* 用于
|
|
51
|
-
*
|
|
56
|
+
* 用于 HASH 和 KEY 类型,根据字段值计算分表
|
|
57
|
+
* - HASH: hash(shardingColumn) % tableCount
|
|
58
|
+
* - KEY: 字段原值直接作为表后缀
|
|
59
|
+
* 例如:user_id, region
|
|
52
60
|
*/
|
|
53
61
|
shardingColumn?: string;
|
|
54
62
|
/**
|
|
@@ -56,6 +64,13 @@ export interface IShardingConfig {
|
|
|
56
64
|
* 用于时间分表类型(YEAR/MONTH/DAY),使用时间分表时必填
|
|
57
65
|
*/
|
|
58
66
|
timeColumn?: string;
|
|
67
|
+
/**
|
|
68
|
+
* 主键字段
|
|
69
|
+
* 用于时间分表场景下,当 condition 中包含主键时,时间字段仅用于路由定位分表,
|
|
70
|
+
* 不参与具体表的查询条件(避免时间精度损失导致查询失败)
|
|
71
|
+
* 例如:id, order_id
|
|
72
|
+
*/
|
|
73
|
+
primaryKey?: string;
|
|
59
74
|
/**
|
|
60
75
|
* 自定义分表路由函数
|
|
61
76
|
* 用于 CUSTOM 类型
|
|
@@ -77,8 +92,8 @@ export interface IShardingConfig {
|
|
|
77
92
|
* 适用于时间分表类型(YEAR/MONTH/DAY)。
|
|
78
93
|
*
|
|
79
94
|
* 默认值:
|
|
80
|
-
* - YEAR:
|
|
81
|
-
* - MONTH:
|
|
95
|
+
* - YEAR: 2(最近2年)
|
|
96
|
+
* - MONTH: 3(最近3个月)
|
|
82
97
|
* - DAY: 7(最近7天)
|
|
83
98
|
*
|
|
84
99
|
* @example
|
|
@@ -207,7 +222,7 @@ export interface IShardingRouterContext {
|
|
|
207
222
|
* SQL 简单名称
|
|
208
223
|
* 用于判断具体的查询类型
|
|
209
224
|
*/
|
|
210
|
-
sqlSimpleName
|
|
225
|
+
sqlSimpleName: KeysOfSimpleSQL;
|
|
211
226
|
}
|
|
212
227
|
/**
|
|
213
228
|
* 时间范围
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShardingType = void 0;
|
|
2
4
|
/**
|
|
3
5
|
* 分表配置模型
|
|
4
6
|
*
|
|
@@ -6,12 +8,10 @@
|
|
|
6
8
|
* - YEAR: 按年分表,如 order_2024, order_2025
|
|
7
9
|
* - MONTH: 按月分表,如 order_202401, order_202402
|
|
8
10
|
* - DAY: 按日分表,如 order_20240101
|
|
9
|
-
* - RANGE: 按范围分表,如 user_0 ~ user_99
|
|
10
11
|
* - HASH: 按哈希分表,如 order_01 ~ order_16
|
|
12
|
+
* - KEY: 按键值分表,字段原值=表后缀,如 order_east, order_west
|
|
11
13
|
* - CUSTOM: 自定义分表规则
|
|
12
14
|
*/
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.ShardingType = void 0;
|
|
15
15
|
/**
|
|
16
16
|
* 分表类型
|
|
17
17
|
*/
|
|
@@ -23,10 +23,10 @@ var ShardingType;
|
|
|
23
23
|
ShardingType["MONTH"] = "month";
|
|
24
24
|
/** 按日分表:table_20240101 */
|
|
25
25
|
ShardingType["DAY"] = "day";
|
|
26
|
-
/** 按范围分表:table_0 ~ table_99 */
|
|
27
|
-
ShardingType["RANGE"] = "range";
|
|
28
26
|
/** 按哈希分表:table_01 ~ table_16 */
|
|
29
27
|
ShardingType["HASH"] = "hash";
|
|
28
|
+
/** 按键值分表:字段原值=表后缀,如 table_east, table_west */
|
|
29
|
+
ShardingType["KEY"] = "key";
|
|
30
30
|
/** 自定义分表规则 */
|
|
31
31
|
ShardingType["CUSTOM"] = "custom";
|
|
32
32
|
})(ShardingType = exports.ShardingType || (exports.ShardingType = {}));
|