midway-fatcms 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.qoder/skills/midway-fatcms/01-quick-start.md +231 -0
  2. package/.qoder/skills/midway-fatcms/02-crud-quick.md +375 -0
  3. package/.qoder/skills/midway-fatcms/03-crud-sharding.md +489 -0
  4. package/.qoder/skills/midway-fatcms/04-condition-operators.md +93 -0
  5. package/.qoder/skills/midway-fatcms/05-configuration.md +290 -0
  6. package/.qoder/skills/midway-fatcms/06-builtin-functions.md +241 -0
  7. package/.qoder/skills/midway-fatcms/07-examples.md +504 -0
  8. package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
  9. package/README.md +9 -9
  10. package/dist/configuration.d.ts +10 -0
  11. package/dist/configuration.js +26 -0
  12. package/dist/controller/base/BaseApiController.d.ts +1 -2
  13. package/dist/controller/base/BaseApiController.js +0 -4
  14. package/dist/controller/gateway/DocGatewayController.js +1 -1
  15. package/dist/controller/helpers.controller.d.ts +6 -0
  16. package/dist/controller/helpers.controller.js +19 -0
  17. package/dist/controller/manage/FlowConfigManageApi.js +4 -2
  18. package/dist/controller/manage/SysConfigMangeApi.js +6 -1
  19. package/dist/controller/manage/UserAccountManageApi.js +7 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +2 -2
  22. package/dist/libs/crud-pro/CrudPro.d.ts +51 -3
  23. package/dist/libs/crud-pro/CrudPro.js +111 -4
  24. package/dist/libs/crud-pro/exceptions.d.ts +7 -0
  25. package/dist/libs/crud-pro/exceptions.js +7 -0
  26. package/dist/libs/crud-pro/interfaces.d.ts +83 -12
  27. package/dist/libs/crud-pro/models/CrudResult.d.ts +116 -0
  28. package/dist/libs/crud-pro/models/CrudResult.js +126 -0
  29. package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -2
  30. package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -0
  31. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +70 -2
  32. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +205 -13
  33. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
  34. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
  35. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +36 -0
  36. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +97 -4
  37. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +2 -0
  38. package/dist/libs/crud-pro/services/CurdProServiceHub.js +6 -0
  39. package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +382 -0
  40. package/dist/libs/crud-pro-quick/CrudProQuick.js +689 -0
  41. package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
  42. package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
  43. package/dist/libs/crud-pro-quick/index.d.ts +36 -0
  44. package/dist/libs/crud-pro-quick/index.js +49 -0
  45. package/dist/libs/crud-pro-quick/models.d.ts +33 -0
  46. package/dist/libs/crud-pro-quick/models.js +2 -0
  47. package/dist/libs/crud-sharding/ShardingBase.d.ts +78 -0
  48. package/dist/libs/crud-sharding/ShardingBase.js +179 -0
  49. package/dist/libs/crud-sharding/ShardingByCustomCrud.d.ts +35 -0
  50. package/dist/libs/crud-sharding/ShardingByCustomCrud.js +297 -0
  51. package/dist/libs/crud-sharding/ShardingByHashCrud.d.ts +38 -0
  52. package/dist/libs/crud-sharding/ShardingByHashCrud.js +86 -0
  53. package/dist/libs/crud-sharding/ShardingByKeyCrud.d.ts +39 -0
  54. package/dist/libs/crud-sharding/ShardingByKeyCrud.js +74 -0
  55. package/dist/libs/crud-sharding/ShardingByTimeCrud.d.ts +66 -0
  56. package/dist/libs/crud-sharding/ShardingByTimeCrud.js +524 -0
  57. package/dist/libs/crud-sharding/ShardingConfig.d.ts +25 -10
  58. package/dist/libs/crud-sharding/ShardingConfig.js +5 -5
  59. package/dist/libs/crud-sharding/ShardingMerger.d.ts +10 -18
  60. package/dist/libs/crud-sharding/ShardingMerger.js +27 -44
  61. package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
  62. package/dist/libs/crud-sharding/ShardingResult.js +16 -0
  63. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
  64. package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
  65. package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
  66. package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
  67. package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  68. package/dist/libs/crud-sharding/index.d.ts +13 -15
  69. package/dist/libs/crud-sharding/index.js +33 -17
  70. package/dist/models/RedisKeys.d.ts +1 -0
  71. package/dist/models/RedisKeys.js +1 -0
  72. package/dist/models/bizmodels.d.ts +2 -6
  73. package/dist/service/SysAppService.d.ts +2 -2
  74. package/dist/service/SysAppService.js +16 -5
  75. package/dist/service/SysConfigService.d.ts +1 -1
  76. package/dist/service/SysConfigService.js +7 -2
  77. package/dist/service/SysDictDataService.js +14 -4
  78. package/dist/service/SysMenuService.js +7 -2
  79. package/dist/service/TableMetaCacheRedisSubscriber.d.ts +31 -0
  80. package/dist/service/TableMetaCacheRedisSubscriber.js +98 -0
  81. package/dist/service/curd/CurdMixService.d.ts +6 -4
  82. package/dist/service/curd/CurdMixService.js +16 -2
  83. package/dist/service/curd/CurdProService.d.ts +149 -29
  84. package/dist/service/curd/CurdProService.js +157 -38
  85. package/dist/service/flow/FlowConfigService.js +7 -2
  86. package/dist/service/flow/FlowInstanceCrudService.js +22 -19
  87. package/package.json +1 -1
  88. package/src/configuration.ts +27 -0
  89. package/src/controller/base/BaseApiController.ts +0 -5
  90. package/src/controller/gateway/DocGatewayController.ts +1 -1
  91. package/src/controller/helpers.controller.ts +15 -0
  92. package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
  93. package/src/controller/manage/FlowConfigManageApi.ts +4 -2
  94. package/src/controller/manage/SysConfigMangeApi.ts +6 -1
  95. package/src/controller/manage/UserAccountManageApi.ts +7 -2
  96. package/src/index.ts +2 -2
  97. package/src/libs/crud-pro/CrudPro.ts +134 -7
  98. package/src/libs/crud-pro/exceptions.ts +8 -0
  99. package/src/libs/crud-pro/interfaces.ts +111 -15
  100. package/src/libs/crud-pro/models/CrudResult.ts +178 -0
  101. package/src/libs/crud-pro/models/RequestModel.ts +2 -2
  102. package/src/libs/crud-pro/models/ServiceHub.ts +4 -0
  103. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +238 -15
  104. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
  105. package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
  106. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +110 -3
  107. package/src/libs/crud-pro/services/CurdProServiceHub.ts +8 -0
  108. package/src/libs/crud-pro-quick/CrudProQuick.ts +782 -0
  109. package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
  110. package/src/libs/crud-pro-quick/index.ts +52 -0
  111. package/src/libs/crud-pro-quick/models.ts +35 -0
  112. package/src/libs/crud-sharding/ShardingBase.ts +256 -0
  113. package/src/libs/crud-sharding/ShardingByCustomCrud.ts +329 -0
  114. package/src/libs/crud-sharding/ShardingByHashCrud.ts +111 -0
  115. package/src/libs/crud-sharding/ShardingByKeyCrud.ts +97 -0
  116. package/src/libs/crud-sharding/ShardingByTimeCrud.ts +628 -0
  117. package/src/libs/crud-sharding/ShardingConfig.ts +28 -10
  118. package/src/libs/crud-sharding/ShardingMerger.ts +35 -63
  119. package/src/libs/crud-sharding/ShardingResult.ts +29 -0
  120. package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
  121. package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
  122. package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  123. package/src/libs/crud-sharding/index.ts +30 -16
  124. package/src/models/RedisKeys.ts +1 -0
  125. package/src/models/bizmodels.ts +4 -7
  126. package/src/service/SysAppService.ts +18 -7
  127. package/src/service/SysConfigService.ts +8 -3
  128. package/src/service/SysDictDataService.ts +14 -4
  129. package/src/service/SysMenuService.ts +7 -2
  130. package/src/service/TableMetaCacheRedisSubscriber.ts +105 -0
  131. package/src/service/crudstd/CrudStdService.ts +2 -2
  132. package/src/service/curd/CurdMixService.ts +26 -5
  133. package/src/service/curd/CurdProService.ts +186 -45
  134. package/src/service/flow/FlowConfigService.ts +7 -2
  135. package/src/service/flow/FlowInstanceCrudService.ts +23 -20
  136. package/.qoder/skills/midway-fatcms-crud/SKILL.md +0 -375
  137. package/.qoder/skills/midway-fatcms-crud/examples.md +0 -990
  138. package/.qoder/skills/midway-fatcms-crud/reference.md +0 -568
  139. package/dist/libs/crud-pro/README.md +0 -809
  140. package/dist/libs/crud-pro/README_FUNC.md +0 -193
  141. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  142. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +0 -363
  143. package/dist/libs/crud-sharding/ShardingCrudPro.js +0 -675
  144. package/dist/libs/crud-sharding/ShardingRouter.d.ts +0 -69
  145. package/dist/libs/crud-sharding/ShardingRouter.js +0 -377
  146. package/dist/models/StandardColumns.d.ts +0 -71
  147. package/dist/models/StandardColumns.js +0 -28
  148. package/dist/service/curd/CrudProQuick.d.ts +0 -190
  149. package/dist/service/curd/CrudProQuick.js +0 -319
  150. package/dist/service/curd/README.md +0 -1100
  151. package/dist/service/curd/fixSoftDelete.d.ts +0 -20
  152. package/src/libs/crud-pro/README.md +0 -809
  153. package/src/libs/crud-pro/README_FUNC.md +0 -193
  154. package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  155. package/src/libs/crud-sharding/ShardingCrudPro.ts +0 -835
  156. package/src/libs/crud-sharding/ShardingRouter.ts +0 -512
  157. package/src/models/StandardColumns.ts +0 -76
  158. package/src/service/curd/CrudProQuick.ts +0 -360
  159. package/src/service/curd/README.md +0 -1100
