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.
- package/dist/libs/crud-pro/CrudPro.d.ts +14 -0
- package/dist/libs/crud-pro/CrudPro.js +61 -0
- package/dist/libs/crud-pro/interfaces.d.ts +20 -0
- package/dist/libs/crud-pro/models/ServiceHub.d.ts +2 -2
- package/dist/libs/crud-pro/models/Transaction.js +6 -1
- package/dist/libs/crud-pro/models/keys.d.ts +1 -0
- package/dist/libs/crud-pro/models/keys.js +2 -0
- package/dist/libs/crud-pro/services/CrudProDataFilterService.d.ts +17 -0
- package/dist/libs/crud-pro/services/CrudProDataFilterService.js +53 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlCondition.d.ts +1 -0
- package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +30 -0
- package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +7 -1
- package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +7 -4
- package/dist/libs/crud-pro/services/CrudProTableMetaService.js +127 -37
- package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +4 -2
- package/dist/libs/crud-pro/services/CurdProServiceHub.js +7 -2
- package/dist/libs/crud-pro/utils/MixinUtils.js +10 -9
- package/dist/middleware/forbidden.middleware.js +13 -2
- package/dist/middleware/global.middleware.js +1 -1
- package/dist/models/SystemEntities.d.ts +2 -1
- package/dist/models/SystemEntities.js +1 -0
- package/dist/service/AuthService.js +3 -0
- package/dist/service/base/BaseService.d.ts +1 -0
- package/dist/service/base/BaseService.js +4 -0
- package/dist/service/crudstd/CrudStdService.js +1 -1
- package/dist/service/proxyapi/ProxyApiService.js +22 -2
- package/dist/service/proxyapi/RouteHandler.d.ts +3 -2
- package/dist/service/proxyapi/RouteTrie.d.ts +1 -1
- package/dist/service/proxyapi/RouteTrie.js +1 -0
- package/dist/service/proxyapi/WeightedRoundRobin.d.ts +1 -1
- package/dist/service/proxyapi/WeightedRoundRobin.js +1 -0
- package/package.json +1 -1
- package/src/libs/crud-pro/CrudPro.ts +71 -0
- package/src/libs/crud-pro/interfaces.ts +22 -0
- package/src/libs/crud-pro/models/ServiceHub.ts +2 -2
- package/src/libs/crud-pro/models/Transaction.ts +5 -1
- package/src/libs/crud-pro/models/keys.ts +6 -2
- package/src/libs/crud-pro/services/CrudProDataFilterService.ts +58 -0
- package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +37 -0
- package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +10 -1
- package/src/libs/crud-pro/services/CrudProTableMetaService.ts +145 -40
- package/src/libs/crud-pro/services/CurdProServiceHub.ts +10 -3
- package/src/libs/crud-pro/utils/MixinUtils.ts +11 -10
- package/src/middleware/forbidden.middleware.ts +17 -7
- package/src/middleware/global.middleware.ts +1 -1
- package/src/models/SystemEntities.ts +1 -0
- package/src/service/AuthService.ts +3 -0
- package/src/service/base/BaseService.ts +5 -0
- package/src/service/crudstd/CrudStdService.ts +2 -1
- package/src/service/proxyapi/ProxyApiService.ts +22 -1
- package/src/service/proxyapi/RouteHandler.ts +4 -2
- package/src/service/proxyapi/RouteTrie.ts +1 -1
- 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
|
-
|
|
278
|
-
|
|
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响应
|
|
@@ -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
|
-
|
|
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
|
};
|
|
@@ -5,7 +5,7 @@ declare class RouteTrie {
|
|
|
5
5
|
constructor();
|
|
6
6
|
addRoute(path: string, handler: RouteHandler): void;
|
|
7
7
|
clearRoute(): void;
|
|
8
|
-
getRouteCount():
|
|
8
|
+
getRouteCount(): number;
|
|
9
9
|
findLongestMatch(path: string): any;
|
|
10
10
|
}
|
|
11
11
|
declare function getRouteTrie(domainCode: string): RouteTrie;
|
package/package.json
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
$
|
|
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
|
-
|
|
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 || [];
|