midway-fatcms 0.0.2 → 0.0.4

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 (53) hide show
  1. package/dist/libs/crud-pro/CrudPro.d.ts +14 -0
  2. package/dist/libs/crud-pro/CrudPro.js +61 -0
  3. package/dist/libs/crud-pro/interfaces.d.ts +20 -0
  4. package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -2
  5. package/dist/libs/crud-pro/models/Transaction.js +6 -1
  6. package/dist/libs/crud-pro/models/keys.d.ts +1 -0
  7. package/dist/libs/crud-pro/models/keys.js +2 -0
  8. package/dist/libs/crud-pro/services/CrudProDataFilterService.d.ts +17 -0
  9. package/dist/libs/crud-pro/services/CrudProDataFilterService.js +53 -0
  10. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.d.ts +1 -0
  11. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +30 -0
  12. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +7 -1
  13. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +7 -4
  14. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +127 -37
  15. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +4 -2
  16. package/dist/libs/crud-pro/services/CurdProServiceHub.js +7 -2
  17. package/dist/libs/crud-pro/utils/MixinUtils.js +10 -9
  18. package/dist/middleware/forbidden.middleware.js +13 -2
  19. package/dist/middleware/global.middleware.js +1 -1
  20. package/dist/models/SystemEntities.d.ts +2 -1
  21. package/dist/models/SystemEntities.js +1 -0
  22. package/dist/service/AuthService.js +3 -0
  23. package/dist/service/base/BaseService.d.ts +1 -0
  24. package/dist/service/base/BaseService.js +4 -0
  25. package/dist/service/crudstd/CrudStdService.js +1 -1
  26. package/dist/service/proxyapi/ProxyApiService.js +22 -2
  27. package/dist/service/proxyapi/RouteHandler.d.ts +3 -2
  28. package/dist/service/proxyapi/RouteTrie.d.ts +1 -1
  29. package/dist/service/proxyapi/RouteTrie.js +1 -0
  30. package/dist/service/proxyapi/WeightedRoundRobin.d.ts +1 -1
  31. package/dist/service/proxyapi/WeightedRoundRobin.js +1 -0
  32. package/package.json +1 -1
  33. package/src/libs/crud-pro/CrudPro.ts +71 -0
  34. package/src/libs/crud-pro/interfaces.ts +22 -0
  35. package/src/libs/crud-pro/models/ServiceHub.ts +2 -2
  36. package/src/libs/crud-pro/models/Transaction.ts +5 -1
  37. package/src/libs/crud-pro/models/keys.ts +6 -2
  38. package/src/libs/crud-pro/services/CrudProDataFilterService.ts +58 -0
  39. package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +37 -0
  40. package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +10 -1
  41. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +145 -40
  42. package/src/libs/crud-pro/services/CurdProServiceHub.ts +10 -3
  43. package/src/libs/crud-pro/utils/MixinUtils.ts +11 -10
  44. package/src/middleware/forbidden.middleware.ts +17 -7
  45. package/src/middleware/global.middleware.ts +1 -1
  46. package/src/models/SystemEntities.ts +1 -0
  47. package/src/service/AuthService.ts +3 -0
  48. package/src/service/base/BaseService.ts +5 -0
  49. package/src/service/crudstd/CrudStdService.ts +2 -1
  50. package/src/service/proxyapi/ProxyApiService.ts +22 -1
  51. package/src/service/proxyapi/RouteHandler.ts +4 -2
  52. package/src/service/proxyapi/RouteTrie.ts +1 -1
  53. package/src/service/proxyapi/WeightedRoundRobin.ts +2 -1