@@ -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();
@@ -122,11 +122,6 @@ export class BaseApiController extends BaseService {
122
122
  }
123
123
 
124
124
 
125
- protected get sysDBUtil() {
126
- const { SystemDbName, SystemDbType } = GLOBAL_STATIC_CONFIG.getConfig();
127
- return this.curdMixService.getBbUtil(SystemDbName, SystemDbType);
128
- }
129
-
130
125
  private addAccountBasicInfoCfgModel(cfgModel: IRequestCfgModel) {
131
126
  let columnsRelation = cfgModel.columnsRelation;
132
127
  if (!columnsRelation) {
@@ -122,7 +122,7 @@ export class DocGatewayController extends BaseApiController {
122
122
  condition: {
123
123
  doc_lib_id: parseInt(docLibId),
124
124
  doc_category_code: {
125
- $notNull: '',
125
+ $notNull: true,
126
126
  },
127
127
  },
128
128
  },
@@ -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=> {
@@ -4,6 +4,7 @@ import { parseCreateSqlToTitleMap, parseTableFieldTitleFromComment } from '@/lib
4
4
  import { checkPermission } from '@/middleware/permission.middleware';
5
5
  import { SystemFuncCode } from '@/models/SystemPerm';
6
6
  import { KeyOfCrudTypes, KeysOfSimpleSQL, SqlDbType } from '@/libs/crud-pro/models/keys';
7
+ import { ExecuteSQLRowsResult } from '@/libs/crud-pro/interfaces';
7
8
  import { SystemTables } from '@/models/SystemTables';
8
9
  import { CommonException } from '@/libs/crud-pro/exceptions';
9
10
  import { parseDatabaseName, toDatabaseNameStr } from '@/libs/crud-pro/utils/DatabaseName';
@@ -192,7 +193,7 @@ export class CrudStandardDesignApi extends BaseApiController {
192
193
  sqlDbType: dbType,
193
194
  crudType: KeyOfCrudTypes.SYS_QUERY,
194
195
  executeSqlArgs: [],
195
- });
196
+ }) as ExecuteSQLRowsResult;
196
197
 
