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
@@ -0,0 +1,178 @@
1
+ import { ResModelAffected } from './ResModel';
2
+ import { ExecuteContext } from './ExecuteContext';
3
+
4
+ /**
5
+ * 结果基类的辅助定位信息
6
+ * 用于错误排查和日志记录
7
+ */
8
+ export interface CrudResultDebugInfo {
9
+ /** 数据库名 */
10
+ sqlDatabase?: string;
11
+ /** 表名 */
12
+ sqlTable?: string;
13
+ /** 方法名(配置中的 method 字段) */
14
+ method?: string;
15
+ /** 查询条件(用于定位问题) */
16
+ condition?: Record<string, any>;
17
+ }
18
+
19
+ /**
20
+ * 结果基类
21
+ *
22
+ * 使用 ES2022 私有字段 #rawContext 存储 ExecuteContext 引用。
23
+ * ES2022 私有字段特性:JSON.stringify 不会序列化 # 开头的私有字段。
24
+ * 因此结果对象可直接返回给 API 前端,自动过滤掉内部上下文。
25
+ *
26
+ * Node.js 兼容性:Node.js 14.6+ 完全支持 ES2022 私有字段。
27
+ */
28
+ abstract class CrudResultBase {
29
+ #rawContext: ExecuteContext;
30
+ /** 辅助定位信息,用于错误排查 */
31
+ readonly debugInfo: CrudResultDebugInfo;
32
+
33
+ constructor(rawContext: ExecuteContext, debugInfo?: CrudResultDebugInfo) {
34
+ this.#rawContext = rawContext;
35
+ this.debugInfo = debugInfo || {};
36
+ }
37
+
38
+ getRawContext(): ExecuteContext {
39
+ return this.#rawContext;
40
+ }
41
+
42
+ /**
43
+ * 生成带辅助定位信息的错误消息
44
+ * @param baseMessage 基础错误信息
45
+ * @returns 包含定位信息的完整错误消息
46
+ */
47
+ protected buildErrorMessage(baseMessage: string): string {
48
+ const parts: string[] = [baseMessage];
49
+
50
+ if (this.debugInfo.sqlDatabase) {
51
+ parts.push(`数据库: ${this.debugInfo.sqlDatabase}`);
52
+ }
53
+ if (this.debugInfo.sqlTable) {
54
+ parts.push(`表: ${this.debugInfo.sqlTable}`);
55
+ }
56
+ if (this.debugInfo.method) {
57
+ parts.push(`方法: ${this.debugInfo.method}`);
58
+ }
59
+ if (this.debugInfo.condition) {
60
+ try {
61
+ parts.push(`条件: ${JSON.stringify(this.debugInfo.condition)}`);
62
+ } catch {
63
+ parts.push(`条件: [无法序列化]`);
64
+ }
65
+ }
66
+
67
+ return parts.join(' | ');
68
+ }
69
+ }
70
+
71
+ /** 写操作结果(INSERT/UPDATE/DELETE) */
72
+ class CrudWriteResult extends CrudResultBase {
73
+ readonly affectedRows: number;
74
+ readonly insertId?: string | number;
75
+
76
+ constructor(data: {
77
+ affectedRows: number;
78
+ insertId?: string | number;
79
+ rawContext: ExecuteContext;
80
+ debugInfo?: CrudResultDebugInfo;
81
+ }) {
82
+ super(data.rawContext, data.debugInfo);
83
+ this.affectedRows = data.affectedRows;
84
+ this.insertId = data.insertId;
85
+ }
86
+ }
87
+
88
+ /** 单条查询结果 */
89
+ class CrudQueryOneResult<T = Record<string, any>> extends CrudResultBase {
90
+ readonly row: T | null;
91
+ readonly found: boolean;
92
+ /** 数据来源的物理表名(分表场景下为实际分表名,如 t_order_202403) */
93
+ readonly fromTable: string;
94
+
95
+ constructor(data: { row: T | null; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo; fromTable: string }) {
96
+ super(data.rawContext, data.debugInfo);
97
+ this.row = data.row;
98
+ this.found = (this.row !== null && this.row !== undefined);
99
+ this.fromTable = data.fromTable;
100
+ }
101
+ }
102
+
103
+ /** 列表查询结果 */
104
+ class CrudQueryListResult<T = Record<string, any>> extends CrudResultBase {
105
+ readonly rows: T[];
106
+ readonly count: number;
107
+
108
+ constructor(data: { rows: T[]; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo }) {
109
+ super(data.rawContext, data.debugInfo);
110
+ this.rows = data.rows || [];
111
+ this.count = this.rows.length;
112
+ }
113
+ }
114
+
115
+ /** 分页查询结果 */
116
+ class CrudQueryPageResult<T = Record<string, any>> extends CrudResultBase {
117
+ readonly rows: T[];
118
+ readonly totalCount: number;
119
+ readonly count: number;
120
+
121
+ constructor(data: { rows: T[]; totalCount: number; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo }) {
122
+ super(data.rawContext, data.debugInfo);
123
+ this.rows = data.rows || [];
124
+ this.totalCount = data.totalCount;
125
+ this.count = this.rows.length;
126
+ }
127
+ }
128
+
129
+ /** 存在性判断结果 */
130
+ class CrudExistResult extends CrudResultBase {
131
+ readonly exists: boolean;
132
+
133
+ constructor(data: { exists: boolean; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo }) {
134
+ super(data.rawContext, data.debugInfo);
135
+ this.exists = data.exists;
136
+ }
137
+ }
138
+
139
+ /** 计数结果 */
140
+ class CrudCountResult extends CrudResultBase {
141
+ readonly count: number;
142
+
143
+ constructor(data: { count: number; rawContext: ExecuteContext; debugInfo?: CrudResultDebugInfo }) {
144
+ super(data.rawContext, data.debugInfo);
145
+ this.count = data.count;
146
+ }
147
+ }
148
+
149
+ /** InsertOrUpdate 结果 */
150
+ class CrudUpsertResult extends CrudResultBase {
151
+ readonly insertAffected?: ResModelAffected;
152
+ readonly updateAffected?: ResModelAffected;
153
+ readonly isExist: boolean;
154
+
155
+ constructor(data: {
156
+ insertAffected?: ResModelAffected;
157
+ updateAffected?: ResModelAffected;
158
+ isExist: boolean;
159
+ rawContext: ExecuteContext;
160
+ debugInfo?: CrudResultDebugInfo;
161
+ }) {
162
+ super(data.rawContext, data.debugInfo);
163
+ this.insertAffected = data.insertAffected;
164
+ this.updateAffected = data.updateAffected;
165
+ this.isExist = data.isExist;
166
+ }
167
+ }
168
+
169
+ export {
170
+ CrudResultBase,
171
+ CrudWriteResult,
172
+ CrudQueryOneResult,
173
+ CrudQueryListResult,
174
+ CrudQueryPageResult,
175
+ CrudExistResult,
176
+ CrudCountResult,
177
+ CrudUpsertResult,
178
+ };
@@ -1,5 +1,5 @@
1
1
  import * as _ from 'lodash';
