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.
Files changed (159) hide show
  1. package/.qoder/skills/midway-fatcms/01-quick-start.md +231 -0
  2. package/.qoder/skills/midway-fatcms/02-crud-quick.md +375 -0
  3. package/.qoder/skills/midway-fatcms/03-crud-sharding.md +489 -0
  4. package/.qoder/skills/midway-fatcms/04-condition-operators.md +93 -0
  5. package/.qoder/skills/midway-fatcms/05-configuration.md +290 -0
  6. package/.qoder/skills/midway-fatcms/06-builtin-functions.md +241 -0
  7. package/.qoder/skills/midway-fatcms/07-examples.md +504 -0
  8. package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
  9. package/README.md +9 -9
  10. package/dist/configuration.d.ts +10 -0
  11. package/dist/configuration.js +26 -0
  12. package/dist/controller/base/BaseApiController.d.ts +1 -2
  13. package/dist/controller/base/BaseApiController.js +0 -4
  14. package/dist/controller/gateway/DocGatewayController.js +1 -1
  15. package/dist/controller/helpers.controller.d.ts +6 -0
  16. package/dist/controller/helpers.controller.js +19 -0
  17. package/dist/controller/manage/FlowConfigManageApi.js +4 -2
  18. package/dist/controller/manage/SysConfigMangeApi.js +6 -1
  19. package/dist/controller/manage/UserAccountManageApi.js +7 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +2 -2
  22. package/dist/libs/crud-pro/CrudPro.d.ts +51 -3
  23. package/dist/libs/crud-pro/CrudPro.js +111 -4
  24. package/dist/libs/crud-pro/exceptions.d.ts +7 -0
  25. package/dist/libs/crud-pro/exceptions.js +7 -0
  26. package/dist/libs/crud-pro/interfaces.d.ts +83 -12
  27. package/dist/libs/crud-pro/models/CrudResult.d.ts +116 -0
  28. package/dist/libs/crud-pro/models/CrudResult.js +126 -0
  29. package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -2
  30. package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
  31. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
  32. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
  33. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
  34. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
  35. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
  36. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -4
  37. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
  38. package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
  39. package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +382 -0
  40. package/dist/libs/crud-pro-quick/CrudProQuick.js +689 -0
  41. package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
  42. package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
  43. package/dist/libs/crud-pro-quick/index.d.ts +36 -0
  44. package/dist/libs/crud-pro-quick/index.js +49 -0
  45. package/dist/libs/crud-pro-quick/models.d.ts +33 -0
  46. package/dist/libs/crud-pro-quick/models.js +2 -0
  47. package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
  48. package/dist/libs/crud-sharding/ShardingBase.js +179 -0
  49. package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
  50. package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
  51. package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
  52. package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
  53. package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
  54. package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
  55. package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
  56. package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
  57. package/dist/libs/crud-sharding/ShardingConfig.d.ts +25 -10
  58. package/dist/libs/crud-sharding/ShardingConfig.js +5 -5
  59. package/dist/libs/crud-sharding/ShardingMerger.d.ts +10 -18
  60. package/dist/libs/crud-sharding/ShardingMerger.js +27 -44
  61. package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
  62. package/dist/libs/crud-sharding/ShardingResult.js +16 -0
  63. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
  64. package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
  65. package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
  66. package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
  67. package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  68. package/dist/libs/crud-sharding/index.d.ts +13 -15
  69. package/dist/libs/crud-sharding/index.js +33 -17
  70. package/dist/models/RedisKeys.d.ts +1 -0
  71. package/dist/models/RedisKeys.js +1 -0
  72. package/dist/models/bizmodels.d.ts +2 -6
  73. package/dist/service/SysAppService.d.ts +2 -2
  74. package/dist/service/SysAppService.js +16 -5
  75. package/dist/service/SysConfigService.d.ts +1 -1
  76. package/dist/service/SysConfigService.js +7 -2
  77. package/dist/service/SysDictDataService.js +14 -4
  78. package/dist/service/SysMenuService.js +7 -2
  79. package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
  80. package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
  81. package/dist/service/curd/CurdMixService.d.ts +6 -4
  82. package/dist/service/curd/CurdMixService.js +16 -2
  83. package/dist/service/curd/CurdProService.d.ts +149 -29
  84. package/dist/service/curd/CurdProService.js +157 -38
  85. package/dist/service/flow/FlowConfigService.js +7 -2
  86. package/dist/service/flow/FlowInstanceCrudService.js +22 -19
  87. package/package.json +1 -1
  88. package/src/configuration.ts +27 -0
  89. package/src/controller/base/BaseApiController.ts +0 -5
  90. package/src/controller/gateway/DocGatewayController.ts +1 -1
  91. package/src/controller/helpers.controller.ts +15 -0
  92. package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
  93. package/src/controller/manage/FlowConfigManageApi.ts +4 -2
  94. package/src/controller/manage/SysConfigMangeApi.ts +6 -1
  95. package/src/controller/manage/UserAccountManageApi.ts +7 -2
  96. package/src/index.ts +2 -2
  97. package/src/libs/crud-pro/CrudPro.ts +134 -7
  98. package/src/libs/crud-pro/exceptions.ts +8 -0
  99. package/src/libs/crud-pro/interfaces.ts +111 -15
  100. package/src/libs/crud-pro/models/CrudResult.ts +178 -0
  101. package/src/libs/crud-pro/models/RequestModel.ts +2 -2
  102. package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
  103. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
  104. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
  105. package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
  106. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -3
  107. package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
  108. package/src/libs/crud-pro-quick/CrudProQuick.ts +782 -0
  109. package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
  110. package/src/libs/crud-pro-quick/index.ts +52 -0
  111. package/src/libs/crud-pro-quick/models.ts +35 -0
  112. package/src/libs/crud-sharding/ShardingBase.ts +256 -0
  113. package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
  114. package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
  115. package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
  116. package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
  117. package/src/libs/crud-sharding/ShardingConfig.ts +28 -10
  118. package/src/libs/crud-sharding/ShardingMerger.ts +35 -63
  119. package/src/libs/crud-sharding/ShardingResult.ts +29 -0
  120. package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
  121. package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
  122. package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  123. package/src/libs/crud-sharding/index.ts +30 -16
  124. package/src/models/RedisKeys.ts +1 -0
  125. package/src/models/bizmodels.ts +4 -7
  126. package/src/service/SysAppService.ts +18 -7
  127. package/src/service/SysConfigService.ts +8 -3
  128. package/src/service/SysDictDataService.ts +14 -4
  129. package/src/service/SysMenuService.ts +7 -2
  130. package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
  131. package/src/service/crudstd/CrudStdService.ts +2 -2
  132. package/src/service/curd/CurdMixService.ts +26 -5
  133. package/src/service/curd/CurdProService.ts +186 -45
  134. package/src/service/flow/FlowConfigService.ts +7 -2
  135. package/src/service/flow/FlowInstanceCrudService.ts +23 -20
  136. package/.qoder/skills/midway-fatcms-crud/SKILL.md +0 -375
  137. package/.qoder/skills/midway-fatcms-crud/examples.md +0 -990
  138. package/.qoder/skills/midway-fatcms-crud/reference.md +0 -568
  139. package/dist/libs/crud-pro/README.md +0 -809
  140. package/dist/libs/crud-pro/README_FUNC.md +0 -193
  141. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  142. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -363
  143. package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -675
  144. package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -69
  145. package/dist/libs/crud-sharding/ShardingRouter.js +0 -377
  146. package/dist/models/StandardColumns.d.ts +0 -71
  147. package/dist/models/StandardColumns.js +0 -28
  148. package/dist/service/curd/CrudProQuick.d.ts +0 -190
  149. package/dist/service/curd/CrudProQuick.js +0 -319
  150. package/dist/service/curd/README.md +0 -1100
  151. package/dist/service/curd/fixSoftDelete.d.ts +0 -20
  152. package/src/libs/crud-pro/README.md +0 -809
  153. package/src/libs/crud-pro/README_FUNC.md +0 -193
  154. package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  155. package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -835
  156. package/src/libs/crud-sharding/ShardingRouter.ts +0 -512
  157. package/src/models/StandardColumns.ts +0 -76
  158. package/src/service/curd/CrudProQuick.ts +0 -360
  159. package/src/service/curd/README.md +0 -1100
