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
|
@@ -29,6 +29,20 @@ declare class CrudPro {
|
|
|
29
29
|
*/
|
|
30
30
|
executeCrudByCfg(reqJson: IRequestModel, cfgJson: IRequestCfgModel): Promise<ExecuteContext>;
|
|
31
31
|
getCachedCfgByMethod(method: string, isEnableCache: boolean): Promise<IRequestCfgModel>;
|
|
32
|
+
/**
|
|
33
|
+
* 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
|
|
34
|
+
* 避免传入不存在的字段导致 SQL 执行报错
|
|
35
|
+
* 注意:只处理 sqlSimpleName 模式,不处理 sqlCfgList 模式
|
|
36
|
+
* @param reqModel 请求模型
|
|
37
|
+
* @param cfgModel 配置模型
|
|
38
|
+
*/
|
|
39
|
+
private filterDataByTableMetaIfNeeded;
|
|
40
|
+
/**
|
|
41
|
+
* 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
|
|
42
|
+
* @param sqlSimpleName 简单 SQL 名称
|
|
43
|
+
* @returns 是否为 INSERT/UPDATE 类型
|
|
44
|
+
*/
|
|
45
|
+
private isSimpleInsertOrUpdateType;
|
|
32
46
|
private executeSQLList;
|
|
33
47
|
private parseRunSqlException;
|
|
34
48
|
private afterExecuteSQLList;
|
|
@@ -7,6 +7,7 @@ const CurdProServiceHub_1 = require("./services/CurdProServiceHub");
|
|
|
7
7
|
const exceptions_1 = require("./exceptions");
|
|
8
8
|
const RequestCfgModel_1 = require("./models/RequestCfgModel");
|
|
9
9
|
const MixinUtils_1 = require("./utils/MixinUtils");
|
|
10
|
+
const keys_1 = require("./models/keys");
|
|
10
11
|
class CrudPro {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.executeContext = new ExecuteContext_1.ExecuteContext();
|
|
@@ -86,6 +87,8 @@ class CrudPro {
|
|
|
86
87
|
const cfgModel = new RequestCfgModel_1.RequestCfgModel(cfgJson);
|
|
87
88
|
exeCtx.setReqModel(reqModel);
|
|
88
89
|
exeCtx.setCfgModel(cfgModel);
|
|
90
|
+
// 如果是 sqlSimpleName模式的 update/insert,则需要将 reqJson.data 部分根据真实的表结构进行过滤
|
|
91
|
+
await this.filterDataByTableMetaIfNeeded(reqModel, cfgModel);
|
|
89
92
|
// 参数校验
|
|
90
93
|
this.serviceHub.validateByAllow(cfgModel, reqModel);
|
|
91
94
|
this.serviceHub.validateByReject(cfgModel, reqModel);
|
|
@@ -115,6 +118,64 @@ class CrudPro {
|
|
|
115
118
|
async getCachedCfgByMethod(method, isEnableCache) {
|
|
116
119
|
return this.serviceHub.getCachedCfgByMethod(method, isEnableCache);
|
|
117
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* 如果是 INSERT/UPDATE 操作(sqlSimpleName 模式),根据真实表结构过滤 reqModel.data 中的字段
|
|
123
|
+
* 避免传入不存在的字段导致 SQL 执行报错
|
|
124
|
+
* 注意:只处理 sqlSimpleName 模式,不处理 sqlCfgList 模式
|
|
125
|
+
* @param reqModel 请求模型
|
|
126
|
+
* @param cfgModel 配置模型
|
|
127
|
+
*/
|
|
128
|
+
async filterDataByTableMetaIfNeeded(reqModel, cfgModel) {
|
|
129
|
+
// 只有在有 data 数据时才需要过滤
|
|
130
|
+
if (!reqModel.data || Object.keys(reqModel.data).length === 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// 只处理 sqlSimpleName 模式
|
|
134
|
+
const sqlSimpleName = cfgModel.sqlSimpleName;
|
|
135
|
+
if (!sqlSimpleName) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// 判断是否为 INSERT/UPDATE 类型的简单 SQL
|
|
139
|
+
if (!this.isSimpleInsertOrUpdateType(sqlSimpleName)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// 构建 SqlCfgModel 用于获取表结构
|
|
143
|
+
const sqlCfgModel = {
|
|
144
|
+
sqlTable: cfgModel.sqlTable,
|
|
145
|
+
sqlSchema: cfgModel.sqlSchema,
|
|
146
|
+
sqlDatabase: cfgModel.sqlDatabase,
|
|
147
|
+
sqlDbType: cfgModel.sqlDbType,
|
|
148
|
+
columns: cfgModel.columns,
|
|
149
|
+
columnsRelation: cfgModel.columnsRelation,
|
|
150
|
+
};
|
|
151
|
+
const filteredData = await this.serviceHub.filterDataByTableMeta(reqModel.data, sqlCfgModel);
|
|
152
|
+
// 记录被过滤的字段
|
|
153
|
+
const originalKeys = Object.keys(reqModel.data);
|
|
154
|
+
const filteredKeys = Object.keys(filteredData);
|
|
155
|
+
const removedKeys = originalKeys.filter(key => !filteredKeys.includes(key));
|
|
156
|
+
if (removedKeys.length > 0) {
|
|
157
|
+
this.executeContext.getLogger().info('CrudPro.filterDataByTableMetaIfNeeded: 过滤掉表中不存在的字段', {
|
|
158
|
+
table: cfgModel.sqlTable,
|
|
159
|
+
removedFields: removedKeys,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// 直接修改 reqModel.data,只保留表中存在的字段
|
|
163
|
+
reqModel.data = filteredData;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 判断 sqlSimpleName 是否为 INSERT/UPDATE 类型
|
|
167
|
+
* @param sqlSimpleName 简单 SQL 名称
|
|
168
|
+
* @returns 是否为 INSERT/UPDATE 类型
|
|
169
|
+
*/
|
|
170
|
+
isSimpleInsertOrUpdateType(sqlSimpleName) {
|
|
171
|
+
const insertOrUpdateTypes = [
|
|
172
|
+
keys_1.KeysOfSimpleSQL.SIMPLE_INSERT,
|
|
173
|
+
keys_1.KeysOfSimpleSQL.SIMPLE_UPDATE,
|
|
174
|
+
keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_ON_DUPLICATE_UPDATE,
|
|
175
|
+
keys_1.KeysOfSimpleSQL.SIMPLE_INSERT_OR_UPDATE,
|
|
176
|
+
];
|
|
177
|
+
return insertOrUpdateTypes.includes(sqlSimpleName);
|
|
178
|
+
}
|
|
118
179
|
async executeSQLList() {
|
|
119
180
|
try {
|
|
120
181
|
await this.serviceHub.executeSqlCfgModels();
|
|
@@ -8,9 +8,19 @@ export interface ICrudProCfg {
|
|
|
8
8
|
sysConfigTableName?: string;
|
|
9
9
|
tableMetaCacheTime?: number;
|
|
10
10
|
}
|
|
11
|
+
export interface ITableColumn {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
isNullable: boolean;
|
|
15
|
+
isPrimaryKey?: boolean;
|
|
16
|
+
defaultValue?: any;
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
comment?: string;
|
|
19
|
+
}
|
|
11
20
|
export interface ITableMeta {
|
|
12
21
|
expiredTime: number;
|
|
13
22
|
tableColumns: string[];
|
|
23
|
+
columnDetails?: ITableColumn[];
|
|
14
24
|
}
|
|
15
25
|
export interface IConnectionPool {
|
|
16
26
|
dbType: SqlDbType;
|
|
@@ -171,5 +181,15 @@ export interface IExecuteUnsafeQueryCtx {
|
|
|
171
181
|
sqlTable: string;
|
|
172
182
|
sqlDatabase: string;
|
|
173
183
|
sqlDbType: SqlDbType;
|
|
184
|
+
sqlSchema?: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 表结构元数据查询参数
|
|
188
|
+
*/
|
|
189
|
+
export interface ITableMetaQuery {
|
|
190
|
+
sqlTable: string;
|
|
191
|
+
sqlDatabase: string;
|
|
192
|
+
sqlDbType: SqlDbType;
|
|
193
|
+
sqlSchema?: string;
|
|
174
194
|
}
|
|
175
195
|
export {};
|
|
@@ -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
|
export interface ICurdProServiceHub {
|
|
8
8
|
getExecuteContext(): ExecuteContext;
|
|
@@ -16,5 +16,5 @@ export interface ICurdProServiceHub {
|
|
|
16
16
|
executeSqlCfgModels(exeCtx: ExecuteContext): Promise<void>;
|
|
17
17
|
convertOriginToExecuteSql(sqlCfgModel: SqlCfgModel): Promise<void>;
|
|
18
18
|
executeFuncCfg(tmpFunCfg: IFuncCfgModel, exeFunCtx: FuncContext): string;
|
|
19
|
-
getTableMeta(
|
|
19
|
+
getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
|
|
20
20
|
}
|
|
@@ -139,7 +139,12 @@ class Transaction {
|
|
|
139
139
|
const connections = this.connectionList;
|
|
140
140
|
for (let i = 0; i < connections.length; i++) {
|
|
141
141
|
const connection = connections[i];
|
|
142
|
-
|
|
142
|
+
try {
|
|
143
|
+
await connection.rollback();
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
console.error('[crud-pro] rollbackTx error', e);
|
|
147
|
+
}
|
|
143
148
|
}
|
|
144
149
|
}
|
|
145
150
|
/**
|
|
@@ -99,6 +99,7 @@ exports.KeysOfCustomSQL = {
|
|
|
99
99
|
*/
|
|
100
100
|
exports.KeysOfConditions = {
|
|
101
101
|
$OR: '$or',
|
|
102
|
+
$AND: '$and',
|
|
102
103
|
$NE: '$ne',
|
|
103
104
|
$LT: '$lt',
|
|
104
105
|
$LTE: '$lte',
|
|
@@ -150,5 +151,6 @@ function initKeysOfConditions() {
|
|
|
150
151
|
// 所有操作符
|
|
151
152
|
exports.KeysOfConditions.ALL_KEYS = new Set([...exports.KeysOfConditions.COMPARE_KEYS]);
|
|
152
153
|
addIgnoreCase(exports.KeysOfConditions.ALL_KEYS, exports.KeysOfConditions.$OR);
|
|
154
|
+
addIgnoreCase(exports.KeysOfConditions.ALL_KEYS, exports.KeysOfConditions.$AND);
|
|
153
155
|
}
|
|
154
156
|
initKeysOfConditions();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CrudProServiceBase } from './CrudProServiceBase';
|
|
2
|
+
import { ISqlCfgModel } from '../interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* 数据过滤服务
|
|
5
|
+
* 用于在 INSERT/UPDATE 操作前,根据真实表结构过滤 data 对象中的字段
|
|
6
|
+
*/
|
|
7
|
+
declare class CrudProDataFilterService extends CrudProServiceBase {
|
|
8
|
+
/**
|
|
9
|
+
* 根据表结构过滤 data 对象
|
|
10
|
+
* 只保留表中存在的字段
|
|
11
|
+
* @param data 原始 data 对象
|
|
12
|
+
* @param sqlCfgModel SQL 配置(可以是 ISqlCfgModel 接口或 SqlCfgModel 对象)
|
|
13
|
+
* @returns 过滤后的 data 对象
|
|
14
|
+
*/
|
|
15
|
+
filterDataByTableMeta(data: Record<string, any>, sqlCfgModel: ISqlCfgModel): Promise<Record<string, any>>;
|
|
16
|
+
}
|
|
17
|
+
export { CrudProDataFilterService };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CrudProDataFilterService = void 0;
|
|
4
|
+
const CrudProServiceBase_1 = require("./CrudProServiceBase");
|
|
5
|
+
/**
|
|
6
|
+
* 数据过滤服务
|
|
7
|
+
* 用于在 INSERT/UPDATE 操作前,根据真实表结构过滤 data 对象中的字段
|
|
8
|
+
*/
|
|
9
|
+
class CrudProDataFilterService extends CrudProServiceBase_1.CrudProServiceBase {
|
|
10
|
+
/**
|
|
11
|
+
* 根据表结构过滤 data 对象
|
|
12
|
+
* 只保留表中存在的字段
|
|
13
|
+
* @param data 原始 data 对象
|
|
14
|
+
* @param sqlCfgModel SQL 配置(可以是 ISqlCfgModel 接口或 SqlCfgModel 对象)
|
|
15
|
+
* @returns 过滤后的 data 对象
|
|
16
|
+
*/
|
|
17
|
+
async filterDataByTableMeta(data, sqlCfgModel) {
|
|
18
|
+
if (!data || Object.keys(data).length === 0) {
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
// 确保 sqlTable 存在
|
|
22
|
+
if (!sqlCfgModel.sqlTable) {
|
|
23
|
+
this.logger.warn('CrudProDataFilterService: sqlTable 为空,跳过过滤');
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
const query = {
|
|
27
|
+
sqlTable: sqlCfgModel.sqlTable,
|
|
28
|
+
sqlDatabase: sqlCfgModel.sqlDatabase,
|
|
29
|
+
sqlDbType: sqlCfgModel.sqlDbType,
|
|
30
|
+
sqlSchema: sqlCfgModel.sqlSchema
|
|
31
|
+
};
|
|
32
|
+
const tableMeta = await this.serviceHub.getTableMeta(query);
|
|
33
|
+
if (!tableMeta || !tableMeta.tableColumns || tableMeta.tableColumns.length === 0) {
|
|
34
|
+
this.logger.warn('CrudProDataFilterService: 无法获取表结构信息,跳过过滤', sqlCfgModel.sqlTable);
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
const validColumns = new Set(tableMeta.tableColumns);
|
|
38
|
+
const filteredData = {};
|
|
39
|
+
for (const [key, value] of Object.entries(data)) {
|
|
40
|
+
if (validColumns.has(key)) {
|
|
41
|
+
filteredData[key] = value;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.logger.debug('CrudProDataFilterService: 过滤掉不存在的字段', {
|
|
45
|
+
table: sqlCfgModel.sqlTable,
|
|
46
|
+
field: key,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return filteredData;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.CrudProDataFilterService = CrudProDataFilterService;
|
|
@@ -82,6 +82,17 @@ class CrudProGenSqlCondition {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// AND
|
|
85
|
+
if (equalsIgnoreCase(keys_1.KeysOfConditions.$AND, key)) {
|
|
86
|
+
TypeUtils_1.TypeUtils.assertJsonArray(value, exceptions_1.Exceptions.REQUEST_OR_PARAM_MUST_ARRAY);
|
|
87
|
+
const a = this.generateByLogicalAnd(value);
|
|
88
|
+
if (a != null) {
|
|
89
|
+
tmpSqlList.push(a.sql);
|
|
90
|
+
if (isNotEmpty(a.args)) {
|
|
91
|
+
tmpArgList.push(...a.args);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// AND
|
|
85
96
|
else if (TypeUtils_1.TypeUtils.isBasicType(value)) {
|
|
86
97
|
tmpSqlList.push(toSqlColumnName(key) + '= ?');
|
|
87
98
|
tmpArgList.push(value);
|
|
@@ -105,6 +116,25 @@ class CrudProGenSqlCondition {
|
|
|
105
116
|
const sql = '(' + tmpSqlList.join(' AND ') + ')';
|
|
106
117
|
return new SqlSegArg_1.SqlSegArg(sql, tmpArgList);
|
|
107
118
|
}
|
|
119
|
+
generateByLogicalAnd(jsonArray) {
|
|
120
|
+
if (MixinUtils_1.MixinUtils.isEmpty(jsonArray)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const tmpSqlList = [];
|
|
124
|
+
const tmpArgList = [];
|
|
125
|
+
for (let i = 0; i < jsonArray.length; i++) {
|
|
126
|
+
const jsonObject = jsonArray[i];
|
|
127
|
+
const a = this.generateByLogicalKey(jsonObject);
|
|
128
|
+
if (a != null) {
|
|
129
|
+
tmpSqlList.push(a.sql);
|
|
130
|
+
if (MixinUtils_1.MixinUtils.isNotEmpty(a.args)) {
|
|
131
|
+
tmpArgList.push(...a.args);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const sql = '(' + tmpSqlList.join(' AND ') + ')';
|
|
136
|
+
return new SqlSegArg_1.SqlSegArg(sql, tmpArgList);
|
|
137
|
+
}
|
|
108
138
|
generateByLogicalOR(jsonArray) {
|
|
109
139
|
if (MixinUtils_1.MixinUtils.isEmpty(jsonArray)) {
|
|
110
140
|
return null;
|
|
@@ -310,7 +310,13 @@ class CrudProOriginToExecuteSql extends CrudProServiceBase_1.CrudProServiceBase
|
|
|
310
310
|
* @private
|
|
311
311
|
*/
|
|
312
312
|
async generateCfgColumns(sqlCfgModel) {
|
|
313
|
-
const
|
|
313
|
+
const query = {
|
|
314
|
+
sqlTable: sqlCfgModel.sqlTable,
|
|
315
|
+
sqlDatabase: sqlCfgModel.sqlDatabase,
|
|
316
|
+
sqlDbType: sqlCfgModel.sqlDbType,
|
|
317
|
+
sqlSchema: sqlCfgModel.sqlSchema
|
|
318
|
+
};
|
|
319
|
+
const tableMeta = await this.serviceHub.getTableMeta(query);
|
|
314
320
|
const tableColumns = tableMeta.tableColumns;
|
|
315
321
|
const cfgColumns = sqlCfgModel.columns || [];
|
|
316
322
|
// 没有配置: 全量
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { CrudProServiceBase } from './CrudProServiceBase';
|
|
2
|
-
import {
|
|
3
|
-
import { ITableMeta } from '../interfaces';
|
|
2
|
+
import { ITableMeta, ITableMetaQuery } from '../interfaces';
|
|
4
3
|
declare class CrudProTableMetaService extends CrudProServiceBase {
|
|
5
|
-
getTableMeta(
|
|
4
|
+
getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
|
|
6
5
|
private loadTableMeta;
|
|
7
|
-
private
|
|
6
|
+
private loadTableColumnDetails;
|
|
7
|
+
private loadMySQLColumnDetails;
|
|
8
|
+
private loadPostgreSQLColumnDetails;
|
|
9
|
+
private loadPostgreSQLColumnComments;
|
|
10
|
+
private loadSQLServerColumnDetails;
|
|
8
11
|
}
|
|
9
12
|
export { CrudProTableMetaService };
|
|
@@ -8,71 +8,161 @@ class CrudProTableMetaCache {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
this.cacheMap = {};
|
|
10
10
|
}
|
|
11
|
-
getCacheKey(
|
|
12
|
-
return `${
|
|
11
|
+
getCacheKey(query) {
|
|
12
|
+
return `${query.sqlDatabase}:::${query.sqlSchema}:::${query.sqlDbType}:::${query.sqlTable}`;
|
|
13
13
|
}
|
|
14
|
-
getMeta(
|
|
15
|
-
const cacheKey = this.getCacheKey(
|
|
14
|
+
getMeta(query) {
|
|
15
|
+
const cacheKey = this.getCacheKey(query);
|
|
16
16
|
const obj = this.cacheMap[cacheKey];
|
|
17
|
-
if (obj && obj.expiredTime
|
|
17
|
+
if (obj && obj.expiredTime > Date.now()) {
|
|
18
18
|
return obj;
|
|
19
19
|
}
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
|
-
setMeta(
|
|
23
|
-
const cacheKey = this.getCacheKey(
|
|
22
|
+
setMeta(query, metaObj) {
|
|
23
|
+
const cacheKey = this.getCacheKey(query);
|
|
24
24
|
this.cacheMap[cacheKey] = metaObj;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
const metaCache = new CrudProTableMetaCache();
|
|
28
28
|
class CrudProTableMetaService extends CrudProServiceBase_1.CrudProServiceBase {
|
|
29
|
-
async getTableMeta(
|
|
30
|
-
let obj = metaCache.getMeta(
|
|
29
|
+
async getTableMeta(query) {
|
|
30
|
+
let obj = metaCache.getMeta(query);
|
|
31
31
|
if (!obj) {
|
|
32
|
-
obj = await this.loadTableMeta(
|
|
33
|
-
metaCache.setMeta(
|
|
32
|
+
obj = await this.loadTableMeta(query);
|
|
33
|
+
metaCache.setMeta(query, obj);
|
|
34
34
|
}
|
|
35
35
|
return obj;
|
|
36
36
|
}
|
|
37
|
-
async loadTableMeta(
|
|
37
|
+
async loadTableMeta(query) {
|
|
38
38
|
const { tableMetaCacheTime } = this.getContextCfg();
|
|
39
39
|
const obj = {
|
|
40
|
-
expiredTime: Date.now() + tableMetaCacheTime || 1000 * 3600 * 24 * 365,
|
|
40
|
+
expiredTime: Date.now() + (tableMetaCacheTime || 1000 * 3600 * 24 * 365),
|
|
41
41
|
tableColumns: [],
|
|
42
|
+
columnDetails: [],
|
|
42
43
|
};
|
|
43
44
|
const baseInfo = {
|
|
44
|
-
sqlTable:
|
|
45
|
-
sqlDatabase:
|
|
46
|
-
sqlDbType:
|
|
45
|
+
sqlTable: query.sqlTable,
|
|
46
|
+
sqlDatabase: query.sqlDatabase,
|
|
47
|
+
sqlDbType: query.sqlDbType,
|
|
47
48
|
};
|
|
48
|
-
obj.
|
|
49
|
+
obj.columnDetails = await this.loadTableColumnDetails(baseInfo);
|
|
50
|
+
obj.tableColumns = obj.columnDetails.map(col => col.name);
|
|
49
51
|
return obj;
|
|
50
52
|
}
|
|
51
|
-
async
|
|
53
|
+
async loadTableColumnDetails(baseInfo) {
|
|
52
54
|
if (baseInfo.sqlDbType === keys_1.SqlDbType.mysql) {
|
|
53
|
-
|
|
54
|
-
const tableDescribe = queryRes.rows || []; //pickAndConvertRowsByMix(queryRes, baseInfo.sqlDbType);
|
|
55
|
-
const tableDescribe2 = JSON.parse(JSON.stringify(tableDescribe));
|
|
56
|
-
return tableDescribe2.map(fieldObj => {
|
|
57
|
-
return fieldObj['Field'];
|
|
58
|
-
});
|
|
55
|
+
return this.loadMySQLColumnDetails(baseInfo);
|
|
59
56
|
}
|
|
60
57
|
else if (baseInfo.sqlDbType === keys_1.SqlDbType.postgres) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
FROM information_schema.columns
|
|
66
|
-
WHERE table_schema = '${schemaname}' and table_name = '${baseInfo.sqlTable}'
|
|
67
|
-
ORDER BY ordinal_position;
|
|
68
|
-
`.trim();
|
|
69
|
-
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
70
|
-
const tableDescribe = queryRes.rows || [];
|
|
71
|
-
return tableDescribe.map(fieldObj => {
|
|
72
|
-
return fieldObj['column_name'];
|
|
73
|
-
});
|
|
58
|
+
return this.loadPostgreSQLColumnDetails(baseInfo);
|
|
59
|
+
}
|
|
60
|
+
else if (baseInfo.sqlDbType === keys_1.SqlDbType.sqlserver) {
|
|
61
|
+
return this.loadSQLServerColumnDetails(baseInfo);
|
|
74
62
|
}
|
|
75
63
|
throw new Error('暂不支持的数据库类型:' + baseInfo.sqlDbType);
|
|
76
64
|
}
|
|
65
|
+
async loadMySQLColumnDetails(baseInfo) {
|
|
66
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, 'DESCRIBE ' + baseInfo.sqlTable);
|
|
67
|
+
const tableDescribe = queryRes.rows || [];
|
|
68
|
+
return tableDescribe.map((fieldObj) => {
|
|
69
|
+
const { Field, Type, Null, Key, Default, Extra } = fieldObj;
|
|
70
|
+
return {
|
|
71
|
+
name: Field,
|
|
72
|
+
type: Type,
|
|
73
|
+
isNullable: Null === 'YES',
|
|
74
|
+
isPrimaryKey: Key === 'PRI',
|
|
75
|
+
defaultValue: Default,
|
|
76
|
+
comment: Extra,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async loadPostgreSQLColumnDetails(baseInfo) {
|
|
81
|
+
const schemaname = baseInfo.sqlSchema || 'public';
|
|
82
|
+
// 获取列基本信息
|
|
83
|
+
const columnArraySql = `
|
|
84
|
+
SELECT
|
|
85
|
+
column_name,
|
|
86
|
+
data_type,
|
|
87
|
+
is_nullable,
|
|
88
|
+
column_default,
|
|
89
|
+
character_maximum_length
|
|
90
|
+
FROM information_schema.columns
|
|
91
|
+
WHERE table_schema = '${schemaname}' AND table_name = '${baseInfo.sqlTable}'
|
|
92
|
+
ORDER BY ordinal_position;
|
|
93
|
+
`.trim();
|
|
94
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
95
|
+
const columnArray = queryRes.rows || [];
|
|
96
|
+
// 获取字段注释
|
|
97
|
+
const commentMap = await this.loadPostgreSQLColumnComments(baseInfo, schemaname);
|
|
98
|
+
return columnArray.map((columnObj) => {
|
|
99
|
+
const { column_name, data_type, is_nullable, column_default, character_maximum_length } = columnObj;
|
|
100
|
+
return {
|
|
101
|
+
name: column_name,
|
|
102
|
+
type: data_type,
|
|
103
|
+
isNullable: is_nullable === 'YES',
|
|
104
|
+
defaultValue: column_default,
|
|
105
|
+
maxLength: character_maximum_length,
|
|
106
|
+
comment: commentMap[column_name],
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async loadPostgreSQLColumnComments(baseInfo, schemaname) {
|
|
111
|
+
const commentArraySql = `
|
|
112
|
+
SELECT
|
|
113
|
+
a.attname AS column_name,
|
|
114
|
+
d.description AS column_comment
|
|
115
|
+
FROM pg_class c
|
|
116
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
117
|
+
JOIN pg_attribute a ON c.oid = a.attrelid
|
|
118
|
+
LEFT JOIN pg_description d ON c.oid = d.objoid AND a.attnum = d.objsubid
|
|
119
|
+
WHERE n.nspname = '${schemaname}'
|
|
120
|
+
AND c.relname = '${baseInfo.sqlTable}'
|
|
121
|
+
AND a.attnum > 0
|
|
122
|
+
ORDER BY a.attnum;
|
|
123
|
+
`.trim();
|
|
124
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, commentArraySql);
|
|
125
|
+
const commentArray = queryRes.rows || [];
|
|
126
|
+
const map = {};
|
|
127
|
+
commentArray.forEach((commentObj) => {
|
|
128
|
+
const { column_name, column_comment } = commentObj;
|
|
129
|
+
map[column_name] = column_comment || '';
|
|
130
|
+
});
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
async loadSQLServerColumnDetails(baseInfo) {
|
|
134
|
+
const columnArraySql = `
|
|
135
|
+
SELECT
|
|
136
|
+
c.name AS column_name,
|
|
137
|
+
t.name AS data_type,
|
|
138
|
+
c.max_length,
|
|
139
|
+
c.precision,
|
|
140
|
+
c.scale,
|
|
141
|
+
c.is_nullable,
|
|
142
|
+
c.is_identity,
|
|
143
|
+
dc.definition AS column_default,
|
|
144
|
+
ep.value AS column_comment
|
|
145
|
+
FROM sys.columns c
|
|
146
|
+
JOIN sys.types t ON c.user_type_id = t.user_type_id
|
|
147
|
+
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
|
|
148
|
+
LEFT JOIN sys.extended_properties ep ON c.object_id = ep.major_id AND c.column_id = ep.minor_id
|
|
149
|
+
WHERE c.object_id = OBJECT_ID('${baseInfo.sqlTable}')
|
|
150
|
+
ORDER BY c.column_id;
|
|
151
|
+
`.trim();
|
|
152
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
153
|
+
const columnArray = queryRes.rows || [];
|
|
154
|
+
return columnArray.map((columnObj) => {
|
|
155
|
+
const { column_name, data_type, is_nullable, column_default, column_comment, max_length, is_identity } = columnObj;
|
|
156
|
+
return {
|
|
157
|
+
name: column_name,
|
|
158
|
+
type: data_type,
|
|
159
|
+
isNullable: is_nullable,
|
|
160
|
+
defaultValue: column_default,
|
|
161
|
+
comment: column_comment,
|
|
162
|
+
maxLength: max_length,
|
|
163
|
+
isPrimaryKey: is_identity,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
77
167
|
}
|
|
78
168
|
exports.CrudProTableMetaService = CrudProTableMetaService;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IFuncCfgModel, IRequestCfgModel, ITableMeta } from '../interfaces';
|
|
1
|
+
import { IFuncCfgModel, IRequestCfgModel, ISqlCfgModel, ITableMeta, ITableMetaQuery } from '../interfaces';
|
|
2
2
|
import { RequestModel } from '../models/RequestModel';
|
|
3
3
|
import { ExecuteContext } from '../models/ExecuteContext';
|
|
4
4
|
import { SqlCfgModel } from '../models/SqlCfgModel';
|
|
@@ -15,6 +15,7 @@ declare class CurdProServiceHub implements ICurdProServiceHub {
|
|
|
15
15
|
private readonly originToExecuteSql;
|
|
16
16
|
private readonly executeFuncService;
|
|
17
17
|
private readonly tableMetaService;
|
|
18
|
+
private readonly dataFilterService;
|
|
18
19
|
constructor(executeContext: ExecuteContext);
|
|
19
20
|
getExecuteContext(): ExecuteContext;
|
|
20
21
|
validateByAllow(cfgModel: RequestCfgModel, reqModel: RequestModel): void;
|
|
@@ -27,6 +28,7 @@ declare class CurdProServiceHub implements ICurdProServiceHub {
|
|
|
27
28
|
executeSqlCfgModels(): Promise<void>;
|
|
28
29
|
convertOriginToExecuteSql(sqlCfgModel: SqlCfgModel): Promise<void>;
|
|
29
30
|
executeFuncCfg(funCfg: IFuncCfgModel, funcContext: FuncContext): any;
|
|
30
|
-
getTableMeta(
|
|
31
|
+
getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
|
|
32
|
+
filterDataByTableMeta(data: Record<string, any>, sqlCfgModel: ISqlCfgModel | SqlCfgModel): Promise<Record<string, any>>;
|
|
31
33
|
}
|
|
32
34
|
export { CurdProServiceHub };
|
|
@@ -9,6 +9,7 @@ const CrudProGenSqlService_1 = require("./CrudProGenSqlService");
|
|
|
9
9
|
const CrudProOriginToExecuteSql_1 = require("./CrudProOriginToExecuteSql");
|
|
10
10
|
const CrudProExecuteFuncService_1 = require("./CrudProExecuteFuncService");
|
|
11
11
|
const CrudProTableMetaService_1 = require("./CrudProTableMetaService");
|
|
12
|
+
const CrudProDataFilterService_1 = require("./CrudProDataFilterService");
|
|
12
13
|
class CurdProServiceHub {
|
|
13
14
|
constructor(executeContext) {
|
|
14
15
|
this.executeContext = executeContext;
|
|
@@ -20,6 +21,7 @@ class CurdProServiceHub {
|
|
|
20
21
|
this.originToExecuteSql = new CrudProOriginToExecuteSql_1.CrudProOriginToExecuteSql(this);
|
|
21
22
|
this.executeFuncService = new CrudProExecuteFuncService_1.CrudProExecuteFuncService(this);
|
|
22
23
|
this.tableMetaService = new CrudProTableMetaService_1.CrudProTableMetaService(this);
|
|
24
|
+
this.dataFilterService = new CrudProDataFilterService_1.CrudProDataFilterService(this);
|
|
23
25
|
}
|
|
24
26
|
getExecuteContext() {
|
|
25
27
|
return this.executeContext;
|
|
@@ -57,8 +59,11 @@ class CurdProServiceHub {
|
|
|
57
59
|
executeFuncCfg(funCfg, funcContext) {
|
|
58
60
|
return this.executeFuncService.executeFuncCfg(funCfg, funcContext);
|
|
59
61
|
}
|
|
60
|
-
async getTableMeta(
|
|
61
|
-
return await this.tableMetaService.getTableMeta(
|
|
62
|
+
async getTableMeta(query) {
|
|
63
|
+
return await this.tableMetaService.getTableMeta(query);
|
|
64
|
+
}
|
|
65
|
+
async filterDataByTableMeta(data, sqlCfgModel) {
|
|
66
|
+
return await this.dataFilterService.filterDataByTableMeta(data, sqlCfgModel);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
exports.CurdProServiceHub = CurdProServiceHub;
|
|
@@ -176,6 +176,16 @@ const MixinUtils = {
|
|
|
176
176
|
if (!obj || !objTravelCallback) {
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
+
if (Array.isArray(obj)) {
|
|
180
|
+
const collection = obj;
|
|
181
|
+
for (let i = 0; i < collection.length; i++) {
|
|
182
|
+
const value = collection[i];
|
|
183
|
+
const curKeyPath = keyPath + '[' + i + ']';
|
|
184
|
+
objTravelCallback(value, null, i, curKeyPath);
|
|
185
|
+
MixinUtils.deepTravelObject(value, objTravelCallback, curKeyPath);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
179
189
|
if (typeof obj === 'object') {
|
|
180
190
|
const keys = Object.keys(obj);
|
|
181
191
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -187,15 +197,6 @@ const MixinUtils = {
|
|
|
187
197
|
MixinUtils.deepTravelObject(value, objTravelCallback, curKeyPath);
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
|
-
if (Array.isArray(obj)) {
|
|
191
|
-
const collection = obj;
|
|
192
|
-
for (let i = 0; i < collection.length; i++) {
|
|
193
|
-
const value = collection[i];
|
|
194
|
-
const curKeyPath = keyPath + '[' + i + ']';
|
|
195
|
-
objTravelCallback(value, null, i, curKeyPath);
|
|
196
|
-
MixinUtils.deepTravelObject(value, objTravelCallback, curKeyPath);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
200
|
},
|
|
200
201
|
removeEmptyAttrs(obj) {
|
|
201
202
|
const result = {};
|