midway-fatcms 0.0.4 → 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.
Files changed (126) hide show
  1. package/README.md +635 -352
  2. package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
  3. package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
  7. package/dist/libs/crud-pro/CrudPro.js +15 -0
  8. package/dist/libs/crud-pro/README.md +809 -0
  9. package/dist/libs/crud-pro/README_FUNC.md +193 -0
  10. package/dist/libs/crud-pro/exceptions.d.ts +2 -0
  11. package/dist/libs/crud-pro/exceptions.js +2 -0
  12. package/dist/libs/crud-pro/interfaces.d.ts +34 -1
  13. package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
  14. package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
  15. package/dist/libs/crud-pro/models/RequestModel.d.ts +41 -1
  16. package/dist/libs/crud-pro/models/RequestModel.js +103 -39
  17. package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
  18. package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
  19. package/dist/libs/crud-pro/models/keys.d.ts +6 -1
  20. package/dist/libs/crud-pro/models/keys.js +5 -0
  21. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
  22. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
  23. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
  24. package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
  25. package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
  26. package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
  27. package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
  28. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
  29. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
  30. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
  31. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
  32. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
  33. package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
  34. package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
  35. package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
  36. package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
  37. package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
  38. package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
  39. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  40. package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
  41. package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
  42. package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
  43. package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
  44. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
  45. package/dist/libs/crud-sharding/ShardingCrudPro.js +699 -0
  46. package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
  47. package/dist/libs/crud-sharding/ShardingMerger.js +280 -0
  48. package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
  49. package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
  50. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
  51. package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
  52. package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
  53. package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
  54. package/dist/libs/crud-sharding/index.d.ts +45 -0
  55. package/dist/libs/crud-sharding/index.js +55 -0
  56. package/dist/models/StandardColumns.d.ts +71 -0
  57. package/dist/models/StandardColumns.js +28 -0
  58. package/dist/service/SysAppService.js +2 -2
  59. package/dist/service/SysConfigService.js +1 -1
  60. package/dist/service/SysDictDataService.js +2 -2
  61. package/dist/service/SysMenuService.js +1 -1
  62. package/dist/service/UserAccountService.d.ts +1 -1
  63. package/dist/service/crudstd/CrudStdService.d.ts +0 -1
  64. package/dist/service/crudstd/CrudStdService.js +0 -27
  65. package/dist/service/curd/CrudProQuick.d.ts +134 -4
  66. package/dist/service/curd/CrudProQuick.js +155 -3
  67. package/dist/service/curd/CurdMixService.d.ts +2 -1
  68. package/dist/service/curd/CurdMixService.js +5 -1
  69. package/dist/service/curd/CurdProService.d.ts +44 -2
  70. package/dist/service/curd/CurdProService.js +53 -1
  71. package/dist/service/curd/README.md +1001 -0
  72. package/dist/service/curd/fixSoftDelete.d.ts +14 -0
  73. package/dist/service/curd/fixSoftDelete.js +29 -11
  74. package/dist/service/flow/FlowConfigService.js +1 -1
  75. package/dist/service/flow/FlowInstanceCrudService.js +1 -1
  76. package/package.json +3 -1
  77. package/src/controller/gateway/AsyncTaskController.ts +1 -1
  78. package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
  79. package/src/index.ts +3 -0
  80. package/src/libs/crud-pro/CrudPro.ts +19 -1
  81. package/src/libs/crud-pro/README.md +809 -0
  82. package/src/libs/crud-pro/README_FUNC.md +193 -0
  83. package/src/libs/crud-pro/exceptions.ts +2 -0
  84. package/src/libs/crud-pro/interfaces.ts +38 -1
  85. package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
  86. package/src/libs/crud-pro/models/RequestModel.ts +108 -44
  87. package/src/libs/crud-pro/models/ResModel.ts +10 -4
  88. package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
  89. package/src/libs/crud-pro/models/keys.ts +5 -0
  90. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
  91. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
  92. package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
  93. package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
  94. package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
  95. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
  96. package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
  97. package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
  98. package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
  99. package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
  100. package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  101. package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
  102. package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
  103. package/src/libs/crud-sharding/ShardingCrudPro.ts +856 -0
  104. package/src/libs/crud-sharding/ShardingMerger.ts +382 -0
  105. package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
  106. package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
  107. package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
  108. package/src/libs/crud-sharding/index.ts +64 -0
  109. package/src/models/StandardColumns.ts +76 -0
  110. package/src/service/FileCenterService.ts +1 -1
  111. package/src/service/SysAppService.ts +2 -2
  112. package/src/service/SysConfigService.ts +1 -1
  113. package/src/service/SysDictDataService.ts +2 -2
  114. package/src/service/SysMenuService.ts +2 -2
  115. package/src/service/WorkbenchService.ts +1 -1
  116. package/src/service/anyapi/AnyApiService.ts +1 -1
  117. package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
  118. package/src/service/crudstd/CrudStdService.ts +0 -32
  119. package/src/service/curd/CrudProQuick.ts +164 -5
  120. package/src/service/curd/CurdMixService.ts +7 -2
  121. package/src/service/curd/CurdProService.ts +62 -3
  122. package/src/service/curd/README.md +1001 -0
  123. package/src/service/curd/fixCfgModel.ts +1 -2
  124. package/src/service/curd/fixSoftDelete.ts +38 -16
  125. package/src/service/flow/FlowConfigService.ts +1 -1
  126. package/src/service/flow/FlowInstanceCrudService.ts +1 -1