@@ -36,3 +36,51 @@ export declare function getCurrentSuffix(shardingType: ShardingType | undefined)
36
36
  * @returns 是否为当前表
37
37
  */
38
38
  export declare function isCurrentTable(tableName: string, baseTable: string, shardingType: ShardingType | undefined): boolean;
39
+ /**
40
+ * 日期字符串的粒度类型
41
+ */
42
+ export declare type DateStringGranularity = 'year' | 'month' | 'day' | 'datetime' | null;
43
+ /**
44
+ * 判断日期字符串的粒度
45
+ *
46
+ * 根据字符串格式推断精度:
47
+ * - 4位纯年:'2024' → 'year'
48
+ * - 7位年-月:'2024-01' → 'month'
49
+ * - 10位年-月-日:'2024-01-15' → 'day'
50
+ * - 带时分秒:'2024-01-15 10:30:00' → 'datetime'(精度已足够,不需要转换)
51
+ *
52
+ * @param value 待判断的值
53
+ * @returns 粒度类型,非日期字符串返回 null
54
+ */
55
+ export declare function detectDateStringGranularity(value: string): DateStringGranularity;
56
+ /**
57
+ * 判断值是否为时间操作符表达式(ICompareCondition / ILogicalCondition)
58
+ *
59
+ * 排除 Date 对象后,typeof === 'object' 且非 null 即为操作符表达式。
60
+ * 判断优先级:`value instanceof Date` → 先于 `typeof === 'object'` 判断。
61
+ *
62
+ * @param value 待判断的值
63
+ * @returns 是否为操作符表达式
64
+ */
65
+ export declare function isOperatorExpression(value: unknown): boolean;
66
+ /**
67
+ * 将日期字符串按粒度转换为 $gte/$lte 范围表达式
68
+ *
69
+ * 仅对 year/month/day 粒度的字符串进行转换,
70
+ * datetime 粒度和非日期字符串不转换返回 null。
71
+ * 闰年2月自动处理(28/29天)。
72
+ *
73
+ * @param value 日期字符串
74
+ * @returns 范围表达式对象,不需要转换时返回 null
75
+ *
76
+ * @example
77
+ * expandDateToRange('2024')
78
+ * // → { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' }
79
+ *
80
+ * expandDateToRange('2024-01')
81
+ * // → { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' }
82
+ *
83
+ * expandDateToRange('2024-01-15')
84
+ * // → { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' }
85
+ */
86
+ export declare function expandDateToRange(value: string): Record<string, string> | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isCurrentTable = exports.getCurrentSuffix = exports.getTimeSuffix = exports.shardingTypeToGranularity = void 0;
3
+ exports.expandDateToRange = exports.isOperatorExpression = exports.detectDateStringGranularity = exports.isCurrentTable = exports.getCurrentSuffix = exports.getTimeSuffix = exports.shardingTypeToGranularity = void 0;
4
4
  const ShardingConfig_1 = require("./ShardingConfig");