2
- import { ILimitOffset, IOrderByItem, IRequestModel, IVisitor } from '../interfaces';
2
+ import { ILimitOffset, IOrderByItem, IRequestModel, IVisitor, IRequestCondition } from '../interfaces';
3
3
  import { MixinUtils } from '../utils/MixinUtils';
4
4
  import { OrderByUtils } from '../utils/OrderByUtils';
5
5
  import { CommonException, Exceptions } from '../exceptions';
@@ -10,7 +10,7 @@ class RequestModel {
10
10
 
11
11
  method: string;
12
12
  columns?: string[]; // "columns": "id,name,age,sex,addr",
13
- condition?: Record<string, any>;
13
+ condition?: IRequestCondition;
14
14
  data?: Record<string, any> | Record<string, any>[]; // 支持单条或批量数据
15
15
  limit: number;
16
16
  offset: number;
@@ -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
  }
@@ -1,15 +1,42 @@
1
1
  import { CrudProServiceBase } from './CrudProServiceBase';
2
2
  import { RequestModel } from '../models/RequestModel';
3
3
  import { RequestCfgModel } from '../models/RequestCfgModel';
4
- import { KeysOfSimpleSQL, SqlDbType } from '../models/keys';
5
- import { ITableMetaQuery } from '../interfaces';
4
+ import { KeysOfConditions, KeysOfSimpleSQL, SqlDbType } from '../models/keys';
5
+ import { IRequestCondition, ITableMetaQuery } from '../interfaces';
6
+
7
+ /** 预编译正则:整数类型(MySQL/PostgreSQL/SQL Server) */
8
+ const REGEX_INTEGER_TYPE = /^(int|bigint|smallint|tinyint|mediumint|integer|serial|bigserial)/;
9
+
10
+ /** 预编译正则:字符串类型(MySQL/PostgreSQL/SQL Server) */
11
+ const REGEX_STRING_TYPE = /^(varchar|char|text|longtext|mediumtext|tinytext|nvarchar|nchar|ntext|character varying|character)/;
12
+
13
+ /** 包含 WHERE 条件的操作类型集合(查询/删除/更新) */
14
+ const CONDITION_TYPES = new Set<string>([
15
+ KeysOfSimpleSQL.SIMPLE_QUERY,
16
+ KeysOfSimpleSQL.SIMPLE_QUERY_ONE,
17
+ KeysOfSimpleSQL.SIMPLE_QUERY_PAGE,
18
+ KeysOfSimpleSQL.SIMPLE_QUERY_COUNT,
19
+ KeysOfSimpleSQL.SIMPLE_QUERY_EXIST,
20
+ KeysOfSimpleSQL.SIMPLE_DELETE,
21
+ KeysOfSimpleSQL.SIMPLE_UPDATE,
22
+ ]);
23
+
24
+ /** INSERT/UPDATE 操作类型集合 */
25
+ const INSERT_OR_UPDATE_TYPES = new Set<string>([
26
+ KeysOfSimpleSQL.SIMPLE_INSERT,
27
+ KeysOfSimpleSQL.SIMPLE_UPDATE,
28
+ KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
29
+ KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
30
+ KeysOfSimpleSQL.SIMPLE_BATCH_INSERT,
31
+ ]);
6
32
 
