midway-fatcms 0.0.17 → 0.0.19

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 (47) hide show
  1. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +2 -37
  2. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +137 -213
  3. package/dist/libs/crud-pro/utils/MixinUtils.d.ts +2 -17
  4. package/dist/libs/crud-pro/utils/MixinUtils.js +4 -84
  5. package/dist/libs/crud-pro/utils/idgene/generateSnowflakeId.d.ts +12 -0
  6. package/dist/libs/crud-pro/utils/idgene/generateSnowflakeId.js +61 -0
  7. package/dist/libs/crud-pro/utils/idgene/generateSnowflakeIdExt.d.ts +27 -0
  8. package/dist/libs/crud-pro/utils/idgene/generateSnowflakeIdExt.js +96 -0
  9. package/dist/libs/crud-pro/utils/idgene/index.d.ts +3 -0
  10. package/dist/libs/crud-pro/utils/idgene/index.js +10 -0
  11. package/dist/libs/crud-pro/utils/idgene/uuid.d.ts +7 -0
  12. package/dist/libs/crud-pro/utils/idgene/uuid.js +28 -0
  13. package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +0 -17
  14. package/dist/libs/crud-pro-quick/fixSoftDelete.js +55 -5
  15. package/dist/libs/crud-pro-quick/models.d.ts +11 -1
  16. package/dist/service/SysAppService.d.ts +0 -3
  17. package/dist/service/SysAppService.js +0 -15
  18. package/dist/service/SysConfigService.d.ts +0 -1
  19. package/dist/service/SysConfigService.js +0 -6
  20. package/dist/service/SysDictDataService.d.ts +0 -2
  21. package/dist/service/SysDictDataService.js +0 -12
  22. package/dist/service/SysMenuService.d.ts +0 -1
  23. package/dist/service/SysMenuService.js +0 -3
  24. package/dist/service/WorkbenchService.d.ts +0 -1
  25. package/dist/service/WorkbenchService.js +0 -3
  26. package/dist/service/base/bizmemcache/BizMemCacheService.js +4 -2
  27. package/dist/service/base/bizmemcache/BizMemCacheStore.js +10 -9
  28. package/dist/service/crudstd/CrudStdService.js +1 -1
  29. package/dist/service/curd/fixCfgModel.js +21 -1
  30. package/package.json +1 -1
  31. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +149 -219
  32. package/src/libs/crud-pro/utils/MixinUtils.ts +309 -400
  33. package/src/libs/crud-pro/utils/idgene/generateSnowflakeId.ts +65 -0
  34. package/src/libs/crud-pro/utils/idgene/generateSnowflakeIdExt.ts +106 -0
  35. package/src/libs/crud-pro/utils/idgene/index.ts +3 -0
  36. package/src/libs/crud-pro/utils/idgene/uuid.ts +26 -0
  37. package/src/libs/crud-pro-quick/fixSoftDelete.ts +84 -18
  38. package/src/libs/crud-pro-quick/models.ts +15 -3
  39. package/src/service/SysAppService.ts +0 -18
  40. package/src/service/SysConfigService.ts +0 -8
  41. package/src/service/SysDictDataService.ts +0 -14
  42. package/src/service/SysMenuService.ts +0 -4
  43. package/src/service/WorkbenchService.ts +0 -5
  44. package/src/service/base/bizmemcache/BizMemCacheService.ts +4 -2
  45. package/src/service/base/bizmemcache/BizMemCacheStore.ts +10 -9
  46. package/src/service/crudstd/CrudStdService.ts +3 -2
  47. package/src/service/curd/fixCfgModel.ts +145 -125
@@ -16,16 +16,17 @@ function createEmptyData() {
16
16
  };
17
17
  }
18
18
  function createEmptyLastTime() {
19
+ const defaultLastTime = -9999;
19
20
  return {
20
- workbenchCachedObj: 0,
21
- sysConfgCacheObj: 0,
22
- sysMenuCacheObj: 0,
23
- dataDictItemCacheObj: 0,
24
- sysAppCacheObj: 0,
25
- sysAppPageCacheObj: 0,
26
- sysAnyApiCacheObj: 0,
27
- sysProxyApiCacheObj: 0,
28
- crudMethodCacheObj: 0,
21
+ workbenchCachedObj: defaultLastTime,
22
+ sysConfgCacheObj: defaultLastTime,
23
+ sysMenuCacheObj: defaultLastTime,
24
+ dataDictItemCacheObj: defaultLastTime,
25
+ sysAppCacheObj: defaultLastTime,
26
+ sysAppPageCacheObj: defaultLastTime,
27
+ sysAnyApiCacheObj: defaultLastTime,
28
+ sysProxyApiCacheObj: defaultLastTime,
29
+ crudMethodCacheObj: defaultLastTime,
29
30
  };
30
31
  }
31
32
  class BizMemCacheStoreInner {
@@ -79,7 +79,7 @@ let CrudStdService = class CrudStdService extends ApiBaseService_1.ApiBaseServic
79
79
  sqlTable: getExecuteTableNameBySettingKey(appInfo, stdAction, sqlSimpleName),
80
80
  sqlSimpleName,
81
81
  updateCfg: {},
82
- enableSoftDelete: deleteStrategy === 'soft',
82
+ enableSoftDelete: deleteStrategy,
83
83
  enableStandardUpdateCfg: Array.isArray(enableStandardUpdateCfg) && enableStandardUpdateCfg.length > 0 ? enableStandardUpdateCfg : ['by', 'at'],
84
84
  enableStandardUpdateCfgCondition: Array.isArray(enableStandardUpdateCfgCondition) && enableStandardUpdateCfgCondition.length > 0 ? enableStandardUpdateCfgCondition : false, // 默认为false
85
85
  };
@@ -65,7 +65,9 @@ function fixCfgModel(cfgModel) {
65
65
  const suffix = cfgArray[i];
66
66
  if (suffix) {
67
67
  const key = `${prefix}_${suffix}`;
68
- obj[key] = mapping[key];
68
+ if (mapping[key]) {
69
+ obj[key] = mapping[key];
70
+ }
69
71
  }
70
72
  }