5
5
  /**
6
6
  * 将 ShardingType 转换为时间粒度
@@ -75,3 +75,124 @@ function isCurrentTable(tableName, baseTable, shardingType) {
75
75
  return tableName === expectedTableName;
76
76
  }
77
77
  exports.isCurrentTable = isCurrentTable;
78
+ /**
79
+ * 判断日期字符串的粒度
80
+ *
81
+ * 根据字符串格式推断精度:
82
+ * - 4位纯年:'2024' → 'year'
83
+ * - 7位年-月:'2024-01' → 'month'
84
+ * - 10位年-月-日:'2024-01-15' → 'day'
85
+ * - 带时分秒:'2024-01-15 10:30:00' → 'datetime'(精度已足够,不需要转换)
86
+ *
87
+ * @param value 待判断的值
88
+ * @returns 粒度类型,非日期字符串返回 null
89
+ */
90
+ function detectDateStringGranularity(value) {
91
+ const trimmed = value.trim();
92
+ // 4位纯年:'2024'
93
+ if (/^\d{4}$/.test(trimmed)) {
94
+ return 'year';
95
+ }
96
+ // 7位年-月:'2024-01'
97
+ if (/^\d{4}-\d{2}$/.test(trimmed)) {
98
+ return 'month';
99
+ }
100
+ // 10位年-月-日:'2024-01-15'
101
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
102
+ return 'day';
103
+ }
104
+ // 带时分秒的完整时间字符串(精度已足够,不需要转换)
105
+ if (/^\d{4}-\d{2}-\d{2}[T ]\d{1,2}/.test(trimmed)) {
106
+ return 'datetime';
107
+ }
108
+ return null;
109
+ }
110
+ exports.detectDateStringGranularity = detectDateStringGranularity;
111
+ /**
112
+ * 判断值是否为时间操作符表达式(ICompareCondition / ILogicalCondition)
113
+ *
114
+ * 排除 Date 对象后,typeof === 'object' 且非 null 即为操作符表达式。
115
+ * 判断优先级:`value instanceof Date` → 先于 `typeof === 'object'` 判断。
116
+ *
117
+ * @param value 待判断的值
118
+ * @returns 是否为操作符表达式
119
+ */
120
+ function isOperatorExpression(value) {
121
+ // Date 对象是精确值,不是操作符
122
+ if (value instanceof Date) {
123
+ return false;
124
+ }
125
+ // 非-null 对象视为操作符表达式(如 { $gte: ... }, { $or: ... })
126
+ return typeof value === 'object' && value !== null && Object.keys(value).length > 0;
127
+ }
128
+ exports.isOperatorExpression = isOperatorExpression;
129
+ /**
130
+ * 获取指定年月的最后一天日期
131
+ *
132
+ * @param year 年份
133
+ * @param month 月份(1-12)
134
+ * @returns 最后一天的日号(28/29/30/31)
135
+ */
136
+ function getLastDayOfMonth(year, month) {
137
+ // new Date(year, month, 0) 返回上个月最后一天
138
+ return new Date(year, month, 0).getDate();
139
+ }
140
+ /**
141
+ * 将日期字符串按粒度转换为 $gte/$lte 范围表达式
142
+ *
143
+ * 仅对 year/month/day 粒度的字符串进行转换,
144
+ * datetime 粒度和非日期字符串不转换返回 null。
145
+ * 闰年2月自动处理(28/29天)。
146
+ *
147
+ * @param value 日期字符串
148
+ * @returns 范围表达式对象,不需要转换时返回 null
149
+ *
150
+ * @example
151
+ * expandDateToRange('2024')
152
+ * // → { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' }
153
+ *
154
+ * expandDateToRange('2024-01')
155
+ * // → { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' }
156
+ *
157
+ * expandDateToRange('2024-01-15')
158
+ * // → { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' }
159
+ */
160
+ function expandDateToRange(value) {
161
+ const granularity = detectDateStringGranularity(value);
162
+ if (!granularity || granularity === 'datetime') {
163
+ return null;
164
+ }
165
+ const trimmed = value.trim();
166
+ switch (granularity) {
167
+ case 'year': {
168
+ // '2024' → 2024-01-01 ~ 2024-12-31
169
+ const year = parseInt(trimmed, 10);
170
+ return {
171
+ $gte: `${year}-01-01 00:00:00`,
172
+ $lte: `${year}-12-31 23:59:59`,
173
+ };
174
+ }
175
+ case 'month': {
176
+ // '2024-01' → 2024-01-01 ~ 2024-01-31
177
+ const [yearStr, monthStr] = trimmed.split('-');
178
+ const year = parseInt(yearStr, 10);
179
+ const month = parseInt(monthStr, 10);
180
+ const lastDay = getLastDayOfMonth(year, month);
181
+ const monthPadded = monthStr.padStart(2, '0');
182
+ return {
183
+ $gte: `${year}-${monthPadded}-01 00:00:00`,
184
+ $lte: `${year}-${monthPadded}-${String(lastDay).padStart(2, '0')} 23:59:59`,
185
+ };
186
+ }
187
+ case 'day': {
188
+ // '2024-01-15' → 2024-01-15 00:00:00 ~ 2024-01-15 23:59:59
189
+ return {
190
+ $gte: `${trimmed} 00:00:00`,
191
+ $lte: `${trimmed} 23:59:59`,
192
+ };
193
+ }
194
+ default:
195
+ return null;
196
+ }
197
+ }
198
+ exports.expandDateToRange = expandDateToRange;
@@ -0,0 +1,488 @@
1
+ # 时间字段清理与转换场景说明
2
+
3
+ ## 背景
4
+
5
+ 时间分表(YEAR/MONTH/DAY)中,`timeColumn` 既是路由键也是查询条件。
6
+ 用户传入的时间值可能精度不一致(如传 `'2024-01-15'` 但数据库存的是 `1705276800000` 毫秒时间戳),
7
+ 直接作为 WHERE 条件会导致匹配失败。
8
+
9
+ ## 校验与清理的职责分离
10
+
11
+ 时间分表的 condition 处理分为两个独立阶段,由不同函数负责:
12
+
13
+ ### 阶段1:校验(validateRoutingFieldForCondition)
14
+
15
+ **职责**:确保写操作的 condition 包含时间字段,用于路由定位分表。
16
+
17
+ **适用操作**:update / delete / restore / insertOrUpdate
18
+
19
+ **校验规则**:
20
+ - condition 中必须存在 timeColumn 字段(值不能为 falsy)
21
+ - 不存在则抛出错误,给出明确提示
22
+ - 查询操作(find*)不校验,允许回退到 recentTableCount
23
+
24
+ ```typescript
25
+ // 校验通过:timeColumn 存在
26
+ { order_id: 'ORD001', created_at: '2024-01-15' } // ✅
27
+ { order_id: 'ORD001', created_at: { $gte: '2024-01' } } // ✅
28
+
29
+ // 校验失败:timeColumn 不存在
30
+ { order_id: 'ORD001' } // ❌ 抛出错误
31
+ ```
32
+
33
+ ### 阶段2:清理/转换(cleanTimeColumnForSingleTableQuery)
34
+
35
+ **职责**:在校验通过后,根据值类型和主键情况,智能处理 timeColumn。
36
+
37
+ **适用操作**:所有操作(读 + 写),在校验之后执行。
38
+
39
+ **处理规则**:按下方「核心规则」和「判断流程图」执行。
40
+
41
+ ### 执行顺序
42
+
43
+ ```
44
+ 写操作请求
45
+
46
+ ├─ 1. validateRoutingFieldForCondition(校验 timeColumn 存在)
47
+ │ └─ 不存在 → 抛出错误,终止
48
+
49
+ └─ 2. cleanTimeColumnForSingleTableQuery(智能处理 timeColumn)
50
+ ├─ 有主键 + 精确值 → 清理
51
+ ├─ 无主键 + 日期粒度字符串 → 转换为范围
52
+ └─ 其他 → 保留原值
53
+
54
+ 读操作请求
55
+
56
+ └─ 1. cleanTimeColumnForSingleTableQuery(智能处理 timeColumn)
57
+ (读操作不校验,允许无 timeColumn)
58
+ ```
59
+
60
+ ## 核心规则
61
+
62
+ ### 规则1:精确时间 + 有主键 → 清理
63
+
64
+ 主键已唯一确定行,timeColumn 仅用于路由,从 WHERE 中移除。
65
+
66
+ ```typescript
67
+ // 配置:{ primaryKey: 'order_id', timeColumn: 'created_at', type: MONTH }
68
+
69
+ // 清理前
70
+ { order_id: 'ORD001', created_at: '2024-01-15' }
71
+ // SQL: WHERE order_id = 'ORD001' AND created_at = '2024-01-15' ← 精度可能不匹配
72
+
73
+ // 清理后
74
+ { order_id: 'ORD001' }
75
+ // SQL: WHERE order_id = 'ORD001' ✅
76
+ ```
77
+
78
+ ### 规则2:精确时间 + 有主键 + 时间是操作符 → 保留
79
+
80
+ 用户明确要按时间操作符查询,不做任何处理。
81
+
82
+ ```typescript
83
+ // $gte/$lte 范围 → 保留
84
+ { order_id: 'ORD001', created_at: { $gte: '2024-01-01', $lte: '2024-06-30' } }
85
+ // SQL: WHERE order_id = 'ORD001' AND created_at >= '2024-01-01' AND created_at <= '2024-06-30' ✅
86
+
87
+ // $range → 保留
88
+ { order_id: 'ORD001', created_at: { $range: ['2024-01-01', '2024-06-30'] } }
89
+ // SQL: WHERE order_id = 'ORD001' AND created_at BETWEEN '2024-01-01' AND '2024-06-30' ✅
90
+
91
+ // $in → 保留
92
+ { order_id: 'ORD001', created_at: { $in: ['2024-01-15', '2024-02-20'] } }
93
+ // SQL: WHERE order_id = 'ORD001' AND created_at IN ('2024-01-15', '2024-02-20') ✅
94
+
95
+ // $null → 保留
96
+ { order_id: 'ORD001', created_at: { $null: true } }
97
+ // SQL: WHERE order_id = 'ORD001' AND created_at IS NULL ✅
98
+
99
+ // $notNull → 保留
100
+ { order_id: 'ORD001', created_at: { $notNull: true } }
101
+ // SQL: WHERE order_id = 'ORD001' AND created_at IS NOT NULL ✅
102
+
103
+ // $ne → 保留
104
+ { order_id: 'ORD001', created_at: { $ne: '2024-01-15' } }
105
+ // SQL: WHERE order_id = 'ORD001' AND created_at != '2024-01-15' ✅
106
+ ```
107
+
108
+ ### 规则3:精确时间 + 无主键 → 转换为范围查询
109
+
110
+ 无主键时,timeColumn 必须参与 WHERE。但精确值可能精度不匹配,
111
+ 需要将日期字符串转换为对应粒度的 `$gte`/`$lte` 范围。
112
+
113
+ #### 3a. 年粒度字符串 → 年范围
114
+
115
+ ```typescript
116
+ // 配置:{ timeColumn: 'created_at', type: MONTH }
117
+
118
+ // 转换前
119
+ { status: 'paid', created_at: '2024' }
120
+
121
+ // 转换后
122
+ { status: 'paid', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' } }
123
+ // SQL: WHERE status = 'paid' AND created_at >= '2024-01-01 00:00:00' AND created_at <= '2024-12-31 23:59:59' ✅
124
+
125
+ // 路由:根据范围定位 t_order_202401 ~ t_order_202412
126
+ ```
127
+
128
+ #### 3b. 月粒度字符串 → 月范围
129
+
130
+ ```typescript
131
+ // 配置:{ timeColumn: 'created_at', type: MONTH }
132
+
133
+ // 转换前
134
+ { status: 'paid', created_at: '2024-01' }
135
+
136
+ // 转换后
137
+ { status: 'paid', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
138
+ // SQL: WHERE status = 'paid' AND created_at >= '2024-01-01 00:00:00' AND created_at <= '2024-01-31 23:59:59' ✅
139
+
140
+ // 路由:定位 t_order_202401
141
+ ```
142
+
143
+ #### 3c. 日粒度字符串 → 日范围
144
+
145
+ ```typescript
146
+ // 配置:{ timeColumn: 'created_at', type: MONTH }
147
+
148
+ // 转换前
149
+ { status: 'paid', created_at: '2024-01-15' }
150
+
151
+ // 转换后
152
+ { status: 'paid', created_at: { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' } }
153
+ // SQL: WHERE status = 'paid' AND created_at >= '2024-01-15 00:00:00' AND created_at <= '2024-01-15 23:59:59' ✅
154
+
155
+ // 路由:定位 t_order_202401
156
+ ```
157
+
158
+ #### 3d. 带时分秒的字符串 → 不转换
159
+
160
+ ```typescript
161
+ // 精确到秒的时间字符串,精度已足够,不转换
162
+ { status: 'paid', created_at: '2024-01-15 10:30:00' }
163
+ // 不转换,直接用于 WHERE
164
+ // SQL: WHERE status = 'paid' AND created_at = '2024-01-15 10:30:00' ✅
165
+
166
+ // 带毫秒的时间戳数字,精度已足够,不转换
167
+ { status: 'paid', created_at: 1705305000000 }
168
+ // 不转换,直接用于 WHERE
169
+ // SQL: WHERE status = 'paid' AND created_at = 1705305000000 ✅
170
+ ```
171
+
172
+ ### 规则4:精确时间 + 无主键 + 时间是不可转换的精确值 → 不转换
173
+
174
+ 数值型时间戳(非日期字符串)无法判断粒度,不做转换。
175
+
176
+ ```typescript
177
+ // 数值型时间戳:无法推断粒度,保留原值
178
+ { status: 'paid', created_at: 1705276800000 }
179
+ // 不转换,直接 WHERE
180
+ // SQL: WHERE status = 'paid' AND created_at = 1705276800000
181
+
182
+ // 但如果有主键,则清理
183
+ { order_id: 'ORD001', created_at: 1705276800000 }
184
+ // → 清理为 { order_id: 'ORD001' }
185
+ ```
186
+
187
+ ### 规则5:操作符时间 → 始终保留
188
+
189
+ 无论有没有主键,操作符表达式一律保留在 WHERE 中。
190
+
191
+ ```typescript
192
+ // 无主键 + 操作符
193
+ { status: 'paid', created_at: { $gte: '2024-01-01', $lte: '2024-06-30' } }
194
+ // 保留,不做任何处理 ✅
195
+
196
+ // 无主键 + $range
197
+ { status: 'paid', created_at: { $range: ['2024-01-01', '2024-06-30'] } }
198
+ // 保留 ✅
199
+
200
+ // 无主键 + $null
201
+ { status: 'paid', created_at: { $null: true } }
202
+ // 保留 ✅
203
+ ```
204
+
205
+ ## 转换逻辑总结
206
+
207
+ ### 判断日期字符串粒度
208
+
209
+ ```
210
+ '2024' → 4位纯年 → 年范围
211
+ '2024-01' → 7位年-月 → 月范围
212
+ '2024-01-15' → 10位年-月-日 → 日范围
213
+ '2024-01-15 10' → 带时 → 日范围(补齐到秒)
214
+ '2024-01-15 10:30'→ 带时分 → 日范围(补齐到秒)
215
+ '2024-01-15 10:30:00' → 精确到秒 → 不转换
216
+ 1705276800000 → number → 不转换(无法推断粒度)
217
+ new Date(...) → Date对象 → 不转换(精确到毫秒,视为精确值)
218
+ ```
219
+
220
+ ### 转换规则表
221
+
222
+ | 条件 | timeColumn 值类型 | 有主键? | 操作 |
223
+ |------|-------------------|---------|------|
224
+ | 1 | string 精确值(年/月/日粒度) | ✅ | 清理(从 WHERE 移除) |
225
+ | 2 | string 精确值(年/月/日粒度) | ❌ | 转换为 $gte/$lte 范围 |
226
+ | 3 | string 精确值(精确到秒) | ✅ | 清理 |
227
+ | 4 | string 精确值(精确到秒) | ❌ | 不转换,保留原值 |
228
+ | 5 | number 精确值 | ✅ | 清理 |
229
+ | 6 | number 精确值 | ❌ | 不转换,保留原值 |
230
+ | 7 | Date 对象 | ✅ | 清理(精确到毫秒,视为精确值) |
231
+ | 8 | Date 对象 | ❌ | 不转换,保留原值(精度已足够) |
232
+ | 9 | ICompareCondition($gte等) | 任意 | 保留,不做处理 |
233
+ | 10 | ICompareCondition($null等) | 任意 | 保留,不做处理 |
234
+
235
+ ## 各分表类型的转换示例
236
+
237
+ ### YEAR 分表
238
+
239
+ ```typescript
240
+ // 配置:{ type: YEAR, timeColumn: 'created_at' }
241
+
242
+ // '2024' → 年范围
243
+ { created_at: '2024' }
244
+ → { created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' } }
245
+ // 路由:t_log_2024
246
+
247
+ // '2024-06' → 月范围
248
+ { created_at: '2024-06' }
249
+ → { created_at: { $gte: '2024-06-01 00:00:00', $lte: '2024-06-30 23:59:59' } }
250
+ // 路由:t_log_2024
251
+
252
+ // '2024-06-15' → 日范围
253
+ { created_at: '2024-06-15' }
254
+ → { created_at: { $gte: '2024-06-15 00:00:00', $lte: '2024-06-15 23:59:59' } }
255
+ // 路由:t_log_2024
256
+ ```
257
+
258
+ ### MONTH 分表
259
+
260
+ ```typescript
261
+ // 配置:{ type: MONTH, timeColumn: 'created_at' }
262
+
263
+ // '2024' → 年范围(跨12张表)
264
+ { created_at: '2024' }
265
+ → { created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' } }
266
+ // 路由:t_order_202401 ~ t_order_202412
267
+
268
+ // '2024-01' → 月范围
269
+ { created_at: '2024-01' }
270
+ → { created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
271
+ // 路由:t_order_202401
272
+
273
+ // '2024-01-15' → 日范围
274
+ { created_at: '2024-01-15' }
275
+ → { created_at: { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' } }
276
+ // 路由:t_order_202401
277
+ ```
278
+
279
+ ### DAY 分表
280
+
281
+ ```typescript
282
+ // 配置:{ type: DAY, timeColumn: 'access_time' }
283
+
284
+ // '2024' → 年范围(跨365张表)
285
+ { access_time: '2024' }
286
+ → { access_time: { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' } }
287
+ // 路由:t_access_20240101 ~ t_access_20241231
288
+
289
+ // '2024-01' → 月范围(跨31张表)
290
+ { access_time: '2024-01' }
291
+ → { access_time: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
292
+ // 路由:t_access_20240101 ~ t_access_20240131
293
+
294
+ // '2024-01-15' → 日范围
295
+ { access_time: '2024-01-15' }
296
+ → { access_time: { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' } }
297
+ // 路由:t_access_20240115
298
+ ```
299
+
300
+ ## 特殊边界场景
301
+
302
+ ### 闰年2月
303
+
304
+ ```typescript
305
+ // 2024 是闰年
306
+ { created_at: '2024-02' }
307
+ → { created_at: { $gte: '2024-02-01 00:00:00', $lte: '2024-02-29 23:59:59' } }
308
+ // ✅ 2月有29天
309
+
310
+ // 2023 不是闰年
311
+ { created_at: '2023-02' }
312
+ → { created_at: { $gte: '2023-02-01 00:00:00', $lte: '2023-02-28 23:59:59' } }
313
+ // ✅ 2月有28天
314
+ ```
315
+
316
+ ### 主键 + 日期粒度字符串 → 清理即可
317
+
318
+ ```typescript
319
+ // 有主键时,日期粒度字符串也直接清理,不需要转换
320
+ // 因为主键已唯一确定行,timeColumn 仅用于路由
321
+
322
+ { order_id: 'ORD001', created_at: '2024' }
323
+ → { order_id: 'ORD001' } // 清理,不需要转换为范围
324
+
325
+ { order_id: 'ORD001', created_at: '2024-01' }
326
+ → { order_id: 'ORD001' } // 清理
327
+
328
+ { order_id: 'ORD001', created_at: '2024-01-15' }
329
+ → { order_id: 'ORD001' } // 清理
330
+ ```
331
+
332
+ ### 组合条件
333
+
334
+ ```typescript
335
+ // 多个普通条件 + 日期粒度字符串
336
+ { status: 'paid', region: 'CN', created_at: '2024-01' }
337
+ → { status: 'paid', region: 'CN', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
338
+ // SQL: WHERE status = 'paid' AND region = 'CN' AND created_at >= '2024-01-01 00:00:00' AND created_at <= '2024-01-31 23:59:59' ✅
339
+
340
+ // 主键 + 其他条件 + 日期粒度字符串 → 只清理 timeColumn
341
+ { order_id: 'ORD001', status: 'paid', created_at: '2024-01-15' }
342
+ → { order_id: 'ORD001', status: 'paid' }
343
+ // SQL: WHERE order_id = 'ORD001' AND status = 'paid' ✅
344
+ ```
345
+
346
+ ### Date 对象
347
+
348
+ JavaScript 的 `Date` 对象 `typeof` 是 `'object'`,但不是 `ICompareCondition`,需要优先识别。
349
+
350
+ 判断优先级:`value instanceof Date` → 先于 `typeof === 'object'` 判断。
351
+
352
+ ```typescript
353
+ // Date 对象精确到毫秒,不存在粒度转换问题
354
+
355
+ // 有主键 → 清理
356
+ { order_id: 'ORD001', created_at: new Date('2024-01-15T10:30:00') }
357
+ → { order_id: 'ORD001' } // 清理
358
+
359
+ // 无主键 → 保留原值(精度已足够,不需要转换)
360
+ { status: 'paid', created_at: new Date('2024-01-15T10:30:00') }
361
+ // 不转换,保留原值
362
+ // SQL: WHERE status = 'paid' AND created_at = '2024-01-15 10:30:00.000' ✅
363
+ ```
364
+
365
+ > **注意**:通过 HTTP API 传参时,Date 对象会被 JSON 序列化为字符串,
366
+ > 只有在代码中直接调用(如测试代码、定时任务)时才会传入 Date 对象。
367
+
368
+ ### timeColumn 本身是唯一条件
369
+
370
+ ```typescript
371
+ // 只有日期粒度字符串 → 转换
372
+ { created_at: '2024-01' }
373
+ → { created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
374
+ // ✅ 既能路由到正确分表,又能精确 WHERE
375
+
376
+ // 只有精确到秒 → 不转换
377
+ { created_at: '2024-01-15 10:30:00' }
378
+ // 不转换,保留原值
379
+ ```
380
+
381
+ ## 非法/边界值处理
382
+
383
+ ### null 值
384
+
385
+ `typeof null === 'object'`,但 null 不是操作符表达式,也不是合法的时间值。
386
+
387
+ ```typescript
388
+ // 误用:想查 NULL 应该用 { $null: true }
389
+ { status: 'paid', created_at: null }
390
+ // → 不清理、不转换,保留原值
391
+ // SQL: WHERE status = 'paid' AND created_at = NULL ← 通常匹配不到数据
392
+
393
+ // 正确写法
394
+ { status: 'paid', created_at: { $null: true } }
395
+ // → 操作符表达式,保留
396
+ // SQL: WHERE status = 'paid' AND created_at IS NULL ✅
397
+ ```
398
+
399
+ 判断逻辑:`isOperatorExpression` 通过 `value !== null` 排除 null,null 被视为非操作符。
400
+ 后续由于 null 不是 string/number/boolean/Date,不会命中任何清理或转换规则,保留原值。
401
+
402
+ ### 空字符串
403
+
404
+ 不匹配任何日期格式,不转换也不清理。
405
+
406
+ ```typescript
407
+ { status: 'paid', created_at: '' }
408
+ // → 不清理、不转换,保留原值
409
+ // SQL: WHERE status = 'paid' AND created_at = '' ← 匹配不到数据,但不会报错
410
+ ```
411
+
412
+ ### 数组值
413
+
414
+ `typeof [] === 'object'`,会被误判为操作符表达式而保留,但这不是合法的 condition 值。
415
+
416
+ ```typescript
417
+ // 非法值,不应使用
418
+ { status: 'paid', created_at: ['2024-01', '2024-02'] }
419
+ // → 被当作操作符表达式保留,但 SQL 生成可能出错
420
+
421
+ // 正确写法:用 $in
422
+ { status: 'paid', created_at: { $in: ['2024-01-01', '2024-02-01'] } }
423
+ ```
424
+
425
+ ### primaryKey 值为操作符
426
+
427
+ 当 primaryKey 的值是操作符表达式(如 `$in`)时,仍触发 timeColumn 清理。
428
+ 因为 primaryKey 的作用是判断"是否有字段能辅助定位行",而非"是否能唯一确定一行"。
429
+
430
+ ```typescript
431
+ // primaryKey 使用 $in
432
+ { order_id: { $in: ['ORD001', 'ORD002'] }, created_at: '2024-01-15' }
433
+ → { order_id: { $in: ['ORD001', 'ORD002'] } } // 清理 created_at
434
+ // SQL: WHERE order_id IN ('ORD001', 'ORD002') ✅
435
+ // created_at 已通过路由定位到 t_order_202401,不需要在 WHERE 中
436
+ ```
437
+
438
+ ## 写操作安全性
439
+
440
+ 无主键写操作(update/delete)中,日期粒度字符串被转换为范围后,
441
+ 可能影响大量数据,需调用方自行保证条件精确性。
442
+
443
+ ```typescript
444
+ // ⚠️ 无主键删除 + 日期粒度字符串 → 转换为范围,可能删除大量数据
445
+ await sharding.delete({
446
+ condition: { created_at: '2024-01' }
447
+ });
448
+ // 转换后: { created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
449
+ // SQL: DELETE FROM t_order_202401 WHERE created_at >= '2024-01-01 00:00:00' AND created_at <= '2024-01-31 23:59:59'
450
+ // ⚠️ 会删除整个1月份的所有数据!
451
+
452
+ // ✅ 推荐:写操作尽量使用主键
453
+ await sharding.delete({
454
+ condition: { order_id: 'ORD001', created_at: '2024-01-15' }
455
+ });
456
+ // 清理后: { order_id: 'ORD001' }
457
+ // SQL: DELETE FROM t_order_202401 WHERE order_id = 'ORD001' ✅ 精确删除
458
+ ```
459
+
460
+ ## 判断流程图
461
+
462
+ ```
463
+ timeColumn 值在 condition 中
464
+
465
+ ├─ 值为 undefined → 不处理
466
+
467
+ ├─ 值 instanceof Date → 精确值(毫秒精度)
468
+ │ ├─ 有 primaryKey → 清理
469
+ │ └─ 无 primaryKey → 保留原值
470
+
471
+ ├─ typeof === 'object' 且不为 null 且不为 Date → 操作符表达式 → 保留
472
+
473
+ ├─ typeof === 'number' → 精确值(时间戳)
474
+ │ ├─ 有 primaryKey → 清理
475
+ │ └─ 无 primaryKey → 保留原值(无法推断粒度)
476
+
477
+ ├─ typeof === 'boolean' → 精确值
478
+ │ ├─ 有 primaryKey → 清理
479
+ │ └─ 无 primaryKey → 保留原值
480
+
481
+ └─ typeof === 'string' → 检测日期粒度
482
+ ├─ 年/月/日粒度('2024', '2024-01', '2024-01-15')
483
+ │ ├─ 有 primaryKey → 清理
484
+ │ └─ 无 primaryKey → 转换为 $gte/$lte 范围
485
+ └─ datetime 粒度 或 不匹配任何格式
486
+ ├─ 有 primaryKey → 清理
487
+ └─ 无 primaryKey → 保留原值
488
+ ```