midway-fatcms 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.qoder/skills/midway-fatcms/02-crud-quick.md +38 -0
- package/.qoder/skills/midway-fatcms/03-crud-sharding.md +37 -36
- package/.qoder/skills/midway-fatcms/07-examples.md +4 -0
- package/dist/configuration.d.ts +10 -0
- package/dist/configuration.js +26 -0
- package/dist/controller/helpers.controller.d.ts +6 -0
- package/dist/controller/helpers.controller.js +19 -0
- package/dist/libs/crud-pro/CrudPro.d.ts +29 -2
- package/dist/libs/crud-pro/CrudPro.js +58 -2
- package/dist/libs/crud-pro/exceptions.d.ts +7 -0
- package/dist/libs/crud-pro/exceptions.js +7 -0
- package/dist/libs/crud-pro/interfaces.d.ts +1 -0
- package/dist/libs/crud-pro/models/CrudResult.d.ts +3 -2
- package/dist/libs/crud-pro/models/CrudResult.js +1 -1
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
- package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -3
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
- package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +93 -6
- package/dist/libs/crud-pro-quick/CrudProQuick.js +192 -32
- package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
- package/dist/libs/crud-sharding/ShardingBase.js +179 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
- package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
- package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
- package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
- package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
- package/dist/libs/crud-sharding/ShardingConfig.d.ts +10 -8
- package/dist/libs/crud-sharding/ShardingConfig.js +3 -3
- package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
- package/dist/libs/crud-sharding/index.d.ts +10 -13
- package/dist/libs/crud-sharding/index.js +21 -17
- package/dist/models/RedisKeys.d.ts +1 -0
- package/dist/models/RedisKeys.js +1 -0
- package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
- package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
- package/dist/service/curd/CurdMixService.d.ts +2 -2
- package/dist/service/curd/CurdProService.d.ts +109 -5
- package/dist/service/curd/CurdProService.js +127 -7
- package/package.json +1 -1
- package/src/configuration.ts +27 -0
- package/src/controller/helpers.controller.ts +15 -0
- package/src/libs/crud-pro/CrudPro.ts +73 -4
- package/src/libs/crud-pro/exceptions.ts +8 -0
- package/src/libs/crud-pro/interfaces.ts +1 -0
- package/src/libs/crud-pro/models/CrudResult.ts +5 -5
- package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
- package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -2
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
- package/src/libs/crud-pro-quick/CrudProQuick.ts +234 -46
- package/src/libs/crud-sharding/ShardingBase.ts +256 -0
- package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
- package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
- package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
- package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
- package/src/libs/crud-sharding/ShardingConfig.ts +10 -8
- package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +1 -1
- package/src/libs/crud-sharding/index.ts +17 -14
- package/src/models/RedisKeys.ts +1 -0
- package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
- package/src/service/curd/CurdMixService.ts +2 -2
- package/src/service/curd/CurdProService.ts +131 -9
- package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -208
- package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -879
- package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -70
- package/dist/libs/crud-sharding/ShardingRouter.js +0 -396
- package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -1105
- package/src/libs/crud-sharding/ShardingRouter.ts +0 -533
|
@@ -38,6 +38,7 @@ const quick = this.curdProService.getQuickCrud({
|
|
|
38
38
|
| `restore(req, table?)` | `CrudWriteResult` | 恢复软删除记录(重置 deleted_at/deleted_by) |
|
|
39
39
|
| `insertOrUpdate(req, table?)` | `CrudUpsertResult` | 先查再插/更 |
|
|
40
40
|
| `insertOnDuplicateUpdate(req, uniqueCols?, table?)` | `CrudWriteResult` | 原生 upsert |
|
|
41
|
+
| `save(req, table?)` | `CrudUpsertResult` | 自动判断 INSERT/UPDATE |
|
|
41
42
|
|
|
42
43
|
### SQL 方法
|
|
43
44
|
|
|
@@ -220,6 +221,21 @@ console.log(result.isExist ? '更新' : '插入');
|
|
|
220
221
|
|
|
221
222
|
### insertOnDuplicateUpdate - 原生 Upsert
|
|
222
223
|
|
|
224
|
+
> **前置条件**:目标表必须拥有 UNIQUE 索引(除自增主键外)。
|
|
225
|
+
>
|
|
226
|
+
> `INSERT ON DUPLICATE KEY UPDATE` 只在 UNIQUE 索引或 PRIMARY KEY 冲突时触发 UPDATE。
|
|
227
|
+
> 如果表只有普通索引(KEY/INDEX),MySQL 不会报错,但**永远走 INSERT 路径,不会触发 UPDATE**,且每次调用都会插入新行。
|
|
228
|
+
>
|
|
229
|
+
> ```sql
|
|
230
|
+
> -- 检查表是否有 UNIQUE 索引
|
|
231
|
+
> SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS
|
|
232
|
+
> WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'your_table'
|
|
233
|
+
> AND NON_UNIQUE = 0 AND INDEX_NAME != 'PRIMARY';
|
|
234
|
+
>
|
|
235
|
+
> -- 添加 UNIQUE 索引
|
|
236
|
+
> ALTER TABLE your_table ADD UNIQUE KEY uk_xxx (column_name);
|
|
237
|
+
> ```
|
|
238
|
+
|
|
223
239
|
```typescript
|
|
224
240
|
const result = await quick.insertOnDuplicateUpdate(
|
|
225
241
|
{ data: { id: 1, name: '用户' } },
|
|
@@ -227,6 +243,28 @@ const result = await quick.insertOnDuplicateUpdate(
|
|
|
227
243
|
);
|
|
228
244
|
```
|
|
229
245
|
|
|
246
|
+
### save - 自动判断 INSERT/UPDATE
|
|
247
|
+
|
|
248
|
+
只需传入 `data`,无需手动指定 `condition`。方法会自动查询表的主键列,根据 `data` 中是否包含主键值来决定操作:
|
|
249
|
+
|
|
250
|
+
- **data 包含主键且值不为 null/undefined** → 调用 `insertOrUpdate`(先查后写,存在则更新,不存在则插入)
|
|
251
|
+
- **data 不包含主键或主键值为 null/undefined** → 调用 `insert`(直接插入)
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// 有主键 → 先查后写
|
|
255
|
+
await quick.save({ data: { id: 1, name: '张三' } });
|
|
256
|
+
|
|
257
|
+
// 无主键 → 直接 INSERT
|
|
258
|
+
await quick.save({ data: { name: '李四' } });
|
|
259
|
+
|
|
260
|
+
// 主键值为 null → 视为无主键,直接 INSERT
|
|
261
|
+
await quick.save({ data: { id: null, name: '王五' } });
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
> **与 insertOrUpdate 的区别**:`save` 不需要传 `condition`,自动从 `data` 中提取主键组成 `condition`。本质是 `insertOrUpdate` 的便捷版。
|
|
265
|
+
>
|
|
266
|
+
> **性能说明**:首次调用 `save` 时会查询表的主键列信息,结果缓存 24 小时,后续调用无额外开销。
|
|
267
|
+
|
|
230
268
|
---
|
|
231
269
|
|
|
232
270
|
## SQL 方法详解
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 分表 CRUD 操作
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
分表模块提供自动路由分表的能力,支持四种策略:时间分表(ShardingByTimeCrud)、哈希分表(ShardingByHashCrud)、键值分表(ShardingByKeyCrud)、自定义分表(ShardingByCustomCrud)。
|
|
4
|
+
|
|
5
|
+
基类 `ShardingBase` 提供通用基础设施。`ShardingByTimeCrud` 和 `ShardingByCustomCrud` 各自包含完整的路由和 CRUD 逻辑;`ShardingByHashCrud` 和 `ShardingByKeyCrud` 内部委托 `ShardingByCustomCrud` 实现,仅在构造函数中定义路由逻辑。
|
|
4
6
|
|
|
5
7
|
## 获取实例
|
|
6
8
|
|
|
@@ -26,20 +28,20 @@ const sharding = this.curdProService.getShardingCrud({
|
|
|
26
28
|
|
|
27
29
|
## 分表类型
|
|
28
30
|
|
|
29
|
-
| 类型 | 枚举值 | 后缀示例 | 适用场景 |
|
|
30
|
-
|
|
31
|
-
| 按年 | `ShardingType.YEAR` | `_2024` | 数据量适中,按年归档 |
|
|
32
|
-
| 按月 | `ShardingType.MONTH` | `_202401` | 大数据量,按月分表 |
|
|
33
|
-
| 按日 | `ShardingType.DAY` | `_20240115` | 超大数据量,按天分表 |
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
| 自定义 | `ShardingType.CUSTOM` | 自定义 |
|
|
31
|
+
| 类型 | 枚举值 | 实现类 | 后缀示例 | 适用场景 |
|
|
32
|
+
|------|--------|--------|----------|----------|
|
|
33
|
+
| 按年 | `ShardingType.YEAR` | `ShardingByTimeCrud` | `_2024` | 数据量适中,按年归档 |
|
|
34
|
+
| 按月 | `ShardingType.MONTH` | `ShardingByTimeCrud` | `_202401` | 大数据量,按月分表 |
|
|
35
|
+
| 按日 | `ShardingType.DAY` | `ShardingByTimeCrud` | `_20240115` | 超大数据量,按天分表 |
|
|
36
|
+
| 哈希 | `ShardingType.HASH` | `ShardingByHashCrud` | `_00`, `_01`... | 均匀分布数据 |
|
|
37
|
+
| 键值 | `ShardingType.KEY` | `ShardingByKeyCrud` | `_east`, `_west`... | 字段原值=表后缀,一值一表 |
|
|
38
|
+
| 自定义 | `ShardingType.CUSTOM` | `ShardingByCustomCrud` | 自定义 | 特殊业务规则(含原 RANGE) |
|
|
37
39
|
|
|
38
40
|
## 配置选项
|
|
39
41
|
|
|
40
42
|
```typescript
|
|
41
43
|
interface IShardingConfig {
|
|
42
|
-
type: ShardingType; //
|
|
44
|
+
type: ShardingType; // 分表类型(YEAR/MONTH/DAY/HASH/KEY/CUSTOM)
|
|
43
45
|
baseTable: string; // 基础表名(必填)
|
|
44
46
|
|
|
45
47
|
// 时间分表(YEAR/MONTH/DAY)
|
|
@@ -52,9 +54,9 @@ interface IShardingConfig {
|
|
|
52
54
|
tableOptions?: string; // 表选项(如MySQL引擎配置)
|
|
53
55
|
};
|
|
54
56
|
|
|
55
|
-
//
|
|
56
|
-
shardingColumn?: string; // 分表字段(
|
|
57
|
-
tableCount?: number; //
|
|
57
|
+
// 哈希/键值分表(HASH/KEY)
|
|
58
|
+
shardingColumn?: string; // 分表字段(HASH/KEY必填)
|
|
59
|
+
tableCount?: number; // 分表数量(仅 HASH,必填)
|
|
58
60
|
|
|
59
61
|
// COUNT 缓存(仅时间分表有效)
|
|
60
62
|
countCache?: {
|
|
@@ -74,8 +76,8 @@ interface IShardingConfig {
|
|
|
74
76
|
|
|
75
77
|
| 方法 | 返回类型 | 说明 |
|
|
76
78
|
|------|----------|------|
|
|
77
|
-
| `findOne(req)` | `CrudQueryOneResult<T>` |
|
|
78
|
-
| `findUniqueOne(req)` | `CrudQueryOneResult<T>` | 唯一查询(0条返回null
|
|
79
|
+
| `findOne(req)` | `CrudQueryOneResult<T>` | 查询单条(顺序查多表,找到即返回,含 fromTable 物理表名) |
|
|
80
|
+
| `findUniqueOne(req)` | `CrudQueryOneResult<T>` | 唯一查询(0条返回null,多条报错,含 fromTable 物理表名) |
|
|
79
81
|
| `find(req)` | `T[]` | 列表查询(多表自动合并) |
|
|
80
82
|
| `findList(req)` | `CrudQueryListResult<T>` | 列表查询(带结果包装) |
|
|
81
83
|
| `findPage(req)` | `CrudQueryPageResult<T>` | 分页查询(跨分表自动处理) |
|
|
@@ -91,8 +93,7 @@ interface IShardingConfig {
|
|
|
91
93
|
| `update(req)` | `CrudWriteResult` | 更新(必须能确定单一目标表) |
|
|
92
94
|
| `delete(req)` | `CrudWriteResult` | 删除(必须能确定单一目标表) |
|
|
93
95
|
| `restore(req)` | `CrudWriteResult` | 恢复软删除记录(重置 deleted_at/deleted_by) |
|
|
94
|
-
| `insertOrUpdate(req)` | `CrudUpsertResult` |
|
|
95
|
-
| `insertOnDuplicateUpdate(req)` | `CrudWriteResult` | 原生upsert(condition决定分表) |
|
|
96
|
+
| `insertOrUpdate(req)` | `CrudUpsertResult` | 插入或更新(统一由 condition 路由定位分表) |
|
|
96
97
|
|
|
97
98
|
### 配置方法
|
|
98
99
|
|
|
@@ -113,8 +114,7 @@ interface IShardingConfig {
|
|
|
113
114
|
| `insert` | `data[分表字段]` | 提取值 → 计算后缀 → 单表 | 插入单条数据 |
|
|
114
115
|
| `batchInsert` | `data[i][分表字段]` | 每条数据单独计算分表 | 支持跨分表并行写入 |
|
|
115
116
|
| `update/delete` | `condition[分表字段]` | 提取值 → 计算后缀 → 单表 | condition决定分表,data是更新内容 |
|
|
116
|
-
| `insertOrUpdate` | `condition[分表字段]` |
|
|
117
|
-
| `insertOnDuplicateUpdate` | `condition[分表字段]` | 提取值 → 计算后缀 → 单表 | condition决定分表 |
|
|
117
|
+
| `insertOrUpdate` | `condition[分表字段]` | condition 路由定位分表,查找与插入在同一分表 | 统一由 condition 决定目标分表 |
|
|
118
118
|
| `find/findPage` | `condition[分表字段]` | 时间范围 → 候选表 → 与真实表取交集 → 多表 | 自动合并多表结果 |
|
|
119
119
|
| `findOne/findUniqueOne` | `condition[分表字段]` | 同 find | 顺序查询各分表 |
|
|
120
120
|
| `findCount/isExist` | `condition[分表字段]` | 同 find | 多表并行查询后汇总 |
|
|
@@ -123,7 +123,8 @@ interface IShardingConfig {
|
|
|
123
123
|
|
|
124
124
|
- **插入类操作**(insert/batchInsert):只使用 `data`,不需要传 `condition`
|
|
125
125
|
- **查询类操作**(find/delete/isExist等):只使用 `condition`,不使用 `data`
|
|
126
|
-
- **更新类操作**(update
|
|
126
|
+
- **更新类操作**(update):`condition` 用于路由,`data` 是更新内容
|
|
127
|
+
- **插入或更新**(insertOrUpdate):统一由 `condition` 路由定位分表,查找与插入在同一分表
|
|
127
128
|
|
|
128
129
|
## 时间分表约束
|
|
129
130
|
|
|
@@ -141,7 +142,6 @@ interface IShardingConfig {
|
|
|
141
142
|
| `delete` | `condition` 必须包含 `timeColumn` | `delete 操作的 condition 必须包含时间字段 'xxx'` |
|
|
142
143
|
| `restore` | `condition` 必须包含 `timeColumn` | `restore 操作的 condition 必须包含时间字段 'xxx'` |
|
|
143
144
|
| `insertOrUpdate` | `data` + `condition` 均须包含 `timeColumn` | 同上 |
|
|
144
|
-
| `insertOnDuplicateUpdate` | `data` + `condition` 均须包含 `timeColumn` | 同上 |
|
|
145
145
|
|
|
146
146
|
```typescript
|
|
147
147
|
// ✅ 正确 - insert 包含时间字段
|
|
@@ -277,7 +277,7 @@ condition: { created_at: '2026' } // → { $gte: '2026-01-01 00:00:00',
|
|
|
277
277
|
|
|
278
278
|
### 软删除支持
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
分表 CRUD 支持软删除模式,启用后 `delete` 操作会设置 `deleted_at` 和 `deleted_by` 字段,而非物理删除:
|
|
281
281
|
|
|
282
282
|
```typescript
|
|
283
283
|
const sharding = this.curdProService
|
|
@@ -410,7 +410,7 @@ const sharding = this.curdProService.getShardingCrud({
|
|
|
410
410
|
type: ShardingType.HASH,
|
|
411
411
|
baseTable: 't_user',
|
|
412
412
|
shardingColumn: 'user_id',
|
|
413
|
-
tableCount: 16, //
|
|
413
|
+
tableCount: 16, // t_user_00 ~ t_user_15
|
|
414
414
|
},
|
|
415
415
|
});
|
|
416
416
|
|
|
@@ -419,15 +419,14 @@ await sharding.insert({
|
|
|
419
419
|
data: { user_id: 10001, name: '张三' } // → 路由到 t_user_05
|
|
420
420
|
});
|
|
421
421
|
|
|
422
|
-
// 查询 -
|
|
422
|
+
// 查询 - 必须提供 user_id 用于路由
|
|
423
423
|
const user = await sharding.findOne({
|
|
424
424
|
condition: { user_id: 10001 } // 根据 user_id 确定分表
|
|
425
425
|
});
|
|
426
426
|
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
condition: { status: 'active' }
|
|
430
|
-
orderBy: 'created_at DESC',
|
|
427
|
+
// ❌ 错误 - 缺少 shardingColumn 会抛出异常
|
|
428
|
+
await sharding.findOne({
|
|
429
|
+
condition: { status: 'active' } // 报错:condition 必须包含分表字段 'user_id'
|
|
431
430
|
});
|
|
432
431
|
```
|
|
433
432
|
|
|
@@ -479,10 +478,12 @@ const sharding = this.curdProService.getShardingCrud({
|
|
|
479
478
|
## 注意事项
|
|
480
479
|
|
|
481
480
|
1. **时间分表写操作必须包含 timeColumn**:insert 的 `data` 或 update/delete 的 `condition` 中必须提供时间字段值
|
|
482
|
-
2.
|
|
483
|
-
3.
|
|
484
|
-
4.
|
|
485
|
-
5.
|
|
486
|
-
6.
|
|
487
|
-
7.
|
|
488
|
-
8.
|
|
481
|
+
2. **哈希/键值分表所有操作必须包含 shardingColumn**:insert 的 `data`、query/update/delete 的 `condition` 中必须提供分表字段值,否则抛出异常
|
|
482
|
+
3. **insertOrUpdate 统一路由**:由 `condition` 路由定位分表,查找与插入在同一分表
|
|
483
|
+
4. **批量插入支持跨分表**:自动按分表分组并行写入
|
|
484
|
+
5. **查询时无时间范围限制**:默认查询最近 N 张分表(YEAR=2, MONTH=3, DAY=7,可配置 `recentTableCount`)
|
|
485
|
+
6. **COUNT 缓存仅对时间分表有效**:哈希/键值/自定义分表不启用缓存
|
|
486
|
+
7. **时间字段智能处理**:有主键时精确值自动清理;无主键时日期粒度字符串自动转换为 $gte/$lte 范围;操作符表达式始终保留
|
|
487
|
+
8. **软删除恢复**:启用 setEnableSoftDelete 后可使用 restore 方法恢复已删除记录
|
|
488
|
+
9. **$gte/$lte 必须精确到秒**:使用操作符查询时间字段时值必须包含时分秒(如 `'2026-04-30 23:59:59'`),不允许只传日期部分(`'2026-04-30'`);如需查整月/整天数据,推荐直接传粒度字符串(`'2026-04'`/`'2026-04-15'`)由系统自动转换
|
|
489
|
+
10. **findOne/findUniqueOne 返回 fromTable**:结果中的 `fromTable` 字段标识数据来源的物理分表名(如 `t_order_202403`),未找到数据时为 undefined
|
|
@@ -196,6 +196,10 @@ await quick.insertOnDuplicateUpdate(
|
|
|
196
196
|
{ data: { id: 1, name: '用户', age: 25 } },
|
|
197
197
|
['id'] // 唯一列,PostgreSQL/SQL Server 必填
|
|
198
198
|
);
|
|
199
|
+
|
|
200
|
+
// save - 自动判断 INSERT/UPDATE(只需 data)
|
|
201
|
+
await quick.save({ data: { id: 1, name: '用户', age: 25 } }); // 有主键 → 先查后写
|
|
202
|
+
await quick.save({ data: { name: '新用户', age: 20 } }); // 无主键 → 直接插入
|
|
199
203
|
```
|
|
200
204
|
|
|
201
205
|
## 软删除与恢复
|
package/dist/configuration.d.ts
CHANGED
|
@@ -3,5 +3,15 @@ export declare class ContainerLifeCycle {
|
|
|
3
3
|
app: koa.Application;
|
|
4
4
|
onConfigLoad(): Promise<any>;
|
|
5
5
|
onReady(): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* 初始化表元数据缓存的 Redis Pub/Sub 广播
|
|
8
|
+
*
|
|
9
|
+
* - 发布客户端:复用已有的 RedisService 实例
|
|
10
|
+
* - 订阅客户端:通过 duplicate() 创建独立连接
|
|
11
|
+
*
|
|
12
|
+
* 集群部署时,任意节点调用 clearTableMetaCacheWithBroadcast()
|
|
13
|
+
* 会通过 Redis 广播通知所有节点清空本地缓存。
|
|
14
|
+
*/
|
|
15
|
+
private initTableMetaCacheRedis;
|
|
6
16
|
private startScheduleOnReady;
|
|
7
17
|
}
|
package/dist/configuration.js
CHANGED
|
@@ -27,6 +27,8 @@ const global_middleware_1 = require("./middleware/global.middleware");
|
|
|
27
27
|
const forbidden_middleware_1 = require("./middleware/forbidden.middleware");
|
|
28
28
|
const schedule_1 = require("./schedule");
|
|
29
29
|
const crypto_utils_1 = require("./libs/utils/crypto-utils");
|
|
30
|
+
const TableMetaCacheRedisSubscriber_1 = require("./service/TableMetaCacheRedisSubscriber");
|
|
31
|
+
const redis_1 = require("@midwayjs/redis");
|
|
30
32
|
let ContainerLifeCycle = class ContainerLifeCycle {
|
|
31
33
|
async onConfigLoad() {
|
|
32
34
|
const config = this.app.getConfig();
|
|
@@ -53,6 +55,10 @@ let ContainerLifeCycle = class ContainerLifeCycle {
|
|
|
53
55
|
* 让ANONYMOUS_CONTEXT获取app对象
|
|
54
56
|
*/
|
|
55
57
|
schedule_1.ANONYMOUS_CONTEXT.setApp(this.app);
|
|
58
|
+
/**
|
|
59
|
+
* 初始化表元数据缓存 Redis 广播
|
|
60
|
+
*/
|
|
61
|
+
await this.initTableMetaCacheRedis();
|
|
56
62
|
/**
|
|
57
63
|
* 启动定时任务
|
|
58
64
|
*/
|
|
@@ -63,6 +69,26 @@ let ContainerLifeCycle = class ContainerLifeCycle {
|
|
|
63
69
|
// add filter
|
|
64
70
|
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
|
|
65
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* 初始化表元数据缓存的 Redis Pub/Sub 广播
|
|
74
|
+
*
|
|
75
|
+
* - 发布客户端:复用已有的 RedisService 实例
|
|
76
|
+
* - 订阅客户端:通过 duplicate() 创建独立连接
|
|
77
|
+
*
|
|
78
|
+
* 集群部署时,任意节点调用 clearTableMetaCacheWithBroadcast()
|
|
79
|
+
* 会通过 Redis 广播通知所有节点清空本地缓存。
|
|
80
|
+
*/
|
|
81
|
+
async initTableMetaCacheRedis() {
|
|
82
|
+
try {
|
|
83
|
+
const redisService = await this.app.getApplicationContext().getAsync(redis_1.RedisService);
|
|
84
|
+
(0, TableMetaCacheRedisSubscriber_1.initTableMetaCachePublishClient)(redisService);
|
|
85
|
+
(0, TableMetaCacheRedisSubscriber_1.initTableMetaCacheSubscriber)(redisService);
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Redis 未配置或不可用时,降级为单节点模式(仅清空本地缓存)
|
|
89
|
+
this.app.getLogger().warn('ContainerLifeCycle ==> initTableMetaCacheRedis Redis不可用,降级为单节点模式:', e.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
66
92
|
async startScheduleOnReady() {
|
|
67
93
|
const logger = this.app.getLogger();
|
|
68
94
|
const config = this.app.getConfig();
|
|
@@ -32,5 +32,11 @@ export declare class HelpersApi {
|
|
|
32
32
|
* @param queryData
|
|
33
33
|
*/
|
|
34
34
|
cryptoAes128CBC(queryData: any): Promise<CommonResult>;
|
|
35
|
+
/**
|
|
36
|
+
* 工具函数: 清空表元数据缓存(集群广播)
|
|
37
|
+
* 清空本节点缓存,并通过 Redis Pub/Sub 广播通知集群中其他节点。
|
|
38
|
+
* 清空后,下次请求会自动从数据库重新加载(懒加载刷新)。
|
|
39
|
+
*/
|
|
40
|
+
clearTableMetaCacheApi(): Promise<CommonResult>;
|
|
35
41
|
private checkLocalPermissionEnv;
|
|
36
42
|
}
|
|
@@ -21,6 +21,7 @@ const functions_1 = require("../libs/utils/functions");
|
|
|
21
21
|
const crypto_utils_1 = require("../libs/utils/crypto-utils");
|
|
22
22
|
const common_dto_1 = require("../libs/utils/common-dto");
|
|
23
23
|
const SystemPerm_1 = require("../models/SystemPerm");
|
|
24
|
+
const TableMetaCacheRedisSubscriber_1 = require("../service/TableMetaCacheRedisSubscriber");
|
|
24
25
|
// http://127.0.0.1:7002/ns/api/helpers/getApiScript?prefix=/ns/api/manage
|
|
25
26
|
// http://127.0.0.1:7002/ns/api/helpers/getApiScript
|
|
26
27
|
// http://127.0.0.1:7002/ns/api/helpers/getApiEnv
|
|
@@ -124,6 +125,18 @@ let HelpersApi = class HelpersApi {
|
|
|
124
125
|
isEqual: input === decrypted,
|
|
125
126
|
});
|
|
126
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* 工具函数: 清空表元数据缓存(集群广播)
|
|
130
|
+
* 清空本节点缓存,并通过 Redis Pub/Sub 广播通知集群中其他节点。
|
|
131
|
+
* 清空后,下次请求会自动从数据库重新加载(懒加载刷新)。
|
|
132
|
+
*/
|
|
133
|
+
async clearTableMetaCacheApi() {
|
|
134
|
+
const stats = (0, TableMetaCacheRedisSubscriber_1.clearTableMetaCacheWithBroadcast)();
|
|
135
|
+
return common_dto_1.CommonResult.successRes({
|
|
136
|
+
message: '表元数据缓存已清空(已广播集群)',
|
|
137
|
+
beforeClear: stats,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
127
140
|
checkLocalPermissionEnv() {
|
|
128
141
|
//是否是开发者
|
|
129
142
|
const isDevelopUser = () => {
|
|
@@ -181,6 +194,12 @@ __decorate([
|
|
|
181
194
|
__metadata("design:paramtypes", [Object]),
|
|
182
195
|
__metadata("design:returntype", Promise)
|
|
183
196
|
], HelpersApi.prototype, "cryptoAes128CBC", null);
|
|
197
|
+
__decorate([
|
|
198
|
+
(0, core_1.Get)('/clearTableMetaCache'),
|
|
199
|
+
__metadata("design:type", Function),
|
|
200
|
+
__metadata("design:paramtypes", []),
|
|
201
|
+
__metadata("design:returntype", Promise)
|
|
202
|
+
], HelpersApi.prototype, "clearTableMetaCacheApi", null);
|
|
184
203
|
HelpersApi = __decorate([
|
|
185
204
|
(0, core_1.Controller)('/ns/api/helpers')
|
|
186
205
|
], HelpersApi);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICrudProCfg, ILogger, IRequestCfgModel, IRequestModel, ISqlCfgModel, ITableListResult, ITableNamesOptions, ITableNamesQuery, IVisitor, ExecuteSQLResult } from './interfaces';
|
|
1
|
+
import { ICrudProCfg, ILogger, IRequestCfgModel, IRequestModel, ISqlCfgModel, ITableListResult, ITableMetaQuery, ITableNamesOptions, ITableNamesQuery, IVisitor, ExecuteSQLResult } from './interfaces';
|
|
2
2
|
import { ExecuteContext } from './models/ExecuteContext';
|
|
3
3
|
import { Transaction } from './models/Transaction';
|
|
4
4
|
import { IExecuteContextFunc } from './models/ExecuteContextFunc';
|
|
@@ -51,6 +51,16 @@ declare class CrudPro {
|
|
|
51
51
|
private executeCrudByCfgInternal;
|
|
52
52
|
getCachedCfgByMethod(method: string, isEnableCache: boolean): Promise<IRequestCfgModel>;
|
|
53
53
|
getAllTableInfos(query: ITableNamesQuery, options?: ITableNamesOptions): Promise<ITableListResult>;
|
|
54
|
+
/**
|
|
55
|
+
* 获取表的主键列名列表
|
|
56
|
+
*
|
|
57
|
+
* 复用 CrudProTableMetaService 的 getTableMeta 缓存,
|
|
58
|
+
* 从 columnDetails 中筛选 isPrimaryKey 的列。
|
|
59
|
+
*
|
|
60
|
+
* @param query 表元数据查询参数
|
|
61
|
+
* @returns 主键列名数组
|
|
62
|
+
*/
|
|
63
|
+
getPrimaryKeyColumns(query: ITableMetaQuery): Promise<string[]>;
|
|
54
64
|
/**
|
|
55
65
|
* 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
|
|
56
66
|
* 避免传入不存在的字段导致 SQL 执行报错
|
|
@@ -59,6 +69,16 @@ declare class CrudPro {
|
|
|
59
69
|
* @param cfgModel 配置模型
|
|
60
70
|
*/
|
|
61
71
|
private filterDataByTableMetaIfNeeded;
|
|
72
|
+
/**
|
|
73
|
+
* 校验特定操作的必填参数
|
|
74
|
+
*
|
|
75
|
+
* - insertOrUpdate 和 update:condition 和 data 均为必填
|
|
76
|
+
* - insertOnDuplicateUpdate 和 insert:只有data 为必填
|
|
77
|
+
* @param reqModel 请求模型
|
|
78
|
+
* @param cfgModel 配置模型
|
|
79
|
+
* @throws CommonException 如果缺少必填参数
|
|
80
|
+
*/
|
|
81
|
+
private validateParamsIfNeeded;
|
|
62
82
|
/**
|
|
63
83
|
* 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
|
|
64
84
|
* @param sqlSimpleName 简单 SQL 名称
|
|
@@ -66,12 +86,19 @@ declare class CrudPro {
|
|
|
66
86
|
*/
|
|
67
87
|
private isSimpleInsertOrUpdateType;
|
|
68
88
|
/**
|
|
69
|
-
*
|
|
89
|
+
* 根据表结构字段类型,自动转换 data 数据格式
|
|
70
90
|
* 例如:PostgreSQL 的 ARRAY 类型字段,需要将 JSON 数组转为 PG 数组字面量格式
|
|
71
91
|
* @param reqModel 请求模型
|
|
72
92
|
* @param cfgModel 配置模型
|
|
73
93
|
*/
|
|
74
94
|
private convertDataFieldTypeIfNeeded;
|
|
95
|
+
/**
|
|
96
|
+
* 查询时:根据表结构字段类型,自动转换 condition 数据格式
|
|
97
|
+
* 防止数据库隐式类型转换导致索引失效(如 WHERE int_col = '123' 导致索引无法命中)
|
|
98
|
+
* @param reqModel 请求模型
|
|
99
|
+
* @param cfgModel 配置模型
|
|
100
|
+
*/
|
|
101
|
+
private convertConditionTypeIfNeeded;
|
|
75
102
|
private executeSQLList;
|
|
76
103
|
private parseRunSqlException;
|
|
77
104
|
private afterExecuteSQLList;
|
|
@@ -138,10 +138,14 @@ class CrudPro {
|
|
|
138
138
|
const cfgModel = new RequestCfgModel_1.RequestCfgModel(cfgJson);
|
|
139
139
|
exeCtx.setReqModel(reqModel);
|
|
140
140
|
exeCtx.setCfgModel(cfgModel);
|
|
141
|
+
// insertOrUpdate 参数校验
|
|
142
|
+
this.validateParamsIfNeeded(reqModel, cfgModel);
|
|
141
143
|
// 如果是 sqlSimpleName模式的 update/insert,则需要将 reqJson.data 部分根据真实的表结构进行过滤
|
|
142
144
|
await this.filterDataByTableMetaIfNeeded(reqModel, cfgModel);
|
|
143
|
-
|
|
145
|
+
//插入和更新时: 根据表结构字段类型,自动转换 data 数据格式(如 PostgreSQL ARRAY 字段)
|
|
144
146
|
await this.convertDataFieldTypeIfNeeded(reqModel, cfgModel);
|
|
147
|
+
//查询时:根据表结构字段类型,自动转换 condition 数据格式(防止数据库隐式类型转换导致索引失效)
|
|
148
|
+
await this.convertConditionTypeIfNeeded(reqModel, cfgModel);
|
|
145
149
|
// 参数校验
|
|
146
150
|
this.serviceHub.validateDataType(cfgModel, reqModel);
|
|
147
151
|
this.serviceHub.validateByAllow(cfgModel, reqModel);
|
|
@@ -175,6 +179,18 @@ class CrudPro {
|
|
|
175
179
|
async getAllTableInfos(query, options) {
|
|
176
180
|
return this.serviceHub.getAllTableInfos(query, options);
|
|
177
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* 获取表的主键列名列表
|
|
184
|
+
*
|
|
185
|
+
* 复用 CrudProTableMetaService 的 getTableMeta 缓存,
|
|
186
|
+
* 从 columnDetails 中筛选 isPrimaryKey 的列。
|
|
187
|
+
*
|
|
188
|
+
* @param query 表元数据查询参数
|
|
189
|
+
* @returns 主键列名数组
|
|
190
|
+
*/
|
|
191
|
+
async getPrimaryKeyColumns(query) {
|
|
192
|
+
return this.serviceHub.getPrimaryKeyColumns(query);
|
|
193
|
+
}
|
|
178
194
|
/**
|
|
179
195
|
* 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
|
|
180
196
|
* 避免传入不存在的字段导致 SQL 执行报错
|
|
@@ -219,6 +235,37 @@ class CrudPro {
|
|
|
219
235
|
// 直接修改 reqModel.data,只保留表中存在的字段
|
|
220
236
|
reqModel.data = filteredData;
|
|
221
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* 校验特定操作的必填参数
|
|
240
|
+
*
|
|
241
|
+
* - insertOrUpdate 和 update:condition 和 data 均为必填
|
|
242
|
+
* - insertOnDuplicateUpdate 和 insert:只有data 为必填
|
|
243
|
+
* @param reqModel 请求模型
|
|
244
|
+
* @param cfgModel 配置模型
|
|
245
|
+
* @throws CommonException 如果缺少必填参数
|
|
246
|
+
*/
|
|
247
|
+
validateParamsIfNeeded(reqModel, cfgModel) {
|
|
248
|
+
// insertOrUpdate 和 update:condition 和 data 均为必填
|
|
249
|
+
if (cfgModel.sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE ||
|
|
250
|
+
cfgModel.sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE) {
|
|
251
|
+
if (!reqModel.condition || Object.keys(reqModel.condition).length === 0) {
|
|
252
|
+
throw new exceptions_1.CommonException(exceptions_1.Exceptions.INSERT_OR_UPDATE_CONDITION_EMPTY, '[CrudPro] insertOrUpdate/update 操作的 condition 不能为空。' +
|
|
253
|
+
'condition 用于判断记录是否存在或定位更新目标。');
|
|
254
|
+
}
|
|
255
|
+
if (!reqModel.data || Object.keys(reqModel.data).length === 0) {
|
|
256
|
+
throw new exceptions_1.CommonException(exceptions_1.Exceptions.INSERT_OR_UPDATE_DATA_EMPTY, '[CrudPro] insertOrUpdate/update 操作的 data 不能为空。' +
|
|
257
|
+
'data 用于指定插入或更新的内容。');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// insertOnDuplicateUpdate 和 insert:只有 data 为必填
|
|
261
|
+
if (cfgModel.sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE ||
|
|
262
|
+
cfgModel.sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_INSERT) {
|
|
263
|
+
if (!reqModel.data || Object.keys(reqModel.data).length === 0) {
|
|
264
|
+
throw new exceptions_1.CommonException(exceptions_1.Exceptions.INSERT_OR_DUPLICATE_UPDATE_DATA_EMPTY, '[CrudPro] insert/insertOnDuplicateUpdate 操作的 data 不能为空。' +
|
|
265
|
+
'data 用于指定插入或更新的内容。');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
222
269
|
/**
|
|
223
270
|
* 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
|
|
224
271
|
* @param sqlSimpleName 简单 SQL 名称
|
|
@@ -234,7 +281,7 @@ class CrudPro {
|
|
|
234
281
|
return insertOrUpdateTypes.includes(sqlSimpleName);
|
|
235
282
|
}
|
|
236
283
|
/**
|
|
237
|
-
*
|
|
284
|
+
* 根据表结构字段类型,自动转换 data 数据格式
|
|
238
285
|
* 例如:PostgreSQL 的 ARRAY 类型字段,需要将 JSON 数组转为 PG 数组字面量格式
|
|
239
286
|
* @param reqModel 请求模型
|
|
240
287
|
* @param cfgModel 配置模型
|
|
@@ -242,6 +289,15 @@ class CrudPro {
|
|
|
242
289
|
async convertDataFieldTypeIfNeeded(reqModel, cfgModel) {
|
|
243
290
|
await this.serviceHub.convertDataTypeByTableMeta(reqModel, cfgModel);
|
|
244
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* 查询时:根据表结构字段类型,自动转换 condition 数据格式
|
|
294
|
+
* 防止数据库隐式类型转换导致索引失效(如 WHERE int_col = '123' 导致索引无法命中)
|
|
295
|
+
* @param reqModel 请求模型
|
|
296
|
+
* @param cfgModel 配置模型
|
|
297
|
+
*/
|
|
298
|
+
async convertConditionTypeIfNeeded(reqModel, cfgModel) {
|
|
299
|
+
await this.serviceHub.convertConditionTypeByTableMeta(reqModel, cfgModel);
|
|
300
|
+
}
|
|
245
301
|
async executeSQLList() {
|
|
246
302
|
try {
|
|
247
303
|
await this.serviceHub.executeSqlCfgModels();
|
|
@@ -56,6 +56,12 @@ export declare enum Exceptions {
|
|
|
56
56
|
DATA_GET_DATA_EMPTY_ON_INSERT_KEYS = "DATA_GET_DATA_EMPTY_ON_INSERT_KEYS",
|
|
57
57
|
DATA_GET_DATA_EMPTY_ON_INSERT_VALUES = "DATA_GET_DATA_EMPTY_ON_INSERT_VALUES",
|
|
58
58
|
BATCH_INSERT_KEYS_MISMATCH = "BATCH_INSERT_KEYS_MISMATCH",
|
|
59
|
+
/**
|
|
60
|
+
* insertOrUpdate 参数缺失
|
|
61
|
+
*/
|
|
62
|
+
INSERT_OR_UPDATE_CONDITION_EMPTY = "INSERT_OR_UPDATE_CONDITION_EMPTY",
|
|
63
|
+
INSERT_OR_UPDATE_DATA_EMPTY = "INSERT_OR_UPDATE_DATA_EMPTY",
|
|
64
|
+
INSERT_OR_DUPLICATE_UPDATE_DATA_EMPTY = "INSERT_ON_DUPLICATE_UPDATE_DATA_EMPTY",
|
|
59
65
|
/**
|
|
60
66
|
* 请求的Method字段为空
|
|
61
67
|
*/
|
|
@@ -79,6 +85,7 @@ export declare enum Exceptions {
|
|
|
79
85
|
RUN_PICK_ERR_VISITOR_FIELD_NULL = "RUN_PICK_ERR_VISITOR_FIELD_NULL",
|
|
80
86
|
RUN_EXECUTE_VALIDATE = "RUN_EXECUTE_VALIDATE",
|
|
81
87
|
RUN_FUNCTION_NOT_FOUND = "RUN_FUNCTION_NOT_FOUND",
|
|
88
|
+
MORE_THAN_ONE_RECORDS_FOUND = "MORE_THAN_ONE_RECORDS_FOUND",
|
|
82
89
|
/**
|
|
83
90
|
* 参数校验错误
|
|
84
91
|
*/
|
|
@@ -60,6 +60,12 @@ var Exceptions;
|
|
|
60
60
|
Exceptions["DATA_GET_DATA_EMPTY_ON_INSERT_KEYS"] = "DATA_GET_DATA_EMPTY_ON_INSERT_KEYS";
|
|
61
61
|
Exceptions["DATA_GET_DATA_EMPTY_ON_INSERT_VALUES"] = "DATA_GET_DATA_EMPTY_ON_INSERT_VALUES";
|
|
62
62
|
Exceptions["BATCH_INSERT_KEYS_MISMATCH"] = "BATCH_INSERT_KEYS_MISMATCH";
|
|
63
|
+
/**
|
|
64
|
+
* insertOrUpdate 参数缺失
|
|
65
|
+
*/
|
|
66
|
+
Exceptions["INSERT_OR_UPDATE_CONDITION_EMPTY"] = "INSERT_OR_UPDATE_CONDITION_EMPTY";
|
|
67
|
+
Exceptions["INSERT_OR_UPDATE_DATA_EMPTY"] = "INSERT_OR_UPDATE_DATA_EMPTY";
|
|
68
|
+
Exceptions["INSERT_OR_DUPLICATE_UPDATE_DATA_EMPTY"] = "INSERT_ON_DUPLICATE_UPDATE_DATA_EMPTY";
|
|
63
69
|
/**
|
|
64
70
|
* 请求的Method字段为空
|
|
65
71
|
*/
|
|
@@ -83,6 +89,7 @@ var Exceptions;
|
|
|
83
89
|
Exceptions["RUN_PICK_ERR_VISITOR_FIELD_NULL"] = "RUN_PICK_ERR_VISITOR_FIELD_NULL";
|
|
84
90
|
Exceptions["RUN_EXECUTE_VALIDATE"] = "RUN_EXECUTE_VALIDATE";
|
|
85
91
|
Exceptions["RUN_FUNCTION_NOT_FOUND"] = "RUN_FUNCTION_NOT_FOUND";
|
|
92
|
+
Exceptions["MORE_THAN_ONE_RECORDS_FOUND"] = "MORE_THAN_ONE_RECORDS_FOUND";
|
|
86
93
|
/**
|
|
87
94
|
* 参数校验错误
|
|
88
95
|
*/
|
|
@@ -51,10 +51,13 @@ declare class CrudWriteResult extends CrudResultBase {
|
|
|
51
51
|
declare class CrudQueryOneResult<T = Record<string, any>> extends CrudResultBase {
|
|
52
52
|
readonly row: T | null;
|
|
53
53
|
readonly found: boolean;
|
|
54
|
+
/** 数据来源的物理表名(分表场景下为实际分表名,如 t_order_202403) */
|
|
55
|
+
readonly fromTable: string;
|
|
54
56
|
constructor(data: {
|
|
55
57
|
row: T | null;
|
|
56
58
|
rawContext: ExecuteContext;
|
|
57
59
|
debugInfo?: CrudResultDebugInfo;
|
|
60
|
+
fromTable: string;
|
|
58
61
|
});
|
|
59
62
|
}
|
|
60
63
|
/** 列表查询结果 */
|
|
@@ -99,12 +102,10 @@ declare class CrudCountResult extends CrudResultBase {
|
|
|
99
102
|
}
|
|
100
103
|
/** InsertOrUpdate 结果 */
|
|
101
104
|
declare class CrudUpsertResult extends CrudResultBase {
|
|
102
|
-
readonly affected?: ResModelAffected;
|
|
103
105
|
readonly insertAffected?: ResModelAffected;
|
|
104
106
|
readonly updateAffected?: ResModelAffected;
|
|
105
107
|
readonly isExist: boolean;
|
|
106
108
|
constructor(data: {
|
|
107
|
-
affected?: ResModelAffected;
|
|
108
109
|
insertAffected?: ResModelAffected;
|
|
109
110
|
updateAffected?: ResModelAffected;
|
|
110
111
|
isExist: boolean;
|
|
@@ -75,6 +75,7 @@ class CrudQueryOneResult extends CrudResultBase {
|
|
|
75
75
|
super(data.rawContext, data.debugInfo);
|
|
76
76
|
this.row = data.row;
|
|
77
77
|
this.found = (this.row !== null && this.row !== undefined);
|
|
78
|
+
this.fromTable = data.fromTable;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
exports.CrudQueryOneResult = CrudQueryOneResult;
|
|
@@ -117,7 +118,6 @@ exports.CrudCountResult = CrudCountResult;
|
|
|
117
118
|
class CrudUpsertResult extends CrudResultBase {
|
|
118
119
|
constructor(data) {
|
|
119
120
|
super(data.rawContext, data.debugInfo);
|
|
120
|
-
this.affected = data.affected;
|
|
121
121
|
this.insertAffected = data.insertAffected;
|
|
122
122
|
this.updateAffected = data.updateAffected;
|
|
123
123
|
this.isExist = data.isExist;
|
|
@@ -17,5 +17,7 @@ export interface ICurdProServiceHub {
|
|
|
17
17
|
convertOriginToExecuteSql(sqlCfgModel: SqlCfgModel): Promise<void>;
|
|
18
18
|
executeFuncCfg(tmpFunCfg: IFuncCfgModel, exeFunCtx: FuncContext): string;
|
|
19
19
|
getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
|
|
20
|
+
getPrimaryKeyColumns(query: ITableMetaQuery): Promise<string[]>;
|
|
20
21
|
convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
|
|
22
|
+
convertConditionTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
|
|
21
23
|
}
|