midway-fatcms 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.qoder/skills/midway-fatcms/01-quick-start.md +231 -0
  2. package/.qoder/skills/midway-fatcms/02-crud-quick.md +337 -0
  3. package/.qoder/skills/midway-fatcms/03-crud-sharding.md +488 -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 +500 -0
  8. package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
  9. package/README.md +9 -9
  10. package/dist/controller/base/BaseApiController.d.ts +1 -2
  11. package/dist/controller/base/BaseApiController.js +0 -4
  12. package/dist/controller/gateway/DocGatewayController.js +1 -1
  13. package/dist/controller/manage/FlowConfigManageApi.js +4 -2
  14. package/dist/controller/manage/SysConfigMangeApi.js +6 -1
  15. package/dist/controller/manage/UserAccountManageApi.js +7 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +2 -2
  18. package/dist/libs/crud-pro/CrudPro.d.ts +23 -2
  19. package/dist/libs/crud-pro/CrudPro.js +53 -2
  20. package/dist/libs/crud-pro/interfaces.d.ts +82 -12
  21. package/dist/libs/crud-pro/models/CrudResult.d.ts +115 -0
  22. package/dist/libs/crud-pro/models/CrudResult.js +126 -0
  23. package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -2
  24. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
  25. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
  26. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +1 -2
  27. package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +295 -0
  28. package/dist/libs/crud-pro-quick/CrudProQuick.js +529 -0
  29. package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
  30. package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
  31. package/dist/libs/crud-pro-quick/index.d.ts +36 -0
  32. package/dist/libs/crud-pro-quick/index.js +49 -0
  33. package/dist/libs/crud-pro-quick/models.d.ts +33 -0
  34. package/dist/libs/crud-pro-quick/models.js +2 -0
  35. package/dist/libs/crud-sharding/ShardingConfig.d.ts +15 -2
  36. package/dist/libs/crud-sharding/ShardingConfig.js +2 -2
  37. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +119 -274
  38. package/dist/libs/crud-sharding/ShardingCrudPro.js +544 -340
  39. package/dist/libs/crud-sharding/ShardingMerger.d.ts +10 -18
  40. package/dist/libs/crud-sharding/ShardingMerger.js +27 -44
  41. package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
  42. package/dist/libs/crud-sharding/ShardingResult.js +16 -0
  43. package/dist/libs/crud-sharding/ShardingRouter.d.ts +1 -0
  44. package/dist/libs/crud-sharding/ShardingRouter.js +25 -6
  45. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
  46. package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
  47. package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
  48. package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
  49. package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  50. package/dist/libs/crud-sharding/index.d.ts +4 -3
  51. package/dist/libs/crud-sharding/index.js +14 -2
  52. package/dist/models/bizmodels.d.ts +2 -6
  53. package/dist/service/SysAppService.d.ts +2 -2
  54. package/dist/service/SysAppService.js +16 -5
  55. package/dist/service/SysConfigService.d.ts +1 -1
  56. package/dist/service/SysConfigService.js +7 -2
  57. package/dist/service/SysDictDataService.js +14 -4
  58. package/dist/service/SysMenuService.js +7 -2
  59. package/dist/service/curd/CurdMixService.d.ts +6 -4
  60. package/dist/service/curd/CurdMixService.js +16 -2
  61. package/dist/service/curd/CurdProService.d.ts +43 -27
  62. package/dist/service/curd/CurdProService.js +32 -33
  63. package/dist/service/flow/FlowConfigService.js +7 -2
  64. package/dist/service/flow/FlowInstanceCrudService.js +22 -19
  65. package/package.json +1 -1
  66. package/src/controller/base/BaseApiController.ts +0 -5
  67. package/src/controller/gateway/DocGatewayController.ts +1 -1
  68. package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
  69. package/src/controller/manage/FlowConfigManageApi.ts +4 -2
  70. package/src/controller/manage/SysConfigMangeApi.ts +6 -1
  71. package/src/controller/manage/UserAccountManageApi.ts +7 -2
  72. package/src/index.ts +2 -2
  73. package/src/libs/crud-pro/CrudPro.ts +62 -4
  74. package/src/libs/crud-pro/interfaces.ts +110 -15
  75. package/src/libs/crud-pro/models/CrudResult.ts +178 -0
  76. package/src/libs/crud-pro/models/RequestModel.ts +2 -2
  77. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
  78. package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
  79. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +1 -2
  80. package/src/libs/crud-pro-quick/CrudProQuick.ts +594 -0
  81. package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
  82. package/src/libs/crud-pro-quick/index.ts +52 -0
  83. package/src/libs/crud-pro-quick/models.ts +35 -0
  84. package/src/libs/crud-sharding/ShardingConfig.ts +18 -2
  85. package/src/libs/crud-sharding/ShardingCrudPro.ts +660 -390
  86. package/src/libs/crud-sharding/ShardingMerger.ts +35 -63
  87. package/src/libs/crud-sharding/ShardingResult.ts +29 -0
  88. package/src/libs/crud-sharding/ShardingRouter.ts +27 -6
  89. package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
  90. package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
  91. package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  92. package/src/libs/crud-sharding/index.ts +14 -3
  93. package/src/models/bizmodels.ts +4 -7
  94. package/src/service/SysAppService.ts +18 -7
  95. package/src/service/SysConfigService.ts +8 -3
  96. package/src/service/SysDictDataService.ts +14 -4
  97. package/src/service/SysMenuService.ts +7 -2
  98. package/src/service/crudstd/CrudStdService.ts +2 -2
  99. package/src/service/curd/CurdMixService.ts +26 -5
  100. package/src/service/curd/CurdProService.ts +58 -39
  101. package/src/service/flow/FlowConfigService.ts +7 -2
  102. package/src/service/flow/FlowInstanceCrudService.ts +23 -20
  103. package/.qoder/skills/midway-fatcms-crud/SKILL.md +0 -375
  104. package/.qoder/skills/midway-fatcms-crud/examples.md +0 -990
  105. package/.qoder/skills/midway-fatcms-crud/reference.md +0 -568
  106. package/dist/libs/crud-pro/README.md +0 -809
  107. package/dist/libs/crud-pro/README_FUNC.md +0 -193
  108. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  109. package/dist/models/StandardColumns.d.ts +0 -71
  110. package/dist/models/StandardColumns.js +0 -28
  111. package/dist/service/curd/CrudProQuick.d.ts +0 -190
  112. package/dist/service/curd/CrudProQuick.js +0 -319
  113. package/dist/service/curd/README.md +0 -1100
  114. package/dist/service/curd/fixSoftDelete.d.ts +0 -20
  115. package/src/libs/crud-pro/README.md +0 -809
  116. package/src/libs/crud-pro/README_FUNC.md +0 -193
  117. package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  118. package/src/models/StandardColumns.ts +0 -76
  119. package/src/service/curd/CrudProQuick.ts +0 -360
  120. package/src/service/curd/README.md +0 -1100