@@ -15,7 +15,11 @@ const BLACK_EQUAL_LIST = [
15
15
  '/config.json',
16
16
  '/backend/.env',
17
17
  '/.env',
18
+ '/.env.dev',
19
+ '/.env.prod',
18
20
  '/.env.local',
21
+ '/.env.staging',
22
+ '/.env.example',
19
23
  '/.env.production',
20
24
  '/.env.development',
21
25
  '/application.yml',
@@ -25,6 +29,7 @@ const BLACK_EQUAL_LIST = [
25
29
  '/config.yml',
26
30
  '/db.ini',
27
31
  '/database.yml',
32
+ '/api/.env',
28
33
  // 安全相关文件
29
34
  '/.well-known/security.txt',
30
35
  '/security.txt',
@@ -274,8 +279,14 @@ let ForbiddenMiddleware = class ForbiddenMiddleware {
274
279
  * 检查是否包含路径遍历政击特征
275
280
  */
276
281
  hasPathTraversal(path) {
277
- const decodedPath = decodeURIComponent(path);
278
- return SUSPICIOUS_QUERY_PATTERNS.some(pattern => decodedPath.includes(pattern));
282
+ try {
283
+ const decodedPath = decodeURIComponent(path);
284
+ return SUSPICIOUS_QUERY_PATTERNS.some(pattern => decodedPath.includes(pattern));
285
+ }
286
+ catch (e) {
287
+ // URL解码失败(如包含非法编码字符),直接判定为可疑请求
288
+ return true;
289
+ }
279
290
  }
280
291
  /**
281
292
  * 拦截处理:返回404响应
@@ -75,7 +75,7 @@ function handleNullRes(res) {
75
75
  * @param res
76
76
  */
77
77
  function handleArrayRes(res) {
78
- if (Array.isArray(res) && res.length) {
78
+ if (Array.isArray(res)) {
79
79
  return { success: true, data: res };
80
80
  }
81
81
  return res;
@@ -64,7 +64,8 @@ export interface IUpstreamInfo {
64
64
  export declare enum ProxyUserContextEnum {
65
65
  NO_PASS = 0,
66
66
  BASIC_INFO = 1,
67
- SESSION_INFO = 2
67
+ SESSION_INFO = 2,
68
+ BASIC_INFO_BIZ_EXT = 3
68
69
  }
69
70
  export interface IProxyApiEntity extends IApiBaseEntity {
70
71
  proxy_name: string;
@@ -6,6 +6,7 @@ var ProxyUserContextEnum;
6
6
  ProxyUserContextEnum[ProxyUserContextEnum["NO_PASS"] = 0] = "NO_PASS";
7
7
  ProxyUserContextEnum[ProxyUserContextEnum["BASIC_INFO"] = 1] = "BASIC_INFO";
8
8
  ProxyUserContextEnum[ProxyUserContextEnum["SESSION_INFO"] = 2] = "SESSION_INFO";
9
+ ProxyUserContextEnum[ProxyUserContextEnum["BASIC_INFO_BIZ_EXT"] = 3] = "BASIC_INFO_BIZ_EXT";
9
10
  })(ProxyUserContextEnum = exports.ProxyUserContextEnum || (exports.ProxyUserContextEnum = {}));
10
11
  var CacheLevelEnum;
11
12
  (function (CacheLevelEnum) {
@@ -150,6 +150,9 @@ let AuthService = class AuthService extends BaseService_1.BaseService {
150
150
  if (!(0, fatcms_request_1.isEnableSuperAdmin)(this.ctx)) {
151
151
  return null;
152
152
  }
153
+ if (!Array.isArray(superAdminList)) {
154
+ return null;
155
+ }
153
156
  return superAdminList.find((s) => {
154
157
  return s.login_name === loginName;
155
158
  });
@@ -39,6 +39,7 @@ export declare class BaseService {
39
39
  protected logError(msg: string, ...args: any): void;
40
40
  protected logWarn(msg: string, ...args: any): void;
41
41
  protected logDebug(msg: string, ...args: any): void;
42
+ protected isProdEnv(): boolean;
42
43
  protected isLocalEnv(): boolean;
43
44
  protected isEnableDebug(): boolean;
44
45
  protected evaluate(expression: string, context: any): string;
@@ -91,6 +91,10 @@ let BaseService = class BaseService {
91
91
  logDebug(msg, ...args) {
92
92
  this.getContextLogger().debug(msg, ...args);
93
93
  }
94
+ isProdEnv() {
95
+ const env = this.ctx.app.env;
96
+ return env === 'prod' || env === 'production';
97
+ }
94
98
  isLocalEnv() {
95
99
  return (0, fatcms_request_1.isLocalEnv)(this.ctx);
96
100
  }
@@ -63,10 +63,10 @@ let CrudStdService = class CrudStdService extends ApiBaseService_1.ApiBaseServic
63
63
  var _a;
64
64
  const appCode = stdAction.appCode;
65
65
  const appInfo = await this.getParsedCrudStdAppForSettingKey(stdAction);
66
- const stdCrudCfgObj = appInfo.stdCrudCfgObj;
67
66
  if (!appInfo || appInfo.status !== 1) {
68
67
  throw new devops_1.BizException('应用不存在或已下线:' + appCode);
69
68
  }
69
+ const stdCrudCfgObj = appInfo.stdCrudCfgObj;
70
70
  //删除策略
71
71
  const deleteStrategy = _.get(stdCrudCfgObj, 'othersSetting.values.deleteStrategy');
72
72
  const databaseName = stdCrudCfgObj.tableBaseInfo.databaseName;
@@ -43,7 +43,8 @@ let ProxyApiService = class ProxyApiService extends ApiBaseService_1.ApiBaseServ
43
43
  if (!cfgInfo) {
44
44
  throw new devops_1.BizException('路径配置没有匹配到', exceptions_1.Exceptions.CFG_NOT_FOUND);
45
45
  }
46
- return this._handleProxyRequestForCfg(cfgInfo, upstream_path);
46
+ const proxyApiEntity = JSON.parse(JSON.stringify(cfgInfo));
47
+ return this._handleProxyRequestForCfg(proxyApiEntity, upstream_path);
47
48
  }
48
49
  /**
49
50
  * 转发请求
@@ -252,7 +253,7 @@ let ProxyApiService = class ProxyApiService extends ApiBaseService_1.ApiBaseServ
252
253
  }
253
254
  }
254
255
  buildUserContextHeader(proxyApiEntity) {
255
- var _a, _b;
256
+ var _a, _b, _c;
256
257
  // 传递整个用户上下文
257
258
  if (proxyApiEntity.user_context === SystemEntities_1.ProxyUserContextEnum.SESSION_INFO) {
258
259
  const isLogin = this.ctx.userSession.isLogin();
@@ -267,6 +268,7 @@ let ProxyApiService = class ProxyApiService extends ApiBaseService_1.ApiBaseServ
267
268
  const workbenchCode = (_b = this.ctx.workbenchInfo) === null || _b === void 0 ? void 0 : _b.workbench_code;
268
269
  const userBasicInfo = { isLogin };
269
270
  if (sessionInfo && isLogin) {
271
+ userBasicInfo.nickName = sessionInfo.nickName;
270
272
  userBasicInfo.loginName = sessionInfo.loginName;
271
273
  userBasicInfo.sessionId = sessionInfo.sessionId;
272
274
  userBasicInfo.accountId = sessionInfo.accountId;
@@ -276,6 +278,24 @@ let ProxyApiService = class ProxyApiService extends ApiBaseService_1.ApiBaseServ
276
278
  userBasicInfo.workbenchCode = workbenchCode;
277
279
  return (0, base64_1.toBase64)(userBasicInfo);
278
280
  }
281
+ // 传递用户基本信息和BizExt
282
+ if (proxyApiEntity.user_context === SystemEntities_1.ProxyUserContextEnum.BASIC_INFO_BIZ_EXT) {
283
+ const isLogin = this.ctx.userSession.isLogin();
284
+ const sessionInfo = this.ctx.userSession.getSessionInfo();
285
+ const workbenchCode = (_c = this.ctx.workbenchInfo) === null || _c === void 0 ? void 0 : _c.workbench_code;
286
+ const userBasicInfo = { isLogin };
287
+ if (sessionInfo && isLogin) {
288
+ userBasicInfo.nickName = sessionInfo.nickName;
289
+ userBasicInfo.loginName = sessionInfo.loginName;
290
+ userBasicInfo.sessionId = sessionInfo.sessionId;
291
+ userBasicInfo.accountId = sessionInfo.accountId;
292
+ userBasicInfo.workbenchCode = sessionInfo.workbenchCode;
293
+ userBasicInfo.accountType = sessionInfo.accountType;
294
+ userBasicInfo.bizExt = sessionInfo.bizExt; // 多了一个bizExt
295
+ }
296
+ userBasicInfo.workbenchCode = workbenchCode;
297
+ return (0, base64_1.toBase64)(userBasicInfo);
298
+ }
279
299
  return null;
280
300
  }
281
301
  };
@@ -1,5 +1,6 @@
1
+ import { IProxyApiEntity } from "../../models/SystemEntities";
1
2
  declare class RouteHandler {
2
- cfgInfo: any;
3
- constructor(cfgInfo: any);
3
+ cfgInfo: IProxyApiEntity;
4
+ constructor(cfgInfo: IProxyApiEntity);
4
5
  }
5
6
  export { RouteHandler };
@@ -5,7 +5,7 @@ declare class RouteTrie {
5
5
  constructor();
6
6
  addRoute(path: string, handler: RouteHandler): void;
7
7
  clearRoute(): void;
8
- getRouteCount(): 0;
8
+ getRouteCount(): number;
9
9
  findLongestMatch(path: string): any;
10
10
  }
11
11
  declare function getRouteTrie(domainCode: string): RouteTrie;
@@ -10,6 +10,7 @@ class RouteTrieNode {
10
10
  }
11
11
  class RouteTrie {
12
12
  constructor() {
13
+ this.routeCount = 0;
13
14
  this.root = new RouteTrieNode();
14
15
  this.routeCount = 0;
15
16
  }
@@ -1,7 +1,7 @@
1
1
  import { IUpstreamItem } from '../../models/SystemEntities';
2
2
  declare class WeightedRoundRobin {
3
3
  private readonly totalWeight;
4
- private servers;
4
+ private readonly servers;
5
5
  constructor(servers: IUpstreamItem[]);
6
6
  next(): IUpstreamItem;
7
7
  }
@@ -24,6 +24,7 @@ class WeightedRoundRobin {
24
24
  // 2. 被选中的服务器减去总权重(实现平滑分配)
25
25
  if (selected) {
26
26
  selected.currentWeight -= this.totalWeight;
27
+ selected.currentWeight = Math.max(selected.currentWeight, 0);
27
28
  return selected;
28
29
  }
29
30
  return this.servers[0]; // 兜底返回第一个元素
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midway-fatcms",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "This is a midway component sample",
5
5
  "main": "dist/index.js",
6
6
  "typings": "index.d.ts",
@@ -7,6 +7,7 @@ import { RequestCfgModel } from './models/RequestCfgModel';
7
7
  import { MixinUtils } from './utils/MixinUtils';
8
8
  import { Transaction } from './models/Transaction';
9
9
  import { IExecuteContextFunc } from './models/ExecuteContextFunc';
10
+ import { KeysOfSimpleSQL } from './models/keys';
10
11
 
11
12
  class CrudPro {
12
13
  private readonly executeContext: ExecuteContext;
@@ -106,6 +107,9 @@ class CrudPro {
106
107
  exeCtx.setReqModel(reqModel);
107
108
  exeCtx.setCfgModel(cfgModel);
108
109
 
110
+ // 如果是 sqlSimpleName模式的 update/insert,则需要将 reqJson.data 部分根据真实的表结构进行过滤
111
+ await this.filterDataByTableMetaIfNeeded(reqModel, cfgModel);
112
+
109
113
  // 参数校验
110
114
  this.serviceHub.validateByAllow(cfgModel, reqModel);
111
115
  this.serviceHub.validateByReject(cfgModel, reqModel);
@@ -144,6 +148,73 @@ class CrudPro {
144
148
  return this.serviceHub.getCachedCfgByMethod(method, isEnableCache);
145
149
  }
146
150
 
151
+ /**
152
+ * 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
153
+ * 避免传入不存在的字段导致 SQL 执行报错
154
+ * 注意:只处理 sqlSimpleName 模式,不处理 sqlCfgList 模式
155
+ * @param reqModel 请求模型
156
+ * @param cfgModel 配置模型
157
+ */
158
+ private async filterDataByTableMetaIfNeeded(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void> {
159
+ // 只有在有 data 数据时才需要过滤
160
+ if (!reqModel.data || Object.keys(reqModel.data).length === 0) {
161
+ return;
162
+ }
163
+
164
+ // 只处理 sqlSimpleName 模式
165
+ const sqlSimpleName = cfgModel.sqlSimpleName;
166
+ if (!sqlSimpleName) {
167
+ return;
168
+ }
169
+
170
+ // 判断是否为 INSERT/UPDATE 类型的简单 SQL
171
+ if (!this.isSimpleInsertOrUpdateType(sqlSimpleName)) {
172
+ return;
173
+ }
174
+
175
+ // 构建 SqlCfgModel 用于获取表结构
176
+ const sqlCfgModel: ISqlCfgModel = {
177
+ sqlTable: cfgModel.sqlTable,
178
+ sqlSchema: cfgModel.sqlSchema,
179
+ sqlDatabase: cfgModel.sqlDatabase,
180
+ sqlDbType: cfgModel.sqlDbType,
181
+ columns: cfgModel.columns,
182
+ columnsRelation: cfgModel.columnsRelation,
183
+ };
184
+
185
+ const filteredData = await this.serviceHub.filterDataByTableMeta(reqModel.data, sqlCfgModel as ISqlCfgModel);
186
+
187
+ // 记录被过滤的字段
188
+ const originalKeys = Object.keys(reqModel.data);
189
+ const filteredKeys = Object.keys(filteredData);
190
+ const removedKeys = originalKeys.filter(key => !filteredKeys.includes(key));
191
+
192
+ if (removedKeys.length > 0) {
193
+ this.executeContext.getLogger().info('CrudPro.filterDataByTableMetaIfNeeded: 过滤掉表中不存在的字段', {
194
+ table: cfgModel.sqlTable,
195
+ removedFields: removedKeys,
196
+ });
197
+ }
198
+
199
+ // 直接修改 reqModel.data,只保留表中存在的字段
200
+ reqModel.data = filteredData;
201
+ }
202
+
203
+ /**
204
+ * 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
205
+ * @param sqlSimpleName 简单 SQL 名称
206
+ * @returns 是否为 INSERT/UPDATE 类型
207
+ */
208
+ private isSimpleInsertOrUpdateType(sqlSimpleName: string): boolean {
209
+ const insertOrUpdateTypes = [
210
+ KeysOfSimpleSQL.SIMPLE_INSERT,
211
+ KeysOfSimpleSQL.SIMPLE_UPDATE,
212
+ KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
213
+ KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
214
+ ];
215
+ return insertOrUpdateTypes.includes(sqlSimpleName as KeysOfSimpleSQL);
216
+ }
217
+
147
218
  private async executeSQLList() {
148
219
  try {
149
220
  await this.serviceHub.executeSqlCfgModels();
@@ -12,9 +12,20 @@ export interface ICrudProCfg {
12
12
  tableMetaCacheTime?: number; // 表格元信息缓存时间
13
13
  }
14
14
 
15
+ export interface ITableColumn {
16
+ name: string; // 字段名
17
+ type: string; // 数据类型
18
+ isNullable: boolean; // 是否可空
19
+ isPrimaryKey?: boolean; // 是否主键
20
+ defaultValue?: any; // 默认值
21
+ maxLength?: number; // 最大长度
22
+ comment?: string; // 字段注释
23
+ }
24
+
15
25
  export interface ITableMeta {
16
26
  expiredTime: number; // 过期时间
17
27
  tableColumns: string[]; // 表格所有字段
28
+ columnDetails?: ITableColumn[]; // 详细的字段信息
18
29
  }
19
30
 
20
31
  export interface IConnectionPool {
@@ -200,4 +211,15 @@ export interface IExecuteUnsafeQueryCtx {
200
211
  sqlTable: string;
201
212
  sqlDatabase: string;
202
213
  sqlDbType: SqlDbType;
214
+ sqlSchema?: string;
215
+ }
216
+
217
+ /**
218
+ * 表结构元数据查询参数
219
+ */
220
+ export interface ITableMetaQuery {
221
+ sqlTable: string;
222
+ sqlDatabase: string;
223
+ sqlDbType: SqlDbType;
224
+ sqlSchema?: string;
203
225
  }
@@ -2,7 +2,7 @@ import { RequestCfgModel } from './RequestCfgModel';
2
2
  import { RequestModel } from './RequestModel';
3
3
  import { SqlCfgModel } from './SqlCfgModel';
4
4
  import { ExecuteContext } from './ExecuteContext';
5
- import { IFuncCfgModel, IRequestCfgModel, ITableMeta } from '../interfaces';
5
+ import { IFuncCfgModel, IRequestCfgModel, ITableMeta, ITableMetaQuery } from '../interfaces';
6
6
  import { FuncContext } from './FuncContext';
7
7
 
8
8
  export interface ICurdProServiceHub {
@@ -28,5 +28,5 @@ export interface ICurdProServiceHub {
28
28
 
29
29
  executeFuncCfg(tmpFunCfg: IFuncCfgModel, exeFunCtx: FuncContext): string;
30
30
 
31
- getTableMeta(sqlCfgModel: SqlCfgModel): Promise<ITableMeta>;
31
+ getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
32
32
  }
@@ -161,7 +161,11 @@ class Transaction {
161
161
  const connections = this.connectionList;
162
162
  for (let i = 0; i < connections.length; i++) {
163
163
  const connection = connections[i];
164
- await connection.rollback();
164
+ try {
165
+ await connection.rollback();
166
+ } catch (e) {
167
+ console.error('[crud-pro] rollbackTx error', e);
168
+ }
165
169
  }
166
170
  }
167
171
 
@@ -101,6 +101,7 @@ export const KeysOfCustomSQL = {
101
101
  */
102
102
  export const KeysOfConditions = {
103
103
  $OR: '$or', //或
104
+ $AND: '$and', //且
104
105
 
105
106
  $NE: '$ne', //不等于
106
107
  $LT: '$lt', //小于
@@ -109,8 +110,10 @@ export const KeysOfConditions = {
109
110
  $GTE: '$gte', //大于或等于
110
111
  $IN: '$in', //in
111
112
  $NIN: '$nin', // not in
112
- $NOT_NULL: '$notNull', // is not null
113
- $NULL: '$null', // is null
113
+
114
+ $NOT_NULL: '$notNull', // is not null ===> {"name": {"$notNull": true}}
115
+ $NULL: '$null', // is null ===> {"name": {"$null": true}}
116
+
114
117
  $RANGE: '$range', // between 1 and 2
115
118
  $LIKE: '$like', //前缀匹配, A% 以A开头 {age:1, name:{"$like":"张"} }
116
119
  $NOT_LIKE: '$notLike', // 前缀匹配 A% 以A开头 {age:1, name:{"$notLike":"张"} }
@@ -160,6 +163,7 @@ function initKeysOfConditions() {
160
163
  // 所有操作符
161
164
  KeysOfConditions.ALL_KEYS = new Set([...KeysOfConditions.COMPARE_KEYS]);
162
165
  addIgnoreCase(KeysOfConditions.ALL_KEYS, KeysOfConditions.$OR);
166
+ addIgnoreCase(KeysOfConditions.ALL_KEYS, KeysOfConditions.$AND);
163
167
  }
164
168
 
165
169
  initKeysOfConditions();
@@ -0,0 +1,58 @@
1
+ import { CrudProServiceBase } from './CrudProServiceBase';
2
+ import { ISqlCfgModel, ITableMetaQuery } from '../interfaces';
3
+
4
+ /**
5
+ * 数据过滤服务
6
+ * 用于在 INSERT/UPDATE 操作前,根据真实表结构过滤 data 对象中的字段
7
+ */
8
+ class CrudProDataFilterService extends CrudProServiceBase {
9
+ /**
10
+ * 根据表结构过滤 data 对象
11
+ * 只保留表中存在的字段
12
+ * @param data 原始 data 对象
13
+ * @param sqlCfgModel SQL 配置(可以是 ISqlCfgModel 接口或 SqlCfgModel 对象)
14
+ * @returns 过滤后的 data 对象
15
+ */
16
+ async filterDataByTableMeta(data: Record<string, any>, sqlCfgModel: ISqlCfgModel): Promise<Record<string, any>> {
17
+ if (!data || Object.keys(data).length === 0) {
18
+ return data;
19
+ }
20
+
21
+ // 确保 sqlTable 存在
22
+ if (!sqlCfgModel.sqlTable) {
23
+ this.logger.warn('CrudProDataFilterService: sqlTable 为空,跳过过滤');
24
+ return data;
25
+ }
26
+
27
+ const query: ITableMetaQuery = {
28
+ sqlTable: sqlCfgModel.sqlTable,
29
+ sqlDatabase: sqlCfgModel.sqlDatabase,
30
+ sqlDbType: sqlCfgModel.sqlDbType,
31
+ sqlSchema: sqlCfgModel.sqlSchema
32
+ }
33
+
34
+ const tableMeta = await this.serviceHub.getTableMeta(query);
35
+ if (!tableMeta || !tableMeta.tableColumns || tableMeta.tableColumns.length === 0) {
36
+ this.logger.warn('CrudProDataFilterService: 无法获取表结构信息,跳过过滤', sqlCfgModel.sqlTable);
37
+ return data;
38
+ }
39
+
40
+ const validColumns = new Set(tableMeta.tableColumns);
41
+ const filteredData: Record<string, any> = {};
42
+
43
+ for (const [key, value] of Object.entries(data)) {
44
+ if (validColumns.has(key)) {
45
+ filteredData[key] = value;
46
+ } else {
47
+ this.logger.debug('CrudProDataFilterService: 过滤掉不存在的字段', {
48
+ table: sqlCfgModel.sqlTable,
49
+ field: key,
50
+ });
51
+ }
52
+ }
53
+
54
+ return filteredData;
55
+ }
56
+ }
57
+
58
+ export { CrudProDataFilterService };
@@ -95,6 +95,19 @@ class CrudProGenSqlCondition {
95
95
  }
96
96
  }
97
97
 
98
+ // AND
99
+ if (equalsIgnoreCase(KeysOfConditions.$AND, key)) {
100
+ TypeUtils.assertJsonArray(value, Exceptions.REQUEST_OR_PARAM_MUST_ARRAY);
101
+
102
+ const a = this.generateByLogicalAnd(value);
103
+ if (a != null) {
104
+ tmpSqlList.push(a.sql);
105
+ if (isNotEmpty(a.args)) {
106
+ tmpArgList.push(...a.args);
107
+ }
108
+ }
109
+ }
110
+
98
111
  // AND
99
112
  else if (TypeUtils.isBasicType(value)) {
100
113
  tmpSqlList.push(toSqlColumnName(key) + '= ?');
@@ -122,6 +135,30 @@ class CrudProGenSqlCondition {
122
135
  return new SqlSegArg(sql, tmpArgList);
123
136
  }
124
137
 
138
+
139
+ private generateByLogicalAnd(jsonArray: IRequestCondition[]): SqlSegArg {
140
+ if (MixinUtils.isEmpty(jsonArray)) {
141
+ return null;
142
+ }
143
+
144
+ const tmpSqlList = [];
145
+ const tmpArgList = [];
146
+
147
+ for (let i = 0; i < jsonArray.length; i++) {
148
+ const jsonObject = jsonArray[i];
149
+ const a = this.generateByLogicalKey(jsonObject);
150
+ if (a != null) {
151
+ tmpSqlList.push(a.sql);
152
+ if (MixinUtils.isNotEmpty(a.args)) {
153
+ tmpArgList.push(...a.args);
154
+ }
155
+ }
156
+ }
157
+ const sql = '(' + tmpSqlList.join(' AND ') + ')';
158
+ return new SqlSegArg(sql, tmpArgList);
159
+ }
160
+
161
+
125
162
  private generateByLogicalOR(jsonArray: IRequestCondition[]): SqlSegArg {
126
163
  if (MixinUtils.isEmpty(jsonArray)) {
127
164
  return null;
@@ -1,5 +1,6 @@
1
1
  import { CrudProServiceBase } from './CrudProServiceBase';
2
2
  import { SqlCfgModel } from '../models/SqlCfgModel';
3
+ import { ITableMetaQuery } from '../interfaces';
3
4
  import { MixinUtils } from '../utils/MixinUtils';
4
5
  import { SqlSegArg } from '../models/SqlSegArg';
5
6
  import { KeysOfCustomSQL, SqlDbType } from '../models/keys';
@@ -365,7 +366,15 @@ class CrudProOriginToExecuteSql extends CrudProServiceBase {
365
366
  * @private
366
367
  */
367
368
  private async generateCfgColumns(sqlCfgModel: SqlCfgModel): Promise<string[]> {
368
- const tableMeta = await this.serviceHub.getTableMeta(sqlCfgModel);
369
+
370
+ const query: ITableMetaQuery = {
371
+ sqlTable: sqlCfgModel.sqlTable,
372
+ sqlDatabase: sqlCfgModel.sqlDatabase,
373
+ sqlDbType: sqlCfgModel.sqlDbType,
374
+ sqlSchema: sqlCfgModel.sqlSchema
375
+ }
376
+
377
+ const tableMeta = await this.serviceHub.getTableMeta(query);
369
378
 
370
379
  const tableColumns = tableMeta.tableColumns;
371
380
  const cfgColumns = sqlCfgModel.columns || [];