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
|
@@ -123,9 +123,39 @@ let CurdProService = class CurdProService extends BaseService_1.BaseService {
|
|
|
123
123
|
super(...arguments);
|
|
124
124
|
this.responseCfgHandlers = {};
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* 注册“响应配置处理器”(response cfg handler)。
|
|
128
|
+
*
|
|
129
|
+
* 这些处理器会在一次 CRUD/SQL 执行完成后被调用,用于对 `ExecuteContext` 做统一的二次加工,
|
|
130
|
+
* 例如:追加扩展数据、做字段脱敏、统一包装返回结构等。
|
|
131
|
+
*
|
|
132
|
+
* @param key 处理器类型标识(业务自定义枚举/常量)
|
|
133
|
+
* @param handler 处理器实现
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* curdProService.setResponseCfgHandlers(RelatedType.USER, {
|
|
137
|
+
* async handleExecuteContextPrepare(ctx) {
|
|
138
|
+
* // 可在执行结果处理前做准备
|
|
139
|
+
* },
|
|
140
|
+
* async handleExecuteContext(ctx) {
|
|
141
|
+
* // 对 ctx.result / ctx.data 等进行二次加工
|
|
142
|
+
* },
|
|
143
|
+
* });
|
|
144
|
+
*/
|
|
126
145
|
setResponseCfgHandlers(key, handler) {
|
|
127
146
|
this.responseCfgHandlers[key] = handler;
|
|
128
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 获取当前已注册的“响应配置处理器”集合。
|
|
150
|
+
*
|
|
151
|
+
* 一般用于调试或在外层框架中统一检查/复用已有 handler。
|
|
152
|
+
*
|
|
153
|
+
* @returns 以 `RelatedType` 为 key 的处理器映射表
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const handlers = curdProService.getResponseCfgHandlers();
|
|
157
|
+
* const userHandler = handlers[RelatedType.USER];
|
|
158
|
+
*/
|
|
129
159
|
getResponseCfgHandlers() {
|
|
130
160
|
return this.responseCfgHandlers;
|
|
131
161
|
}
|
|
@@ -153,8 +183,21 @@ let CurdProService = class CurdProService extends BaseService_1.BaseService {
|
|
|
153
183
|
return crudPro;
|
|
154
184
|
}
|
|
155
185
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
186
|
+
* 通过 `method` 走配置化 CRUD 执行(从缓存/加载的配置中解析)。
|
|
187
|
+
*
|
|
188
|
+
* 适用于:前端/调用方只传 `method` + 入参,由服务端根据 `method` 找到对应的 CRUD 配置并执行。
|
|
189
|
+
* 在非调试模式下会启用配置缓存(调试模式关闭缓存,便于开发即时生效)。
|
|
190
|
+
*
|
|
191
|
+
* @param reqJson 请求模型,必须包含 `method`
|
|
192
|
+
* @returns `ExecuteContext`(包含执行过程与结果数据)
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* const ctx = await curdProService.executeCrud({
|
|
196
|
+
* method: 'user.list',
|
|
197
|
+
* pageNo: 1,
|
|
198
|
+
* pageSize: 20,
|
|
199
|
+
* });
|
|
200
|
+
* console.log(ctx.result);
|
|
158
201
|
*/
|
|
159
202
|
async executeCrud(reqJson) {
|
|
160
203
|
if (!reqJson.method) {
|
|
@@ -165,12 +208,45 @@ let CurdProService = class CurdProService extends BaseService_1.BaseService {
|
|
|
165
208
|
const isEnableCache = !this.isEnableDebug();
|
|
166
209
|
return curdPro.executeCrud(reqJson, isEnableCache);
|
|
167
210
|
}
|
|
168
|
-
|
|
211
|
+
/**
|
|
212
|
+
* 直接执行一段 SQL(不依赖 `method` 配置)。
|
|
213
|
+
*
|
|
214
|
+
* 适用于:后台管理/运维脚本/内部工具需要直接跑 SQL 的场景(仍会复用本服务的连接池、日志、visitor、事务等上下文)。
|
|
215
|
+
*
|
|
216
|
+
* @param sqlCfgModel SQL 执行配置(数据库、类型、SQL、参数等)
|
|
217
|
+
* @returns SQL 执行结果
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* const res = await curdProService.executeSQL({
|
|
221
|
+
* sqlDbType: SqlDbType.mysql,
|
|
222
|
+
* sqlDatabase: 'mydb',
|
|
223
|
+
* executeSql: 'select * from t_user where id = ?',
|
|
224
|
+
* executeSqlArgs: [1],
|
|
225
|
+
* });
|
|
226
|
+
*/
|
|
169
227
|
async executeSQL(sqlCfgModel) {
|
|
170
228
|
const curdPro = this.getCrudPro();
|
|
171
229
|
return curdPro.executeSQL(sqlCfgModel);
|
|
172
230
|
}
|
|
173
|
-
|
|
231
|
+
/**
|
|
232
|
+
* 直接按指定配置执行一次 CRUD(不走缓存查找)。
|
|
233
|
+
*
|
|
234
|
+
* 适用于:调用方已经拿到了/动态生成了本次要执行的 CRUD 配置,想直接执行并得到结果。
|
|
235
|
+
* 该方法会对 `cfgModel` 做两类标准化处理:
|
|
236
|
+
* - 统一修正/补全配置(`fixCfgModel`)
|
|
237
|
+
* - 应用软删除规则(`fixSoftDelete`)
|
|
238
|
+
*
|
|
239
|
+
* @param reqJson 本次请求入参
|
|
240
|
+
* @param cfgModel 本次请求对应的 CRUD 配置
|
|
241
|
+
* @returns `ExecuteContext`
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* const cfg = await curdProService.getCachedCfgByMethod('user.list');
|
|
245
|
+
* const ctx = await curdProService.executeCrudByCfg(
|
|
246
|
+
* { method: 'user.list', pageNo: 1, pageSize: 20 },
|
|
247
|
+
* cfg as any
|
|
248
|
+
* );
|
|
249
|
+
*/
|
|
174
250
|
async executeCrudByCfg(reqJson, cfgModel) {
|
|
175
251
|
this.logInfo('executeCrudByCfg', cfgModel);
|
|
176
252
|
const curdPro = this.getCrudPro();
|
|
@@ -210,7 +286,7 @@ let CurdProService = class CurdProService extends BaseService_1.BaseService {
|
|
|
210
286
|
* 获取分表 CRUD 操作器
|
|
211
287
|
*
|
|
212
288
|
* @param param 参数对象
|
|
213
|
-
* @returns
|
|
289
|
+
* @returns ShardingBase 实例(实际类型根据 shardingConfig.type 决定)
|
|
214
290
|
*
|
|
215
291
|
* @example
|
|
216
292
|
* const sharding = curdProService.getShardingCrud({
|
|
@@ -225,16 +301,60 @@ let CurdProService = class CurdProService extends BaseService_1.BaseService {
|
|
|
225
301
|
*/
|
|
226
302
|
getShardingCrud(param) {
|
|
227
303
|
const { sqlDatabase, sqlDbType, shardingConfig } = param;
|
|
228
|
-
|
|
229
|
-
|
|
304
|
+
const factory = () => this.getCrudPro();
|
|
305
|
+
let sharding;
|
|
306
|
+
switch (shardingConfig.type) {
|
|
307
|
+
case crud_sharding_1.ShardingType.YEAR:
|
|
308
|
+
case crud_sharding_1.ShardingType.MONTH:
|
|
309
|
+
case crud_sharding_1.ShardingType.DAY:
|
|
310
|
+
sharding = new crud_sharding_1.ShardingByTimeCrud(factory, shardingConfig);
|
|
311
|
+
break;
|
|
312
|
+
case crud_sharding_1.ShardingType.HASH:
|
|
313
|
+
sharding = new crud_sharding_1.ShardingByHashCrud(factory, shardingConfig);
|
|
314
|
+
break;
|
|
315
|
+
case crud_sharding_1.ShardingType.KEY:
|
|
316
|
+
sharding = new crud_sharding_1.ShardingByKeyCrud(factory, shardingConfig);
|
|
317
|
+
break;
|
|
318
|
+
case crud_sharding_1.ShardingType.CUSTOM:
|
|
319
|
+
sharding = new crud_sharding_1.ShardingByCustomCrud(factory, shardingConfig);
|
|
320
|
+
break;
|
|
321
|
+
default:
|
|
322
|
+
throw new Error(`[CurdProService] 不支持的分表类型: ${shardingConfig.type}`);
|
|
323
|
+
}
|
|
230
324
|
sharding.setBaseCfg({ sqlDatabase, sqlDbType });
|
|
231
325
|
return sharding;
|
|
232
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* 根据 `method` 获取(可能来自缓存的)CRUD 配置。
|
|
329
|
+
*
|
|
330
|
+
* 适用于:你只想先拿到配置做检查/二次加工/审计,再决定是否执行。
|
|
331
|
+
* 非调试模式会启用缓存(调试模式关闭缓存)。
|
|
332
|
+
*
|
|
333
|
+
* @param method 配置方法名,例如:`user.list`
|
|
334
|
+
* @returns CRUD 配置模型
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* const cfg = await curdProService.getCachedCfgByMethod('user.list');
|
|
338
|
+
* console.log(cfg.sqlSimpleName);
|
|
339
|
+
*/
|
|
233
340
|
async getCachedCfgByMethod(method) {
|
|
234
341
|
const curdPro = this.getCrudPro();
|
|
235
342
|
const isEnableCache = !this.isEnableDebug(); // 开发环境不使用缓存
|
|
236
343
|
return curdPro.getCachedCfgByMethod(method, isEnableCache);
|
|
237
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* 获取数据库的表/视图等结构信息列表(用于数据源探测、表选择器、代码生成等场景)。
|
|
347
|
+
*
|
|
348
|
+
* @param query 查询条件(例如:指定数据库、关键字过滤、分页等)
|
|
349
|
+
* @param options 可选项(例如:是否包含视图/系统表、大小写处理等,具体以接口定义为准)
|
|
350
|
+
* @returns 表信息列表结果
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* const tables = await curdProService.getAllTableInfos(
|
|
354
|
+
* { sqlDbType: SqlDbType.mysql, sqlDatabase: 'mydb' },
|
|
355
|
+
* { skipCache: true }
|
|
356
|
+
* );
|
|
357
|
+
*/
|
|
238
358
|
async getAllTableInfos(query, options) {
|
|
239
359
|
const curdPro = this.getCrudPro();
|
|
240
360
|
return curdPro.getAllTableInfos(query, options);
|
package/package.json
CHANGED
package/src/configuration.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { GlobalMiddleware } from './middleware/global.middleware';
|
|
|
15
15
|
import { ForbiddenMiddleware } from './middleware/forbidden.middleware';
|
|
16
16
|
import { SCHEDULE_QUEUE, ANONYMOUS_CONTEXT, INNER_SCHEDULE_INTERVAL } from './schedule';
|
|
17
17
|
import { privateAES } from './libs/utils/crypto-utils';
|
|
18
|
+
import { initTableMetaCachePublishClient, initTableMetaCacheSubscriber } from './service/TableMetaCacheRedisSubscriber';
|
|
19
|
+
import { RedisService } from '@midwayjs/redis';
|
|
18
20
|
|
|
19
21
|
@Configuration({
|
|
20
22
|
// namespace: 'fatcms',
|
|
@@ -65,6 +67,11 @@ export class ContainerLifeCycle {
|
|
|
65
67
|
*/
|
|
66
68
|
ANONYMOUS_CONTEXT.setApp(this.app);
|
|
67
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 初始化表元数据缓存 Redis 广播
|
|
72
|
+
*/
|
|
73
|
+
await this.initTableMetaCacheRedis();
|
|
74
|
+
|
|
68
75
|
/**
|
|
69
76
|
* 启动定时任务
|
|
70
77
|
*/
|
|
@@ -78,6 +85,26 @@ export class ContainerLifeCycle {
|
|
|
78
85
|
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
/**
|
|
89
|
+
* 初始化表元数据缓存的 Redis Pub/Sub 广播
|
|
90
|
+
*
|
|
91
|
+
* - 发布客户端:复用已有的 RedisService 实例
|
|
92
|
+
* - 订阅客户端:通过 duplicate() 创建独立连接
|
|
93
|
+
*
|
|
94
|
+
* 集群部署时,任意节点调用 clearTableMetaCacheWithBroadcast()
|
|
95
|
+
* 会通过 Redis 广播通知所有节点清空本地缓存。
|
|
96
|
+
*/
|
|
97
|
+
private async initTableMetaCacheRedis() {
|
|
98
|
+
try {
|
|
99
|
+
const redisService = await this.app.getApplicationContext().getAsync(RedisService);
|
|
100
|
+
initTableMetaCachePublishClient(redisService);
|
|
101
|
+
initTableMetaCacheSubscriber(redisService);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
// Redis 未配置或不可用时,降级为单节点模式(仅清空本地缓存)
|
|
104
|
+
this.app.getLogger().warn('ContainerLifeCycle ==> initTableMetaCacheRedis Redis不可用,降级为单节点模式:', e.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
private async startScheduleOnReady() {
|
|
82
109
|
const logger = this.app.getLogger();
|
|
83
110
|
const config = this.app.getConfig();
|
|
@@ -7,6 +7,7 @@ import { createUniqueId } from '@/libs/utils/functions';
|
|
|
7
7
|
import { privateAES } from '@/libs/utils/crypto-utils';
|
|
8
8
|
import { CommonResult } from '@/libs/utils/common-dto';
|
|
9
9
|
import {SystemRoleCode} from "@/models/SystemPerm";
|
|
10
|
+
import { clearTableMetaCacheWithBroadcast } from '@/service/TableMetaCacheRedisSubscriber';
|
|
10
11
|
|
|
11
12
|
// http://127.0.0.1:7002/ns/api/helpers/getApiScript?prefix=/ns/api/manage
|
|
12
13
|
// http://127.0.0.1:7002/ns/api/helpers/getApiScript
|
|
@@ -145,6 +146,20 @@ export class HelpersApi {
|
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/**
|
|
150
|
+
* 工具函数: 清空表元数据缓存(集群广播)
|
|
151
|
+
* 清空本节点缓存,并通过 Redis Pub/Sub 广播通知集群中其他节点。
|
|
152
|
+
* 清空后,下次请求会自动从数据库重新加载(懒加载刷新)。
|
|
153
|
+
*/
|
|
154
|
+
@Get('/clearTableMetaCache')
|
|
155
|
+
async clearTableMetaCacheApi() {
|
|
156
|
+
const stats = clearTableMetaCacheWithBroadcast();
|
|
157
|
+
return CommonResult.successRes({
|
|
158
|
+
message: '表元数据缓存已清空(已广播集群)',
|
|
159
|
+
beforeClear: stats,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
private checkLocalPermissionEnv() {
|
|
149
164
|
//是否是开发者
|
|
150
165
|
const isDevelopUser = (): boolean=> {
|
|
@@ -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 { RequestModel } from './models/RequestModel';
|
|
4
4
|
import { CurdProServiceHub } from './services/CurdProServiceHub';
|
|
@@ -165,12 +165,16 @@ class CrudPro {
|
|
|
165
165
|
exeCtx.setReqModel(reqModel);
|
|
166
166
|
exeCtx.setCfgModel(cfgModel);
|
|
167
167
|
|
|
168
|
+
// insertOrUpdate 参数校验
|
|
169
|
+
this.validateParamsIfNeeded(reqModel, cfgModel);
|
|
170
|
+
|
|
168
171
|
// 如果是 sqlSimpleName模式的 update/insert,则需要将 reqJson.data 部分根据真实的表结构进行过滤
|
|
169
172
|
await this.filterDataByTableMetaIfNeeded(reqModel, cfgModel);
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
//插入和更新时: 根据表结构字段类型,自动转换 data 数据格式(如 PostgreSQL ARRAY 字段)
|
|
172
175
|
await this.convertDataFieldTypeIfNeeded(reqModel, cfgModel);
|
|
173
|
-
|
|
176
|
+
//查询时:根据表结构字段类型,自动转换 condition 数据格式(防止数据库隐式类型转换导致索引失效)
|
|
177
|
+
await this.convertConditionTypeIfNeeded(reqModel, cfgModel);
|
|
174
178
|
// 参数校验
|
|
175
179
|
this.serviceHub.validateDataType(cfgModel, reqModel);
|
|
176
180
|
this.serviceHub.validateByAllow(cfgModel, reqModel);
|
|
@@ -214,6 +218,19 @@ class CrudPro {
|
|
|
214
218
|
return this.serviceHub.getAllTableInfos(query, options);
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
/**
|
|
222
|
+
* 获取表的主键列名列表
|
|
223
|
+
*
|
|
224
|
+
* 复用 CrudProTableMetaService 的 getTableMeta 缓存,
|
|
225
|
+
* 从 columnDetails 中筛选 isPrimaryKey 的列。
|
|
226
|
+
*
|
|
227
|
+
* @param query 表元数据查询参数
|
|
228
|
+
* @returns 主键列名数组
|
|
229
|
+
*/
|
|
230
|
+
public async getPrimaryKeyColumns(query: ITableMetaQuery): Promise<string[]> {
|
|
231
|
+
return this.serviceHub.getPrimaryKeyColumns(query);
|
|
232
|
+
}
|
|
233
|
+
|
|
217
234
|
/**
|
|
218
235
|
* 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
|
|
219
236
|
* 避免传入不存在的字段导致 SQL 执行报错
|
|
@@ -266,6 +283,48 @@ class CrudPro {
|
|
|
266
283
|
reqModel.data = filteredData;
|
|
267
284
|
}
|
|
268
285
|
|
|
286
|
+
/**
|
|
287
|
+
* 校验特定操作的必填参数
|
|
288
|
+
*
|
|
289
|
+
* - insertOrUpdate 和 update:condition 和 data 均为必填
|
|
290
|
+
* - insertOnDuplicateUpdate 和 insert:只有data 为必填
|
|
291
|
+
* @param reqModel 请求模型
|
|
292
|
+
* @param cfgModel 配置模型
|
|
293
|
+
* @throws CommonException 如果缺少必填参数
|
|
294
|
+
*/
|
|
295
|
+
private validateParamsIfNeeded(reqModel: RequestModel, cfgModel: RequestCfgModel): void {
|
|
296
|
+
// insertOrUpdate 和 update:condition 和 data 均为必填
|
|
297
|
+
if (cfgModel.sqlSimpleName === KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE ||
|
|
298
|
+
cfgModel.sqlSimpleName === KeysOfSimpleSQL.SIMPLE_UPDATE) {
|
|
299
|
+
if (!reqModel.condition || Object.keys(reqModel.condition).length === 0) {
|
|
300
|
+
throw new CommonException(
|
|
301
|
+
Exceptions.INSERT_OR_UPDATE_CONDITION_EMPTY,
|
|
302
|
+
'[CrudPro] insertOrUpdate/update 操作的 condition 不能为空。' +
|
|
303
|
+
'condition 用于判断记录是否存在或定位更新目标。'
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (!reqModel.data || Object.keys(reqModel.data as Record<string, any>).length === 0) {
|
|
307
|
+
throw new CommonException(
|
|
308
|
+
Exceptions.INSERT_OR_UPDATE_DATA_EMPTY,
|
|
309
|
+
'[CrudPro] insertOrUpdate/update 操作的 data 不能为空。' +
|
|
310
|
+
'data 用于指定插入或更新的内容。'
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// insertOnDuplicateUpdate 和 insert:只有 data 为必填
|
|
316
|
+
if (cfgModel.sqlSimpleName === KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE ||
|
|
317
|
+
cfgModel.sqlSimpleName === KeysOfSimpleSQL.SIMPLE_INSERT) {
|
|
318
|
+
if (!reqModel.data || Object.keys(reqModel.data as Record<string, any>).length === 0) {
|
|
319
|
+
throw new CommonException(
|
|
320
|
+
Exceptions.INSERT_OR_DUPLICATE_UPDATE_DATA_EMPTY,
|
|
321
|
+
'[CrudPro] insert/insertOnDuplicateUpdate 操作的 data 不能为空。' +
|
|
322
|
+
'data 用于指定插入或更新的内容。'
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
269
328
|
/**
|
|
270
329
|
* 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
|
|
271
330
|
* @param sqlSimpleName 简单 SQL 名称
|
|
@@ -282,7 +341,7 @@ class CrudPro {
|
|
|
282
341
|
}
|
|
283
342
|
|
|
284
343
|
/**
|
|
285
|
-
*
|
|
344
|
+
* 根据表结构字段类型,自动转换 data 数据格式
|
|
286
345
|
* 例如:PostgreSQL 的 ARRAY 类型字段,需要将 JSON 数组转为 PG 数组字面量格式
|
|
287
346
|
* @param reqModel 请求模型
|
|
288
347
|
* @param cfgModel 配置模型
|
|
@@ -291,6 +350,16 @@ class CrudPro {
|
|
|
291
350
|
await this.serviceHub.convertDataTypeByTableMeta(reqModel, cfgModel);
|
|
292
351
|
}
|
|
293
352
|
|
|
353
|
+
/**
|
|
354
|
+
* 查询时:根据表结构字段类型,自动转换 condition 数据格式
|
|
355
|
+
* 防止数据库隐式类型转换导致索引失效(如 WHERE int_col = '123' 导致索引无法命中)
|
|
356
|
+
* @param reqModel 请求模型
|
|
357
|
+
* @param cfgModel 配置模型
|
|
358
|
+
*/
|
|
359
|
+
private async convertConditionTypeIfNeeded(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
|
|
360
|
+
await this.serviceHub.convertConditionTypeByTableMeta(reqModel, cfgModel);
|
|
361
|
+
}
|
|
362
|
+
|
|
294
363
|
private async executeSQLList() {
|
|
295
364
|
try {
|
|
296
365
|
await this.serviceHub.executeSqlCfgModels();
|
|
@@ -61,6 +61,13 @@ export enum Exceptions {
|
|
|
61
61
|
DATA_GET_DATA_EMPTY_ON_INSERT_VALUES = 'DATA_GET_DATA_EMPTY_ON_INSERT_VALUES',
|
|
62
62
|
BATCH_INSERT_KEYS_MISMATCH = 'BATCH_INSERT_KEYS_MISMATCH', // 批量插入时字段不一致
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* insertOrUpdate 参数缺失
|
|
66
|
+
*/
|
|
67
|
+
INSERT_OR_UPDATE_CONDITION_EMPTY = 'INSERT_OR_UPDATE_CONDITION_EMPTY', // insertOrUpdate/update 的 condition 不能为空
|
|
68
|
+
INSERT_OR_UPDATE_DATA_EMPTY = 'INSERT_OR_UPDATE_DATA_EMPTY', // insertOrUpdate/update 的 data 不能为空
|
|
69
|
+
INSERT_OR_DUPLICATE_UPDATE_DATA_EMPTY = 'INSERT_ON_DUPLICATE_UPDATE_DATA_EMPTY', // insertOnDuplicateUpdate/insert 的 data 不能为空
|
|
70
|
+
|
|
64
71
|
/**
|
|
65
72
|
* 请求的Method字段为空
|
|
66
73
|
*/
|
|
@@ -85,6 +92,7 @@ export enum Exceptions {
|
|
|
85
92
|
RUN_PICK_ERR_VISITOR_FIELD_NULL = 'RUN_PICK_ERR_VISITOR_FIELD_NULL',
|
|
86
93
|
RUN_EXECUTE_VALIDATE = 'RUN_EXECUTE_VALIDATE',
|
|
87
94
|
RUN_FUNCTION_NOT_FOUND = 'RUN_FUNCTION_NOT_FOUND',
|
|
95
|
+
MORE_THAN_ONE_RECORDS_FOUND = 'MORE_THAN_ONE_RECORDS_FOUND',
|
|
88
96
|
|
|
89
97
|
/**
|
|
90
98
|
* 参数校验错误
|
|
@@ -89,11 +89,14 @@ class CrudWriteResult extends CrudResultBase {
|
|
|
89
89
|
class CrudQueryOneResult<T = Record<string, any>> extends CrudResultBase {
|
|
90
90
|
readonly row: T | null;
|
|
91
91
|
readonly found: boolean;
|
|
92
|
+
/** 数据来源的物理表名(分表场景下为实际分表名,如 t_order_202403) */
|
|
93
|
+
readonly fromTable: string;
|
|
92
94
|
|
|
93
|
-
constructor(data: { row: T | null; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo }) {
|
|
95
|
+
constructor(data: { row: T | null; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo; fromTable: string }) {
|
|
94
96
|
super(data.rawContext, data.debugInfo);
|
|
95
97
|
this.row = data.row;
|
|
96
|
-
this.found = (this.row !== null && this.row !== undefined
|
|
98
|
+
this.found = (this.row !== null && this.row !== undefined);
|
|
99
|
+
this.fromTable = data.fromTable;
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -145,13 +148,11 @@ class CrudCountResult extends CrudResultBase {
|
|
|
145
148
|
|
|
146
149
|
/** InsertOrUpdate 结果 */
|
|
147
150
|
class CrudUpsertResult extends CrudResultBase {
|
|
148
|
-
readonly affected?: ResModelAffected;
|
|
149
151
|
readonly insertAffected?: ResModelAffected;
|
|
150
152
|
readonly updateAffected?: ResModelAffected;
|
|
151
153
|
readonly isExist: boolean;
|
|
152
154
|
|
|
153
155
|
constructor(data: {
|
|
154
|
-
affected?: ResModelAffected;
|
|
155
156
|
insertAffected?: ResModelAffected;
|
|
156
157
|
updateAffected?: ResModelAffected;
|
|
157
158
|
isExist: boolean;
|
|
@@ -159,7 +160,6 @@ class CrudUpsertResult extends CrudResultBase {
|
|
|
159
160
|
debugInfo?: CrudResultDebugInfo;
|
|
160
161
|
}) {
|
|
161
162
|
super(data.rawContext, data.debugInfo);
|
|
162
|
-
this.affected = data.affected;
|
|
163
163
|
this.insertAffected = data.insertAffected;
|
|
164
164
|
this.updateAffected = data.updateAffected;
|
|
165
165
|
this.isExist = data.isExist;
|
|
@@ -30,5 +30,9 @@ export interface ICurdProServiceHub {
|
|
|
30
30
|
|
|
31
31
|
getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
|
|
32
32
|
|
|
33
|
+
getPrimaryKeyColumns(query: ITableMetaQuery): Promise<string[]>;
|
|
34
|
+
|
|
33
35
|
convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
|
|
36
|
+
|
|
37
|
+
convertConditionTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
|
|
34
38
|
}
|