@@ -0,0 +1,488 @@
1
+ # ShardingCrudPro 分表操作
2
+
3
+ ShardingCrudPro 提供自动路由分表的能力,支持时间分表、哈希分表、范围分表、自定义分表四种策略。
4
+
5
+ ## 获取实例
6
+
7
+ ```typescript
8
+ import { ShardingType } from 'midway-fatcms';
9
+
10
+ const sharding = this.curdProService.getShardingCrud({
11
+ sqlDatabase: 'mydb',
12
+ sqlDbType: SqlDbType.mysql,
13
+ shardingConfig: {
14
+ type: ShardingType.MONTH,
15
+ baseTable: 't_order',
16
+ timeColumn: 'created_at',
17
+ autoCreateTable: true, // 自动创建分表(可选)
18
+ recentTableCount: 3, // 无时间范围时查询最近N张表(可选)
19
+ countCache: { // COUNT 查询缓存(仅时间分表,可选)
20
+ ttlSeconds: 300, // 缓存5分钟
21
+ maxSize: 1000, // 最多缓存1000个表
22
+ },
23
+ },
24
+ });
25
+ ```
26
+
27
+ ## 分表类型
28
+
29
+ | 类型 | 枚举值 | 后缀示例 | 适用场景 |
30
+ |------|--------|----------|----------|
31
+ | 按年 | `ShardingType.YEAR` | `_2024` | 数据量适中,按年归档 |
32
+ | 按月 | `ShardingType.MONTH` | `_202401` | 大数据量,按月分表 |
33
+ | 按日 | `ShardingType.DAY` | `_20240115` | 超大数据量,按天分表 |
34
+ | 范围 | `ShardingType.RANGE` | `_0`, `_1`... | 按数值范围分布 |
35
+ | 哈希 | `ShardingType.HASH` | `_01`, `_02`... | 均匀分布数据 |
36
+ | 自定义 | `ShardingType.CUSTOM` | 自定义 | 特殊业务规则 |
37
+
38
+ ## 配置选项
39
+
40
+ ```typescript
41
+ interface IShardingConfig {
42
+ type: ShardingType; // 分表类型(必填)
43
+ baseTable: string; // 基础表名(必填)
44
+
45
+ // 时间分表(YEAR/MONTH/DAY)
46
+ timeColumn?: string; // 时间字段名(时间分表必填)
47
+ primaryKey?: string; // 主键字段(时间分表可选,用于清理时间精度问题)
48
+ recentTableCount?: number; // 无时间条件时查询最近N张表(默认:YEAR=2, MONTH=3, DAY=7)
49
+ autoCreateTable?: boolean; // 自动创建分表(默认false)
50
+ tableCreateOptions?: { // 分表创建选项
51
+ copyIndexes?: boolean; // 复制原表索引
52
+ tableOptions?: string; // 表选项(如MySQL引擎配置)
53
+ };
54
+
55
+ // 范围/哈希分表(RANGE/HASH)
56
+ shardingColumn?: string; // 分表字段(RANGE/HASH必填)
57
+ tableCount?: number; // 分表数量(RANGE默认10,HASH默认16)
58
+
59
+ // COUNT 缓存(仅时间分表有效)
60
+ countCache?: {
61
+ ttlSeconds?: number; // 缓存有效期(秒),默认300
62
+ maxSize?: number; // 最大缓存条目,默认1000
63
+ };
64
+
65
+ // 自定义配置(CUSTOM)
66
+ customRouter?: (ctx) => string | string[]; // 自定义路由函数
67
+ suffixFormatter?: (suffix) => string; // 后缀格式化函数
68
+ }
69
+ ```
70
+
71
+ ## API 总览
72
+
73
+ ### 查询方法
74
+
75
+ | 方法 | 返回类型 | 说明 |
76
+ |------|----------|------|
77
+ | `findOne(req)` | `CrudQueryOneResult<T>` | 查询单条(顺序查多表,找到即返回) |
78
+ | `findUniqueOne(req)` | `CrudQueryOneResult<T>` | 唯一查询(0条返回null,多条报错) |
79
+ | `find(req)` | `T[]` | 列表查询(多表自动合并) |
80
+ | `findList(req)` | `CrudQueryListResult<T>` | 列表查询(带结果包装) |
81
+ | `findPage(req)` | `CrudQueryPageResult<T>` | 分页查询(跨分表自动处理) |
82
+ | `findCount(req)` | `CrudCountResult` | 统计总数(多表并行查询后汇总) |
83
+ | `isExist(req)` | `CrudExistResult` | 存在性判断(多表并行,任一存在即返回true) |
84
+
85
+ ### 写入方法
86
+
87
+ | 方法 | 返回类型 | 说明 |
88
+ |------|----------|------|
89
+ | `insert(req)` | `CrudWriteResult` | 插入单条(自动路由到对应分表) |
90
+ | `batchInsert(req)` | `ShardingBatchInsertResult` | 批量插入(自动按分表分组并行写入) |
91
+ | `update(req)` | `CrudWriteResult` | 更新(必须能确定单一目标表) |
92
+ | `delete(req)` | `CrudWriteResult` | 删除(必须能确定单一目标表) |
93
+ | `restore(req)` | `CrudWriteResult` | 恢复软删除记录(重置 deleted_at/deleted_by) |
94
+ | `insertOrUpdate(req)` | `CrudUpsertResult` | 插入或更新(condition决定分表) |
95
+ | `insertOnDuplicateUpdate(req)` | `CrudWriteResult` | 原生upsert(condition决定分表) |
96
+
97
+ ### 配置方法
98
+
99
+ | 方法 | 返回类型 | 说明 |
100
+ |------|----------|------|
101
+ | `setBaseCfg(cfg)` | `this` | 设置基础配置,支持链式调用 |
102
+ | `setEnableSoftDelete(enable)` | `this` | 设置是否启用软删除,支持链式调用 |
103
+ | `getConfig()` | `IShardingConfig` | 获取分表配置 |
104
+
105
+ ## 路由规则详解
106
+
107
+ 分表路由核心原则:**根据操作类型决定从 `data` 还是 `condition` 中提取分表字段**。
108
+
109
+ ### 操作类型与字段来源对照表
110
+
111
+ | 操作类型 | 字段来源 | 路由逻辑 | 说明 |
112
+ |---------|---------|---------|------|
113
+ | `insert` | `data[分表字段]` | 提取值 → 计算后缀 → 单表 | 插入单条数据 |
114
+ | `batchInsert` | `data[i][分表字段]` | 每条数据单独计算分表 | 支持跨分表并行写入 |
115
+ | `update/delete` | `condition[分表字段]` | 提取值 → 计算后缀 → 单表 | condition决定分表,data是更新内容 |
116
+ | `insertOrUpdate` | `condition[分表字段]` | 提取值 → 计算后缀 → 单表 | condition决定分表 |
117
+ | `insertOnDuplicateUpdate` | `condition[分表字段]` | 提取值 → 计算后缀 → 单表 | condition决定分表 |
118
+ | `find/findPage` | `condition[分表字段]` | 时间范围 → 候选表 → 与真实表取交集 → 多表 | 自动合并多表结果 |
119
+ | `findOne/findUniqueOne` | `condition[分表字段]` | 同 find | 顺序查询各分表 |
120
+ | `findCount/isExist` | `condition[分表字段]` | 同 find | 多表并行查询后汇总 |
121
+
122
+ ### 关键结论
123
+
124
+ - **插入类操作**(insert/batchInsert):只使用 `data`,不需要传 `condition`
125
+ - **查询类操作**(find/delete/isExist等):只使用 `condition`,不使用 `data`
126
+ - **更新类操作**(update/insertOrUpdate/insertOnDuplicateUpdate):`condition` 用于路由,`data` 是更新内容
127
+
128
+ ## 时间分表约束
129
+
130
+ ### 核心原则:写操作必须包含 timeColumn
131
+
132
+ 时间分表根据 timeColumn 路由到正确的分表。**所有写操作(insert/update/delete/restore)必须在 data 或 condition 中提供 timeColumn 字段**,否则无法确定目标分表。
133
+
134
+ ### 写入校验
135
+
136
+ | 操作 | 校验规则 | 错误提示 |
137
+ |------|---------|----------|
138
+ | `insert` | `data` 必须包含 `timeColumn` | `insert 操作的 data 必须包含时间字段 'xxx'` |
139
+ | `batchInsert` | 每条 `data[i]` 必须包含 `timeColumn` | `batchInsert 操作的 data[i] 必须包含时间字段 'xxx'` |
140
+ | `update` | `condition` 必须包含 `timeColumn` | `update 操作的 condition 必须包含时间字段 'xxx'` |
141
+ | `delete` | `condition` 必须包含 `timeColumn` | `delete 操作的 condition 必须包含时间字段 'xxx'` |
142
+ | `restore` | `condition` 必须包含 `timeColumn` | `restore 操作的 condition 必须包含时间字段 'xxx'` |
143
+ | `insertOrUpdate` | `data` + `condition` 均须包含 `timeColumn` | 同上 |
144
+ | `insertOnDuplicateUpdate` | `data` + `condition` 均须包含 `timeColumn` | 同上 |
145
+
146
+ ```typescript
147
+ // ✅ 正确 - insert 包含时间字段
148
+ await sharding.insert({
149
+ data: { order_id: '001', amount: 100, created_at: '2024-03-15' }
150
+ });
151
+
152
+ // ❌ 错误 - insert 缺少时间字段
153
+ await sharding.insert({
154
+ data: { order_id: '001', amount: 100 } // 报错:无法路由到目标分表
155
+ });
156
+
157
+ // ✅ 正确 - update 包含时间字段
158
+ await sharding.update({
159
+ condition: { order_id: '001', created_at: '2024-03-15' },
160
+ data: { status: 'shipped' }
161
+ });
162
+
163
+ // ❌ 错误 - update 缺少时间字段
164
+ await sharding.update({
165
+ condition: { order_id: '001' }, // 报错:无法路由到目标分表
166
+ data: { status: 'shipped' }
167
+ });
168
+ ```
169
+
170
+ ### 查询约束
171
+
172
+ 时间分表查询(`find`/`findPage`/`findList`)在多表场景下有以下强制约束:
173
+
174
+ | 约束项 | 要求 | 原因 |
175
+ |-------|------|------|
176
+ | **orderBy 必传** | 必须传 orderBy 参数 | 分表查询必须明确排序方式 |
177
+ | **排序字段** | 必须为 `timeColumn DESC` 或 `timeColumn ASC` | 利用分表顺序特性,无需内存排序 |
178
+ | **排序方向** | 支持 `DESC`(最新在前)和 `ASC`(最旧在前) | DESC时表顺序新→旧,ASC时旧→新 |
179
+
180
+ ```typescript
181
+ // ✅ 正确用法
182
+ await sharding.find({
183
+ condition: { status: 'pending' },
184
+ orderBy: 'created_at DESC', // 必须为 timeColumn DESC/ASC
185
+ });
186
+
187
+ // ❌ 错误用法
188
+ await sharding.find({
189
+ condition: { status: 'pending' },
190
+ orderBy: 'id DESC', // 错误:不是时间字段
191
+ });
192
+ ```
193
+
194
+ ### 时间字段智能处理
195
+
196
+ 时间分表中,`timeColumn` 既是路由键也是查询条件。用户传入的时间值可能精度不一致(如传 `'2024-01-15'` 但数据库存的是毫秒时间戳),直接作为 WHERE 条件可能导致匹配失败。系统会根据值类型和主键情况自动智能处理:
197
+
198
+ #### 处理规则
199
+
200
+ | timeColumn 值类型 | 有 primaryKey? | 操作 | 原因 |
201
+ |-------------------|---------------|------|------|
202
+ | 操作符表达式($gte/$lte/$range/$in/$null 等) | 任意 | **保留** | 用户明确要按操作符查询 |
203
+ | 精确值(string/number/boolean/Date) | 有 | **清理**(移除出 WHERE) | 主键已唯一确定行,timeColumn 仅用于路由 |
204
+ | 日期粒度字符串(`'2024'`/`'2024-01'`/`'2024-01-15'`) | 无 | **转换为 $gte/$lte 范围** | 精确值可能精度不匹配,转为范围更安全 |
205
+ | 其他精确值(时间戳/datetime/null等) | 无 | **保留原值** | 无法推断粒度或精度已足够 |
206
+
207
+ #### 有主键 → 清理
208
+
209
+ ```typescript
210
+ // 配置:{ primaryKey: 'order_id', timeColumn: 'created_at' }
211
+
212
+ // 清理前
213
+ { order_id: 'ORD001', created_at: '2024-01-15' }
214
+ // SQL: WHERE order_id = 'ORD001' AND created_at = '2024-01-15' ← 精度可能不匹配
215
+
216
+ // 自动清理后
217
+ { order_id: 'ORD001' }
218
+ // SQL: WHERE order_id = 'ORD001' ← 精准匹配
219
+ ```
220
+
221
+ #### 无主键 + 日期粒度字符串 → 转换为范围
222
+
223
+ ```typescript
224
+ // '2024' → 年范围
225
+ { status: 'paid', created_at: '2024' }
226
+ → { status: 'paid', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-12-31 23:59:59' } }
227
+
228
+ // '2024-01' → 月范围(闰年自动处理)
229
+ { status: 'paid', created_at: '2024-01' }
230
+ → { status: 'paid', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-01-31 23:59:59' } }
231
+
232
+ // '2024-01-15' → 日范围
233
+ { status: 'paid', created_at: '2024-01-15' }
234
+ → { status: 'paid', created_at: { $gte: '2024-01-15 00:00:00', $lte: '2024-01-15 23:59:59' } }
235
+
236
+ // '2024-01-15 10:30:00' → 精确到秒,不转换
237
+ { status: 'paid', created_at: '2024-01-15 10:30:00' }
238
+ // 保留原值
239
+ ```
240
+
241
+ #### 操作符表达式 → 始终保留
242
+
243
+ ```typescript
244
+ // 无论有无主键,操作符表达式一律保留
245
+ { order_id: 'ORD001', created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-06-30 23:59:59' } }
246
+ // 不做任何处理,SQL: WHERE order_id = 'ORD001' AND created_at >= '2024-01-01 00:00:00' AND created_at <= '2024-06-30 23:59:59'
247
+ ```
248
+
249
+ 适用场景:所有操作(读 + 写)的 condition 都会经过智能处理。
250
+
251
+ #### ⚠️ $gte/$lte 时间精度要求
252
+
253
+ 当使用 `$gte`/`$lte`/`$gt`/`$lt` 操作符查询时间字段时,**值必须精确到秒**(`YYYY-MM-DD HH:mm:ss`),不允许只传日期部分。
254
+
255
+ 原因:MySQL 将 `'2026-04-30'` 等价于 `'2026-04-30 00:00:00'`,导致 `$lte: '2026-04-30'` 会丢失当天所有数据。
256
+
257
+ ```typescript
258
+ // ✅ 正确 - 精确到秒,覆盖完整月份
259
+ condition: { created_at: { $gte: '2026-04-01 00:00:00', $lte: '2026-04-30 23:59:59' } }
260
+
261
+ // ❌ 错误 - 缺少时间部分,4月30日当天数据全部丢失
262
+ condition: { created_at: { $gte: '2026-04-01', $lte: '2026-04-30' } }
263
+ // 等价于 created_at <= '2026-04-30 00:00:00',4月30日全天数据被排除
264
+
265
+ // ✅ 正确 - 精确到秒,覆盖完整日期
266
+ condition: { created_at: { $gte: '2026-04-15 00:00:00', $lte: '2026-04-15 23:59:59' } }
267
+ ```
268
+
269
+ **替代方案**:如果只想查某个月/某天的数据,直接传日期粒度字符串即可,系统会自动转换为精确到秒的范围:
270
+
271
+ ```typescript
272
+ // ✅ 推荐 - 直接传粒度字符串,系统自动转范围(精确到秒)
273
+ condition: { created_at: '2026-04' } // → { $gte: '2026-04-01 00:00:00', $lte: '2026-04-30 23:59:59' }
274
+ condition: { created_at: '2026-04-15' } // → { $gte: '2026-04-15 00:00:00', $lte: '2026-04-15 23:59:59' }
275
+ condition: { created_at: '2026' } // → { $gte: '2026-01-01 00:00:00', $lte: '2026-12-31 23:59:59' }
276
+ ```
277
+
278
+ ### 软删除支持
279
+
280
+ ShardingCrudPro 支持软删除模式,启用后 `delete` 操作会设置 `deleted_at` 和 `deleted_by` 字段,而非物理删除:
281
+
282
+ ```typescript
283
+ const sharding = this.curdProService
284
+ .getShardingCrud({
285
+ sqlDatabase: 'mydb',
286
+ sqlDbType: SqlDbType.mysql,
287
+ shardingConfig: {
288
+ type: ShardingType.MONTH,
289
+ baseTable: 't_order',
290
+ timeColumn: 'created_at',
291
+ },
292
+ })
293
+ .setEnableSoftDelete(true);
294
+
295
+ // 软删除
296
+ await sharding.delete({ condition: { id: 1, created_at: '2024-03-15' } });
297
+
298
+ // 恢复软删除的记录
299
+ await sharding.restore({ condition: { id: 1, created_at: '2024-03-15' } });
300
+ ```
301
+
302
+ ## 查询行为详解
303
+
304
+ ### 配置了 recentTableCount
305
+
306
+ ```typescript
307
+ const sharding = this.curdProService.getShardingCrud({
308
+ sqlDatabase: 'mydb',
309
+ sqlDbType: SqlDbType.mysql,
310
+ shardingConfig: {
311
+ type: ShardingType.MONTH,
312
+ baseTable: 't_order',
313
+ recentTableCount: 3, // 最近3张表
314
+ },
315
+ });
316
+ ```
317
+
318
+ **行为**:从真实存在的表中取最近N张,再与查询条件取交集。
319
+
320
+ | 场景 | 行为 |
321
+ |------|------|
322
+ | 无时间条件 | 查询最近3张真实存在的表 |
323
+ | 有时间范围 | 与最近3张表取交集 |
324
+ | 时间范围不在最近N张内 | 可能返回空结果 |
325
+
326
+ ### 未配置 recentTableCount
327
+
328
+ **行为**:查询条件推导出的表与"真实存在的表"取交集,最多返回3张(按时间倒序取最新的)。
329
+
330
+ | 场景 | 行为 |
331
+ |------|------|
332
+ | 无时间条件 | 所有真实表最多取3张 |
333
+ | 有时间范围 | 条件推导的表与真实表取交集,最多3张 |
334
+ | 历史数据查询 | 只要表还存在就能查到 |
335
+
336
+ ## 使用示例
337
+
338
+ ### 按月分表
339
+
340
+ ```typescript
341
+ import { ShardingType } from 'midway-fatcms';
342
+
343
+ const sharding = this.curdProService.getShardingCrud({
344
+ sqlDatabase: 'mydb',
345
+ sqlDbType: SqlDbType.mysql,
346
+ shardingConfig: {
347
+ type: ShardingType.MONTH,
348
+ baseTable: 't_order',
349
+ timeColumn: 'created_at',
350
+ autoCreateTable: true,
351
+ },
352
+ });
353
+
354
+ // 插入数据 - 自动路由到 t_order_202403
355
+ await sharding.insert({
356
+ data: {
357
+ order_id: '001',
358
+ amount: 100,
359
+ created_at: '2024-03-15', // ← 分表字段在 data 中
360
+ }
361
+ });
362
+
363
+ // 批量插入 - 自动按月份分组并行写入
364
+ const result = await sharding.batchInsert({
365
+ data: [
366
+ { order_id: '001', created_at: '2024-01-15' }, // → t_order_202401
367
+ { order_id: '002', created_at: '2024-02-10' }, // → t_order_202402
368
+ { order_id: '003', created_at: '2024-03-05' }, // → t_order_202403
369
+ ]
370
+ });
371
+ console.log(result.totalAffected); // 3
372
+ console.log(result.tableCount); // 3(涉及3个分表)
373
+
374
+ // 分页查询 - 跨分表自动合并(必须传 orderBy)
375
+ const page = await sharding.findPage({
376
+ condition: {
377
+ status: 'paid',
378
+ created_at: { $gte: '2024-01-01 00:00:00', $lte: '2024-03-31 23:59:59' }
379
+ },
380
+ pageNo: 1,
381
+ pageSize: 10,
382
+ orderBy: 'created_at DESC', // 必须传,且格式固定
383
+ });
384
+ console.log(page.rows, page.totalCount);
385
+
386
+ // 链式配置
387
+ const sharding2 = this.curdProService
388
+ .getShardingCrud({
389
+ sqlDatabase: 'mydb',
390
+ sqlDbType: SqlDbType.mysql,
391
+ shardingConfig: {
392
+ type: ShardingType.MONTH,
393
+ baseTable: 't_order',
394
+ timeColumn: 'created_at',
395
+ },
396
+ })
397
+ .setBaseCfg({
398
+ enableStandardUpdateCfg: true,
399
+ enableSoftDelete: true,
400
+ });
401
+ ```
402
+
403
+ ### 哈希分表
404
+
405
+ ```typescript
406
+ const sharding = this.curdProService.getShardingCrud({
407
+ sqlDatabase: 'mydb',
408
+ sqlDbType: SqlDbType.mysql,
409
+ shardingConfig: {
410
+ type: ShardingType.HASH,
411
+ baseTable: 't_user',
412
+ shardingColumn: 'user_id',
413
+ tableCount: 16, // t_user_01 ~ t_user_16
414
+ },
415
+ });
416
+
417
+ // 插入 - 根据 user_id 哈希路由
418
+ await sharding.insert({
419
+ data: { user_id: 10001, name: '张三' } // → 路由到 t_user_05
420
+ });
421
+
422
+ // 查询 - 需要提供 user_id 用于路由
423
+ const user = await sharding.findOne({
424
+ condition: { user_id: 10001 } // 根据 user_id 确定分表
425
+ });
426
+
427
+ // 查询多用户 - 无 user_id 时扫描多个分表
428
+ const users = await sharding.find({
429
+ condition: { status: 'active' },
430
+ orderBy: 'created_at DESC',
431
+ });
432
+ ```
433
+
434
+ ## COUNT 缓存(时间分表专用)
435
+
436
+ 分页查询时需要先统计各分表的记录数。对于历史数据表(非当前时间周期),COUNT 值相对稳定,启用缓存可大幅提升性能。
437
+
438
+ ```typescript
439
+ const sharding = this.curdProService.getShardingCrud({
440
+ sqlDatabase: 'mydb',
441
+ sqlDbType: SqlDbType.mysql,
442
+ shardingConfig: {
443
+ type: ShardingType.MONTH,
444
+ baseTable: 't_order',
445
+ timeColumn: 'created_at',
446
+ countCache: {
447
+ ttlSeconds: 300, // 缓存5分钟
448
+ maxSize: 1000, // 最多缓存1000个表的COUNT
449
+ },
450
+ },
451
+ });
452
+ ```
453
+
454
+ **缓存策略**:
455
+ - **历史表**:启用缓存,COUNT 结果缓存 TTL 秒
456
+ - **当前表**:实时查询,确保数据准确性
457
+ - **LRU 淘汰**:缓存满时淘汰最久未使用的条目
458
+
459
+ **效果对比**(查询2024年1-3月数据,分页导航第1-5页):
460
+
461
+ | 步骤 | 无缓存 | 有缓存(5分钟TTL) |
462
+ |------|-------|------------------|
463
+ | 第1页 | 查3张表 COUNT | 查3张表 COUNT |
464
+ | 第2-5页 | 各查3张表 COUNT | 从缓存读取 |
465
+ | **总计** | **15次 COUNT 查询** | **3次 COUNT 查询** |
466
+
467
+ ## 错误处理
468
+
469
+ | 场景 | 错误信息 |
470
+ |------|----------|
471
+ | insert 缺少 timeColumn | `insert 操作的 data 必须包含时间字段 'xxx',用于路由到正确的分表` |
472
+ | update 缺少 timeColumn | `update 操作的 condition 必须包含时间字段 'xxx',用于路由到正确的分表` |
473
+ | restore 无条件 | `restore 操作必须指定恢复条件` |
474
+ | 插入到不存在的表 | `分表 xxx 不存在,请先创建` |
475
+ | 查询缺少 orderBy | `查询操作必须传 orderBy 参数` |
476
+ | orderBy 字段错误 | `orderBy 首个排序字段必须为 'xxx'` |
477
+ | CUSTOM 缺少 router | `CUSTOM 分表必须提供 customRouter 函数` |
478
+
479
+ ## 注意事项
480
+
481
+ 1. **时间分表写操作必须包含 timeColumn**:insert 的 `data` 或 update/delete 的 `condition` 中必须提供时间字段值
482
+ 2. **插入操作必须包含分表字段**:`data` 中必须有分表字段值
483
+ 3. **批量插入支持跨分表**:自动按分表分组并行写入
484
+ 4. **查询时无时间范围限制**:默认查询最近 N 张分表(YEAR=2, MONTH=3, DAY=7,可配置 `recentTableCount`)
485
+ 5. **COUNT 缓存仅对时间分表有效**:哈希/范围分表不启用缓存
486
+ 6. **时间字段智能处理**:有主键时精确值自动清理;无主键时日期粒度字符串自动转换为 $gte/$lte 范围;操作符表达式始终保留
487
+ 7. **软删除恢复**:启用 setEnableSoftDelete 后可使用 restore 方法恢复已删除记录
488
+ 8. **$gte/$lte 必须精确到秒**:使用操作符查询时间字段时值必须包含时分秒(如 `'2026-04-30 23:59:59'`),不允许只传日期部分(`'2026-04-30'`);如需查整月/整天数据,推荐直接传粒度字符串(`'2026-04'`/`'2026-04-15'`)由系统自动转换
@@ -0,0 +1,93 @@
1
+ # 条件操作符大全
2
+
3
+ ## 比较操作符
4
+
5
+ | 操作符 | SQL 等价 | 示例 |
6
+ |--------|----------|------|
7
+ | $gt | > | { age: { $gt: 18 } } |
8
+ | $gte | >= | { age: { $gte: 18 } } |
9
+ | $lt | < | { age: { $lt: 60 } } |
10
+ | $lte | <= | { age: { $lte: 60 } } |
11
+ | $ne | != | { status: { $ne: 'deleted' } } |
12
+
13
+ ## 范围操作符
14
+
15
+ | 操作符 | SQL 等价 | 示例 |
16
+ |--------|----------|------|
17
+ | $in | IN | { id: { $in: [1, 2, 3] } } |
18
+ | $nin | NOT IN | { type: { $nin: ['test'] } } |
19
+ | $range | BETWEEN ... AND | { amount: { $range: [100, 500] } } |
20
+
21
+ ## 模糊查询
22
+
23
+ | 操作符 | SQL 等价 | 示例 |
24
+ |--------|----------|------|
25
+ | $like | LIKE 'A%'(前缀匹配) | { name: { $like: '张' } } |
26
+ | $notLike | NOT LIKE 'A%'(不匹配前缀) | { name: { $notLike: 'test%' } } |
27
+ | $likeInclude | LIKE '%A%'(包含匹配) | { name: { $likeInclude: '张三' } } |
28
+ | $notLikeInclude | NOT LIKE '%A%'(不包含) | { name: { $notLikeInclude: 'test' } } |
29
+
30
+ ## NULL 判断
31
+
32
+ | 操作符 | SQL 等价 | 示例 |
33
+ |--------|----------|------|
34
+ | $null | IS NULL | { deleted_at: { $null: true } } |
35
+ | $notNull | IS NOT NULL | { phone: { $notNull: true } } |
36
+
37
+ > **注意**:`$null` 和 `$notNull` 的值必须为 `true` 才生效。传 `false` 等同于未设置该条件(不会生成 IS NULL / IS NOT NULL)。
38
+
39
+ ## 全文搜索(MySQL)
40
+
41
+ | 操作符 | 说明 | 示例 |
42
+ |--------|------|------|
43
+ | $match | 全文搜索 | { content: { $match: 'keyword' } } |
44
+ | $matchBool | 布尔全文搜索 | { content: { $matchBool: '+keyword -exclude' } } |
45
+
46
+ ## JSON 操作
47
+
48
+ | 操作符 | 说明 | 示例 |
49
+ |--------|------|------|
50
+ | $jsonArrayContains | JSON 数组包含 | { tags: { $jsonArrayContains: 'tag1' } } |
51
+
52
+ ## 逻辑组合
53
+
54
+ ```typescript
55
+ // AND
56
+ { $and: [{ status: 'active' }, { age: { $gte: 18 } }] }
57
+
58
+ // OR
59
+ { $or: [{ status: 'pending' }, { status: 'processing' }] }
60
+
61
+ // AND + OR 组合
62
+ {
63
+ $and: [
64
+ { department: 'tech' },
65
+ { $or: [{ level: 'senior' }, { years: { $gte: 5 } }] }
66
+ ]
67
+ }
68
+ ```
69
+
70
+ ## 排序语法
71
+
72
+ ```typescript
73
+ // 字符串格式(标准 SQL 格式)
74
+ orderBy: 'created_at DESC'
75
+ orderBy: 'created_at DESC, amount ASC'
76
+
77
+ // 简写格式(+ 升序,- 降序)
78
+ orderBy: 'created_at-' // 降序
79
+ orderBy: 'age+' // 升序
80
+ orderBy: 'department+,age-,created_at-' // 多字段
81
+
82
+ // 数组格式
83
+ orderBy: [
84
+ { fieldName: 'department', orderType: 'ASC' },
85
+ { fieldName: 'created_at', orderType: 'DESC' },
86
+ ]
87
+
88
+ // 混合数组
89
+ orderBy: [
90
+ { fieldName: 'created_at', orderType: 'DESC' },
91
+ 'amount+',
92
+ ]
93
+ ```