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
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { CrudProServiceBase } from './CrudProServiceBase';
|
|
2
|
-
import {
|
|
3
|
-
import { IExecuteUnsafeQueryCtx, ITableMeta } from '../interfaces';
|
|
2
|
+
import { IExecuteUnsafeQueryCtx, ITableColumn, ITableMeta, ITableMetaQuery } from '../interfaces';
|
|
4
3
|
import { SqlDbType } from '../models/keys';
|
|
5
4
|
// import { pickAndConvertRowsByMix } from '../utils/sqlConvert/convertMix';
|
|
6
5
|
|
|
7
6
|
class CrudProTableMetaCache {
|
|
8
7
|
private cacheMap: Record<string, ITableMeta> = {};
|
|
9
|
-
private getCacheKey(
|
|
10
|
-
return `${
|
|
8
|
+
private getCacheKey(query: ITableMetaQuery) {
|
|
9
|
+
return `${query.sqlDatabase}:::${query.sqlSchema}:::${query.sqlDbType}:::${query.sqlTable}`;
|
|
11
10
|
}
|
|
12
|
-
public getMeta(
|
|
13
|
-
const cacheKey = this.getCacheKey(
|
|
11
|
+
public getMeta(query: ITableMetaQuery) {
|
|
12
|
+
const cacheKey = this.getCacheKey(query);
|
|
14
13
|
const obj = this.cacheMap[cacheKey];
|
|
15
|
-
if (obj && obj.expiredTime
|
|
14
|
+
if (obj && obj.expiredTime > Date.now()) {
|
|
16
15
|
return obj;
|
|
17
16
|
}
|
|
18
17
|
return null;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
public setMeta(
|
|
22
|
-
const cacheKey = this.getCacheKey(
|
|
20
|
+
public setMeta(query: ITableMetaQuery, metaObj: ITableMeta) {
|
|
21
|
+
const cacheKey = this.getCacheKey(query);
|
|
23
22
|
this.cacheMap[cacheKey] = metaObj;
|
|
24
23
|
}
|
|
25
24
|
}
|
|
@@ -27,60 +26,166 @@ class CrudProTableMetaCache {
|
|
|
27
26
|
const metaCache = new CrudProTableMetaCache();
|
|
28
27
|
|
|
29
28
|
class CrudProTableMetaService extends CrudProServiceBase {
|
|
30
|
-
async getTableMeta(
|
|
31
|
-
let obj = metaCache.getMeta(
|
|
29
|
+
public async getTableMeta(query: ITableMetaQuery): Promise<ITableMeta> {
|
|
30
|
+
let obj = metaCache.getMeta(query);
|
|
32
31
|
if (!obj) {
|
|
33
|
-
obj = await this.loadTableMeta(
|
|
34
|
-
metaCache.setMeta(
|
|
32
|
+
obj = await this.loadTableMeta(query);
|
|
33
|
+
metaCache.setMeta(query, obj);
|
|
35
34
|
}
|
|
36
35
|
return obj;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
private async loadTableMeta(
|
|
38
|
+
private async loadTableMeta(query: ITableMetaQuery): Promise<ITableMeta> {
|
|
40
39
|
const { tableMetaCacheTime } = this.getContextCfg();
|
|
41
40
|
|
|
42
|
-
const obj = {
|
|
43
|
-
expiredTime: Date.now() + tableMetaCacheTime || 1000 * 3600 * 24 * 365,
|
|
41
|
+
const obj: ITableMeta = {
|
|
42
|
+
expiredTime: Date.now() + (tableMetaCacheTime || 1000 * 3600 * 24 * 365),
|
|
44
43
|
tableColumns: [],
|
|
44
|
+
columnDetails: [],
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const baseInfo: IExecuteUnsafeQueryCtx = {
|
|
48
|
-
sqlTable:
|
|
49
|
-
sqlDatabase:
|
|
50
|
-
sqlDbType:
|
|
48
|
+
sqlTable: query.sqlTable,
|
|
49
|
+
sqlDatabase: query.sqlDatabase,
|
|
50
|
+
sqlDbType: query.sqlDbType,
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
obj.
|
|
53
|
+
obj.columnDetails = await this.loadTableColumnDetails(baseInfo);
|
|
54
|
+
obj.tableColumns = obj.columnDetails.map(col => col.name);
|
|
54
55
|
|
|
55
56
|
return obj;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
private async
|
|
59
|
+
private async loadTableColumnDetails(baseInfo: IExecuteUnsafeQueryCtx): Promise<ITableColumn[]> {
|
|
59
60
|
if (baseInfo.sqlDbType === SqlDbType.mysql) {
|
|
60
|
-
|
|
61
|
-
const tableDescribe = queryRes.rows || []; //pickAndConvertRowsByMix(queryRes, baseInfo.sqlDbType);
|
|
62
|
-
const tableDescribe2 = JSON.parse(JSON.stringify(tableDescribe));
|
|
63
|
-
return tableDescribe2.map(fieldObj => {
|
|
64
|
-
return fieldObj['Field'];
|
|
65
|
-
});
|
|
61
|
+
return this.loadMySQLColumnDetails(baseInfo);
|
|
66
62
|
} else if (baseInfo.sqlDbType === SqlDbType.postgres) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
FROM information_schema.columns
|
|
72
|
-
WHERE table_schema = '${schemaname}' and table_name = '${baseInfo.sqlTable}'
|
|
73
|
-
ORDER BY ordinal_position;
|
|
74
|
-
`.trim();
|
|
75
|
-
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
76
|
-
const tableDescribe = queryRes.rows || [];
|
|
77
|
-
return tableDescribe.map(fieldObj => {
|
|
78
|
-
return fieldObj['column_name'];
|
|
79
|
-
});
|
|
63
|
+
return this.loadPostgreSQLColumnDetails(baseInfo);
|
|
64
|
+
} else if (baseInfo.sqlDbType === SqlDbType.sqlserver) {
|
|
65
|
+
return this.loadSQLServerColumnDetails(baseInfo);
|
|
80
66
|
}
|
|
81
67
|
|
|
82
68
|
throw new Error('暂不支持的数据库类型:' + baseInfo.sqlDbType);
|
|
83
69
|
}
|
|
70
|
+
|
|
71
|
+
private async loadMySQLColumnDetails(baseInfo: IExecuteUnsafeQueryCtx): Promise<ITableColumn[]> {
|
|
72
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, 'DESCRIBE ' + baseInfo.sqlTable);
|
|
73
|
+
const tableDescribe = queryRes.rows || [];
|
|
74
|
+
|
|
75
|
+
return tableDescribe.map((fieldObj: any) => {
|
|
76
|
+
const { Field, Type, Null, Key, Default, Extra } = fieldObj;
|
|
77
|
+
return {
|
|
78
|
+
name: Field,
|
|
79
|
+
type: Type,
|
|
80
|
+
isNullable: Null === 'YES',
|
|
81
|
+
isPrimaryKey: Key === 'PRI',
|
|
82
|
+
defaultValue: Default,
|
|
83
|
+
comment: Extra,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async loadPostgreSQLColumnDetails(baseInfo: IExecuteUnsafeQueryCtx): Promise<ITableColumn[]> {
|
|
89
|
+
const schemaname = baseInfo.sqlSchema || 'public';
|
|
90
|
+
|
|
91
|
+
// 获取列基本信息
|
|
92
|
+
const columnArraySql = `
|
|
93
|
+
SELECT
|
|
94
|
+
column_name,
|
|
95
|
+
data_type,
|
|
96
|
+
is_nullable,
|
|
97
|
+
column_default,
|
|
98
|
+
character_maximum_length
|
|
99
|
+
FROM information_schema.columns
|
|
100
|
+
WHERE table_schema = '${schemaname}' AND table_name = '${baseInfo.sqlTable}'
|
|
101
|
+
ORDER BY ordinal_position;
|
|
102
|
+
`.trim();
|
|
103
|
+
|
|
104
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
105
|
+
const columnArray = queryRes.rows || [];
|
|
106
|
+
|
|
107
|
+
// 获取字段注释
|
|
108
|
+
const commentMap = await this.loadPostgreSQLColumnComments(baseInfo, schemaname);
|
|
109
|
+
|
|
110
|
+
return columnArray.map((columnObj: any) => {
|
|
111
|
+
const { column_name, data_type, is_nullable, column_default, character_maximum_length } = columnObj;
|
|
112
|
+
return {
|
|
113
|
+
name: column_name,
|
|
114
|
+
type: data_type,
|
|
115
|
+
isNullable: is_nullable === 'YES',
|
|
116
|
+
defaultValue: column_default,
|
|
117
|
+
maxLength: character_maximum_length,
|
|
118
|
+
comment: commentMap[column_name],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async loadPostgreSQLColumnComments(
|
|
124
|
+
baseInfo: IExecuteUnsafeQueryCtx,
|
|
125
|
+
schemaname: string
|
|
126
|
+
): Promise<Record<string, string>> {
|
|
127
|
+
const commentArraySql = `
|
|
128
|
+
SELECT
|
|
129
|
+
a.attname AS column_name,
|
|
130
|
+
d.description AS column_comment
|
|
131
|
+
FROM pg_class c
|
|
132
|
+
JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
133
|
+
JOIN pg_attribute a ON c.oid = a.attrelid
|
|
134
|
+
LEFT JOIN pg_description d ON c.oid = d.objoid AND a.attnum = d.objsubid
|
|
135
|
+
WHERE n.nspname = '${schemaname}'
|
|
136
|
+
AND c.relname = '${baseInfo.sqlTable}'
|
|
137
|
+
AND a.attnum > 0
|
|
138
|
+
ORDER BY a.attnum;
|
|
139
|
+
`.trim();
|
|
140
|
+
|
|
141
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, commentArraySql);
|
|
142
|
+
const commentArray = queryRes.rows || [];
|
|
143
|
+
|
|
144
|
+
const map: Record<string, string> = {};
|
|
145
|
+
commentArray.forEach((commentObj: any) => {
|
|
146
|
+
const { column_name, column_comment } = commentObj;
|
|
147
|
+
map[column_name] = column_comment || '';
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return map;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async loadSQLServerColumnDetails(baseInfo: IExecuteUnsafeQueryCtx): Promise<ITableColumn[]> {
|
|
154
|
+
const columnArraySql = `
|
|
155
|
+
SELECT
|
|
156
|
+
c.name AS column_name,
|
|
157
|
+
t.name AS data_type,
|
|
158
|
+
c.max_length,
|
|
159
|
+
c.precision,
|
|
160
|
+
c.scale,
|
|
161
|
+
c.is_nullable,
|
|
162
|
+
c.is_identity,
|
|
163
|
+
dc.definition AS column_default,
|
|
164
|
+
ep.value AS column_comment
|
|
165
|
+
FROM sys.columns c
|
|
166
|
+
JOIN sys.types t ON c.user_type_id = t.user_type_id
|
|
167
|
+
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
|
|
168
|
+
LEFT JOIN sys.extended_properties ep ON c.object_id = ep.major_id AND c.column_id = ep.minor_id
|
|
169
|
+
WHERE c.object_id = OBJECT_ID('${baseInfo.sqlTable}')
|
|
170
|
+
ORDER BY c.column_id;
|
|
171
|
+
`.trim();
|
|
172
|
+
|
|
173
|
+
const queryRes = await this.executeUnsafeQuery(baseInfo, columnArraySql);
|
|
174
|
+
const columnArray = queryRes.rows || [];
|
|
175
|
+
|
|
176
|
+
return columnArray.map((columnObj: any) => {
|
|
177
|
+
const { column_name, data_type, is_nullable, column_default, column_comment, max_length, is_identity } = columnObj;
|
|
178
|
+
return {
|
|
179
|
+
name: column_name,
|
|
180
|
+
type: data_type,
|
|
181
|
+
isNullable: is_nullable,
|
|
182
|
+
defaultValue: column_default,
|
|
183
|
+
comment: column_comment,
|
|
184
|
+
maxLength: max_length,
|
|
185
|
+
isPrimaryKey: is_identity,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
84
189
|
}
|
|
85
190
|
|
|
86
191
|
export { 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 { CrudProFieldValidateService } from './CrudProFieldValidateService';
|
|
4
4
|
import { CrudProFieldUpdateService } from './CrudProFieldUpdateService';
|
|
@@ -13,6 +13,7 @@ import { ICurdProServiceHub } from '../models/ServiceHub';
|
|
|
13
13
|
import { FuncContext } from '../models/FuncContext';
|
|
14
14
|
import { CrudProExecuteFuncService } from './CrudProExecuteFuncService';
|
|
15
15
|
import { CrudProTableMetaService } from './CrudProTableMetaService';
|
|
16
|
+
import { CrudProDataFilterService } from './CrudProDataFilterService';
|
|
16
17
|
|
|
17
18
|
class CurdProServiceHub implements ICurdProServiceHub {
|
|
18
19
|
private readonly executeContext: ExecuteContext;
|
|
@@ -25,6 +26,7 @@ class CurdProServiceHub implements ICurdProServiceHub {
|
|
|
25
26
|
private readonly originToExecuteSql: CrudProOriginToExecuteSql;
|
|
26
27
|
private readonly executeFuncService: CrudProExecuteFuncService;
|
|
27
28
|
private readonly tableMetaService: CrudProTableMetaService;
|
|
29
|
+
private readonly dataFilterService: CrudProDataFilterService;
|
|
28
30
|
|
|
29
31
|
constructor(executeContext: ExecuteContext) {
|
|
30
32
|
this.executeContext = executeContext;
|
|
@@ -36,6 +38,7 @@ class CurdProServiceHub implements ICurdProServiceHub {
|
|
|
36
38
|
this.originToExecuteSql = new CrudProOriginToExecuteSql(this);
|
|
37
39
|
this.executeFuncService = new CrudProExecuteFuncService(this);
|
|
38
40
|
this.tableMetaService = new CrudProTableMetaService(this);
|
|
41
|
+
this.dataFilterService = new CrudProDataFilterService(this);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
getExecuteContext(): ExecuteContext {
|
|
@@ -84,8 +87,12 @@ class CurdProServiceHub implements ICurdProServiceHub {
|
|
|
84
87
|
return this.executeFuncService.executeFuncCfg(funCfg, funcContext);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
async getTableMeta(
|
|
88
|
-
return await this.tableMetaService.getTableMeta(
|
|
90
|
+
async getTableMeta(query: ITableMetaQuery): Promise<ITableMeta> {
|
|
91
|
+
return await this.tableMetaService.getTableMeta(query);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async filterDataByTableMeta(data: Record<string, any>, sqlCfgModel: ISqlCfgModel | SqlCfgModel): Promise<Record<string, any>> {
|
|
95
|
+
return await this.dataFilterService.filterDataByTableMeta(data, sqlCfgModel);
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -201,9 +201,19 @@ const MixinUtils = {
|
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
if (Array.isArray(obj)) {
|
|
205
|
+
const collection = obj;
|
|
206
|
+
for (let i = 0; i < collection.length; i++) {
|
|
207
|
+
const value = collection[i];
|
|
208
|
+
const curKeyPath = keyPath + '[' + i + ']';
|
|
209
|
+
objTravelCallback(value, null, i, curKeyPath);
|
|
210
|
+
MixinUtils.deepTravelObject(value, objTravelCallback, curKeyPath);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
204
215
|
if (typeof obj === 'object') {
|
|
205
216
|
const keys = Object.keys(obj);
|
|
206
|
-
|
|
207
217
|
for (let i = 0; i < keys.length; i++) {
|
|
208
218
|
const objKey = keys[i];
|
|
209
219
|
const keyStr = objKey;
|
|
@@ -214,15 +224,6 @@ const MixinUtils = {
|
|
|
214
224
|
}
|
|
215
225
|
}
|
|
216
226
|
|
|
217
|
-
if (Array.isArray(obj)) {
|
|
218
|
-
const collection = obj;
|
|
219
|
-
for (let i = 0; i < collection.length; i++) {
|
|
220
|
-
const value = collection[i];
|
|
221
|
-
const curKeyPath = keyPath + '[' + i + ']';
|
|
222
|
-
objTravelCallback(value, null, i, curKeyPath);
|
|
223
|
-
MixinUtils.deepTravelObject(value, objTravelCallback, curKeyPath);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
227
|
},
|
|
227
228
|
removeEmptyAttrs(obj: any): any {
|
|
228
229
|
const result = {};
|
|
@@ -8,7 +8,11 @@ const BLACK_EQUAL_LIST = [
|
|
|
8
8
|
'/config.json',
|
|
9
9
|
'/backend/.env',
|
|
10
10
|
'/.env',
|
|
11
|
+
'/.env.dev',
|
|
12
|
+
'/.env.prod',
|
|
11
13
|
'/.env.local',
|
|
14
|
+
'/.env.staging',
|
|
15
|
+
'/.env.example',
|
|
12
16
|
'/.env.production',
|
|
13
17
|
'/.env.development',
|
|
14
18
|
'/application.yml',
|
|
@@ -18,6 +22,7 @@ const BLACK_EQUAL_LIST = [
|
|
|
18
22
|
'/config.yml',
|
|
19
23
|
'/db.ini',
|
|
20
24
|
'/database.yml',
|
|
25
|
+
'/api/.env',
|
|
21
26
|
// 安全相关文件
|
|
22
27
|
'/.well-known/security.txt',
|
|
23
28
|
'/security.txt',
|
|
@@ -163,7 +168,7 @@ const SUSPICIOUS_USER_AGENTS = [
|
|
|
163
168
|
|
|
164
169
|
/**
|
|
165
170
|
* 安全防护中间件 - 黑名单路径拦截
|
|
166
|
-
*
|
|
171
|
+
*
|
|
167
172
|
* 核心职责:
|
|
168
173
|
* 1. 防御恶意爬虫:拦截常见的配置文件探测请求(.env、config.json等)
|
|
169
174
|
* 2. 防御漏洞扫描:阻止安全扫描工具对敏感目录的探测(.git、.aws等)
|
|
@@ -171,13 +176,13 @@ const SUSPICIOUS_USER_AGENTS = [
|
|
|
171
176
|
* 4. 防御路径遍历:检测并阻止 ../ 等路径遍历攻击尝试
|
|
172
177
|
* 5. 识别攻击工具:检测User-Agent中的sqlmap、nikto等渗透测试工具
|
|
173
178
|
* 6. 性能优化:提前拦截无效请求,避免进入业务逻辑层消耗资源
|
|
174
|
-
*
|
|
179
|
+
*
|
|
175
180
|
* 应用场景:
|
|
176
181
|
* - 公网暴露的Web应用:防止自动化工具批量扫描敏感路径
|
|
177
182
|
* - 云原生部署环境:保护云服务配置文件不被探测(.aws、.env等)
|
|
178
183
|
* - 多技术栈迁移:新系统可能残留旧技术栈痕迹,统一拦截避免误暴露
|
|
179
184
|
* - 安全合规要求:主动防御已知的常见攻击路径,降低安全风险
|
|
180
|
-
*
|
|
185
|
+
*
|
|
181
186
|
* 拦截策略:
|
|
182
187
|
* - User-Agent检测:识别常见扫描工具(sqlmap, nikto, nmap, metasploit等)
|
|
183
188
|
* - 路径遍历检测:阻止 ../, ..\, %2e%2e%2f 等编码后的遍历尝试
|
|
@@ -185,13 +190,13 @@ const SUSPICIOUS_USER_AGENTS = [
|
|
|
185
190
|
* - 前缀匹配:.git/、.svn/、.aws/等版本控制和云服务目录
|
|
186
191
|
* - 模糊匹配:wp-admin、wp-content等WordPress相关路径
|
|
187
192
|
* - 后缀匹配:.php/.jsp/.asp等脚本文件、.bak/.sql等敏感文件
|
|
188
|
-
*
|
|
193
|
+
*
|
|
189
194
|
* 防御能力增强:
|
|
190
195
|
* - 支持30+种敏感配置文件拦截
|
|
191
196
|
* - 支持50+种敏感目录前缀拦截
|
|
192
197
|
* - 支持40+种危险文件后缀拦截
|
|
193
198
|
* - 支持10+种常见攻击工具识别
|
|
194
|
-
*
|
|
199
|
+
*
|
|
195
200
|
* 注意事项:
|
|
196
201
|
* 此中间件拦截的路径在实际项目中并不存在,仅为安全防护层。
|
|
197
202
|
* 被拦截的请求会立即返回404,不会进入后续业务逻辑。
|
|
@@ -288,8 +293,13 @@ export class ForbiddenMiddleware implements IMiddleware<Context, NextFunction> {
|
|
|
288
293
|
* 检查是否包含路径遍历政击特征
|
|
289
294
|
*/
|
|
290
295
|
private hasPathTraversal(path: string): boolean {
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
try {
|
|
297
|
+
const decodedPath = decodeURIComponent(path);
|
|
298
|
+
return SUSPICIOUS_QUERY_PATTERNS.some(pattern => decodedPath.includes(pattern));
|
|
299
|
+
} catch (e) {
|
|
300
|
+
// URL解码失败(如包含非法编码字符),直接判定为可疑请求
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
293
303
|
}
|
|
294
304
|
|
|
295
305
|
/**
|
|
@@ -178,6 +178,9 @@ export class AuthService extends BaseService {
|
|
|
178
178
|
if (!isEnableSuperAdmin(this.ctx)) {
|
|
179
179
|
return null;
|
|
180
180
|
}
|
|
181
|
+
if(!Array.isArray(superAdminList)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
181
184
|
return superAdminList.find((s)=>{
|
|
182
185
|
return s.login_name === loginName;
|
|
183
186
|
})
|
|
@@ -112,6 +112,11 @@ export class BaseService {
|
|
|
112
112
|
this.getContextLogger().debug(msg, ...args);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
protected isProdEnv(): boolean {
|
|
116
|
+
const env = this.ctx.app.env;
|
|
117
|
+
return env === 'prod' || env === 'production';
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
protected isLocalEnv(): boolean {
|
|
116
121
|
return isLocalEnv(this.ctx);
|
|
117
122
|
}
|
|
@@ -95,11 +95,12 @@ export class CrudStdService extends ApiBaseService {
|
|
|
95
95
|
public async executeStdQuery(stdAction: ICrudStdActionParams, params: IRequestModelCrudProExt, sqlSimpleName: KeysOfSimpleSQL): Promise<ExecuteContext> {
|
|
96
96
|
const appCode = stdAction.appCode;
|
|
97
97
|
const appInfo = await this.getParsedCrudStdAppForSettingKey(stdAction);
|
|
98
|
-
const stdCrudCfgObj = appInfo.stdCrudCfgObj;
|
|
99
98
|
|
|
100
99
|
if (!appInfo || appInfo.status !== 1) {
|
|
101
100
|
throw new BizException('应用不存在或已下线:' + appCode);
|
|
102
101
|
}
|
|
102
|
+
|
|
103
|
+
const stdCrudCfgObj = appInfo.stdCrudCfgObj;
|
|
103
104
|
|
|
104
105
|
//删除策略
|
|
105
106
|
const deleteStrategy = _.get(stdCrudCfgObj, 'othersSetting.values.deleteStrategy');
|
|
@@ -46,7 +46,8 @@ export class ProxyApiService extends ApiBaseService {
|
|
|
46
46
|
if (!cfgInfo) {
|
|
47
47
|
throw new BizException('路径配置没有匹配到', Exceptions.CFG_NOT_FOUND);
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
const proxyApiEntity: IProxyApiEntity = JSON.parse(JSON.stringify(cfgInfo));
|
|
50
|
+
return this._handleProxyRequestForCfg(proxyApiEntity, upstream_path);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
@@ -286,6 +287,7 @@ export class ProxyApiService extends ApiBaseService {
|
|
|
286
287
|
const workbenchCode = this.ctx.workbenchInfo?.workbench_code;
|
|
287
288
|
const userBasicInfo: any = { isLogin };
|
|
288
289
|
if (sessionInfo && isLogin) {
|
|
290
|
+
userBasicInfo.nickName = sessionInfo.nickName;
|
|
289
291
|
userBasicInfo.loginName = sessionInfo.loginName;
|
|
290
292
|
userBasicInfo.sessionId = sessionInfo.sessionId;
|
|
291
293
|
userBasicInfo.accountId = sessionInfo.accountId;
|
|
@@ -296,6 +298,25 @@ export class ProxyApiService extends ApiBaseService {
|
|
|
296
298
|
return toBase64(userBasicInfo);
|
|
297
299
|
}
|
|
298
300
|
|
|
301
|
+
// 传递用户基本信息和BizExt
|
|
302
|
+
if (proxyApiEntity.user_context === ProxyUserContextEnum.BASIC_INFO_BIZ_EXT) {
|
|
303
|
+
const isLogin = this.ctx.userSession.isLogin();
|
|
304
|
+
const sessionInfo = this.ctx.userSession.getSessionInfo();
|
|
305
|
+
const workbenchCode = this.ctx.workbenchInfo?.workbench_code;
|
|
306
|
+
const userBasicInfo: any = { isLogin };
|
|
307
|
+
if (sessionInfo && isLogin) {
|
|
308
|
+
userBasicInfo.nickName = sessionInfo.nickName;
|
|
309
|
+
userBasicInfo.loginName = sessionInfo.loginName;
|
|
310
|
+
userBasicInfo.sessionId = sessionInfo.sessionId;
|
|
311
|
+
userBasicInfo.accountId = sessionInfo.accountId;
|
|
312
|
+
userBasicInfo.workbenchCode = sessionInfo.workbenchCode;
|
|
313
|
+
userBasicInfo.accountType = sessionInfo.accountType;
|
|
314
|
+
userBasicInfo.bizExt = sessionInfo.bizExt; // 多了一个bizExt
|
|
315
|
+
}
|
|
316
|
+
userBasicInfo.workbenchCode = workbenchCode;
|
|
317
|
+
return toBase64(userBasicInfo);
|
|
318
|
+
}
|
|
319
|
+
|
|
299
320
|
return null;
|
|
300
321
|
}
|
|
301
322
|
}
|
|
@@ -7,7 +7,7 @@ interface IUpstreamItemExt extends IUpstreamItem {
|
|
|
7
7
|
|
|
8
8
|
class WeightedRoundRobin {
|
|
9
9
|
private readonly totalWeight: number;
|
|
10
|
-
private servers: IUpstreamItemExt[];
|
|
10
|
+
private readonly servers: IUpstreamItemExt[];
|
|
11
11
|
constructor(servers: IUpstreamItem[]) {
|
|
12
12
|
this.servers = servers.map(server => ({
|
|
13
13
|
...server,
|
|
@@ -34,6 +34,7 @@ class WeightedRoundRobin {
|
|
|
34
34
|
// 2. 被选中的服务器减去总权重(实现平滑分配)
|
|
35
35
|
if (selected) {
|
|
36
36
|
selected.currentWeight -= this.totalWeight;
|
|
37
|
+
selected.currentWeight = Math.max(selected.currentWeight, 0);
|
|
37
38
|
return selected;
|
|
38
39
|
}
|
|
39
40
|
|