197
198
  const fields = columnArray.map(columnObj => {
198
199
  const { column_name, is_nullable, column_default, data_type, is_identity, column_description, ...others } = columnObj;
@@ -242,7 +243,7 @@ export class CrudStandardDesignApi extends BaseApiController {
242
243
  sqlDbType: dbType,
243
244
  crudType: KeyOfCrudTypes.SYS_QUERY,
244
245
  executeSqlArgs: [schemaname, tableName],
245
- });
246
+ }) as ExecuteSQLRowsResult;
246
247
 
247
248
  const commentMap = await this.getTableFieldsCommentsMapOfPostgreSQL(dbName, schemaname, tableName);
248
249
 
@@ -309,7 +310,7 @@ export class CrudStandardDesignApi extends BaseApiController {
309
310
  sqlDbType: dbType,
310
311
  crudType: KeyOfCrudTypes.SYS_QUERY,
311
312
  executeSqlArgs: [schemaname, tableName],
312
- });
313
+ }) as ExecuteSQLRowsResult;
313
314
 
314
315
  const map: any = {};
315
316
  commentArray.forEach(commentObj => {
@@ -46,7 +46,8 @@ export class FlowConfigManageApi extends BaseApiController {
46
46
  @Post('/updateFlowConfig')
47
47
  async updateFlowConfig() {
48
48
  const body: IRequestModel = this.ctx.request.body as any;
49
- const { flow_code, flow_version } = body.condition;
49
+ const flow_code = String(body.condition.flow_code);
50
+ const flow_version = Number(body.condition.flow_version);
50
51
 
51
52
  await this.flowConfigService.remove_cache_flow_config_one(flow_code, flow_version);
52
53
 
@@ -63,7 +64,8 @@ export class FlowConfigManageApi extends BaseApiController {
63
64
  @Post('/deleteFlowConfig')
64
65
  async deleteFlowConfig() {
65
66
  const body: IRequestModel = this.ctx.request.body as any;
66
- const { flow_code, flow_version } = body.condition;
67
+ const flow_code = String(body.condition.flow_code);
68
+ const flow_version = Number(body.condition.flow_version);
67
69
  await this.flowConfigService.remove_cache_flow_config_one(flow_code, flow_version);
68
70
 
69
71
  return this.executeSysSimpleSQL(FLOW_TABLES.flow_config, KeysOfSimpleSQL.SIMPLE_DELETE, {
@@ -61,7 +61,12 @@ export class SysConfigMangeApi extends BaseApiController {
61
61
  return CommonResult.errorRes('id不能为空');
62
62
  }
63
63
 
64
- const sysConfig = await this.curdMixService.getBbUtil(SystemDbName, SystemDbType).getUniqueOne({ condition: body.condition }, SystemTables.sys_configs);
64
+ const sysConfigRes = await this.curdMixService.getQuickCrud({
65
+ sqlDatabase: SystemDbName,
66
+ sqlDbType: SystemDbType,
67
+ sqlTable: SystemTables.sys_configs,
68
+ }).findOne({ condition: body.condition });
69
+ const sysConfig = sysConfigRes.row;
65
70
  if (!sysConfig) {
66
71
  return CommonResult.errorRes('配置项不存在');
67
72
  }
@@ -55,9 +55,14 @@ export class UserAccountManageApi extends BaseApiController {
55
55
  return CommonResult.errorRes('id不能为空');
56
56
  }
57
57
 
58
- const userAccountInfo = await this.curdMixService.getBbUtil(SystemDbName, SystemDbType).getUniqueOne({
58
+ const userAccountInfoRes = await this.curdMixService.getQuickCrud({
59
+ sqlDatabase: SystemDbName,
60
+ sqlDbType: SystemDbType,
61
+ sqlTable: SystemTables.sys_user_account,
62
+ }).findUniqueOne({
59
63
  condition: body.condition
60
- }, SystemTables.sys_user_account);
64
+ });
65
+ const userAccountInfo = userAccountInfoRes.row;
61
66
 
62
67
  if (!userAccountInfo) {
63
68
  return CommonResult.errorRes('用户信息不存在');
package/src/index.ts CHANGED
@@ -70,7 +70,7 @@ export * from './service/curd/CurdMixByWorkbenchService';
70
70
  export * from './service/curd/CurdMixService';
71
71
  export * from './service/curd/CurdMixUtils';
72
72
  export * from './service/curd/CurdProService';
73
- export * from './service/curd/CrudProQuick';
73
+ export * from './libs/crud-pro-quick';
74
74
  export * from './service/flow/FlowConfigService';
75
75
  export * from './service/flow/FlowInstanceService';
76
76
  export * from './service/flow/FlowInstanceCrudService';
@@ -87,7 +87,6 @@ export * from './models/contextLogger';
87
87
  export * from './models/devops';
88
88
  export * from './models/SystemTables';
89
89
  export * from './models/AsyncTaskModel';
90
- export * from './models/StandardColumns';
91
90
 
92
91
  export * from './schedule/index';
93
92
 
@@ -114,5 +113,6 @@ export * from './libs/crud-pro/models/RequestCfgModel';
114
113
  export * from './libs/crud-pro/models/SqlSegArg';
115
114
  export * from './libs/crud-pro/models/ResModel';
116
115
  export * from './libs/crud-sharding/index';
116
+ export * from './libs/crud-pro-quick/index';
117
117
 
118
118
  export * from './libs/global-config/global-config';
@@ -1,4 +1,4 @@
1
- import { ICrudProCfg, ILogger, IRequestCfgModel, IRequestModel, ISqlCfgModel, ITableListResult, ITableNamesOptions, ITableNamesQuery, IVisitor } 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';
@@ -13,11 +13,44 @@ class CrudPro {
13
13
  private readonly executeContext: ExecuteContext;
14
14
  private readonly serviceHub: CurdProServiceHub;
15
15
 
16
+ /** 标记是否已被使用(防止重复使用导致状态污染) */
17
+ private _isUsed: boolean = false;
18
+ /** 标记是否正在执行中(防止并发调用) */
19
+ private _isExecuting: boolean = false;
20
+
16
21
  constructor() {
17
22
  this.executeContext = new ExecuteContext();
18
23
  this.serviceHub = new CurdProServiceHub(this.executeContext);
19
24
  }
20
25
 
26
+ /**
27
+ * 检查是否可以开始执行
28
+ * @throws 如果已使用或正在执行中则抛出异常
29
+ */
30
+ private checkAndMarkExecuting(): void {
31
+ if (this._isUsed) {
32
+ throw new Error(
33
+ '[CrudPro] 此实例已被使用,不能重复使用。' +
34
+ '每次操作请创建新的 CrudPro 实例,可通过 curdProService.getCrudPro() 或工厂函数获取。'
35
+ );
36
+ }
37
+ if (this._isExecuting) {
38
+ throw new Error(
39
+ '[CrudPro] 此实例正在执行中,不能并发调用。' +
40
+ '请等待当前操作完成后再进行下一次调用,或创建新的实例。'
41
+ );
42
+ }
43
+ this._isExecuting = true;
44
+ }
45
+
46
+ /**
47
+ * 标记执行完成
48
+ */
49
+ private markExecutionComplete(): void {
50
+ this._isExecuting = false;
51
+ this._isUsed = true;
52
+ }
53
+
21
54
  // 必填
22
55
  set transaction(transaction: Transaction) {
23
56
  this.executeContext.setTransaction(transaction);
@@ -30,6 +63,10 @@ class CrudPro {
30
63
  set visitor(visitor: IVisitor) {
31
64
  this.executeContext.setVisitor(visitor);
32
65
  }
66
+ // 必填
67
+ getVisitor() {
68
+ return this.executeContext.getVisitor();
69
+ }
33
70
 
34
71
  set contextFunc(contextFunc: IExecuteContextFunc) {
35
72
  contextFunc.setExecuteContext(this.executeContext);
@@ -43,8 +80,11 @@ class CrudPro {
43
80
  /**
44
81
  * 直接执行一个SQL
45
82
  * @param sqlCfgModel
83
+ * @returns 返回未被包装的数据库原始结果(根据 resPicker 配置返回不同类型,无 {code, data, message} 包装层)
46
84
  */
47
- public async executeSQL(sqlCfgModel: ISqlCfgModel): Promise<any> {
85
+ public async executeSQL(sqlCfgModel: ISqlCfgModel): Promise<ExecuteSQLResult> {
86
+ this.checkAndMarkExecuting();
87
+
48
88
  const RES_NAME = 'executeResult';
49
89
 
50
90
  const sqlCfgModel2 = {
@@ -64,8 +104,12 @@ class CrudPro {
64
104
  sqlCfgList: [sqlCfgModel2],
65
105
  };
66
106
 
67
- const ss = await this.executeCrudByCfg({}, cfgModel);
68
- return ss.getResModelItem(RES_NAME);
107
+ try {
108
+ const ss = await this.executeCrudByCfgInternal({}, cfgModel);
109
+ return ss.getResModelItem(RES_NAME);
110
+ } finally {
111
+ this.markExecutionComplete();
112
+ }
69
113
  }
70
114
 
71
115
  /**
@@ -87,6 +131,20 @@ class CrudPro {
87
131
  * @param cfgJson
88
132
  */
89
133
  public async executeCrudByCfg(reqJson: IRequestModel, cfgJson: IRequestCfgModel): Promise<ExecuteContext> {
134
+ this.checkAndMarkExecuting();
135
+ try {
136
+ return await this.executeCrudByCfgInternal(reqJson, cfgJson);
137
+ } finally {
138
+ this.markExecutionComplete();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * 内部执行方法(不含检查逻辑,供内部调用)
144
+ * @param reqJson
145
+ * @param cfgJson
146
+ */
147
+ private async executeCrudByCfgInternal(reqJson: IRequestModel, cfgJson: IRequestCfgModel): Promise<ExecuteContext> {
90
148
  const logger = this.executeContext.getLogger();
91
149
  logger.info('CurdPro executeCrudByCfg', cfgJson);
92
150
 
@@ -107,12 +165,16 @@ class CrudPro {
107
165
  exeCtx.setReqModel(reqModel);
108
166
  exeCtx.setCfgModel(cfgModel);
109
167
 
168
+ // insertOrUpdate 参数校验
169
+ this.validateParamsIfNeeded(reqModel, cfgModel);
170
+
110
171
  // 如果是 sqlSimpleName模式的 update/insert,则需要将 reqJson.data 部分根据真实的表结构进行过滤
111
172
  await this.filterDataByTableMetaIfNeeded(reqModel, cfgModel);
112
173
 
113
- // 根据表结构字段类型,自动转换数据格式(如 PostgreSQL ARRAY 字段)
174
+ //插入和更新时: 根据表结构字段类型,自动转换 data 数据格式(如 PostgreSQL ARRAY 字段)
114
175
  await this.convertDataFieldTypeIfNeeded(reqModel, cfgModel);
115
-
176
+ //查询时:根据表结构字段类型,自动转换 condition 数据格式(防止数据库隐式类型转换导致索引失效)
177
+ await this.convertConditionTypeIfNeeded(reqModel, cfgModel);
116
178
  // 参数校验
117
179
  this.serviceHub.validateDataType(cfgModel, reqModel);
118
180
  this.serviceHub.validateByAllow(cfgModel, reqModel);
@@ -156,6 +218,19 @@ class CrudPro {
156
218
  return this.serviceHub.getAllTableInfos(query, options);
157
219
  }
158
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
+
159
234
  /**
160
235
  * 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
161
236
  * 避免传入不存在的字段导致 SQL 执行报错
@@ -208,6 +283,48 @@ class CrudPro {
208
283
  reqModel.data = filteredData;
209
284
  }
210
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
+
211
328
  /**
212
329
  * 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
213
330
  * @param sqlSimpleName 简单 SQL 名称
@@ -224,7 +341,7 @@ class CrudPro {
224
341
  }
225
342
 
226
343
  /**
227
- * 根据表结构字段类型,自动转换数据格式
344
+ * 根据表结构字段类型,自动转换 data 数据格式
228
345
  * 例如:PostgreSQL 的 ARRAY 类型字段,需要将 JSON 数组转为 PG 数组字面量格式
229
346
  * @param reqModel 请求模型
230
347
  * @param cfgModel 配置模型
@@ -233,6 +350,16 @@ class CrudPro {
233
350
  await this.serviceHub.convertDataTypeByTableMeta(reqModel, cfgModel);
234
351
  }
235
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
+
236
363
  private async executeSQLList() {
237
364
  try {
238
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
  * 参数校验错误
@@ -17,6 +17,7 @@ export interface ITableColumn {
17
17
  type: string; // 数据类型
18
18
  isNullable: boolean; // 是否可空
19
19
  isPrimaryKey?: boolean; // 是否主键
20
+ isIdentity?: boolean; // 是否自增列(SQL Server)
20
21
  defaultValue?: any; // 默认值
21
22
  maxLength?: number; // 最大长度
22
23
  comment?: string; // 字段注释
@@ -53,7 +54,7 @@ export interface IPoolConnectionClient {
53
54
  export interface IRequestModel {
54
55
  method?: string;
55
56
  columns?: string | string[]; // "columns": "id,name,age,sex,addr",
56
- condition?: Record<string, any>; // 拼接查询条件
57
+ condition?: IRequestCondition; // 拼接查询条件
57
58
  data?: Record<string, any> | Record<string, any>[]; // 单条或批量插入/更新的数据部分
58
59
  orderBy?: string | IOrderByItem[];
59
60
  limit?: number;
@@ -64,23 +65,57 @@ export interface IRequestModel {
64
65
 
65
66
  type IBasicType = boolean | string | number;
66
67
 
68
+ /**
69
+ * 比较操作符条件
70
+ * 支持 MongoDB 风格的所有查询操作符
71
+ */
67
72
  export interface ICompareCondition {
68
- $gt?: IBasicType;
69
- $gte?: IBasicType;
70
- $lt?: IBasicType;
71
- $lte?: IBasicType;
72
- $ne?: IBasicType;
73
-
74
- $in?: IRequestCondition[];
75
- $nin?: IRequestCondition[];
76
- $like?: IBasicType;
77
- $match?: IBasicType;
78
- $matchBool?: IBasicType;
79
- $range?: number[];
73
+ // 比较操作符
74
+ $ne?: IBasicType; // 不等于
75
+ $lt?: IBasicType; // 小于
76
+ $lte?: IBasicType; // 小于或等于
77
+ $gt?: IBasicType; // 大于
78
+ $gte?: IBasicType; // 大于或等于
79
+
80
+ // 集合操作符
81
+ $in?: IBasicType[]; // 在集合中
82
+ $nin?: IBasicType[]; // 不在集合中
83
+
84
+ // 范围操作符
85
+ $range?: [number, number]; // 范围查询 [min, max]
86
+
87
+ // 模糊匹配操作符
88
+ $like?: string; // 前缀匹配 A%
89
+ $notLike?: string; // 非前缀匹配
90
+ $likeInclude?: string; // 包含匹配 %A%
91
+ $notLikeInclude?: string; // 不包含匹配
92
+
93
+ // 全文索引操作符
94
+ $match?: string; // MySQL 全文索引
95
+ $matchBool?: string | boolean; // MySQL 布尔全文索引
96
+
97
+ // NULL 判断操作符
98
+ $null?: boolean; // IS NULL
99
+ $notNull?: boolean; // IS NOT NULL
100
+
101
+ // JSON 操作符
102
+ $jsonArrayContains?: string; // JSON 数组包含
103
+ }
104
+
105
+ /**
106
+ * 逻辑操作符条件
107
+ */
108
+ export interface ILogicalCondition {
109
+ $or?: IRequestCondition[]; // 逻辑或
110
+ $and?: IRequestCondition[]; // 逻辑与
80
111
  }
81
112
 
82
- export interface IRequestCondition extends Record<string, any>, ICompareCondition {
83
- $or?: IRequestCondition[];
113
+ /**
114
+ * 查询条件类型
115
+ * 支持字段值直接匹配或比较操作符
116
+ * 支持 $or, $and 逻辑组合
117
+ */
118
+ export interface IRequestCondition extends Record<string, IBasicType | ICompareCondition | ILogicalCondition | IRequestCondition[] | undefined>, ILogicalCondition {
84
119
  }
85
120
 
86
121
  /**
@@ -163,6 +198,67 @@ export interface ISqlCfgModel extends IBaseCfgModel {
163
198
  crudType?: string;
164
199
  }
165
200
 
201
+ /**
202
+ * executeSQL 的返回结果类型说明(未被包装的直接结果)
203
+ *
204
+ * 注意:executeSQL 返回的是数据库查询的原始结果,没有 {code, data, message} 包装层。
205
+ * 返回类型取决于 resPicker 配置,请根据实际 SQL 类型选择合适的类型断言。
206
+ *
207
+ * 使用示例:
208
+ * // 查询列表 - 返回数组
209
+ * const rows = await executeSQL({ executeSql: 'SELECT * FROM users', resPicker: KeysOfSqlResPicker.RESULT_ROWS }) as ExecuteSQLRowResult<User>;
210
+ *
211
+ * // 查询单行 - 返回对象或null
212
+ * const user = await executeSQL({ executeSql: 'SELECT * FROM users WHERE id=?', executeSqlArgs: [1], resPicker: KeysOfSqlResPicker.RESULT_FIRST_ROW }) as ExecuteSQLSingleResult<User>;
213
+ *
214
+ * // 查询总数 - 返回数字
215
+ * const count = await executeSQL({ executeSql: 'SELECT COUNT(*) as total_count FROM users', resPicker: KeysOfSqlResPicker.RESULT_TOTAL_COUNT }) as ExecuteSQLCountResult;
216
+ *
217
+ * // 判断存在 - 返回布尔
218
+ * const exists = await executeSQL({ executeSql: 'SELECT 1 as is_exist FROM users WHERE id=?', executeSqlArgs: [1], resPicker: KeysOfSqlResPicker.RESULT_IS_EXIST }) as ExecuteSQLExistResult;
219
+ *
220
+ * // 增删改操作 - 返回受影响行数信息
221
+ * const result = await executeSQL({ executeSql: 'INSERT INTO users(name) VALUES(?)', executeSqlArgs: ['张三'], resPicker: KeysOfSqlResPicker.UPDATE_RESULT }) as ExecuteSQLUpdateResult;
222
+ */
223
+
224
+ /** 查询多行时的返回类型(默认情况):原始数据行数组,无包装 */
225
+ export type ExecuteSQLRowsResult<T = Record<string, any>> = T[];
226
+
227
+ /** 查询单行时的返回类型:首行对象或null,无包装 */
228
+ export type ExecuteSQLSingleResult<T = Record<string, any>> = T | null;
229
+
230
+ /** 查询数量时的返回类型:数字,无包装 */
231
+ export type ExecuteSQLCountResult = number;
232
+
233
+ /** 判断存在时的返回类型:布尔值,无包装 */
234
+ export type ExecuteSQLExistResult = boolean;
235
+
236
+ /** 增删改操作时的返回类型:包含 insertId 和 affectedRows,无包装 */
237
+ export interface ExecuteSQLUpdateResult {
238
+ /** 插入记录的自增ID(仅INSERT操作有效) */
239
+ insertId: any;
240
+ /** 受影响的行数 */
241
+ affectedRows: number;
242
+ }
243
+
244
+ /**
245
+ * executeSQL 的通用返回类型(未被包装的直接结果)
246
+ * 根据 resPicker 的不同配置,实际返回类型可能是:
247
+ * - ExecuteSQLRowResult<T> : 多行数据(默认)
248
+ * - ExecuteSQLSingleResult<T> : 单行数据或null
249
+ * - ExecuteSQLCountResult : 数字
250
+ * - ExecuteSQLExistResult : 布尔值
251
+ * - ExecuteSQLUpdateResult : 增删改结果
252
+ *
253
+ * 警告:此类型为联合类型,使用时建议根据实际 resPicker 进行类型断言以获得精确类型提示
254
+ */
255
+ export type ExecuteSQLResult<T = Record<string, any>> =
256
+ | ExecuteSQLRowsResult<T>
257
+ | ExecuteSQLSingleResult<T>
258
+ | ExecuteSQLCountResult
259
+ | ExecuteSQLExistResult
260
+ | ExecuteSQLUpdateResult;
261
+
166
262
  /**
167
263
  * 自定义校验函数。如果不正确请抛出异常。{code:'xxx',message:'xxx'}
168
264
  */