midway-fatcms 0.0.12 → 0.0.15
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/controller/base/BaseApiController.d.ts +2 -0
- package/dist/controller/base/BaseApiController.js +5 -0
- package/dist/controller/gateway/DocGatewayController.js +7 -5
- package/dist/controller/gateway/PublicApiController.js +32 -3
- package/dist/controller/helpers.controller.d.ts +2 -0
- package/dist/controller/helpers.controller.js +17 -0
- package/dist/controller/home.controller.js +2 -1
- package/dist/controller/manage/DataDictManageApi.d.ts +2 -0
- package/dist/controller/manage/DataDictManageApi.js +41 -9
- package/dist/controller/manage/DocManageApi.js +2 -2
- package/dist/controller/myinfo/AuthController.js +17 -1
- package/dist/controller/render/AppRenderController.js +7 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/libs/crud-pro/CrudPro.d.ts +0 -1
- package/dist/libs/crud-pro/CrudPro.js +2 -11
- package/dist/libs/crud-pro/exceptions.d.ts +6 -0
- package/dist/libs/crud-pro/exceptions.js +6 -0
- package/dist/libs/crud-pro/utils/SqlErrorParseUtils.d.ts +22 -0
- package/dist/libs/crud-pro/utils/SqlErrorParseUtils.js +219 -0
- package/dist/libs/utils/functions.js +3 -0
- package/dist/models/SystemTables.d.ts +1 -0
- package/dist/models/SystemTables.js +1 -0
- package/dist/service/ActionLogService.d.ts +14 -0
- package/dist/service/ActionLogService.js +50 -0
- package/dist/service/anyapi/AnyApiService.js +1 -1
- package/dist/service/crudstd/CrudStdService.d.ts +1 -2
- package/dist/service/crudstd/CrudStdService.js +3 -4
- package/dist/service/curd/CurdMixByDictService.js +2 -1
- package/dist/service/curd/CurdMixBySysConfigService.js +1 -1
- package/dist/service/curd/CurdMixByWorkbenchService.js +1 -1
- package/dist/service/proxyapi/ProxyApiLoadService.js +3 -0
- package/package.json +1 -1
- package/src/controller/base/BaseApiController.ts +3 -1
- package/src/controller/gateway/DocGatewayController.ts +9 -5
- package/src/controller/gateway/PublicApiController.ts +39 -4
- package/src/controller/helpers.controller.ts +24 -2
- package/src/controller/home.controller.ts +3 -1
- package/src/controller/manage/DataDictManageApi.ts +48 -11
- package/src/controller/manage/DocManageApi.ts +2 -2
- package/src/controller/myinfo/AuthController.ts +23 -1
- package/src/controller/render/AppRenderController.ts +9 -7
- package/src/index.ts +1 -1
- package/src/libs/crud-pro/CrudPro.ts +2 -12
- package/src/libs/crud-pro/exceptions.ts +6 -0
- package/src/libs/crud-pro/utils/SqlErrorParseUtils.ts +236 -0
- package/src/libs/utils/functions.ts +3 -0
- package/src/models/SystemTables.ts +1 -2
- package/src/service/ActionLogService.ts +52 -0
- package/src/service/anyapi/AnyApiService.ts +2 -2
- package/src/service/crudstd/CrudStdService.ts +4 -5
- package/src/service/curd/CurdMixByDictService.ts +2 -1
- package/src/service/curd/CurdMixBySysConfigService.ts +1 -1
- package/src/service/curd/CurdMixByWorkbenchService.ts +1 -1
- package/src/service/proxyapi/ProxyApiLoadService.ts +4 -1
|
@@ -6,7 +6,9 @@ import { checkPermission } from '@/middleware/permission.middleware';
|
|
|
6
6
|
import { refreshCache } from '@/middleware/cacherefresh.middleware';
|
|
7
7
|
import { CacheNameEnum } from '@/models/bizmodels';
|
|
8
8
|
import { SystemFuncCode } from '@/models/SystemPerm';
|
|
9
|
-
import {CommonResult} from "@/libs/utils/common-dto";
|
|
9
|
+
import { CommonResult } from "@/libs/utils/common-dto";
|
|
10
|
+
import { CommonException, Exceptions } from '@/libs/crud-pro/exceptions';
|
|
11
|
+
import { GLOBAL_STATIC_CONFIG } from '@/libs/global-config/global-config';
|
|
10
12
|
|
|
11
13
|
@Controller('/ns/api/manage/dataDict', { middleware: [checkPermission(SystemFuncCode.DataDictMangeRead)] })
|
|
12
14
|
export class DataDictManageApi extends BaseApiController {
|
|
@@ -29,12 +31,47 @@ export class DataDictManageApi extends BaseApiController {
|
|
|
29
31
|
|
|
30
32
|
@Post('/createDataDict', { middleware: [checkPermission(SystemFuncCode.DataDictMangeWrite), refreshCache(CacheNameEnum.GetDataDictItemsByDictCode)] })
|
|
31
33
|
async createDataDict() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const body = this.ctx.request.body as any;
|
|
35
|
+
const dict_code = body?.data?.dict_code;
|
|
36
|
+
try {
|
|
37
|
+
return await this.executeSysSimpleSQL('sys_data_dict', KeysOfSimpleSQL.SIMPLE_INSERT, {
|
|
38
|
+
enableSoftDelete: true,
|
|
39
|
+
validateCfg: {
|
|
40
|
+
'data.dict_code': [KeysOfValidators.REQUIRED],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_DUP_ENTRY && dict_code) {
|
|
45
|
+
const isSoftDeleted = await this.isSoftDeletedDictCode(dict_code);
|
|
46
|
+
if (isSoftDeleted) {
|
|
47
|
+
throw new CommonException(
|
|
48
|
+
Exceptions.RUN_SQL_EXCEPTION_ER_DUP_ENTRY,
|
|
49
|
+
'该 dict_code 对应的数据曾被软删除,无法直接创建。请联系 DBA 处理,或更换 dict_code 后重试。'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw e;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 查询 dict_code 是否仅被软删除记录占用(不含 deleted_at=0 的活跃记录) */
|
|
58
|
+
private async isSoftDeletedDictCode(dict_code: string): Promise<boolean> {
|
|
59
|
+
const { SystemDbName, SystemDbType } = GLOBAL_STATIC_CONFIG.getConfig();
|
|
60
|
+
const result = await this.curdMixService.executeCrudByCfg(
|
|
61
|
+
{ condition: { dict_code } },
|
|
62
|
+
{
|
|
63
|
+
sqlTable: 'sys_data_dict',
|
|
64
|
+
sqlSimpleName: KeysOfSimpleSQL.SIMPLE_QUERY_ONE,
|
|
65
|
+
sqlDatabase: SystemDbName,
|
|
66
|
+
sqlDbType: SystemDbType,
|
|
67
|
+
enableSoftDelete: false,
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
const row = result.getOneObj();
|
|
71
|
+
if (!row) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return Number(row.deleted_at) !== 0;
|
|
38
75
|
}
|
|
39
76
|
|
|
40
77
|
@Post('/updateDataDict', { middleware: [checkPermission(SystemFuncCode.DataDictMangeWrite), refreshCache(CacheNameEnum.GetDataDictItemsByDictCode)] })
|
|
@@ -42,7 +79,7 @@ export class DataDictManageApi extends BaseApiController {
|
|
|
42
79
|
return this.executeSysSimpleSQL('sys_data_dict', KeysOfSimpleSQL.SIMPLE_UPDATE, {
|
|
43
80
|
enableSoftDelete: true,
|
|
44
81
|
validateCfg: {
|
|
45
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
82
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
46
83
|
},
|
|
47
84
|
});
|
|
48
85
|
}
|
|
@@ -57,7 +94,7 @@ export class DataDictManageApi extends BaseApiController {
|
|
|
57
94
|
return this.executeSysSimpleSQL('sys_data_dict', KeysOfSimpleSQL.SIMPLE_DELETE, {
|
|
58
95
|
enableSoftDelete: true,
|
|
59
96
|
validateCfg: {
|
|
60
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
97
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
61
98
|
'condition.dict_code': [KeysOfValidators.REQUIRED],
|
|
62
99
|
},
|
|
63
100
|
});
|
|
@@ -94,7 +131,7 @@ export class DataDictManageApi extends BaseApiController {
|
|
|
94
131
|
return this.executeSysSimpleSQL('sys_data_dict_item', KeysOfSimpleSQL.SIMPLE_UPDATE, {
|
|
95
132
|
enableSoftDelete: true,
|
|
96
133
|
validateCfg: {
|
|
97
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
134
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
98
135
|
},
|
|
99
136
|
});
|
|
100
137
|
}
|
|
@@ -104,7 +141,7 @@ export class DataDictManageApi extends BaseApiController {
|
|
|
104
141
|
return this.executeSysSimpleSQL('sys_data_dict_item', KeysOfSimpleSQL.SIMPLE_DELETE, {
|
|
105
142
|
enableSoftDelete: true,
|
|
106
143
|
validateCfg: {
|
|
107
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
144
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
108
145
|
'condition.dict_code': [KeysOfValidators.REQUIRED],
|
|
109
146
|
},
|
|
110
147
|
});
|
|
@@ -80,7 +80,7 @@ export class DocManageApi extends BaseApiController {
|
|
|
80
80
|
return this.executeSysSimpleSQL('sys_doc', KeysOfSimpleSQL.SIMPLE_UPDATE, {
|
|
81
81
|
enableSoftDelete: true,
|
|
82
82
|
validateCfg: {
|
|
83
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
83
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
84
84
|
},
|
|
85
85
|
updateCfg: {
|
|
86
86
|
...updateCfg,
|
|
@@ -94,7 +94,7 @@ export class DocManageApi extends BaseApiController {
|
|
|
94
94
|
return this.executeSysSimpleSQL('sys_doc', KeysOfSimpleSQL.SIMPLE_DELETE, {
|
|
95
95
|
enableSoftDelete: true,
|
|
96
96
|
validateCfg: {
|
|
97
|
-
'condition.id': [KeysOfValidators.REQUIRED
|
|
97
|
+
'condition.id': [KeysOfValidators.REQUIRED],
|
|
98
98
|
},
|
|
99
99
|
updateCfg: {
|
|
100
100
|
'condition.workbench_code': { contextAsString: CTX_WORKBENCH_CODE },
|
|
@@ -60,6 +60,15 @@ export class AuthController extends BaseApiController {
|
|
|
60
60
|
|
|
61
61
|
const userSessionInfo = await this.authService.createUserSession(loginName, workbench.workbench_code);
|
|
62
62
|
this.ctx.cookies.set(SESSION_ID_KEY, userSessionInfo.sessionId, sessionCookieCfg);
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
await this.actionLogService.insertActionLogTable({
|
|
66
|
+
actionModule: '用户账号',
|
|
67
|
+
actionName: '登录',
|
|
68
|
+
actionMessage: '登录成功',
|
|
69
|
+
actionTarget: `${userSessionInfo?.nickName}(${userSessionInfo?.accountId})`
|
|
70
|
+
});
|
|
71
|
+
|
|
63
72
|
return CommonResult.successMsg('登录成功', removePrivateField(userSessionInfo));
|
|
64
73
|
}
|
|
65
74
|
|
|
@@ -112,7 +121,20 @@ export class AuthController extends BaseApiController {
|
|
|
112
121
|
return CommonResult.errorRes('开放体验账号不支持修改密码');
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
const res = await this.userAccountService.chgUserPassword(loginName, unsaltedPwd);
|
|
127
|
+
|
|
128
|
+
if (res.success) {
|
|
129
|
+
await this.actionLogService.insertActionLogTable({
|
|
130
|
+
actionModule: '用户账号',
|
|
131
|
+
actionName: '修改密码',
|
|
132
|
+
actionMessage: '修改密码成功',
|
|
133
|
+
actionTarget: this.ctx.userSession.getSessionInfo()?.nickName
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return res;
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
/**
|
|
@@ -7,6 +7,7 @@ import { privateAES } from '@/libs/utils/crypto-utils';
|
|
|
7
7
|
import { SysAppService } from "@/service/SysAppService";
|
|
8
8
|
import { ISessionInfo } from '@/models/userSession';
|
|
9
9
|
import { AuthService } from '@/service/AuthService';
|
|
10
|
+
import { isEntityOK } from '@/libs/utils/functions';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 渲染独立外部应用
|
|
@@ -33,14 +34,9 @@ export class AppRenderController extends BaseApiController {
|
|
|
33
34
|
|
|
34
35
|
const appInfo = await this.sysAppService.getSysAppOne(appCode);
|
|
35
36
|
|
|
36
|
-
if (!appInfo) {
|
|
37
|
+
if (!isEntityOK(appInfo)) {
|
|
37
38
|
this.ctx.status = 404;
|
|
38
|
-
return this.ctx.render('404_app', { appCode, errorMsg: '
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (appInfo.status !== 1) {
|
|
42
|
-
this.ctx.status = 404;
|
|
43
|
-
return this.ctx.render('404_app', { appCode, errorMsg: ' 应用已下线' });
|
|
39
|
+
return this.ctx.render('404_app', { appCode, errorMsg: '应用不存在或已下线' });
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
const isSupportWorkbench = await this.isSupportCurrentWorkbench(appInfo);
|
|
@@ -51,6 +47,12 @@ export class AppRenderController extends BaseApiController {
|
|
|
51
47
|
|
|
52
48
|
const workbenchInfo = await this.workbenchService.getCurrentHostWorkbenchInfo();
|
|
53
49
|
|
|
50
|
+
if (!isEntityOK(workbenchInfo)) {
|
|
51
|
+
this.ctx.status = 404;
|
|
52
|
+
return this.ctx.render('404_app', { appCode, errorMsg: '站点不存在或已下线' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
54
56
|
const html_content = appInfo.html_content || '<b style="color: red">错误:未配置HTML模版,请检查!!</b>';
|
|
55
57
|
const userInfo = await this.getUserAndRefreshSessionInfo();
|
|
56
58
|
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,6 @@ export * from './controller/gateway/AsyncTaskController';
|
|
|
12
12
|
export * from './controller/helpers.controller';
|
|
13
13
|
export * from './controller/home.controller';
|
|
14
14
|
export * from './controller/manage/AnyApiMangeApi';
|
|
15
|
-
export * from './controller/manage/AppLogMangeApi';
|
|
16
15
|
export * from './controller/manage/AppMangeApi';
|
|
17
16
|
export * from './controller/manage/AppPageMangeApi';
|
|
18
17
|
export * from './controller/manage/AppSchemaHistoryApi';
|
|
@@ -49,6 +48,7 @@ export * from './middleware/tx.middleware';
|
|
|
49
48
|
export * from './middleware/permission.middleware';
|
|
50
49
|
export * from './middleware/redislock.middleware';
|
|
51
50
|
export * from './service/AuthService';
|
|
51
|
+
export * from './service/ActionLogService';
|
|
52
52
|
export * from './service/EnumInfoService';
|
|
53
53
|
export * from './service/FileCenterService';
|
|
54
54
|
export * from './service/SysConfigService';
|
|
@@ -5,6 +5,7 @@ import { CurdProServiceHub } from './services/CurdProServiceHub';
|
|
|
5
5
|
import { CommonException, Exceptions } from './exceptions';
|
|
6
6
|
import { RequestCfgModel } from './models/RequestCfgModel';
|
|
7
7
|
import { MixinUtils } from './utils/MixinUtils';
|
|
8
|
+
import { SqlErrorParseUtils } from './utils/SqlErrorParseUtils';
|
|
8
9
|
import { Transaction } from './models/Transaction';
|
|
9
10
|
import { IExecuteContextFunc } from './models/ExecuteContextFunc';
|
|
10
11
|
import { KeysOfSimpleSQL } from './models/keys';
|
|
@@ -417,21 +418,10 @@ class CrudPro {
|
|
|
417
418
|
const message = MixinUtils.getErrorMessage(e);
|
|
418
419
|
const logger = this.executeContext.getLogger();
|
|
419
420
|
logger.error('RUN_SQL_EXCEPTION', message);
|
|
420
|
-
throw
|
|
421
|
+
throw SqlErrorParseUtils.parseRunSqlException(e || {});
|
|
421
422
|
}
|
|
422
423
|
}
|
|
423
424
|
|
|
424
|
-
private parseRunSqlException(e: any): CommonException {
|
|
425
|
-
const sqlMessage = '' + e.sqlMessage;
|
|
426
|
-
if (e.code === 'ER_DUP_ENTRY' && sqlMessage.startsWith('Duplicate entry')) {
|
|
427
|
-
return new CommonException(Exceptions.RUN_SQL_EXCEPTION_ER_DUP_ENTRY, '此对象实体已存在,不能创建重复对象');
|
|
428
|
-
}
|
|
429
|
-
if (e.code === 'ER_NO_SUCH_TABLE') {
|
|
430
|
-
return new CommonException(Exceptions.RUN_SQL_EXCEPTION_ER_NO_SUCH_TABLE, '查询的表不存在:' + sqlMessage);
|
|
431
|
-
}
|
|
432
|
-
return e;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
425
|
private async afterExecuteSQLList() {
|
|
436
426
|
return this.executeContext.contextFunc.afterExecuteSQLList();
|
|
437
427
|
}
|
|
@@ -86,6 +86,12 @@ export enum Exceptions {
|
|
|
86
86
|
RUN_SQL_EXCEPTION = 'RUN_SQL_EXCEPTION',
|
|
87
87
|
RUN_SQL_EXCEPTION_ER_DUP_ENTRY = 'RUN_SQL_EXCEPTION_ER_DUP_ENTRY',
|
|
88
88
|
RUN_SQL_EXCEPTION_ER_NO_SUCH_TABLE = 'RUN_SQL_EXCEPTION_ER_NO_SUCH_TABLE',
|
|
89
|
+
RUN_SQL_EXCEPTION_ER_FK_PARENT_REFERENCED = 'RUN_SQL_EXCEPTION_ER_FK_PARENT_REFERENCED',
|
|
90
|
+
RUN_SQL_EXCEPTION_ER_FK_PARENT_NOT_FOUND = 'RUN_SQL_EXCEPTION_ER_FK_PARENT_NOT_FOUND',
|
|
91
|
+
RUN_SQL_EXCEPTION_ER_NOT_NULL = 'RUN_SQL_EXCEPTION_ER_NOT_NULL',
|
|
92
|
+
RUN_SQL_EXCEPTION_ER_DATA_TOO_LONG = 'RUN_SQL_EXCEPTION_ER_DATA_TOO_LONG',
|
|
93
|
+
RUN_SQL_EXCEPTION_ER_BAD_FIELD = 'RUN_SQL_EXCEPTION_ER_BAD_FIELD',
|
|
94
|
+
RUN_SQL_EXCEPTION_ER_DEADLOCK = 'RUN_SQL_EXCEPTION_ER_DEADLOCK',
|
|
89
95
|
RUN_IS_NEED_EXECUTE_ERR = 'RUN_IS_NEED_EXECUTE_ERR',
|
|
90
96
|
RUN_PICK_ERR_VISITOR_NULL = 'RUN_PICK_ERR_VISITOR_NULL',
|
|
91
97
|
RUN_PICK_ERR_VISITOR_FIELD = 'RUN_PICK_ERR_VISITOR_FIELD',
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { CommonException, Exceptions } from '../exceptions';
|
|
2
|
+
|
|
3
|
+
interface ISqlErrorRule {
|
|
4
|
+
code: string;
|
|
5
|
+
match: (e: any, message: string) => boolean;
|
|
6
|
+
message: string | ((message: string) => string);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SqlErrorParseUtils = {
|
|
10
|
+
/**
|
|
11
|
+
* 从驱动原始错误中提取可读 SQL 错误信息(兼容 MySQL / PostgreSQL / SQL Server)
|
|
12
|
+
*/
|
|
13
|
+
getSqlErrorMessage(e: any): string {
|
|
14
|
+
return '' + (e?.sqlMessage || e?.message || '');
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
isDuplicateEntryError(e: any, sqlMessage?: string): boolean {
|
|
18
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
19
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_DUP_ENTRY) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// MySQL
|
|
23
|
+
if (e?.code === 'ER_DUP_ENTRY' || e?.errno === 1062) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
// PostgreSQL
|
|
27
|
+
if (e?.code === '23505') {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// SQL Server
|
|
31
|
+
if (e?.number === 2627 || e?.number === 2601) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return /duplicate key/i.test(message)
|
|
35
|
+
|| /unique constraint/i.test(message)
|
|
36
|
+
|| /unique key constraint/i.test(message)
|
|
37
|
+
|| message.startsWith('Duplicate entry');
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
isNoSuchTableError(e: any, sqlMessage?: string): boolean {
|
|
41
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
42
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_NO_SUCH_TABLE) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// MySQL
|
|
46
|
+
if (e?.code === 'ER_NO_SUCH_TABLE' || e?.errno === 1146) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// PostgreSQL
|
|
50
|
+
if (e?.code === '42P01') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
// SQL Server
|
|
54
|
+
if (e?.number === 208) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return /doesn't exist/i.test(message)
|
|
58
|
+
|| /does not exist/i.test(message)
|
|
59
|
+
|| /invalid object name/i.test(message)
|
|
60
|
+
|| /no such table/i.test(message);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/** 外键约束:父记录被引用,无法删除或修改 */
|
|
64
|
+
isForeignKeyParentReferencedError(e: any, sqlMessage?: string): boolean {
|
|
65
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
66
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_FK_PARENT_REFERENCED) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (e?.code === 'ER_ROW_IS_REFERENCED_2' || e?.errno === 1451) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return /Cannot delete or update a parent row/i.test(message)
|
|
73
|
+
|| /update or delete on table .* violates foreign key constraint/i.test(message)
|
|
74
|
+
|| /DELETE statement conflicted with the REFERENCE constraint/i.test(message)
|
|
75
|
+
|| /UPDATE statement conflicted with the REFERENCE constraint/i.test(message);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/** 外键约束:关联的主数据不存在 */
|
|
79
|
+
isForeignKeyParentNotFoundError(e: any, sqlMessage?: string): boolean {
|
|
80
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
81
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_FK_PARENT_NOT_FOUND) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (e?.code === 'ER_NO_REFERENCED_ROW_2' || e?.errno === 1452) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// PostgreSQL / SQL Server 与父记录被引用共用错误码,需靠消息区分
|
|
88
|
+
if (e?.code === '23503' || e?.number === 547) {
|
|
89
|
+
return /insert or update on table/i.test(message)
|
|
90
|
+
|| /INSERT statement conflicted with the FOREIGN KEY constraint/i.test(message)
|
|
91
|
+
|| /INSERT statement conflicted with the REFERENCE constraint/i.test(message)
|
|
92
|
+
|| /violates foreign key constraint/i.test(message);
|
|
93
|
+
}
|
|
94
|
+
return /Cannot add or update a child row/i.test(message)
|
|
95
|
+
|| /insert or update on table .* violates foreign key constraint/i.test(message);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
isNotNullViolationError(e: any, sqlMessage?: string): boolean {
|
|
99
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
100
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_NOT_NULL) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (e?.code === 'ER_BAD_NULL_ERROR' || e?.errno === 1048) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (e?.code === '23502') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (e?.number === 515) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return /cannot be null/i.test(message)
|
|
113
|
+
|| /null value in column/i.test(message)
|
|
114
|
+
|| /violates not-null constraint/i.test(message)
|
|
115
|
+
|| /Cannot insert the value NULL into column/i.test(message);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
isDataTooLongError(e: any, sqlMessage?: string): boolean {
|
|
119
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
120
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_DATA_TOO_LONG) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (e?.code === 'ER_DATA_TOO_LONG' || e?.errno === 1406) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (e?.code === '22001') {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (e?.number === 2628 || e?.number === 8152) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return /data too long/i.test(message)
|
|
133
|
+
|| /value too long/i.test(message)
|
|
134
|
+
|| /string data, right truncation/i.test(message)
|
|
135
|
+
|| /would be truncated/i.test(message);
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
isBadFieldError(e: any, sqlMessage?: string): boolean {
|
|
139
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
140
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_BAD_FIELD) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (e?.code === 'ER_BAD_FIELD_ERROR' || e?.errno === 1054) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (e?.code === '42703') {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (e?.number === 207) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return /unknown column/i.test(message)
|
|
153
|
+
|| /Invalid column name/i.test(message);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
isDeadlockError(e: any, sqlMessage?: string): boolean {
|
|
157
|
+
const message = sqlMessage ?? SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
158
|
+
if (e?.code === Exceptions.RUN_SQL_EXCEPTION_ER_DEADLOCK) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (e?.code === 'ER_LOCK_DEADLOCK' || e?.errno === 1213) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if (e?.code === '40P01') {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (e?.number === 1205) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return /deadlock/i.test(message);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 将数据库驱动原始错误规范化为 CommonException
|
|
175
|
+
*/
|
|
176
|
+
parseRunSqlException(e: any): CommonException {
|
|
177
|
+
if (e instanceof CommonException) {
|
|
178
|
+
return e;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const sqlMessage = SqlErrorParseUtils.getSqlErrorMessage(e);
|
|
182
|
+
const rules: ISqlErrorRule[] = [
|
|
183
|
+
{
|
|
184
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_DUP_ENTRY,
|
|
185
|
+
match: SqlErrorParseUtils.isDuplicateEntryError,
|
|
186
|
+
message: '此条数据已存在,不能重复创建',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_NO_SUCH_TABLE,
|
|
190
|
+
match: SqlErrorParseUtils.isNoSuchTableError,
|
|
191
|
+
message: (message) => '查询的表不存在:' + message,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_FK_PARENT_REFERENCED,
|
|
195
|
+
match: SqlErrorParseUtils.isForeignKeyParentReferencedError,
|
|
196
|
+
message: '无法删除或修改:该数据已被其他记录引用,请先解除关联后再试',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_FK_PARENT_NOT_FOUND,
|
|
200
|
+
match: SqlErrorParseUtils.isForeignKeyParentNotFoundError,
|
|
201
|
+
message: '无法保存:所关联的数据不存在或已被删除',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_NOT_NULL,
|
|
205
|
+
match: SqlErrorParseUtils.isNotNullViolationError,
|
|
206
|
+
message: '操作失败:必填字段不能为空',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_DATA_TOO_LONG,
|
|
210
|
+
match: SqlErrorParseUtils.isDataTooLongError,
|
|
211
|
+
message: '操作失败:输入内容超出字段长度限制',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_BAD_FIELD,
|
|
215
|
+
match: SqlErrorParseUtils.isBadFieldError,
|
|
216
|
+
message: '操作失败:字段配置有误,请联系管理员检查表结构或接口配置',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
code: Exceptions.RUN_SQL_EXCEPTION_ER_DEADLOCK,
|
|
220
|
+
match: SqlErrorParseUtils.isDeadlockError,
|
|
221
|
+
message: '操作失败:系统繁忙,请稍后重试',
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
for (const rule of rules) {
|
|
226
|
+
if (rule.match(e, sqlMessage)) {
|
|
227
|
+
const message = typeof rule.message === 'function' ? rule.message(sqlMessage) : rule.message;
|
|
228
|
+
return new CommonException(rule.code, message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return e;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export { SqlErrorParseUtils };
|
|
@@ -130,6 +130,9 @@ function isEntityDeleted(entity: any): boolean {
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
function isEntityOK(entity: any): boolean {
|
|
133
|
+
if(!entity) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
133
136
|
const isOK = entity?.status === 1 || entity?.status === '1';
|
|
134
137
|
return isOK && !isEntityDeleted(entity);
|
|
135
138
|
}
|
|
@@ -22,8 +22,7 @@ export const SystemTables = {
|
|
|
22
22
|
sys_visit_stats: 'sys_visit_stats',
|
|
23
23
|
sys_async_tasks: 'sys_async_tasks',
|
|
24
24
|
sys_config_changelog: 'sys_config_changelog',
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
sys_action_log: 'sys_action_log',
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
export const SystemDevOpsWorkbench = 'devops';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Inject, Provide } from '@midwayjs/core';
|
|
2
|
+
import { CurdMixService } from './curd/CurdMixService';
|
|
3
|
+
import { SystemTables } from '@/models/SystemTables';
|
|
4
|
+
import { GLOBAL_STATIC_CONFIG } from '@/libs/global-config/global-config';
|
|
5
|
+
import { BaseService } from "@/service/base/BaseService";
|
|
6
|
+
import { DateTimeUtils } from '@/libs/crud-pro/utils/DateTimeUtils';
|
|
7
|
+
import { CrudWriteResult } from '@/libs/crud-pro/models/CrudResult';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export interface IActionLoginInfo {
|
|
11
|
+
actionModule: string;
|
|
12
|
+
actionName: string;
|
|
13
|
+
actionMessage: string;
|
|
14
|
+
actionTarget: string;
|
|
15
|
+
createdBy?: string;
|
|
16
|
+
modifiedBy?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Provide()
|
|
20
|
+
export class ActionLogService extends BaseService {
|
|
21
|
+
|
|
22
|
+
@Inject()
|
|
23
|
+
private curdMixService: CurdMixService;
|
|
24
|
+
|
|
25
|
+
public async insertActionLogTable(logInfo: IActionLoginInfo): Promise<CrudWriteResult> {
|
|
26
|
+
|
|
27
|
+
const { SystemDbName, SystemDbType } = GLOBAL_STATIC_CONFIG.getConfig();
|
|
28
|
+
const actionTable = this.curdMixService.getQuickCrud({
|
|
29
|
+
sqlDatabase: SystemDbName,
|
|
30
|
+
sqlDbType: SystemDbType,
|
|
31
|
+
sqlTable: SystemTables.sys_action_log,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const sessionInfo = this.ctx.userSession.getSessionInfo();
|
|
35
|
+
return actionTable.insert({
|
|
36
|
+
data: {
|
|
37
|
+
action_module: logInfo.actionModule,
|
|
38
|
+
action_name: logInfo.actionName,
|
|
39
|
+
action_message: logInfo.actionMessage,
|
|
40
|
+
created_by: logInfo.createdBy || sessionInfo?.accountId || '',
|
|
41
|
+
created_at: DateTimeUtils.getCurrentTimeString(),
|
|
42
|
+
modified_at: DateTimeUtils.getCurrentTimeString(),
|
|
43
|
+
modified_by: logInfo.modifiedBy || sessionInfo?.accountId || '',
|
|
44
|
+
status: 1,
|
|
45
|
+
action_target: logInfo.actionTarget
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
}
|
|
@@ -7,7 +7,7 @@ import { BizException } from '@/models/devops';
|
|
|
7
7
|
import { CurdMixService } from '../curd/CurdMixService';
|
|
8
8
|
import { ISysAnyApiEntity } from '@/models/SystemEntities';
|
|
9
9
|
import { AnyApiSandboxService, IRunInSandboxParams } from './AnyApiSandboxService';
|
|
10
|
-
import { parseJsonObject } from '@/libs/utils/functions';
|
|
10
|
+
import { isEntityOK, parseJsonObject } from '@/libs/utils/functions';
|
|
11
11
|
import * as _ from 'lodash';
|
|
12
12
|
import { MixinUtils } from '@/libs/crud-pro/utils/MixinUtils';
|
|
13
13
|
import { WorkbenchService } from '../WorkbenchService';
|
|
@@ -37,7 +37,7 @@ export class AnyApiService extends ApiBaseService {
|
|
|
37
37
|
|
|
38
38
|
async executeAnyApiMethod(methodCode: string, headers: any, body: any, query: any) {
|
|
39
39
|
const anyApi = await this.getAnyApiMethod(methodCode);
|
|
40
|
-
if (!anyApi
|
|
40
|
+
if (!isEntityOK(anyApi)) {
|
|
41
41
|
throw new BizException('接口不存在或已下线:' + methodCode);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -6,7 +6,7 @@ import { Context } from '@midwayjs/koa';
|
|
|
6
6
|
import { CurdMixService } from '../curd/CurdMixService';
|
|
7
7
|
import { IRequestCfgModel, IRequestModel } from '@/libs/crud-pro/interfaces';
|
|
8
8
|
import { KeysOfAuthType, KeysOfSimpleSQL, SqlDbType } from '@/libs/crud-pro/models/keys';
|
|
9
|
-
import { parseJsonObject } from '@/libs/utils/functions';
|
|
9
|
+
import { isEntityOK, parseJsonObject } from '@/libs/utils/functions';
|
|
10
10
|
import { ICrudStdAppInfo, ICrudStdAppInfoForSettingKey, IRequestCfgModel2 } from '@/models/bizmodels';
|
|
11
11
|
import { BizException } from '@/models/devops';
|
|
12
12
|
import { ExecuteContext } from '@/libs/crud-pro/models/ExecuteContext';
|
|
@@ -95,7 +95,7 @@ export class CrudStdService extends ApiBaseService {
|
|
|
95
95
|
const appCode = stdAction.appCode;
|
|
96
96
|
const appInfo = await this.getParsedCrudStdAppForSettingKey(stdAction);
|
|
97
97
|
|
|
98
|
-
if (!appInfo
|
|
98
|
+
if (!isEntityOK(appInfo)) {
|
|
99
99
|
throw new BizException('应用不存在或已下线:' + appCode);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -219,8 +219,7 @@ export class CrudStdService extends ApiBaseService {
|
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
221
|
* 获取appInfo 并且拿到当前settingKey相关的信息
|
|
222
|
-
* @param
|
|
223
|
-
* @param settingKey
|
|
222
|
+
* @param stdAction
|
|
224
223
|
*/
|
|
225
224
|
public async getParsedCrudStdAppForSettingKey(stdAction: ICrudStdActionParams): Promise<ICrudStdAppInfoForSettingKey> {
|
|
226
225
|
const { appCode, settingKey, buttonSettingKey } = stdAction || {};
|
|
@@ -279,7 +278,7 @@ export class CrudStdService extends ApiBaseService {
|
|
|
279
278
|
*/
|
|
280
279
|
public async executeStdActionByReq(stdAction: ICrudStdActionParams, params: IRequestModelCrudProExt): Promise<ExecuteContext> {
|
|
281
280
|
const appInfo = await this.getParsedCrudStdAppForSettingKey(stdAction);
|
|
282
|
-
if (!appInfo
|
|
281
|
+
if (!isEntityOK(appInfo)) {
|
|
283
282
|
throw new BizException('应用不存在或已下线:' + stdAction.appCode);
|
|
284
283
|
}
|
|
285
284
|
|
|
@@ -68,7 +68,8 @@ export class CurdMixByDictService implements IExecuteContextHandler {
|
|
|
68
68
|
if (noCacheCodes.length > 0) {
|
|
69
69
|
const res1 = await this.curdProService.executeCrudByCfg(
|
|
70
70
|
{
|
|
71
|
-
|
|
71
|
+
columns: ['dict_code', 'label', 'value', 'style', 'code'],
|
|
72
|
+
condition: { dict_code: { $in: noCacheCodes }, deleted_at: 0, status: 1 },
|
|
72
73
|
},
|
|
73
74
|
{
|
|
74
75
|
sqlTable: SystemTables.sys_data_dict_item,
|
|
@@ -60,7 +60,7 @@ export class CurdMixBySysConfigService implements IExecuteContextHandler {
|
|
|
60
60
|
const { SystemDbName, SystemDbType } = GLOBAL_STATIC_CONFIG.getConfig();
|
|
61
61
|
const res1 = await this.curdProService.executeCrudByCfg(
|
|
62
62
|
{
|
|
63
|
-
condition: { config_code: { $in: notCachedCodes }, deleted_at: 0 },
|
|
63
|
+
condition: { config_code: { $in: notCachedCodes }, deleted_at: 0, status: 1 },
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
sqlTable: SystemTables.sys_configs,
|
|
@@ -38,7 +38,7 @@ export class CurdMixByWorkbenchService implements IExecuteContextHandler {
|
|
|
38
38
|
const service: CurdMixByWorkbenchService = await ctx.requestContext.getAsync(CurdMixByWorkbenchService);
|
|
39
39
|
const reqJson = {
|
|
40
40
|
columns: 'workbench_code,workbench_name,workbench_domain',
|
|
41
|
-
condition: { deleted_at: 0 },
|
|
41
|
+
condition: { deleted_at: 0, status: 1 },
|
|
42
42
|
};
|
|
43
43
|
const res = await service.curdProService.executeCrudByCfg(reqJson, {
|
|
44
44
|
sqlTable: SystemTables.sys_workbench,
|