71
73
  }
@@ -85,6 +87,24 @@ function fixCfgModel(cfgModel) {
85
87
  if (sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_INSERT) {
86
88
  const updateCfgObj1 = buildUpdateCfgBy(dataCfgArray, 'data.created');
87
89
  _.merge(cfgModel.updateCfg, updateCfgObj1);
90
+ // 启用: 雪花ID
91
+ if (dataCfgArray.includes('idBySnowflakeId')) {
92
+ _.merge(cfgModel.updateCfg, {
93
+ 'data.id': { functionName: 'generateSnowflakeId' },
94
+ });
95
+ }
96
+ // 启用: 扩展的雪花ID: -128bit,base58(建议数据库varchar(24))
97
+ if (dataCfgArray.includes('idBySnowflakeIdExt')) {
98
+ _.merge(cfgModel.updateCfg, {
99
+ 'data.id': { functionName: 'generateSnowflakeIdExt' },
100
+ });
101
+ }
102
+ // 启用: UUID
103
+ if (dataCfgArray.includes('idByUUID')) {
104
+ _.merge(cfgModel.updateCfg, {
105
+ 'data.id': { functionName: 'uuid' },
106
+ });
107
+ }
88
108
  }
89
109
  // 更新语句
90
110
  if (sqlSimpleName === keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midway-fatcms",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "This is a midway component sample",
5
5
  "main": "dist/index.js",
6
6
  "typings": "index.d.ts",
@@ -4,18 +4,21 @@ import { RequestCfgModel } from '../models/RequestCfgModel';
4
4
  import { KeysOfConditions, KeysOfSimpleSQL, SqlDbType } from '../models/keys';
5
5
  import { IRequestCondition, ITableMetaQuery } from '../interfaces';
6
6
 
7
- // ============ 甯搁噺瀹氫箟 ============
7
+ // ============ 常量定义 ============
8
8
 
9
- /** 棰勭紪璇戞鍒欙細鏁存暟绫诲瀷锛圡ySQL/PostgreSQL/SQL Server锛?*/
9
+ /** 整数类型正则(MySQL/PostgreSQL/SQL Server) */
10
10
  const REGEX_INTEGER_TYPE = /^(int|int2|int4|int8|bigint|smallint|smallserial|tinyint|mediumint|integer|serial|bigserial)/;
11
11
 
12
- /** 棰勭紪璇戞鍒欙細BIGINT绫诲瀷锛圡ySQL/PostgreSQL/SQL Server锛?*/
12
+ /** BIGINT 类型正则 */
13
13
  const REGEX_BIGINT_TYPE = /^(bigint|int8|bigserial)/;
14
14
 
15
- /** 棰勭紪璇戞鍒欙細瀛楃涓茬被鍨嬶紙MySQL/PostgreSQL/SQL Server锛?*/
15
+ /** 字符串类型正则(MySQL/PostgreSQL/SQL Server) */
16
16
  const REGEX_STRING_TYPE = /^(varchar|char|text|longtext|mediumtext|tinytext|nvarchar|nchar|ntext|character varying|character)/;
17
17
 
18
- /** 鍖呭惈 WHERE 鏉′欢鐨勬搷浣滅被鍨嬮泦鍚堬紙鏌ヨ/鍒犻櫎/鏇存柊锛?*/
18
+ /** PG / MySQL 原生 boolean 列名 */
19
+ const REGEX_NATIVE_BOOLEAN_TYPE = /^(boolean|bool)$/;
20
+
21
+ /** 含 WHERE 条件的操作类型(查询/删除/更新) */
19
22
  const CONDITION_TYPES = new Set<string>([
20
23
  KeysOfSimpleSQL.SIMPLE_QUERY,
21
24
  KeysOfSimpleSQL.SIMPLE_QUERY_ONE,
@@ -26,7 +29,7 @@ const CONDITION_TYPES = new Set<string>([
26
29
  KeysOfSimpleSQL.SIMPLE_UPDATE,
27
30
  ]);
28
31
 
29
- /** INSERT/UPDATE 鎿嶄綔绫诲瀷闆嗗悎 */
32
+ /** INSERT/UPDATE 操作类型 */
30
33
  const INSERT_OR_UPDATE_TYPES = new Set<string>([
31
34
  KeysOfSimpleSQL.SIMPLE_INSERT,
32
35
  KeysOfSimpleSQL.SIMPLE_UPDATE,
@@ -35,41 +38,60 @@ const INSERT_OR_UPDATE_TYPES = new Set<string>([
35
38
  KeysOfSimpleSQL.SIMPLE_BATCH_INSERT,
36
39
  ]);
37
40
 
38
- // ============ 鍏叡宸ュ叿鍑芥暟 ============
41
+ // ============ 工具函数 ============
39
42
 
40
- /** 鍒ゆ柇瀛楁绫诲瀷鏄惁涓烘暣鏁扮被鍨嬶紙瑕嗙洊 MySQL/PostgreSQL/SQL Server锛?*/
41
43
  function isIntegerType(type: string): boolean {
42
44
  return REGEX_INTEGER_TYPE.test(type);
43
45
  }
44
46
 
45
- /** 鍒ゆ柇瀛楁绫诲瀷鏄惁涓?BIGINT 绫诲瀷锛圝S Number 绮惧害涓嶅锛宑ondition 鍊间笉杞崲锛?*/
47
+ /** BIGINT 列不转 number,避免 JS 精度丢失 */
46
48
  function isBigintType(type: string): boolean {
47
49
  return REGEX_BIGINT_TYPE.test(type);
48
50
  }
49
51
 
50
- /** 鍒ゆ柇瀛楁绫诲瀷鏄惁涓哄瓧绗︿覆绫诲瀷锛堣鐩?MySQL/PostgreSQL/SQL Server锛?*/
51
52
  function isStringType(type: string): boolean {
52
53
  return REGEX_STRING_TYPE.test(type);
53
54
  }
54
55
 
55
- /** 鍒ゆ柇 sqlSimpleName 鏄惁涓哄寘鍚?WHERE 鏉′欢鐨勬搷浣滅被鍨嬶紙鏌ヨ/鍒犻櫎/鏇存柊锛?*/
56
+ /** 驱动原生支持 JS boolean 绑定的列:PG boolean;SQL Server bit(MySQL tinyint(1) 仍转 0/1) */
57
+ function shouldKeepBooleanValue(type: string, sqlDbType: SqlDbType): boolean {
58
+ const t = type.toLowerCase().trim();
59
+ if (sqlDbType === SqlDbType.postgres && REGEX_NATIVE_BOOLEAN_TYPE.test(t)) {
60
+ return true;
61
+ }
62
+ if (sqlDbType === SqlDbType.sqlserver && t === 'bit') {
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+
68
+ /** MySQL 布尔列(boolean/bool/tinyint(1)),底层存 0/1,需转 number */
69
+ function isMysqlBooleanColumn(type: string): boolean {
70
+ const t = type.toLowerCase().trim();
71
+ return REGEX_NATIVE_BOOLEAN_TYPE.test(t) || /^tinyint\s*\(\s*1\s*\)/.test(t);
72
+ }
73
+
74
+ /** 需转为 number 的整型列(含 MySQL boolean/bool/tinyint(1));排除 BIGINT,避免 JS 精度丢失 */
75
+ function isIntegerLikeColumn(type: string, sqlDbType: SqlDbType): boolean {
76
+ if (isIntegerType(type) && !isBigintType(type)) {
77
+ return true;
78
+ }
79
+ return sqlDbType === SqlDbType.mysql && isMysqlBooleanColumn(type);
80
+ }
81
+
56
82
  function isConditionType(sqlSimpleName: string): boolean {
57
83
  return !!sqlSimpleName && CONDITION_TYPES.has(sqlSimpleName);
58
84
  }
59
85
 
60
- /** 鍒ゆ柇鍊兼槸鍚︿负鍩烘湰绫诲瀷锛坰tring | number | boolean锛?*/
61
86
  function isBasicType(value: any): boolean {
62
87
  return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
63
88
  }
64
89
 
65
- /** 鍒ゆ柇 sqlSimpleName 鏄惁涓?INSERT/UPDATE 绫诲瀷 */
66
90
  function isInsertOrUpdateType(sqlSimpleName: string): boolean {
67
91
  return !!sqlSimpleName && INSERT_OR_UPDATE_TYPES.has(sqlSimpleName);
68
92
  }
69
93
 
70
- /**
71
- * 鑾峰彇琛ㄥ厓鏁版嵁锛岃幏鍙栧け璐ユ椂杈撳嚭 warn 鏃ュ織骞惰繑鍥?null
72
- */
94
+ /** 获取表元数据,失败时 warn 并返回 null */
73
95
  async function getTableMetaOrWarn(
74
96
  serviceHub: any, logger: any, cfgModel: RequestCfgModel, actionName: string
75
97
  ) {
@@ -82,33 +104,73 @@ async function getTableMetaOrWarn(
82
104
 
83
105
  const tableMeta = await serviceHub.getTableMeta(query);
84
106
  if (!tableMeta || !tableMeta.columnDetails || tableMeta.columnDetails.length === 0) {
85
- logger.warn('CrudProDataTypeConvertService: 鏃犳硶鑾峰彇琛ㄧ粨鏋勪俊鎭紝璺宠繃' + actionName, cfgModel.sqlTable);
107
+ logger.warn('CrudProDataTypeConvertService: 无法获取表结构信息,跳过' + actionName, cfgModel.sqlTable);
86
108
  return null;
87
109
  }
88
110
  return tableMeta;
89
111
  }
90
112
 
91
- // ============ Data 杞崲澶勭悊鍣?============
113
+ function buildColumnTypeMap(tableMeta: { columnDetails: Array<{ name: string; type: string }> }): Map<string, string> {
114
+ const typeMap = new Map<string, string>();
115
+ for (const col of tableMeta.columnDetails) {
116
+ typeMap.set(col.name, ('' + col.type).toLowerCase());
117
+ }
118
+ return typeMap;
119
+ }
92
120
 
93
121
  /**
94
- * INSERT/UPDATE 鏁版嵁杞崲澶勭悊鍣?
95
- * 璐熻矗 data 鐨勭被鍨嬭浆鎹細PG ARRAY 瀛楅潰閲忋€佸璞?鏁扮粍 JSON 搴忓垪鍖?
122
+ * 按字段类型转换单个值:
123
+ * PG boolean / SS bit 保持 boolean;整数列(含 MySQL tinyint(1))boolean→0/1;字符串列 number/boolean→string
96
124
  */
125
+ function convertBasicValueByFieldType(value: any, fieldType: string, sqlDbType: SqlDbType): any {
126
+ if (value === null || value === undefined) {
127
+ return value;
128
+ }
129
+
130
+ const valueType = typeof value;
131
+
132
+ // PG boolean / SS bit:驱动原生支持 boolean 绑定
133
+ if (valueType === 'boolean' && shouldKeepBooleanValue(fieldType, sqlDbType)) {
134
+ return value;
135
+ }
136
+
137
+ // 非 BIGINT 整型列(含 MySQL boolean/bool/tinyint(1)):string→number;boolean→0/1
138
+ // BIGINT 不在此转换,由驱动参数化在 DB 端匹配,避免雪花 ID 等精度丢失
139
+ if (isIntegerLikeColumn(fieldType, sqlDbType)) {
140
+ if (valueType === 'string') {
141
+ const num = Number(value);
142
+ if (!isNaN(num) && Number.isInteger(num)) {
143
+ // 超 MAX_SAFE_INTEGER 保持 string,避免精度丢失
144
+ if (num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER) {
145
+ return value;
146
+ }
147
+ return num;
148
+ }
149
+ }
150
+ if (valueType === 'boolean') {
151
+ return value ? 1 : 0;
152
+ }
153
+ }
154
+
155
+ if (isStringType(fieldType)) {
156
+ if (valueType === 'number') {
157
+ return '' + value;
158
+ }
159
+ if (valueType === 'boolean') {
160
+ return value ? 'true' : 'false';
161
+ }
162
+ }
163
+
164
+ return value;
165
+ }
166
+
167
+ // ============ Data 转换 ============
168
+
169
+ /** INSERT/UPDATE 数据转换:类型强转、PG ARRAY 字面量、对象/数组 JSON 序列化 */
97
170
  class DataConvertHandler {
98
171
  constructor(private serviceHub: any, private logger: any) {}
99
172
 
100
- /**
101
- * INSERT/UPDATE 鏃讹細鏍规嵁琛ㄧ粨鏋勫瓧娈电被鍨嬭嚜鍔ㄨ浆鎹?reqModel.data 涓殑鏁版嵁鏍煎紡
102
- *
103
- * 瀹炵幇姝ラ锛?
104
- * 1. 鍒ゆ柇鎿嶄綔绫诲瀷鏄惁涓?INSERT/UPDATE锛屽惁鍒欒烦杩?
105
- * 2. PostgreSQL锛氳幏鍙栬〃鍏冩暟鎹紝璇嗗埆 ARRAY 绫诲瀷瀛楁锛屽皢 JS 鏁扮粍杞负 PG 鏁扮粍瀛楅潰閲?
106
- * 3. 涓夊簱閫氱敤锛氶亶鍘?data 涓墍鏈夊€硷紝灏嗗璞?鏁扮粍鑷姩 JSON.stringify()
107
- * 涓嶄緷璧栧垪绫诲瀷鍒ゆ柇锛屽洜涓?longtext/varchar 绛夐潪 JSON 鍒椾篃鍙兘瀛?JSON 瀵硅薄
108
- *
109
- * @param reqModel 璇锋眰妯″瀷
110
- * @param cfgModel 閰嶇疆妯″瀷
111
- */
173
+ /** 按表字段类型转换 reqModel.data */
112
174
  async convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
113
175
  if (!isInsertOrUpdateType(cfgModel.sqlSimpleName)) {
114
176
  return;
@@ -120,10 +182,16 @@ class DataConvertHandler {
120
182
  const data = reqModel.data;
121
183
  const isBatch = Array.isArray(data);
122
184
 
123
- // ---- PostgreSQL 涓撴湁锛欰RRAY 绫诲瀷杞崲锛堥渶瑕佽〃鍏冩暟鎹瘑鍒?ARRAY 鍒楋級 ----
124
- if (cfgModel.sqlDbType === SqlDbType.postgres) {
125
- const tableMeta = await getTableMetaOrWarn(this.serviceHub, this.logger, cfgModel, 'PG ARRAY杞崲');
126
- if (tableMeta) {
185
+ const tableMeta = await getTableMetaOrWarn(this.serviceHub, this.logger, cfgModel, 'data类型转换');
186
+ if (tableMeta) {
187
+ const typeMap = buildColumnTypeMap(tableMeta);
188
+
189
+ // 按列类型强转基础值(boolean 等适配实际 DB 字段)
190
+ if (isBatch) { for (const row of data) this.convertDataFieldsByType(row, typeMap, cfgModel.sqlDbType); }
191
+ else { this.convertDataFieldsByType(data, typeMap, cfgModel.sqlDbType); }
192
+
193
+ // PostgreSQL:ARRAY 列 JS 数组 → PG 数组字面量
194
+ if (cfgModel.sqlDbType === SqlDbType.postgres) {
127
195
  const arrayFieldSet = new Set<string>();
128
196
  for (const col of tableMeta.columnDetails) {
129
197
  if (('' + col.type).toLowerCase() === 'array') {
@@ -137,21 +205,37 @@ class DataConvertHandler {
137
205
  }
138
206
  }
139
207
 
140
- // ---- 涓夊簱閫氱敤锛氬璞?鏁扮粍鍊艰嚜鍔?JSON 搴忓垪鍖?----
141
- // 涓嶄緷璧栧垪绫诲瀷鍒ゆ柇锛歭ongtext/varchar 瀛?JSON銆丮ySQL json銆丳G jsonb 绛夊満鏅粺涓€瑕嗙洊
142
- // PG ARRAY 鍒楃殑鍊煎凡鍦ㄤ笂闈㈡楠よ浆涓哄瓧绗︿覆锛屾澶勪笉浼氳搴忓垪鍖?
208
+ // 三库通用:对象/数组 JSON.stringify(不依赖列类型)
143
209
  if (isBatch) { for (const row of data) this.serializeObjectValues(row); }
144
210
  else { this.serializeObjectValues(data); }
145
211
  }
146
212
 
147
- // ---- PostgreSQL 涓撴湁锛欰RRAY 鍒楄浆鎹?----
213
+ /** 按表字段类型转换 data 中的基础类型值 */
214
+ private convertDataFieldsByType(
215
+ dataObj: Record<string, any>,
216
+ typeMap: Map<string, string>,
217
+ sqlDbType: SqlDbType
218
+ ): void {
219
+ for (const key of Object.keys(dataObj)) {
220
+ const value = dataObj[key];
221
+ if (value === null || value === undefined) {
222
+ continue;
223
+ }
224
+ // $sqlFunc 表达式由 SqlFuncUtils 在 SQL 生成阶段处理
225
+ if (typeof value === 'object' && typeof value['$sqlFunc'] === 'string') {
226
+ continue;
227
+ }
228
+ const fieldType = typeMap.get(key);
229
+ if (!fieldType) {
230
+ continue;
231
+ }
232
+ if (isBasicType(value)) {
233
+ dataObj[key] = convertBasicValueByFieldType(value, fieldType, sqlDbType);
234
+ }
235
+ }
236
+ }
148
237
 
149
- /**
150
- * 閬嶅巻鏁版嵁瀵硅薄涓殑 PG ARRAY 绫诲瀷瀛楁锛屽皢 JS 鏁扮粍杞负 PG 鏁扮粍瀛楅潰閲忔牸寮?
151
- *
152
- * 杞崲娴佺▼锛欽S 鏁扮粍 鈫?tryParseArray() 鈫?toPgArrayLiteral() 鈫?PG 鏁扮粍瀛楅潰閲?
153
- * 渚嬶細[1, 2, 3] 鈫?'{1,2,3}'锛孾"a", "b"] 鈫?'{"a","b"}'
154
- */
238
+ /** JS 数组 → PG 数组字面量,如 [1,2,3] → '{1,2,3}' */
155
239
  private convertPgArrayFields(dataObj: Record<string, any>, arrayFieldSet: Set<string>): void {
156
240
  const keys = Object.keys(dataObj);
157
241
  for (const key of keys) {
@@ -166,10 +250,7 @@ class DataConvertHandler {
166
250
  }
167
251
  }
168
252
 
169
- /**
170
- * 灏濊瘯灏嗗€艰В鏋愪负鏁扮粍
171
- * 鏀寔锛氭暟缁勫璞°€丣SON 鏁扮粍瀛楃涓诧紙濡?'["a","b"]'锛?
172
- */
253
+ /** 解析为数组,支持数组对象和 JSON 数组字符串 */
173
254
  private tryParseArray(value: any): any {
174
255
  if (value === null || value === undefined) {
175
256
  return null;
@@ -190,16 +271,7 @@ class DataConvertHandler {
190
271
  return null;
191
272
  }
192
273
 
193
- /**
194
- * 灏?JS 鏁扮粍杞负 PostgreSQL 鏁扮粍瀛楅潰閲忔牸寮?
195
- *
196
- * PG 鏁扮粍瀛楅潰閲忚鍒欙細
197
- * - 瀛楃涓诧細鍙屽紩鍙峰寘瑁癸紝鍐呴儴 " 杞箟涓?\"锛孿 杞箟涓?\\ 渚嬶細{"hello","world"}
198
- * - 鏁板瓧锛氫笉鍔犲紩鍙? 渚嬶細{1,2,3}
199
- * - 甯冨皵锛氳浆涓?t/f 涓嶅姞寮曞彿 渚嬶細{t,f}
200
- * - null/undefined锛氳緭鍑?NULL 涓嶅姞寮曞彿 渚嬶細{1,NULL,3}
201
- * - 绌烘暟缁勶細杈撳嚭 {}
202
- */
274
+ /** JS 数组 → PG 数组字面量格式 */
203
275
  private toPgArrayLiteral(arr: any[]): string {
204
276
  if (arr.length === 0) {
205
277
  return '{}';
@@ -208,7 +280,7 @@ class DataConvertHandler {
208
280
  return '{' + items.join(',') + '}';
209
281
  }
210
282
 
211
- /** 灏嗗崟涓?JS 鍊艰浆涓?PG 鏁扮粍鍏冪礌鐨勫瓧闈㈤噺琛ㄧず */
283
+ /** 单个值 PG 数组元素字面量 */
212
284
  private toPgArrayItem(value: any): string {
213
285
  if (value === null || value === undefined) {
214
286
  return 'NULL';
@@ -219,29 +291,13 @@ class DataConvertHandler {
219
291
  if (typeof value === 'boolean') {
220
292
  return value ? 't' : 'f';
221
293
  }
222
- // 瀛楃涓插強鍏跺畠绫诲瀷锛氳浆涔夊悗鐢ㄥ弻寮曞彿鍖呰9
223
294
  const escaped = ('' + value)
224
295
  .replace(/\\/g, '\\\\')
225
296
  .replace(/"/g, '\\"');
226
297
  return '"' + escaped + '"';
227
298
  }
228
299
 
229
- // ---- 涓夊簱閫氱敤锛氬璞?鏁扮粍鍊艰嚜鍔?JSON 搴忓垪鍖?----
230
-
231
- /**
232
- * 閬嶅巻鏁版嵁瀵硅薄涓殑鎵€鏈夊€硷紝灏嗗璞?鏁扮粍鑷姩搴忓垪鍖栦负 JSON 瀛楃涓?
233
- *
234
- * 涓嶄緷璧栧垪绫诲瀷鍒ゆ柇锛屽師鍥狅細
235
- * - longtext/varchar 鍒椾篃鍙兘瀛?JSON 瀵硅薄锛屾寜鍒楃被鍨嬭繃婊や細婕忔帀
236
- * - 鍙鍊兼槸瀵硅薄鎴栨暟缁勶紝椹卞姩閮芥棤娉曠洿鎺ュ鐞嗭紙.toString() 杈撳嚭 [object Object]锛夛紝
237
- * 鍞竴鍚堢悊鐨勬搷浣滃氨鏄?JSON.stringify()
238
- *
239
- * 杞崲瑙勫垯锛?
240
- * - 瀵硅薄锛坱ypeof === 'object' 涓旈潪 null銆侀潪 Date銆侀潪 Buffer锛夆啋 JSON.stringify()
241
- * - 鏁扮粍锛圥G ARRAY 鍒楃殑鍊煎凡鍦ㄥ墠闈㈡楠よ浆涓哄瓧绗︿覆锛屾澶勯亣鍒扮殑閮芥槸闇€瑕佸簭鍒楀寲鐨勬櫘閫氭暟缁勶級鈫?JSON.stringify()
242
- * - 宸茬粡鏄瓧绗︿覆鐨勫€间笉閲嶅搴忓垪鍖栵紙璋冪敤鏂瑰彲鑳藉凡鎵嬪姩 JSON.stringify()锛?
243
- * - null / undefined / Date / Buffer 涓嶅鐞?
244
- */
300
+ /** 对象/数组 JSON 字符串,跳过 $sqlFunc、Date、Buffer */
245
301
  private serializeObjectValues(dataObj: Record<string, any>): void {
246
302
  const keys = Object.keys(dataObj);
247
303
  for (const key of keys) {
@@ -249,21 +305,17 @@ class DataConvertHandler {
249
305
  if (value === null || value === undefined) {
250
306
  continue;
251
307
  }
252
- // 宸茬粡鏄瓧绗︿覆锛岃鏄庤皟鐢ㄦ柟宸叉墜鍔?JSON.stringify()锛屼笉閲嶅搴忓垪鍖?
253
308
  if (typeof value === 'string') {
254
309
  continue;
255
310
  }
256
- // 璺宠繃 $sqlFunc 琛ㄨ揪寮忓璞★紙INC/DEC/MUL/NOW 绛夛級锛屼笉鍋?JSON.stringify
257
- // 杩欑被瀵硅薄灏嗗湪 SQL 鐢熸垚闃舵鐢?SqlFuncUtils 瑙f瀽涓?SQL 鐗囨
311
+ // $sqlFunc 表达式由 SqlFuncUtils 在 SQL 生成阶段处理
258
312
  if (typeof value === 'object' && typeof value['$sqlFunc'] === 'string') {
259
313
  continue;
260
314
  }
261
- // 鏁扮粍锛歅G ARRAY 鍒楀凡鍦ㄥ墠搴忔楠よ浆涓?PG 鏁扮粍瀛楅潰閲忓瓧绗︿覆锛屾澶勯亣鍒扮殑鍧囦负闇€搴忓垪鍖栫殑鏅€氭暟缁?
262
315
  if (Array.isArray(value)) {
263
316
  dataObj[key] = JSON.stringify(value);
264
317
  continue;
265
318
  }
266
- // 绾璞★紙闈?Date銆侀潪 Buffer锛夆啋 搴忓垪鍖?
267
319
  if (typeof value === 'object' && !(value instanceof Date) && !Buffer.isBuffer(value)) {
268
320
  dataObj[key] = JSON.stringify(value);
269
321
  }
@@ -271,33 +323,13 @@ class DataConvertHandler {
271
323
  }
272
324
  }
273
325
 
274
- // ============ Condition 杞崲澶勭悊鍣?============
326
+ // ============ Condition 转换 ============
275
327
 
276
- /**
277
- * SELECT/DELETE/UPDATE 鏉′欢杞崲澶勭悊鍣?
278
- * 璐熻矗 condition 鐨勭被鍨嬪己杞紝闃叉闅愬紡绫诲瀷杞崲瀵艰嚧绱㈠紩澶辨晥
279
- */
328
+ /** 查询/删除/更新条件类型强转,防止隐式转换导致索引失效 */
280
329
  class ConditionConvertHandler {
281
330
  constructor(private serviceHub: any, private logger: any) {}
282
331
 
283
- /**
284
- * SELECT/DELETE/UPDATE 鏃讹細鏍规嵁琛ㄧ粨鏋勫瓧娈电被鍨嬭嚜鍔ㄨ浆鎹?condition 涓殑鏁版嵁鏍煎紡
285
- * 闃叉鏁版嵁搴撻殣寮忕被鍨嬭浆鎹㈠鑷寸储寮曞け鏁堬紙MySQL / PostgreSQL / SQL Server 涓夊簱閫氱敤锛?
286
- *
287
- * 瀹炵幇姝ラ锛?
288
- * 1. 鍒ゆ柇鎿嶄綔绫诲瀷鏄惁涓烘煡璇?鍒犻櫎/鏇存柊锛堣繖浜涙搷浣滃寘鍚?WHERE 鏉′欢锛?
289
- * 2. 鑾峰彇琛ㄥ厓鏁版嵁锛屾瀯寤哄瓧娈靛悕鈫掔被鍨嬫槧灏?
290
- * 3. 閫掑綊閬嶅巻 condition锛堟敮鎸?$or/$and 宓屽锛夛紝鎸夊瓧娈电被鍨嬭浆鎹㈠€?
291
- *
292
- * 杞崲瑙勫垯锛堝弻鍚戜繚鎶わ紝浠呭鐞嗘渶甯稿缓绱㈠紩鐨勫垪绫诲瀷锛夛細
293
- * - 鏁存暟鍒?int/bigint/smallint/tinyint/mediumint) + 瀛楃涓插€?鈫?number
294
- * - 鏁存暟鍒?+ 甯冨皵鍊?鈫?0/1
295
- * - 瀛楃涓插垪(varchar/char/text绛? + 鏁板瓧鍊?鈫?string
296
- * - 瀛楃涓插垪 + 甯冨皵鍊?鈫?string
297
- *
298
- * @param reqModel 璇锋眰妯″瀷
299
- * @param cfgModel 閰嶇疆妯″瀷
300
- */
332
+ /** 按表字段类型转换 reqModel.condition */
301
333
  async convertConditionTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
302
334
  if (!isConditionType(cfgModel.sqlSimpleName)) {
303
335
  return;
@@ -306,29 +338,17 @@ class ConditionConvertHandler {
306
338
  return;
307
339
  }
308
340
 
309
- const tableMeta = await getTableMetaOrWarn(this.serviceHub, this.logger, cfgModel, '鏉′欢绫诲瀷杞崲');
341
+ const tableMeta = await getTableMetaOrWarn(this.serviceHub, this.logger, cfgModel, '条件类型转换');
310
342
  if (!tableMeta) {
311
343
  return;
312
344
  }
313
345
 
314
- // 鏋勫缓瀛楁鍚嶁啋绫诲瀷鏄犲皠锛堝叏灏忓啓渚夸簬鍖归厤锛?
315
- const typeMap = new Map<string, string>();
316
- for (const col of tableMeta.columnDetails) {
317
- typeMap.set(col.name, ('' + col.type).toLowerCase());
318
- }
346
+ const typeMap = buildColumnTypeMap(tableMeta);
319
347
 
320
348
  this.convertConditionFields(reqModel.condition, typeMap, cfgModel.sqlDbType);
321
349
  }
322
350
 
323
- /**
324
- * 閫掑綊閬嶅巻 condition锛屽姣忎釜瀛楁鍊煎仛绫诲瀷杞崲
325
- *
326
- * 瀹炵幇姝ラ锛?
327
- * 1. 閬嶅巻 condition 鐨勬瘡涓?key
328
- * 2. 鑻?key 涓?$or/$and 閫昏緫鎿嶄綔绗︼紝閫掑綊澶勭悊瀛愭潯浠舵暟缁?
329
- * 3. 鑻?key 涓哄瓧娈靛悕涓斿€间负鍩烘湰绫诲瀷锛岀洿鎺ヨ浆鎹?
330
- * 4. 鑻?key 涓哄瓧娈靛悕涓斿€间负姣旇緝鎿嶄綔绗﹀璞?$gt/$in绛?锛岄亶鍘嗘搷浣滅閫愬€艰浆鎹?
331
- */
351
+ /** 递归遍历 condition,支持 $or/$and 嵌套 */
332
352
  private convertConditionFields(
333
353
  condition: IRequestCondition,
334
354
  typeMap: Map<string, string>,
@@ -341,7 +361,6 @@ class ConditionConvertHandler {
341
361
  for (const key of Object.keys(condition)) {
342
362
  const value = condition[key];
343
363
 
344
- // $or / $and 閫昏緫鎿嶄綔绗︼細閫掑綊澶勭悊姣忎釜瀛愭潯浠?
345
364
  if (key === KeysOfConditions.$OR || key === KeysOfConditions.$AND) {
346
365
  if (Array.isArray(value)) {
347
366
  for (const subCondition of value) {
@@ -351,140 +370,51 @@ class ConditionConvertHandler {
351
370
  continue;
352
371
  }
353
372
 
354
- // 濡傛灉 key 鏄瘮杈冩搷浣滅锛堝嚭鐜板湪鍐呭眰锛夛紝璺宠繃
355
373
  if (KeysOfConditions.COMPARE_KEYS.has(key)) {
356
374
  continue;
357
375
  }
358
376
 
359
- // key 鏄瓧娈靛悕
360
377
  const fieldType = typeMap.get(key);
361
378
  if (!fieldType) {
362
- continue; // condition 涓紩鐢ㄧ殑瀛楁涓嶅湪琛ㄧ粨鏋勪腑锛屼笉鍋氳浆鎹?
379
+ continue;
363
380
  }
364
381
 
365
382
  if (isBasicType(value)) {
366
- // 绠€鍗曠瓑鍊兼潯浠讹細{ age: "18" } 鈫?{ age: 18 }
367
- condition[key] = this.convertValueByType(value, fieldType, sqlDbType);
383
+ condition[key] = convertBasicValueByFieldType(value, fieldType, sqlDbType);
368
384
  } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
369
- // 姣旇緝鎿嶄綔绗︽潯浠讹細{ age: { "$gt": "18", "$lt": "30" } }
370
385
  for (const opKey of Object.keys(value)) {
371
386
  if (KeysOfConditions.COMPARE_KEYS.has(opKey)) {
372
387
  const opValue = value[opKey];
373
388
  if (opKey === KeysOfConditions.$IN || opKey === KeysOfConditions.$NIN) {
374
- // $in/$nin 鐨勫€兼槸鏁扮粍锛岄€愪釜杞崲
375
389
  if (Array.isArray(opValue)) {
376
- value[opKey] = opValue.map(v => this.convertValueByType(v, fieldType, sqlDbType));
390
+ value[opKey] = opValue.map(v => convertBasicValueByFieldType(v, fieldType, sqlDbType));
377
391
  }
378
392
  } else if (opKey === KeysOfConditions.$RANGE) {
379
- // $range 鐨勫€兼槸 [min, max]
380
393
  if (Array.isArray(opValue)) {
381
- value[opKey] = opValue.map(v => this.convertValueByType(v, fieldType, sqlDbType));
394
+ value[opKey] = opValue.map(v => convertBasicValueByFieldType(v, fieldType, sqlDbType));
382
395
  }
383
396
  } else {
384
- value[opKey] = this.convertValueByType(opValue, fieldType, sqlDbType);
397
+ value[opKey] = convertBasicValueByFieldType(opValue, fieldType, sqlDbType);
385
398
  }
386
399
  }
387
400
  }
388
401
  }
389
402
  }
390
403
  }
391
-
392
- /**
393
- * 鏍规嵁瀛楁绫诲瀷杞崲鍗曚釜鍊硷紝闃叉闅愬紡绫诲瀷杞崲瀵艰嚧绱㈠紩澶辨晥
394
- *
395
- * 杞崲瑙勫垯锛堝弻鍚戜繚鎶わ級锛?
396
- * - 鏁存暟鍒?+ 瀛楃涓插€?鈫?number锛圵HERE int_col = '123' 绱㈠紩澶辨晥锛?
397
- * - 鏁存暟鍒?+ 甯冨皵鍊?鈫?0/1
398
- * - 瀛楃涓插垪 + 鏁板瓧鍊?鈫?string锛圵HERE varchar_col = 123 绱㈠紩澶辨晥锛?
399
- * - 瀛楃涓插垪 + 甯冨皵鍊?鈫?string
400
- */
401
- private convertValueByType(value: any, fieldType: string, sqlDbType: SqlDbType): any {
402
- if (value === null || value === undefined) {
403
- return value;
404
- }
405
-
406
- const valueType = typeof value;
407
-
408
- // 鏁存暟鍒楋細string 鈫?number, boolean 鈫?0/1
409
- // BIGINT 绫诲瀷涓嶈浆鎹紙isBigintType 鎺掗櫎锛夛紝鐢遍┍鍔ㄥ弬鏁板寲鏌ヨ鍦?DB 鏈嶅姟绔鐞嗙被鍨嬪尮閰嶏紝
410
- // 閬垮厤 JS Number 绮惧害涓㈠け锛堥洩鑺盜D绾?2^41~2^63锛?
411
- // 闈?BIGINT 鏁存暟鍒楃殑瀹夊叏缃戯細鑻ュ€兼剰澶栬秴杩?MAX_SAFE_INTEGER锛屼篃淇濇寔 string
412
- if (isIntegerType(fieldType) && !isBigintType(fieldType)) {
413
- if (valueType === 'string') {
414
- const num = Number(value);
415
- if (!isNaN(num) && Number.isInteger(num)) {
416
- if (num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER) {
417
- return value; // 瓒呭畨鍏ㄦ暣鏁拌寖鍥达紝淇濇寔 string 閬垮厤绮惧害涓㈠け
418
- }
419
- return num;
420
- }
421
- }
422
- if (valueType === 'boolean') {
423
- return value ? 1 : 0;
424
- }
425
- }
426
-
427
- // 瀛楃涓插垪锛歯umber 鈫?string, boolean 鈫?string
428
- if (isStringType(fieldType)) {
429
- if (valueType === 'number') {
430
- return '' + value;
431
- }
432
- if (valueType === 'boolean') {
433
- return value ? 'true' : 'false';
434
- }
435
- }
436
-
437
- return value;
438
- }
439
404
  }
440
405
 
441
- // ============ 鍏叡鍏ュ彛 ============
406
+ // ============ 入口 ============
442
407
 
443
408
  /**
444
- * 鏁版嵁绫诲瀷杞崲鏈嶅姟
445
- * 鏍规嵁琛ㄧ粨鏋勫瓧娈电被鍨嬶紝鑷姩杞崲鏁版嵁鏍煎紡锛岀‘淇濇暟鎹笌鏁版嵁搴撴柟瑷€鍏煎锛岄槻姝㈤殣寮忕被鍨嬭浆鎹㈠鑷寸储寮曞け鏁?
446
- *
447
- * 鎸夋暟鎹簱鍒嗙被锛?
448
- * - PostgreSQL 涓撴湁锛欰RRAY 鍒?鈫?PG 鏁扮粍瀛楅潰閲?
449
- * - 涓夊簱閫氱敤锛堝€肩被鍨嬮┍鍔級锛氬璞?鏁扮粍 鈫?JSON.stringify()锛岃鐩?json/jsonb/longtext瀛楯SON 绛夋墍鏈夊満鏅?
450
- * - 涓夊簱閫氱敤锛氭煡璇㈡潯浠剁被鍨嬪己杞紝闃叉绱㈠紩澶辨晥
409
+ * 数据类型转换服务
410
+ * data/condition 类型强转、PG ARRAY 字面量、对象/数组 JSON 序列化
451
411
  */
452
412
  class CrudProDataTypeConvertService extends CrudProServiceBase {
453
413
 
454
- /**
455
- * INSERT/UPDATE 鏃讹細鏍规嵁琛ㄧ粨鏋勫瓧娈电被鍨嬭嚜鍔ㄨ浆鎹?reqModel.data 涓殑鏁版嵁鏍煎紡
456
- *
457
- * 瀹炵幇姝ラ锛?
458
- * 1. 鍒ゆ柇鎿嶄綔绫诲瀷鏄惁涓?INSERT/UPDATE锛屽惁鍒欒烦杩?
459
- * 2. PostgreSQL锛氳幏鍙栬〃鍏冩暟鎹紝璇嗗埆 ARRAY 绫诲瀷瀛楁锛屽皢 JS 鏁扮粍杞负 PG 鏁扮粍瀛楅潰閲?
460
- * 3. 涓夊簱閫氱敤锛氶亶鍘?data 涓墍鏈夊€硷紝灏嗗璞?鏁扮粍鑷姩 JSON.stringify()
461
- * 涓嶄緷璧栧垪绫诲瀷鍒ゆ柇锛屽洜涓?longtext/varchar 绛夐潪 JSON 鍒椾篃鍙兘瀛?JSON 瀵硅薄
462
- *
463
- * @param reqModel 璇锋眰妯″瀷
464
- * @param cfgModel 閰嶇疆妯″瀷
465
- */
466
414
  async convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
467
415
  return new DataConvertHandler(this.serviceHub, this.logger).convertDataTypeByTableMeta(reqModel, cfgModel);
468
416
  }
469
417
 
470
- /**
471
- * SELECT/DELETE/UPDATE 鏃讹細鏍规嵁琛ㄧ粨鏋勫瓧娈电被鍨嬭嚜鍔ㄨ浆鎹?condition 涓殑鏁版嵁鏍煎紡
472
- * 闃叉鏁版嵁搴撻殣寮忕被鍨嬭浆鎹㈠鑷寸储寮曞け鏁堬紙MySQL / PostgreSQL / SQL Server 涓夊簱閫氱敤锛?
473
- *
474
- * 瀹炵幇姝ラ锛?
475
- * 1. 鍒ゆ柇鎿嶄綔绫诲瀷鏄惁涓烘煡璇?鍒犻櫎/鏇存柊锛堣繖浜涙搷浣滃寘鍚?WHERE 鏉′欢锛?
476
- * 2. 鑾峰彇琛ㄥ厓鏁版嵁锛屾瀯寤哄瓧娈靛悕鈫掔被鍨嬫槧灏?
477
- * 3. 閫掑綊閬嶅巻 condition锛堟敮鎸?$or/$and 宓屽锛夛紝鎸夊瓧娈电被鍨嬭浆鎹㈠€?
478
- *
479
- * 杞崲瑙勫垯锛堝弻鍚戜繚鎶わ紝浠呭鐞嗘渶甯稿缓绱㈠紩鐨勫垪绫诲瀷锛夛細
480
- * - 鏁存暟鍒?int/bigint/smallint/tinyint/mediumint) + 瀛楃涓插€?鈫?number
481
- * - 鏁存暟鍒?+ 甯冨皵鍊?鈫?0/1
482
- * - 瀛楃涓插垪(varchar/char/text绛? + 鏁板瓧鍊?鈫?string
483
- * - 瀛楃涓插垪 + 甯冨皵鍊?鈫?string
484
- *
485
- * @param reqModel 璇锋眰妯″瀷
486
- * @param cfgModel 閰嶇疆妯″瀷
487
- */
488
418
  async convertConditionTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
489
419
  return new ConditionConvertHandler(this.serviceHub, this.logger).convertConditionTypeByTableMeta(reqModel, cfgModel);
490
420
  }