@@ -0,0 +1,944 @@
1
+ # 分表 CRUD 使用指南
2
+
3
+ ShardingCrudPro 提供时间、范围、哈希、自定义四种分表策略,支持跨分表查询与自动合并分页。
4
+
5
+ > 在服务层通过 `curdProService.getShardingCrud()` 获取实例,底层 CrudPro 用法见 [crud-pro/README.md](../crud-pro/README.md)。
6
+
7
+ ## 快速开始
8
+
9
+ ```typescript
10
+ import { ShardingType } from '@/libs/crud-sharding';
11
+
12
+ // 1. 获取分表操作器
13
+ const sharding = curdProService.getShardingCrud('mydb', SqlDbType.mysql, {
14
+ type: ShardingType.MONTH,
15
+ baseTable: 't_order',
16
+ timeColumn: 'created_at',
17
+ });
18
+
19
+ // 2. 插入(自动路由到 t_order_202403)
20
+ await sharding.insert({
21
+ data: { order_id: '001', amount: 100, created_at: '2024-03-15' }
22
+ });
23
+
24
+ // 3. 分页查询(自动跨表合并)
25
+ const result = await sharding.queryPage({
26
+ condition: { status: 'completed' },
27
+ pageNo: 1,
28
+ pageSize: 10,
29
+ orderBy: 'created_at DESC',
30
+ });
31
+ ```
32
+
33
+ ## 配置参考
34
+
35
+ ```typescript
36
+ interface IShardingConfig {
37
+ type: ShardingType; // 分表类型(YEAR/MONTH/DAY/RANGE/HASH/CUSTOM)
38
+ baseTable: string; // 基础表名
39
+
40
+ // 时间分表
41
+ timeColumn?: string; // 时间字段名(默认 created_at)
42
+ recentTableCount?: number; // 无时间条件时查询最近N张表(默认3)
43
+
44
+ // 范围/哈希分表
45
+ shardingColumn?: string; // 分表字段名
46
+ tableCount?: number; // 分表数量(默认10)
47
+
48
+ // COUNT 缓存(仅时间分表有效)
49
+ countCache?: {
50
+ ttlSeconds?: number; // 缓存有效期(秒),默认300
51
+ maxSize?: number; // 最大缓存条目数,默认1000
52
+ };
53
+
54
+ // 自定义配置
55
+ customRouter?: Function; // CUSTOM 类型的自定义路由函数
56
+ suffixFormatter?: Function; // 后缀格式化函数
57
+ }
58
+ ```
59
+
60
+ ## 一、路由策略总览
61
+
62
+ 分表路由的核心逻辑:**根据操作类型决定从 `data` 还是 `condition` 中提取分表字段**。
63
+
64
+ | 操作类型 | 实际使用的字段 | 路由依据 | 字段提取逻辑 | 说明 |
65
+ |---------|--------------|---------|-------------|------|
66
+ | `insert` | `data` | `reqJson.data` | `data[分表字段]` | 插入单条数据 |
67
+ | `batchInsert` | `data` (数组) | `reqJson.data` | `data[i][分表字段]` | 智能批量插入,支持跨分表 |
68
+ | `update` | `condition` + `data` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表,data是更新内容 |
69
+ | `delete` | `condition` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表 |
70
+ | `insertOrUpdate` | `condition` + `data` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表,**注意:data中的分表字段可能与condition指向不同分表** |
71
+ | `insertOnDuplicateUpdate` | `condition` + `data` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表,**注意:data中的分表字段可能与condition指向不同分表** |
72
+ | `queryOne` | `condition` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表 |
73
+ | `query` | `condition` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表,**必须传 orderBy** |
74
+ | `queryPage` | `condition` + 分页 | `reqJson.condition` | `condition[分表字段]` | 条件决定分表,**必须传 orderBy** |
75
+ | `queryCount` | `condition` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表 |
76
+ | `isExist` | `condition` | `reqJson.condition` | `condition[分表字段]` | 条件决定分表 |
77
+
78
+ ### 关键结论
79
+
80
+ - **插入类操作**(insert/batchInsert):只使用 `data`,不使用 `condition`
81
+ - **查询类操作**(query/delete/isExist等):只使用 `condition`,不使用 `data`
82
+ - **更新类操作**(update/insertOrUpdate/insertOnDuplicateUpdate):`condition` 用于路由,`data` 是更新内容
83
+
84
+ ## 二、查询能力边界
85
+
86
+ ### 2.1 使用约束
87
+
88
+ 时间分表查询(`query`/`queryPage`)有以下强制约束:
89
+
90
+ | 约束项 | 要求 | 原因 |
91
+ |-------|------|------|
92
+ | **orderBy 必传** | 必须传 orderBy 参数 | 分表查询必须明确排序方式 |
93
+ | **排序字段** | 必须为 `timeColumn DESC` 或 `timeColumn ASC`(如 `created_at DESC` / `created_at ASC`) | 利用分表顺序特性,无需内存排序 |
94
+ | **排序方向** | 支持 `DESC`(最新在前)和 `ASC`(最旧在前) | DESC 时表顺序 新→旧,ASC 时表顺序 旧→新 |
95
+
96
+ ### 2.2 不支持的场景
97
+
98
+ 以下场景会直接抛出异常:
99
+
100
+ ```typescript
101
+ // ❌ 缺少 orderBy
102
+ await sharding.query({ condition: { status: 'pending' } });
103
+ // Error: [ShardingCrudPro] 查询操作必须传 orderBy 参数。期望值: 'created_at DESC' 或 'created_at ASC'
104
+
105
+ // ❌ 排序字段错误
106
+ await sharding.query({
107
+ condition: { status: 'pending' },
108
+ orderBy: 'id DESC'
109
+ });
110
+ // Error: [ShardingCrudPro] orderBy 参数必须是 'created_at DESC' 或 'created_at ASC',当前值: 'id DESC'
111
+ ```
112
+
113
+ ### 2.3 正确用法
114
+
115
+ ```typescript
116
+ // ✅ 字符串格式 - DESC(最新在前)
117
+ await sharding.query({
118
+ condition: { status: 'pending' },
119
+ orderBy: 'created_at DESC'
120
+ });
121
+
122
+ // ✅ 字符串格式 - ASC(最旧在前)
123
+ await sharding.query({
124
+ condition: { status: 'pending' },
125
+ orderBy: 'created_at ASC'
126
+ });
127
+
128
+ // ✅ 数组格式(单个排序字段)
129
+ await sharding.query({
130
+ condition: { status: 'pending' },
131
+ orderBy: [{ fieldName: 'created_at', orderType: 'DESC' }]
132
+ });
133
+
134
+ // ✅ 数组格式(多字段排序,首字段必须为 timeColumn DESC/ASC)
135
+ await sharding.query({
136
+ condition: { status: 'pending' },
137
+ orderBy: ['created_at DESC', 'id ASC']
138
+ });
139
+
140
+ // ✅ 分页查询
141
+ const result = await sharding.queryPage({
142
+ condition: { status: 'pending' },
143
+ pageNo: 1,
144
+ pageSize: 10,
145
+ orderBy: 'created_at DESC' // 或 'created_at ASC'
146
+ });
147
+ ```
148
+
149
+ ### 2.4 性能优化原理
150
+
151
+ 在 `orderBy = timeColumn DESC/ASC` 约束下:
152
+
153
+ 1. **表顺序保证**:ShardingRouter 返回的表顺序为 **新→旧**,ASC 时由 ShardingCrudPro 反转为 **旧→新**
154
+ 2. **无需内存排序**:每表内部按 `timeColumn DESC/ASC` 排序后,直接拼接即正确顺序
155
+ 3. **直接定位目标表**:根据各表 count 累计值,直接跳过无关表,只查询包含目标数据的表
156
+ 4. **精确表内偏移**:计算目标表内的 offset 和 limit,避免取多余数据
157
+
158
+ ```
159
+ 查询请求:pageNo=100, pageSize=10
160
+ 表顺序:[t_order_202403, t_order_202402, t_order_202401]
161
+ ↓ 最新表 ↓ 次新表 ↓ 最旧表
162
+
163
+ 执行流程:
164
+ 1. 统计各表数量:[5, 20, 1000]
165
+ 2. 累计计算:[5, 25, 1025]
166
+ 3. 目标范围:第 990~1000 条
167
+ 4. 定位:202403 累计5 < 990,跳过;202402 累计25 < 990,跳过
168
+ 5. 命中:202401 累计1025 >= 990
169
+ - 表内偏移 innerOffset = 990 - 25 = 965
170
+ - 取条数 innerLimit = 1000 - 990 = 10
171
+ 6. 只查 202401 一张表:OFFSET 965 LIMIT 10
172
+ ```
173
+
174
+ ## 三、各操作的路由规则
175
+
176
+ ### 写操作(insert/update/delete)
177
+
178
+ 写操作必须路由到**单一目标表**,无法确定时抛出异常。
179
+
180
+ | 操作 | 字段来源 | 路由方式 | 备注 |
181
+ |------|---------|---------|------|
182
+ | `insert` | `data[分表字段]` | 提取值 → 计算后缀 | 分表字段必须在 data 中 |
183
+ | `batchInsert` | `data[i][分表字段]` | 每条数据单独计算 | 自动按分表分组,支持跨分表并行写入 |
184
+ | `update` | `condition[分表字段]` | 提取值 → 计算后缀 | condition 决定分表,data 是更新内容 |
185
+ | `delete` | `condition[分表字段]` | 提取值 → 计算后缀 | condition 决定分表 |
186
+ | `insertOrUpdate` | `condition[分表字段]` | 提取值 → 计算后缀 | condition 决定分表 |
187
+ | `insertOnDuplicateUpdate` | `condition[分表字段]` | 提取值 → 计算后缀 | condition 决定分表 |
188
+
189
+ > **注意**:`insertOrUpdate`/`insertOnDuplicateUpdate` 使用 `condition` 路由,如果 `data` 中的分表字段指向不同分表,可能导致数据插入位置不符合预期。
190
+
191
+ ### 查询操作(query/queryOne/queryPage/queryCount/isExist)
192
+
193
+ 查询操作可路由到**多个分表**,结果由 ShardingMerger 自动合并。
194
+
195
+ - 自动过滤不存在的表
196
+ - 支持跨分表分页、排序
197
+ - 时间分表:根据 condition 中的时间范围生成候选表,与真实存在的表取交集
198
+ - 范围/哈希分表:condition 中有分表字段值 → 路由到单表;无 → 查询所有分表
199
+
200
+ ### 各分表策略路由差异
201
+
202
+ **时间分表(YEAR/MONTH/DAY)**:
203
+
204
+ | 操作 | 路由逻辑 |
205
+ |------|---------|
206
+ | insert/batchInsert | 从 data 提取时间 → 计算后缀 → 单表 |
207
+ | update/delete | 从 condition 提取时间 → 计算后缀 → 单表 |
208
+ | query 系列 | 从 condition 提取时间范围 → 生成候选表 → 与真实表取交集 → 多表 |
209
+
210
+ 查询时无法确定时间范围,默认返回最近N个分表(年3个/月12个/日7个)。
211
+
212
+ **范围分表(RANGE)**:
213
+
214
+ - 数值字段:`Math.abs(value) % tableCount`
215
+ - 非数值字段:`Math.abs(hash(String(value))) % tableCount`
216
+
217
+ **哈希分表(HASH)**:
218
+
219
+ - 使用 djb2 哈希算法:`Math.abs(simpleHash(String(value))) % tableCount`
220
+ - 后缀零填充:`String(index).padStart(2, '0')`
221
+
222
+ **自定义分表(CUSTOM)**:
223
+
224
+ - 调用用户提供的 `customRouter` 函数,完全自定义路由逻辑
225
+
226
+ ### batchInsert 跨分表
227
+
228
+ `batchInsert` 为每条数据单独计算目标分表,自动按分表分组后并行写入:
229
+
230
+ ```typescript
231
+ const result = await sharding.batchInsert({
232
+ data: [
233
+ { order_id: '001', created_at: '2024-01-15' }, // → t_order_202401
234
+ { order_id: '002', created_at: '2024-02-10' }, // → t_order_202402
235
+ { order_id: '003', created_at: '2024-03-05' }, // → t_order_202403
236
+ ]
237
+ });
238
+ console.log(result.tableCount); // 3(跨3张分表)
239
+ console.log(result.totalAffected); // 3
240
+ ```
241
+
242
+ ### 注意事项
243
+
244
+ 1. **插入类操作只使用 data**:`insert`/`batchInsert` 只需要在 `data` 中包含分表字段,不需要传 `condition`
245
+ 2. **查询类操作只使用 condition**:`query`/`delete`/`isExist` 等只需要在 `condition` 中包含分表字段,不需要传 `data`
246
+ 3. **更新类操作需要同时传 condition 和 data**:`condition` 用于路由,`data` 是更新内容
247
+ 4. **batchInsert 支持跨分表**:`batchInsert` 会自动按分表分组,支持数据分布在不同分表的场景
248
+ 5. **查询的时间范围限制**:时间分表查询时,如果无法确定时间范围,默认只查询最近N个分表(避免全表扫描)
249
+
250
+ ## 四、使用示例
251
+
252
+ ### 4.1 插入数据(只传 data)
253
+
254
+ ```typescript
255
+ const sharding = new ShardingCrudPro(crudPro, {
256
+ type: ShardingType.MONTH,
257
+ baseTable: 't_order',
258
+ timeColumn: 'created_at',
259
+ });
260
+
261
+ // 路由到 t_order_202403
262
+ await sharding.insert({
263
+ data: {
264
+ order_id: '001',
265
+ amount: 100,
266
+ created_at: '2024-03-15' // ← 分表字段必须在 data 中
267
+ }
268
+ // 注意:insert 操作不需要传 condition
269
+ });
270
+ ```
271
+
272
+ ### 4.2 更新数据
273
+
274
+ ```typescript
275
+ // 路由到 t_order_202403
276
+ await sharding.update({
277
+ condition: {
278
+ created_at: '2024-03-15' // ← 分表字段在 condition 中(用于路由)
279
+ },
280
+ data: { amount: 200 } // ← 更新内容(不参与路由)
281
+ });
282
+ ```
283
+
284
+ ### 4.3 删除数据
285
+
286
+ ```typescript
287
+ // 路由到 t_order_202403
288
+ await sharding.delete({
289
+ condition: {
290
+ created_at: '2024-03-15' // ← 分表字段在 condition 中
291
+ }
292
+ // 注意:delete 操作不需要传 data
293
+ });
294
+ ```
295
+
296
+ ### 4.4 查询数据
297
+
298
+ ```typescript
299
+ // 查询 t_order_202401, t_order_202402, t_order_202403
300
+ const orders = await sharding.query({
301
+ condition: {
302
+ created_at: {
303
+ $gte: '2024-01-01',
304
+ $lte: '2024-03-31' // ← 分表字段范围在 condition 中
305
+ }
306
+ }
307
+ });
308
+ ```
309
+
310
+ ### 4.5 批量插入
311
+
312
+ ```typescript
313
+ // 数据自动分组到不同分表
314
+ const result = await sharding.batchInsert({
315
+ data: [
316
+ { order_id: '001', created_at: '2024-01-15' }, // → t_order_202401
317
+ { order_id: '002', created_at: '2024-02-10' }, // → t_order_202402
318
+ { order_id: '003', created_at: '2024-03-05' }, // → t_order_202403
319
+ ]
320
+ // 注意:batchInsert 只需要传 data
321
+ });
322
+
323
+ console.log(result.tableCount); // 3
324
+ console.log(result.totalAffected); // 3
325
+ ```
326
+
327
+ ### 4.6 分页查询
328
+
329
+ ```typescript
330
+ // 场景1:基础分页查询(单表)
331
+ const result = await sharding.queryPage({
332
+ condition: { status: 'completed' },
333
+ pageNo: 1,
334
+ pageSize: 10,
335
+ orderBy: 'created_at DESC', // 必须传,且必须为 timeColumn DESC
336
+ });
337
+ console.log(result.rows); // 10条记录
338
+ console.log(result.total_count); // 符合条件的总数
339
+
340
+ // 场景2:跨多表分页查询(自动合并)
341
+ // 假设存在表:t_order_202401(50条), t_order_202402(80条), t_order_202403(30条)
342
+ // 查询第2页,每页20条(全局第21-40条)
343
+ const result2 = await sharding.queryPage({
344
+ condition: {
345
+ created_at: { $gte: '2024-01-01', $lte: '2024-03-31' },
346
+ status: 'pending'
347
+ },
348
+ pageNo: 2,
349
+ pageSize: 20,
350
+ orderBy: 'created_at DESC',
351
+ });
352
+ // 执行过程:
353
+ // 1. 统计各表数量:[50, 80, 30],累计:[50, 130, 160]
354
+ // 2. 目标范围:第21-40条(全局偏移20,取20条)
355
+ // 3. 定位:202401累计50 >= 21? 是,命中202401
356
+ // 4. 202401表内偏移:21 - 1 = 20(从第21条开始,即偏移20),取20条
357
+ // 结果仅包含202401的数据,无需跨表合并
358
+
359
+ // 场景3:大数据量分页(第100页)
360
+ const result3 = await sharding.queryPage({
361
+ condition: { created_at: { $gte: '2024-01-01' } },
362
+ pageNo: 100,
363
+ pageSize: 10,
364
+ orderBy: 'created_at DESC',
365
+ });
366
+ // 自动定位到包含第990-1000条数据的分表,只查询必要的表
367
+
368
+ // 场景4:带列筛选的分页
369
+ const result4 = await sharding.queryPage({
370
+ condition: { status: 'paid' },
371
+ columns: ['order_id', 'amount', 'created_at'], // 只返回指定列
372
+ pageNo: 1,
373
+ pageSize: 5,
374
+ orderBy: 'created_at DESC',
375
+ });
376
+
377
+ // 场景5:最后一页(数据不足pageSize)
378
+ const lastPage = await sharding.queryPage({
379
+ condition: {},
380
+ pageNo: 999, // 超出实际页数
381
+ pageSize: 10,
382
+ orderBy: 'created_at DESC',
383
+ });
384
+ // 返回空数组:lastPage.rows = [], lastPage.total_count = 实际总数
385
+
386
+ // 场景6:orderBy 数组格式(基础)
387
+ const result6 = await sharding.queryPage({
388
+ condition: { status: 'completed' },
389
+ pageNo: 1,
390
+ pageSize: 10,
391
+ orderBy: [{ fieldName: 'created_at', orderType: 'desc' }], // 单个排序字段
392
+ });
393
+
394
+ // 场景7:orderBy 数组格式(多字段排序)
395
+ // 首个字段必须是 timeColumn DESC,后续可附加次级排序
396
+ const result7 = await sharding.queryPage({
397
+ condition: { type: 'order' },
398
+ pageNo: 1,
399
+ pageSize: 20,
400
+ orderBy: [
401
+ { fieldName: 'created_at', orderType: 'desc' }, // 主排序:时间倒序(必须)
402
+ { fieldName: 'order_id', orderType: 'asc' } // 次排序:订单号正序
403
+ ],
404
+ });
405
+ // 说明:分表顺序已由 created_at DESC 保证,order_id 排序在单表内生效
406
+ // 最终结果按分表顺序拼接,每表内部按 (created_at DESC, order_id ASC) 排序
407
+
408
+ // 场景8:orderBy 数组格式(混合字符串和对象)
409
+ // 数组中字符串会被自动转为对象:'amount' → { fieldName: 'amount', orderType: 'asc' }
410
+ const result8 = await sharding.queryPage({
411
+ condition: { user_id: 'U123' },
412
+ pageNo: 2,
413
+ pageSize: 15,
414
+ orderBy: [
415
+ { fieldName: 'created_at', orderType: 'desc' }, // 对象格式
416
+ 'amount' // 字符串格式,自动转为 { fieldName: 'amount', orderType: 'asc' }
417
+ ],
418
+ });
419
+
420
+ // 场景9:orderBy 字符串简写格式(+/- 后缀)
421
+ const result9 = await sharding.queryPage({
422
+ condition: { category: 'electronics' },
423
+ pageNo: 1,
424
+ pageSize: 10,
425
+ orderBy: 'created_at-,amount+,order_id' // created_at降序, amount升序, order_id升序(默认)
426
+ });
427
+
428
+ // 场景10:orderBy 字符串标准格式(空格分隔)
429
+ const result10 = await sharding.queryPage({
430
+ condition: { status: 'paid' },
431
+ pageNo: 1,
432
+ pageSize: 10,
433
+ orderBy: 'created_at DESC, amount ASC' // 标准 SQL 格式
434
+ });
435
+
436
+ // 场景11:配置了 recentTableCount 的分页(只在最近N张表中查询)
437
+ // 配置:recentTableCount: 3,存在表:[202401, 202402, 202403, 202404, 202405]
438
+ // 无时间条件时,只取最近3张:202403, 202404, 202405
439
+ const result11 = await sharding.queryPage({
440
+ condition: { status: 'completed' }, // 无时间条件
441
+ pageNo: 1,
442
+ pageSize: 10,
443
+ orderBy: 'created_at DESC',
444
+ });
445
+ // 行为:仅在 202403, 202404, 202405 三张表中分页,忽略更早的表
446
+ // 如果这3张表总共只有25条数据,查询第3页(pageNo=3, offset=20)只能返回5条
447
+
448
+ // 场景12:未配置 recentTableCount 的分页(默认最多3张表)
449
+ // 配置:未设置 recentTableCount,存在表:[202401, 202402, 202403, 202404, 202405]
450
+ // 无时间条件时,所有真实表取交集后最多取3张(按时间倒序取最新的3张)
451
+ const result12 = await sharding.queryPage({
452
+ condition: { status: 'completed' }, // 无时间条件
453
+ pageNo: 1,
454
+ pageSize: 10,
455
+ orderBy: 'created_at DESC',
456
+ });
457
+ // 行为:与场景11类似,默认也是取最近3张表(202403, 202404, 202405)
458
+ // 区别在于:recentTableCount 显式配置时,会严格限制只查这N张表
459
+
460
+ // 场景13:配置 recentTableCount 但时间条件超出范围
461
+ // 配置:recentTableCount: 3,最近3张:[202403, 202404, 202405]
462
+ // 查询条件指定了 202401 的时间范围
463
+ const result13 = await sharding.queryPage({
464
+ condition: {
465
+ created_at: { $gte: '2024-01-01', $lte: '2024-01-31' }, // 202401月份
466
+ status: 'pending'
467
+ },
468
+ pageNo: 1,
469
+ pageSize: 10,
470
+ orderBy: 'created_at DESC',
471
+ });
472
+ // 行为:recentTableCount 限制与查询条件取交集
473
+ // 最近3张 [202403, 202404, 202405] 与 202401 无交集
474
+ // 结果:查询不到任何表,返回空结果
475
+
476
+ // 场景14:未配置 recentTableCount 时时间条件与真实表取交集
477
+ // 配置:未设置 recentTableCount,存在表:[202403, 202404, 202405](假设202401已删除)
478
+ // 查询条件指定了包含 202401-202405 的时间范围
479
+ const result14 = await sharding.queryPage({
480
+ condition: {
481
+ created_at: { $gte: '2024-01-01', $lte: '2024-05-31' }, // 1-5月
482
+ status: 'pending'
483
+ },
484
+ pageNo: 1,
485
+ pageSize: 10,
486
+ orderBy: 'created_at DESC',
487
+ });
488
+ // 行为:查询条件推导出的表 [202401, 202402, 202403, 202404, 202405]
489
+ // 与真实存在的表 [202403, 202404, 202405] 取交集
490
+ // 最终查询:202403, 202404, 202405(共3张,未超过上限)
491
+ ```
492
+
493
+ ## 五、时间分表查询行为
494
+
495
+ **核心原则:** 根据是否配置 `recentTableCount` 决定查询行为
496
+
497
+ | 配置 | 行为 |
498
+ |------|------|
499
+ | 配置了 `recentTableCount` | 从真实存在的表中取最近N张,再与查询条件取交集 |
500
+ | 未配置 `recentTableCount` | 所有真实存在的表与查询条件取交集,最多返回3张 |
501
+
502
+ **核心差异总结:**
503
+
504
+ - **配置了 `recentTableCount`**:先确定"最近N张表",再与查询条件取交集。如果查询的时间范围不在最近N张内,可能查不到数据。
505
+
506
+ - **未配置 `recentTableCount`**:查询条件推导出的表与"真实存在的表"取交集,最多返回3张(按时间倒序取最新的)。即使查询的是历史数据,只要表还存在就能查到。
507
+
508
+ ### 5.1 配置了 recentTableCount
509
+
510
+ ```typescript
511
+ const sharding = new ShardingCrudPro(crudPro, {
512
+ type: ShardingType.MONTH,
513
+ baseTable: 't_order',
514
+ recentTableCount: 3, // 最近3张表
515
+ });
516
+
517
+ // 场景1:无时间条件,查询最近3张真实存在的表
518
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
519
+ // 实际查询:t_order_202602, t_order_202603, t_order_202604(按时间倒序)
520
+ await sharding.query({ condition: { status: 'pending' } });
521
+
522
+ // 场景2:有时间范围,与最近3张取交集
523
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
524
+ // 最近3张:t_order_202602, t_order_202603, t_order_202604
525
+ // 查询范围:2024-01 ~ 2024-03(对应 t_order_202401, t_order_202402, t_order_202403)
526
+ // 实际查询:返回空(无交集)
527
+ await sharding.query({
528
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-03-31' } }
529
+ });
530
+
531
+ // 场景3:有时间范围,与最近3张有交集
532
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
533
+ // 最近3张:t_order_202602, t_order_202603, t_order_202604
534
+ // 查询范围:2024-02 ~ 2024-06(对应 t_order_202402 ~ t_order_202406)
535
+ // 实际查询:t_order_202602, t_order_202603, t_order_202604(交集)
536
+ await sharding.query({
537
+ condition: { created_at: { $gte: '2024-02-01', $lte: '2024-06-30' } }
538
+ });
539
+
540
+ // 场景4:单一时间值,与最近3张取交集
541
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
542
+ // 最近3张:t_order_202602, t_order_202603, t_order_202604
543
+ // 查询:2024-03-15(对应 t_order_202403)
544
+ // 实际查询:返回空(不在最近3张中)
545
+ await sharding.query({
546
+ condition: { created_at: '2024-03-15' }
547
+ });
548
+ ```
549
+
550
+ ### 5.2 未配置 recentTableCount
551
+
552
+ ```typescript
553
+ const sharding = new ShardingCrudPro(crudPro, {
554
+ type: ShardingType.MONTH,
555
+ baseTable: 't_order',
556
+ // 未配置 recentTableCount
557
+ });
558
+
559
+ // 场景1:无时间条件,所有真实表最多取3张
560
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
561
+ // 实际查询:t_order_202602, t_order_202603, t_order_202604(最后3张)
562
+ await sharding.query({ condition: { status: 'pending' } });
563
+
564
+ // 场景2:有时间范围,所有真实表与条件取交集,最多3张
565
+ // 假设真实存在的表:t_order_202401 ~ t_order_202604(20几个表)
566
+ // 查询范围:2024-01 ~ 2024-06(对应 t_order_202401 ~ t_order_202406)
567
+ // 实际查询:t_order_202404, t_order_202405, t_order_202406(交集后取3张)
568
+ await sharding.query({
569
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-06-30' } }
570
+ });
571
+
572
+ // 场景3:有时间范围,交集不足3张
573
+ // 假设真实存在的表:t_order_202401, t_order_202402, t_order_202403, t_order_202604
574
+ // 查询范围:2024-01 ~ 2024-03(对应 t_order_202401 ~ t_order_202403)
575
+ // 实际查询:t_order_202403, t_order_202402, t_order_202401(交集后3张)
576
+ await sharding.query({
577
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-03-31' } }
578
+ });
579
+ ```
580
+
581
+ ## 六、表存在性检查
582
+
583
+ ### 6.1 查询时的表存在性过滤
584
+
585
+ 所有查询操作(`query`/`queryOne`/`queryPage`/`queryCount`/`isExist`)都会自动过滤掉不存在的表:
586
+
587
+ ```typescript
588
+ // 假设数据库中只存在 t_order_202403, t_order_202404
589
+ // 但路由计算返回了 t_order_202401 ~ t_order_202404
590
+
591
+ const orders = await sharding.query({
592
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-04-30' } }
593
+ });
594
+
595
+ // 实际只查询存在的表:t_order_202403, t_order_202404
596
+ // 不存在的表会被自动过滤,不会报错
597
+ ```
598
+
599
+ ### 6.2 插入时的表存在性检查
600
+
601
+ 插入操作(`insert`/`batchInsert`)会先检查目标表是否存在:
602
+
603
+ ```typescript
604
+ // 路由到 t_order_202405,但该表不存在
605
+ try {
606
+ await sharding.insert({
607
+ data: { order_id: '001', created_at: '2024-05-15' }
608
+ });
609
+ } catch (e) {
610
+ // 抛出异常:分表 t_order_202405 不存在,请先创建
611
+ }
612
+ ```
613
+
614
+ **注意:** 自动创建分表功能暂未实现,需要预先创建好分表。
615
+
616
+ ## 七、完整路由场景对照表
617
+
618
+ ### 7.1 时间分表(YEAR/MONTH/DAY)
619
+
620
+ | 操作 | 字段来源 | 路由逻辑 | 表存在性处理 | 返回 |
621
+ |-----|---------|---------|-------------|------|
622
+ | `insert` | `data[timeColumn]` | 提取时间 → 计算后缀 | 检查存在性,不存在报错 | 单表 |
623
+ | `batchInsert` | `data[i][timeColumn]` | 每条数据计算分表 | 检查存在性,不存在报错 | 多表(按表分组,支持跨分表) |
624
+ | `update` | `condition[timeColumn]` | 提取时间 → 计算后缀 | 不检查,表不存在时数据库报错 | 单表 |
625
+ | `delete` | `condition[timeColumn]` | 提取时间 → 计算后缀 | 不检查,表不存在时数据库报错 | 单表 |
626
+ | `insertOrUpdate` | `condition[timeColumn]` | 提取时间 → 计算后缀 | 不检查 | 单表 |
627
+ | `insertOnDuplicateUpdate` | `condition[timeColumn]` | 提取时间 → 计算后缀 | 不检查 | 单表 |
628
+ | `query` | `condition[timeColumn]` | 与真实存在的表取交集 | 过滤不存在的表 | 多表 |
629
+ | `queryOne` | `condition[timeColumn]` | 同 query | 过滤不存在的表 | 多表(顺序查) |
630
+ | `queryPage` | `condition[timeColumn]` | 同 query | 过滤不存在的表 | 多表 |
631
+ | `queryCount` | `condition[timeColumn]` | 同 query | 过滤不存在的表 | 多表(结果求和) |
632
+ | `isExist` | `condition[timeColumn]` | 同 query | 过滤不存在的表 | 多表(任一为true) |
633
+
634
+ ### 7.2 范围分表(RANGE)
635
+
636
+ | 操作 | 字段来源 | 路由逻辑 | 表存在性处理 | 返回 |
637
+ |-----|---------|---------|-------------|------|
638
+ | `insert` | `data[shardingColumn]` | value % tableCount | 检查存在性,不存在报错 | 单表 |
639
+ | `batchInsert` | `data[i][shardingColumn]` | 每条数据计算分表 | 检查存在性,不存在报错 | 多表(按表分组,支持跨分表) |
640
+ | `update` | `condition[shardingColumn]` | value % tableCount | 不检查,表不存在时数据库报错 | 单表 |
641
+ | `delete` | `condition[shardingColumn]` | value % tableCount | 不检查,表不存在时数据库报错 | 单表 |
642
+ | `insertOrUpdate` | `condition[shardingColumn]` | value % tableCount | 不检查 | 单表 |
643
+ | `insertOnDuplicateUpdate` | `condition[shardingColumn]` | value % tableCount | 不检查 | 单表 |
644
+ | `query` | `condition[shardingColumn]` | 有值→单表;无→所有分表 | 过滤不存在的表 | 单表/多表 |
645
+ | `queryOne` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
646
+ | `queryPage` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
647
+ | `queryCount` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
648
+ | `isExist` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
649
+
650
+ ### 7.3 哈希分表(HASH)
651
+
652
+ | 操作 | 字段来源 | 路由逻辑 | 表存在性处理 | 返回 |
653
+ |-----|---------|---------|-------------|------|
654
+ | `insert` | `data[shardingColumn]` | hash(value) % tableCount | 检查存在性,不存在报错 | 单表 |
655
+ | `batchInsert` | `data[i][shardingColumn]` | 每条数据计算分表 | 检查存在性,不存在报错 | 多表(按表分组,支持跨分表) |
656
+ | `update` | `condition[shardingColumn]` | hash(value) % tableCount | 不检查,表不存在时数据库报错 | 单表 |
657
+ | `delete` | `condition[shardingColumn]` | hash(value) % tableCount | 不检查,表不存在时数据库报错 | 单表 |
658
+ | `insertOrUpdate` | `condition[shardingColumn]` | hash(value) % tableCount | 不检查 | 单表 |
659
+ | `insertOnDuplicateUpdate` | `condition[shardingColumn]` | hash(value) % tableCount | 不检查 | 单表 |
660
+ | `query` | `condition[shardingColumn]` | 有值→单表;无→所有分表 | 过滤不存在的表 | 单表/多表 |
661
+ | `queryOne` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
662
+ | `queryPage` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
663
+ | `queryCount` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
664
+ | `isExist` | `condition[shardingColumn]` | 同 query | 过滤不存在的表 | 单表/多表 |
665
+
666
+ ### 7.4 自定义分表(CUSTOM)
667
+
668
+ | 操作 | 字段来源 | 路由逻辑 | 表存在性处理 | 返回 |
669
+ |-----|---------|---------|-------------|------|
670
+ | `insert` | `data` | 调用 customRouter | 检查存在性,不存在报错 | 单表 |
671
+ | `batchInsert` | `data[i]` | 每条数据调用 customRouter | 检查存在性,不存在报错 | 多表(按表分组,支持跨分表) |
672
+ | `update` | `condition` | 调用 customRouter | 不检查,表不存在时数据库报错 | 单表 |
673
+ | `delete` | `condition` | 调用 customRouter | 不检查,表不存在时数据库报错 | 单表 |
674
+ | `insertOrUpdate` | `condition` | 调用 customRouter | 不检查 | 单表 |
675
+ | `insertOnDuplicateUpdate` | `condition` | 调用 customRouter | 不检查 | 单表 |
676
+ | `query` | `condition` | 调用 customRouter | 过滤不存在的表 | 单表/多表 |
677
+ | `queryOne` | `condition` | 调用 customRouter | 过滤不存在的表 | 单表/多表 |
678
+ | `queryPage` | `condition` | 调用 customRouter | 过滤不存在的表 | 单表/多表 |
679
+ | `queryCount` | `condition` | 调用 customRouter | 过滤不存在的表 | 单表/多表 |
680
+ | `isExist` | `condition` | 调用 customRouter | 过滤不存在的表 | 单表/多表 |
681
+
682
+ ## 八、错误处理与边界情况
683
+
684
+ ### 8.1 路由失败的情况
685
+
686
+ | 场景 | 错误信息 | 说明 |
687
+ |-----|---------|------|
688
+ | insert 缺少分表字段 | `insert 操作无法从 data 中获取分表字段 xxx 的值` | data 中必须包含 timeColumn/shardingColumn |
689
+ | update 缺少分表字段 | `查询/更新/删除操作无法从 condition 中获取分表字段 xxx 的值` | condition 中必须包含分表字段 |
690
+ | 插入到不存在的表 | `分表 xxx 不存在,请先创建` | 需要预先创建分表 |
691
+ | CUSTOM 缺少 router | `CUSTOM 分表必须提供 customRouter 函数` | 配置中必须提供 customRouter |
692
+
693
+ ### 8.2 边界情况处理
694
+
695
+ ```typescript
696
+ // 情况1:查询时所有候选表都不存在
697
+ const orders = await sharding.query({
698
+ condition: { created_at: { $gte: '2020-01-01', $lte: '2020-12-31' } } // 这些表都不存在
699
+ });
700
+ // 返回空数组 [],不会报错
701
+
702
+ // 情况2:没有真实存在的表
703
+ const sharding = new ShardingCrudPro(crudPro, {
704
+ type: ShardingType.MONTH,
705
+ baseTable: 't_new_table', // 没有任何分表
706
+ });
707
+ const orders = await sharding.query({ condition: {} });
708
+ // 返回空数组 [],不会报错
709
+
710
+ // 情况3:时间范围超大(超过100个分表)
711
+ const orders = await sharding.query({
712
+ condition: { created_at: { $gte: '2020-01-01', $lte: '2025-12-31' } } // 超过100个月
713
+ });
714
+ // 最多返回100个分表,避免性能问题
715
+ ```
716
+
717
+ ## 九、性能优化
718
+
719
+ ### 9.1 COUNT 查询缓存(时间分表专用)
720
+
721
+ 分页查询时需要先统计各分表的记录数,对于历史数据表(非当前月/日/年),COUNT 值相对稳定,可以启用缓存减少数据库查询。
722
+
723
+ **核心设计原则:**
724
+ - **历史表启用缓存**:非当前时间周期的表 COUNT 结果会被缓存
725
+ - **当前表实时查询**:当前月/日/年的表始终实时查询,确保数据准确性
726
+ - **LRU 淘汰机制**:缓存满时自动淘汰最久未使用的条目
727
+ - **TTL 过期机制**:缓存有有效期,过期后自动重新查询
728
+
729
+ **使用示例:**
730
+
731
+ ```typescript
732
+ const sharding = new ShardingCrudPro(crudPro, {
733
+ type: ShardingType.MONTH,
734
+ baseTable: 't_order',
735
+ timeColumn: 'created_at',
736
+ countCache: {
737
+ ttlSeconds: 300, // 缓存5分钟
738
+ maxSize: 1000, // 最多缓存1000个表的COUNT
739
+ },
740
+ });
741
+
742
+ // 第一次查询 202401、202402、202403 的 COUNT,会写入缓存
743
+ const result1 = await sharding.queryPage({
744
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-03-31' } },
745
+ pageNo: 1,
746
+ pageSize: 10,
747
+ orderBy: 'created_at DESC',
748
+ });
749
+
750
+ // 第二次查询相同范围,202401/202402/202403 的 COUNT 从缓存读取
751
+ // 202404(当前月)如果有数据,仍然实时查询
752
+ const result2 = await sharding.queryPage({
753
+ condition: { created_at: { $gte: '2024-01-01', $lte: '2024-04-30' } },
754
+ pageNo: 2,
755
+ pageSize: 10,
756
+ orderBy: 'created_at DESC',
757
+ });
758
+ ```
759
+
760
+ **缓存命中率分析:**
761
+
762
+ | 场景 | 缓存效果 |
763
+ |------|---------|
764
+ | 历史数据分页导航(第1页→第10页)| 高,COUNT 相同直接复用 |
765
+ | 不同时间范围查询 | 中,重叠部分复用 |
766
+ | 实时数据查询(当前月)| 无缓存,确保实时性 |
767
+ | 带复杂条件的查询 | 不同条件独立缓存 |
768
+
769
+ **注意事项:**
770
+ - 仅对 `YEAR`/`MONTH`/`DAY` 时间分表类型有效
771
+ - 缓存键包含表名+条件哈希,不同条件独立缓存
772
+ - 可通过 `sharding['merger']['countCache']?.clear()` 手动清空缓存
773
+
774
+ ### 9.2 缓存实现原理
775
+
776
+ #### 如何判断"当前表"
777
+
778
+ 缓存系统根据分表类型和当前时间自动识别"当前表":
779
+
780
+ | 分表类型 | 当前表示例(假设今天2024-04-10)| 缓存策略 |
781
+ |---------|------------------------------|---------|
782
+ | YEAR | t_order_2024 | 不缓存 |
783
+ | MONTH | t_order_202404 | 不缓存 |
784
+ | DAY | t_order_20240410 | 不缓存 |
785
+ | 历史表 | t_order_202403, t_order_202402... | 启用缓存 |
786
+
787
+ **判断逻辑:**
788
+ ```typescript
789
+ // 根据分表类型计算当前后缀
790
+ const now = new Date();
791
+
792
+ // YEAR: "2024"
793
+ // MONTH: "202404"
794
+ // DAY: "20240410"
795
+ const currentSuffix = getCurrentSuffix(now);
796
+ const expectedTable = `${baseTable}_${currentSuffix}`;
797
+
798
+ // 如果是当前表,实时查询;否则使用缓存
799
+ const isCurrent = (tableName === expectedTable);
800
+ ```
801
+
802
+ #### 缓存键设计
803
+
804
+ 缓存键由两部分组成,确保不同表、不同条件独立存储:
805
+
806
+ ```
807
+ 缓存键 = 表名 + "#" + 条件哈希
808
+
809
+ 示例:
810
+ - t_order_202401#* // 无条件查询
811
+ - t_order_202401#a1b2c3d4 // condition: { status: 'completed' }
812
+ - t_order_202401#x9y8z7w6 // condition: { status: 'pending', type: 'order' }
813
+ ```
814
+
815
+ **条件哈希算法:**
816
+ 1. 对 condition 对象按键排序后 JSON 序列化
817
+ 2. 使用 djb2 哈希算法生成 32 位整数
818
+ 3. 转为 36 进制字符串压缩存储
819
+
820
+ ```typescript
821
+ function shardingHashCondition(condition: object): string {
822
+ const sorted = sortKeys(condition);
823
+ const str = JSON.stringify(sorted);
824
+
825
+ let hash = 5381;
826
+ for (let i = 0; i < str.length; i++) {
827
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
828
+ }
829
+ return (hash >>> 0).toString(36);
830
+ }
831
+ ```
832
+
833
+ #### LRU 淘汰机制
834
+
835
+ 当缓存条目数达到上限(默认1000)时,自动淘汰最久未使用的条目:
836
+
837
+ ```typescript
838
+ // Map 保持插入顺序,第一个元素就是最久未使用的
839
+ const firstKey = this.cache.keys().next().value;
840
+ this.cache.delete(firstKey);
841
+ ```
842
+
843
+ **访问更新策略:**
844
+ - 缓存命中时:删除旧条目,重新插入(移到末尾)
845
+ - 缓存未命中时:直接插入新条目
846
+ - 淘汰时:删除 Map 头部(最久未使用)
847
+
848
+ #### TTL 过期机制
849
+
850
+ 每个缓存条目记录缓存时间,查询时检查是否过期:
851
+
852
+ ```typescript
853
+ interface CacheEntry {
854
+ count: number; // 缓存的 COUNT 值
855
+ cachedAt: number; // 缓存时间戳(毫秒)
856
+ key: string; // 缓存键
857
+ }
858
+
859
+ // 查询时检查
860
+ const entry = cache.get(key);
861
+ if (Date.now() - entry.cachedAt > ttlMs) {
862
+ cache.delete(key); // 过期删除
863
+ return undefined; // 返回未命中
864
+ }
865
+ ```
866
+
867
+ ### 9.3 缓存效果对比
868
+
869
+ **场景:查询2024年1-3月数据,分页导航第1-5页**
870
+
871
+ | 步骤 | 无缓存 | 有缓存(5分钟TTL)| 说明 |
872
+ |------|-------|------------------|------|
873
+ | 第1页 | 查3张表 COUNT | 查3张表 COUNT | 首次查询,无缓存 |
874
+ | 第2页 | 查3张表 COUNT | 从缓存读取 | 历史表 COUNT 未变 |
875
+ | 第3页 | 查3张表 COUNT | 从缓存读取 | 历史表 COUNT 未变 |
876
+ | 第4页 | 查3张表 COUNT | 从缓存读取 | 历史表 COUNT 未变 |
877
+ | 第5页 | 查3张表 COUNT | 从缓存读取 | 历史表 COUNT 未变 |
878
+ | **总计** | **15次 COUNT 查询** | **3次 COUNT 查询** | **节省80%查询** |
879
+
880
+ ### 9.4 高级配置示例
881
+
882
+ ```typescript
883
+ // 高频读场景:长缓存时间 + 大容量
884
+ const readHeavySharding = new ShardingCrudPro(crudPro, {
885
+ type: ShardingType.MONTH,
886
+ baseTable: 't_order',
887
+ timeColumn: 'created_at',
888
+ countCache: {
889
+ ttlSeconds: 1800, // 30分钟,历史数据很少变化
890
+ maxSize: 5000, // 缓存5000个表的COUNT
891
+ },
892
+ });
893
+
894
+ // 实时性要求高:短缓存时间 + 小容量
895
+ const nearRealTimeSharding = new ShardingCrudPro(crudPro, {
896
+ type: ShardingType.MONTH,
897
+ baseTable: 't_order',
898
+ timeColumn: 'created_at',
899
+ countCache: {
900
+ ttlSeconds: 60, // 1分钟,快速刷新
901
+ maxSize: 100, // 只缓存最近查询的表
902
+ },
903
+ });
904
+
905
+ // 不启用缓存(实时性最高)
906
+ const noCacheSharding = new ShardingCrudPro(crudPro, {
907
+ type: ShardingType.MONTH,
908
+ baseTable: 't_order',
909
+ timeColumn: 'created_at',
910
+ // 不配置 countCache
911
+ });
912
+ ```
913
+
914
+ ## 十、公共工具函数
915
+
916
+ 分表模块提供以下工具函数,可用于自定义逻辑:
917
+
918
+ ```typescript
919
+ import {
920
+ getTimeSuffix, // 获取日期的时间后缀
921
+ getCurrentSuffix, // 获取当前时间后缀
922
+ isCurrentTable, // 判断是否为当前表
923
+ shardingTypeToGranularity, // 分表类型转时间粒度
924
+ } from '@/libs/crud-sharding';
925
+
926
+ // 获取任意日期的时间后缀
927
+ const suffix1 = getTimeSuffix(new Date('2024-03-15'), 'month'); // "202403"
928
+ const suffix2 = getTimeSuffix(new Date('2024-03-15'), 'day'); // "20240315"
929
+
930
+ // 获取当前时间后缀(根据分表类型)
931
+ const currentSuffix = getCurrentSuffix(ShardingType.MONTH); // 如 "202404"
932
+
933
+ // 判断表名是否为当前表
934
+ const isCurrent = isCurrentTable('t_order_202404', 't_order', ShardingType.MONTH);
935
+ ```
936
+
937
+ **函数签名:**
938
+
939
+ | 函数 | 参数 | 返回值 | 说明 |
940
+ |------|------|--------|------|
941
+ | `getTimeSuffix(date, granularity)` | `Date`, `'year'\|'month'\|'day'` | `string` | 获取日期对应的时间后缀 |
942
+ | `getCurrentSuffix(shardingType)` | `ShardingType` | `string` | 获取当前时间周期的后缀 |
943
+ | `isCurrentTable(table, baseTable, type)` | `string`, `string`, `ShardingType` | `boolean` | 判断是否为当前表 |
944
+ | `shardingTypeToGranularity(type)` | `ShardingType` | `ShardingTimeGranularity\|undefined` | 分表类型转时间粒度 |