7
33
  /**
8
34
  * 数据类型转换服务
9
- * 根据表结构字段类型,在 INSERT/UPDATE 操作前自动转换数据格式,确保数据与数据库方言兼容
35
+ * 根据表结构字段类型,自动转换数据格式,确保数据与数据库方言兼容,防止隐式类型转换导致索引失效
10
36
  *
11
37
  * 当前支持的转换:
12
- * - PostgreSQL ARRAY 类型:JSON 数组 → PG 数组字面量 {"a","b","c"}
38
+ * - PostgreSQL ARRAY 类型(INSERT/UPDATE data):JSON 数组 → PG 数组字面量 {"a","b","c"}
39
+ * - 查询条件类型强转(SELECT/DELETE/UPDATE condition):字符串 → 数字/布尔,防止隐式类型转换导致索引失效
13
40
  */
14
41
  class CrudProDataTypeConvertService extends CrudProServiceBase {
15
42
 
@@ -150,21 +177,217 @@ class CrudProDataTypeConvertService extends CrudProServiceBase {
150
177
  return '"' + escaped + '"';
151
178
  }
152
179
 
180
+ /**
181
+ * 查询时:根据表结构字段类型,自动转换 condition 中的数据格式
182
+ * 防止数据库隐式类型转换导致索引失效
183
+ *
184
+ * 实现步骤:
185
+ * 1. 判断操作类型是否为查询/删除/更新(这些操作包含 WHERE 条件)
186
+ * 2. 获取表元数据,构建字段名→类型映射
187
+ * 3. 递归遍历 condition(支持 $or/$and 嵌套),按字段类型转换值
188
+ *
189
+ * 转换规则(双向保护,仅处理最常建索引的列类型):
190
+ * - 整数列(int/bigint/smallint/tinyint/mediumint) + 字符串值 → number
191
+ * - 整数列 + 布尔值 → 0/1
192
+ * - 字符串列(varchar/char/text等) + 数字值 → string
193
+ * - 字符串列 + 布尔值 → string
194
+ *
195
+ * @param reqModel 请求模型
196
+ * @param cfgModel 配置模型
197
+ */
198
+ async convertConditionTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
199
+ const sqlSimpleName = cfgModel.sqlSimpleName;
200
+ if (!this.isConditionType(sqlSimpleName)) {
201
+ return;
202
+ }
203
+
204
+ if (!reqModel.condition || Object.keys(reqModel.condition).length === 0) {
205
+ return;
206
+ }
207
+
208
+ const query: ITableMetaQuery = {
209
+ sqlTable: cfgModel.sqlTable,
210
+ sqlDatabase: cfgModel.sqlDatabase,
211
+ sqlDbType: cfgModel.sqlDbType,
212
+ sqlSchema: cfgModel.sqlSchema,
213
+ };
214
+
215
+ const tableMeta = await this.serviceHub.getTableMeta(query);
216
+ if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
217
+ this.logger.warn('CrudProDataTypeConvertService: 无法获取表结构信息,跳过条件类型转换', cfgModel.sqlTable);
218
+ return;
219
+ }
220
+
221
+ // 构建字段名→类型映射(全小写便于匹配)
222
+ const typeMap = new Map<string, string>();
223
+ for (const col of tableMeta.columnDetails) {
224
+ typeMap.set(col.name, ('' + col.type).toLowerCase());
225
+ }
226
+
227
+ this.convertConditionFields(reqModel.condition, typeMap, cfgModel.sqlDbType);
228
+ }
229
+
230
+ /**
231
+ * 递归遍历 condition,对每个字段值做类型转换
232
+ *
233
+ * 实现步骤:
234
+ * 1. 遍历 condition 的每个 key
235
+ * 2. 若 key 为 $or/$and 逻辑操作符,递归处理子条件数组
236
+ * 3. 若 key 为字段名且值为基本类型,直接转换
237
+ * 4. 若 key 为字段名且值为比较操作符对象($gt/$in等),遍历操作符逐值转换
238
+ *
239
+ * @param condition 查询条件对象
240
+ * @param typeMap 字段名→类型映射
241
+ * @param sqlDbType 数据库类型
242
+ */
243
+ private convertConditionFields(
244
+ condition: IRequestCondition,
245
+ typeMap: Map<string, string>,
246
+ sqlDbType: SqlDbType
247
+ ): void {
248
+ if (!condition || typeof condition !== 'object') {
249
+ return;
250
+ }
251
+
252
+ for (const key of Object.keys(condition)) {
253
+ const value = condition[key];
254
+
255
+ // $or / $and 逻辑操作符:递归处理每个子条件
256
+ if (key === KeysOfConditions.$OR || key === KeysOfConditions.$AND) {
257
+ if (Array.isArray(value)) {
258
+ for (const subCondition of value) {
259
+ this.convertConditionFields(subCondition, typeMap, sqlDbType);
260
+ }
261
+ }
262
+ continue;
263
+ }
264
+
265
+ // 如果 key 是比较操作符(出现在内层),跳过
266
+ if (KeysOfConditions.COMPARE_KEYS.has(key)) {
267
+ continue;
268
+ }
269
+
270
+ // key 是字段名
271
+ const fieldType = typeMap.get(key);
272
+ if (!fieldType) {
273
+ continue; // condition 中引用的字段不在表结构中,不做转换
274
+ }
275
+
276
+ if (this.isBasicType(value)) {
277
+ // 简单等值条件:{ age: "18" } → { age: 18 }
278
+ condition[key] = this.convertValueByType(value, fieldType, sqlDbType);
279
+ }
280
+
281
+
282
+ else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
283
+ // 比较操作符条件:{ age: { "$gt": "18", "$lt": "30" } }
284
+ for (const opKey of Object.keys(value)) {
285
+ if (KeysOfConditions.COMPARE_KEYS.has(opKey)) {
286
+ const opValue = value[opKey];
287
+ if (opKey === KeysOfConditions.$IN || opKey === KeysOfConditions.$NIN) {
288
+ // $in/$nin 的值是数组,逐个转换
289
+ if (Array.isArray(opValue)) {
290
+ value[opKey] = opValue.map(v => this.convertValueByType(v, fieldType, sqlDbType));
291
+ }
292
+ } else if (opKey === KeysOfConditions.$RANGE) {
293
+ // $range 的值是 [min, max]
294
+ if (Array.isArray(opValue)) {
295
+ value[opKey] = opValue.map(v => this.convertValueByType(v, fieldType, sqlDbType));
296
+ }
297
+ } else {
298
+ value[opKey] = this.convertValueByType(opValue, fieldType, sqlDbType);
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+
306
+ /**
307
+ * 根据字段类型转换单个值,防止隐式类型转换导致索引失效
308
+ *
309
+ * 转换规则(双向保护):
310
+ * - 整数列 + 字符串值 → number(WHERE int_col = '123' 索引失效)
311
+ * - 整数列 + 布尔值 → 0/1
312
+ * - 字符串列 + 数字值 → string(WHERE varchar_col = 123 索引失效)
313
+ * - 字符串列 + 布尔值 → string
314
+ *
315
+ * @param value 原始值
316
+ * @param fieldType 字段类型(小写)
317
+ * @param sqlDbType 数据库类型
318
+ * @returns 转换后的值
319
+ */
320
+ private convertValueByType(value: any, fieldType: string, sqlDbType: SqlDbType): any {
321
+ if (value === null || value === undefined) {
322
+ return value;
323
+ }
324
+
325
+ const valueType = typeof value;
326
+
327
+ // 整数列:string → number, boolean → 0/1
328
+ if (this.isIntegerType(fieldType)) {
329
+ if (valueType === 'string') {
330
+ const num = Number(value);
331
+ if (!isNaN(num) && Number.isInteger(num)) {
332
+ return num;
333
+ }
334
+ }
335
+ if (valueType === 'boolean') {
336
+ return value ? 1 : 0;
337
+ }
338
+ }
339
+
340
+ // 字符串列:number → string, boolean → string
341
+ if (this.isStringType(fieldType)) {
342
+ if (valueType === 'number') {
343
+ return '' + value;
344
+ }
345
+ if (valueType === 'boolean') {
346
+ return value ? 'true' : 'false';
347
+ }
348
+ }
349
+
350
+
351
+ return value;
352
+ }
353
+
354
+ /**
355
+ * 判断字段类型是否为整数类型
356
+ * 覆盖 MySQL/PostgreSQL/SQL Server 三种数据库的整数类型名
357
+ */
358
+ private isIntegerType(type: string): boolean {
359
+ return REGEX_INTEGER_TYPE.test(type);
360
+ }
361
+
362
+ /**
363
+ * 判断字段类型是否为字符串类型
364
+ * 覆盖 MySQL/PostgreSQL/SQL Server 三种数据库的字符串类型名
365
+ */
366
+ private isStringType(type: string): boolean {
367
+ return REGEX_STRING_TYPE.test(type);
368
+ }
369
+
370
+
371
+ /**
372
+ * 判断 sqlSimpleName 是否为包含 WHERE 条件的操作类型(查询/删除/更新)
373
+ * 这些操作的 condition 会参与 WHERE 子句,索引失效问题同样存在
374
+ */
375
+ private isConditionType(sqlSimpleName: string): boolean {
376
+ return !!sqlSimpleName && CONDITION_TYPES.has(sqlSimpleName);
377
+ }
378
+
379
+ /**
380
+ * 判断值是否为基本类型(string | number | boolean)
381
+ */
382
+ private isBasicType(value: any): boolean {
383
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
384
+ }
385
+
153
386
  /**
154
387
  * 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
155
388
  */
156
389
  private isInsertOrUpdateType(sqlSimpleName: string): boolean {
157
- if (!sqlSimpleName) {
158
- return false;
159
- }
160
- const types = [
161
- KeysOfSimpleSQL.SIMPLE_INSERT,
162
- KeysOfSimpleSQL.SIMPLE_UPDATE,
163
- KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
164
- KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
165
- KeysOfSimpleSQL.SIMPLE_BATCH_INSERT,
166
- ];
167
- return types.includes(sqlSimpleName as KeysOfSimpleSQL);
390
+ return !!sqlSimpleName && INSERT_OR_UPDATE_TYPES.has(sqlSimpleName);
168
391
  }
169
392
  }
170
393
 
@@ -18,6 +18,9 @@ class CrudProExecuteSqlService extends CrudProServiceBase {
18
18
  async executeSqlCfgModels(): Promise<void> {
19
19
  const exeCtx = this.getExecuteContext();
20
20
  const sqlCfgModels = exeCtx.getSqlCfgModels();
21
+
22
+ console.log("CrudProExecuteSqlService===> " + JSON.stringify(sqlCfgModels))
23
+
21
24
  for (let i = 0; i < sqlCfgModels.length; i++) {
22
25
  const sqlCfgModel = sqlCfgModels[i];
23
26
  const checkResult = this.executeSqlCfgModelPreCheck(sqlCfgModel);
@@ -87,9 +90,45 @@ class CrudProExecuteSqlService extends CrudProServiceBase {
87
90
  return false;
88
91
  }
89
92
 
93
+ const errorMsg = `禁止执行的SQL: ${sqlCfgModel.executeSql}`;
94
+
90
95
  if (KeyOfCrudTypes.NOT_CRUD === crudType) {
91
- // 只支持增删改查,其他语句不支持。
92
- throw new CommonException(Exceptions.CFG_NOT_SUPPORT_THE_SQL, sqlCfgModel.executeSql);
96
+ // 在 isNativeSQL 模式下,允许特定的 DDL 操作(CREATE TABLE、CREATE INDEX 等),但禁止 DROP TABLE
97
+ if (sqlCfgModel.isNativeSQL) {
98
+ // 禁止的 DDL 操作(破坏性操作)
99
+ const forbiddenPatterns = [
100
+ /^\s*DROP\s+TABLE/i,
101
+ /^\s*DROP\s+DATABASE/i,
102
+ /^\s*DROP\s+SCHEMA/i,
103
+ /^\s*TRUNCATE\s+TABLE/i,
104
+ ];
105
+
106
+ for (const pattern of forbiddenPatterns) {
107
+ if (pattern.test(sqlCfgModel.executeSql)) {
108
+ throw new CommonException(Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
109
+ }
110
+ }
111
+
112
+ // 允许的 DDL 操作白名单
113
+ const allowedPatterns = [
114
+ /^\s*CREATE\s+TABLE/i,
115
+ /^\s*CREATE\s+INDEX/i,
116
+ /^\s*CREATE\s+UNIQUE\s+INDEX/i,
117
+ /^\s*ALTER\s+TABLE/i,
118
+ ];
119
+
120
+ const isAllowed = allowedPatterns.some(pattern => pattern.test(sqlCfgModel.executeSql));
121
+
122
+ if (isAllowed) {
123
+ // 允许的 DDL 操作,继续执行后续校验
124
+ } else {
125
+ // 非允许的 DDL 操作,仍然抛出异常
126
+ throw new CommonException(Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
127
+ }
128
+ } else {
129
+ // 非 isNativeSQL 模式下,只支持增删改查,其他语句不支持。
130
+ throw new CommonException(Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
131
+ }
93
132
  }
94
133
 
95
134
  // 此SQL不需要执行
@@ -86,7 +86,7 @@ class CrudProGenSqlCondition {
86
86
  if (equalsIgnoreCase(KeysOfConditions.$OR, key)) {
87
87
  TypeUtils.assertJsonArray(value, Exceptions.REQUEST_OR_PARAM_MUST_ARRAY);
88
88
 
89
- const a = this.generateByLogicalOR(value);
89
+ const a = this.generateByLogicalOR(value as IRequestCondition[]);
90
90
  if (a != null) {
91
91
  tmpSqlList.push(a.sql);
92
92
  if (isNotEmpty(a.args)) {
@@ -99,7 +99,7 @@ class CrudProGenSqlCondition {
99
99
  if (equalsIgnoreCase(KeysOfConditions.$AND, key)) {
100
100
  TypeUtils.assertJsonArray(value, Exceptions.REQUEST_OR_PARAM_MUST_ARRAY);
101
101
 
102
- const a = this.generateByLogicalAnd(value);
102
+ const a = this.generateByLogicalAnd(value as IRequestCondition[]);
103
103
  if (a != null) {
104
104
  tmpSqlList.push(a.sql);
105
105
  if (isNotEmpty(a.args)) {
@@ -114,7 +114,7 @@ class CrudProGenSqlCondition {
114
114
  tmpArgList.push(value);
115
115
  } else {
116
116
  // AND
117
- const a = this.generateByCompareKey(key, value);
117
+ const a = this.generateByCompareKey(key, value as ICompareCondition);
118
118
 
119
119
  if (a != null) {
120
120
  tmpSqlList.push(a.sql);
@@ -209,11 +209,15 @@ class CrudProGenSqlCondition {
209
209
  let tmpSql = '';
210
210
 
211
211
  if (equalsIgnoreCase(KeysOfConditions.$NULL, compare)) {
212
- tmpSql = `${toSqlColumnName(key)} is null `;
213
- hasNoArgSql = true;
212
+ if (value0 === true) {
213
+ tmpSql = `${toSqlColumnName(key)} is null `;
214
+ hasNoArgSql = true;
215
+ }
214
216
  } else if (equalsIgnoreCase(KeysOfConditions.$NOT_NULL, compare)) {
215
- tmpSql = `${toSqlColumnName(key)} is not null `;
216
- hasNoArgSql = true;
217
+ if (value0 === true) {
218
+ tmpSql = `${toSqlColumnName(key)} is not null `;
219
+ hasNoArgSql = true;
220
+ }
217
221
  } else if (equalsIgnoreCase(KeysOfConditions.$GT, compare)) {
218
222
  tmpArgList.push(value0);
219
223
  tmpSql = `${toSqlColumnName(key)} > ?`;