midway-fatcms 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/controller/gateway/DocGatewayController.js +7 -5
  2. package/dist/controller/gateway/PublicApiController.js +32 -3
  3. package/dist/controller/home.controller.js +2 -1
  4. package/dist/controller/manage/DataDictManageApi.d.ts +2 -0
  5. package/dist/controller/manage/DataDictManageApi.js +41 -9
  6. package/dist/controller/manage/DocManageApi.js +2 -2
  7. package/dist/controller/render/AppRenderController.js +7 -6
  8. package/dist/libs/crud-pro/CrudPro.d.ts +0 -1
  9. package/dist/libs/crud-pro/CrudPro.js +2 -11
  10. package/dist/libs/crud-pro/exceptions.d.ts +6 -0
  11. package/dist/libs/crud-pro/exceptions.js +6 -0
  12. package/dist/libs/crud-pro/utils/SqlErrorParseUtils.d.ts +22 -0
  13. package/dist/libs/crud-pro/utils/SqlErrorParseUtils.js +219 -0
  14. package/dist/libs/utils/functions.js +3 -0
  15. package/dist/service/anyapi/AnyApiService.js +1 -1
  16. package/dist/service/crudstd/CrudStdService.js +2 -2
  17. package/dist/service/curd/CurdMixByDictService.js +1 -1
  18. package/dist/service/curd/CurdMixBySysConfigService.js +1 -1
  19. package/dist/service/curd/CurdMixByWorkbenchService.js +1 -1
  20. package/dist/service/proxyapi/ProxyApiLoadService.js +3 -0
  21. package/package.json +1 -1
  22. package/src/controller/gateway/DocGatewayController.ts +9 -5
  23. package/src/controller/gateway/PublicApiController.ts +39 -4
  24. package/src/controller/home.controller.ts +3 -1
  25. package/src/controller/manage/DataDictManageApi.ts +48 -11
  26. package/src/controller/manage/DocManageApi.ts +2 -2
  27. package/src/controller/render/AppRenderController.ts +9 -7
  28. package/src/libs/crud-pro/CrudPro.ts +2 -12
  29. package/src/libs/crud-pro/exceptions.ts +6 -0
  30. package/src/libs/crud-pro/utils/SqlErrorParseUtils.ts +236 -0
  31. package/src/libs/utils/functions.ts +3 -0
  32. package/src/service/anyapi/AnyApiService.ts +2 -2
  33. package/src/service/crudstd/CrudStdService.ts +3 -3
  34. package/src/service/curd/CurdMixByDictService.ts +1 -1
  35. package/src/service/curd/CurdMixBySysConfigService.ts +1 -1
  36. package/src/service/curd/CurdMixByWorkbenchService.ts +1 -1
  37. package/src/service/proxyapi/ProxyApiLoadService.ts +4 -1
@@ -115,6 +115,9 @@ let ProxyApiLoadService = class ProxyApiLoadService extends BaseService_1.BaseSe
115
115
  });
116
116
  const rows0 = res.getResRows();
117
117
  const rows = rows0.filter(s => {
118
+ if (!(0, functions_1.isEntityOK)(s)) {
119
+ return false;
120
+ }
118
121
  if (!s.workbench_code_array) {
119
122
  throw new exceptions_1.CommonException('IS_SUPPORT_CURRENT_WORKBENCH', '未配置支持的站点字段');
120
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midway-fatcms",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "This is a midway component sample",
5
5
  "main": "dist/index.js",
6
6
  "typings": "index.d.ts",
@@ -6,6 +6,7 @@ import { GLOBAL_STATIC_CONFIG } from '@/libs/global-config/global-config';
6
6
  import { KeysOfSimpleSQL } from '@/libs/crud-pro/models/keys';
7
7
  import { WorkbenchService } from '@/service/WorkbenchService';
8
8
  import { CommonResult } from '@/libs/utils/common-dto';
9
+ import { isEntityOK } from '@/libs/utils/functions';
9
10
  import { getRealIpSafe } from '@/libs/utils/fatcms-request';
10
11
 
11
12
  function checkIsNumber(value: string, name: string) {
@@ -91,13 +92,16 @@ export class DocGatewayController extends BaseApiController {
91
92
  }
92
93
  );
93
94
 
94
- // 更新PV数据
95
+
95
96
  const docObj = res.getOneObj();
96
- if (docObj) {
97
- const pv: number = parseInt(docObj.pv) || 0;
98
- await this.updateDocPV(docId, docLibId, pv);
97
+
98
+ if (!isEntityOK(docObj)) {
99
+ return CommonResult.errorRes("文档不存在");
99
100
  }
100
101
 
102
+ // 更新PV数据
103
+ const pv: number = parseInt(docObj.pv) || 0;
104
+ await this.updateDocPV(docId, docLibId, pv);
101
105
  return res;
102
106
  }
103
107
 
@@ -160,7 +164,7 @@ export class DocGatewayController extends BaseApiController {
160
164
  );
161
165
  const docLibInfo = s.getOneObj();
162
166
 
163
- if (!docLibInfo) {
167
+ if (!isEntityOK(docLibInfo)) {
164
168
  throw '此文档库查询失败';
165
169
  }
166
170
 
@@ -4,7 +4,7 @@ import * as _ from 'lodash';
4
4
  import { BaseApiController } from '../base/BaseApiController';
5
5
  import { EnumInfoService } from '@/service/EnumInfoService';
6
6
  import { CommonResult } from '@/libs/utils/common-dto';
7
- import { parseJsonObject } from '@/libs/utils/functions';
7
+ import { isEntityOK, parseJsonObject } from '@/libs/utils/functions';
8
8
  import { WorkbenchService } from '@/service/WorkbenchService';
9
9
  import { SystemRoleCode } from '@/models/SystemPerm';
10
10
  import { KeysOfAuthType } from '@/libs/crud-pro/models/keys';
@@ -71,10 +71,33 @@ export class PublicApiController extends BaseApiController {
71
71
  const allMenuList = await this.sysMenuService.getCachedMenusList();
72
72
 
73
73
  const menuList = allMenuList.filter(e => {
74
- return workbenchMenuCodeSet.has(e.menu_code) && e.status === 1;
74
+ return workbenchMenuCodeSet.has(e.menu_code) && isEntityOK(e);
75
75
  });
76
76
 
77
77
 
78
+ const addPermissionInfo = (menu_list: any[], level: number) => {
79
+ if (!Array.isArray(menu_list)) {
80
+ return [];
81
+ }
82
+ if (level > 10) {
83
+ return menu_list;
84
+ }
85
+ return menu_list.map(e => {
86
+ if (Array.isArray(e.children)) {
87
+ e.children = addPermissionInfo(e.children, level + 1);
88
+ }
89
+ const view_auth_config = e.view_auth_config;
90
+ const view_auth_type: KeysOfAuthType = e.view_auth_type || KeysOfAuthType.byFuncCode;
91
+ if (!view_auth_config) {
92
+ return { ...e, hasPermission: true };
93
+ }
94
+ const hasPermission = this.ctx.userSession.isAuthPass(view_auth_type, view_auth_config);
95
+ return { ...e, hasPermission };
96
+ });
97
+ };
98
+
99
+
100
+
78
101
  const mapResult: any = {};
79
102
  for (let i = 0; i < menuList.length; i++) {
80
103
  const rowElement = cloneObject(menuList[i]);
@@ -86,7 +109,8 @@ export class PublicApiController extends BaseApiController {
86
109
 
87
110
  const menu_config_content = rowElement.menu_config_content;
88
111
  delete rowElement.menu_config_content;
89
- const menu_list = parseJsonObject(menu_config_content) || [];
112
+ let menu_list = parseJsonObject(menu_config_content) || [];
113
+ menu_list = addPermissionInfo(menu_list, 0);
90
114
  mapResult[menu_code] = { ...rowElement, menu_list, hasPermission };
91
115
  }
92
116
 
@@ -121,10 +145,21 @@ export class PublicApiController extends BaseApiController {
121
145
  }
122
146
 
123
147
  if (pathname && workbenchCode) {
124
- resultData.pageData = await this.sysAppService.getSysAppPageOne({
148
+
149
+ const pageData = await this.sysAppService.getSysAppPageOne({
125
150
  page_path: pathname,
126
151
  workbench_code: workbenchCode
127
152
  });
153
+
154
+ if (pageData) {
155
+ const clonePageInfo = { ...pageData };
156
+ const view_auth_config = clonePageInfo.view_auth_config;
157
+ const view_auth_type: KeysOfAuthType = clonePageInfo.view_auth_type;
158
+ const hasPermission = this.ctx.userSession.isAuthPass(view_auth_type, view_auth_config);
159
+ clonePageInfo.hasPermission = hasPermission;
160
+ resultData.pageData = clonePageInfo;
161
+ }
162
+
128
163
  }
129
164
 
130
165
  return CommonResult.successRes(resultData);
@@ -6,6 +6,7 @@ import { createRenderUtils } from '@/libs/utils/render-utils';
6
6
  import { privateAES } from '@/libs/utils/crypto-utils';
7
7
  import { AuthService } from '@/service/AuthService';
8
8
  import { ISessionInfo } from '@/models/userSession';
9
+ import { isEntityOK } from '@/libs/utils/functions';
9
10
 
10
11
  @Controller('/')
11
12
  export class HomeController extends BaseApiController {
@@ -25,7 +26,7 @@ export class HomeController extends BaseApiController {
25
26
 
26
27
  const workbenchInfo = await this.workbenchService.getCurrentHostWorkbenchInfo();
27
28
 
28
- if (!workbenchInfo || workbenchInfo.status !== 1) {
29
+ if (!isEntityOK(workbenchInfo)) {
29
30
  this.logInfo(`域名不存在:hostname = ${hostname}, host=${host}`);
30
31
  const infos = {
31
32
  host,
@@ -35,6 +36,7 @@ export class HomeController extends BaseApiController {
35
36
  return this.ctx.render('404_workbench', infos);
36
37
  }
37
38
 
39
+
38
40
  const html_content = workbenchInfo.html_content || '未配置HTML模版';
39
41
 
40
42
  const userInfo = await this.getUserAndRefreshSessionInfo();
@@ -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
- return this.executeSysSimpleSQL('sys_data_dict', KeysOfSimpleSQL.SIMPLE_INSERT, {
33
- enableSoftDelete: true,
34
- validateCfg: {
35
- 'data.dict_code': [KeysOfValidators.REQUIRED],
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, KeysOfValidators.NUMERIC],
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, KeysOfValidators.NUMERIC],
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, KeysOfValidators.NUMERIC],
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, KeysOfValidators.NUMERIC],
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, KeysOfValidators.NUMERIC],
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, KeysOfValidators.NUMERIC],
97
+ 'condition.id': [KeysOfValidators.REQUIRED],
98
98
  },
99
99
  updateCfg: {
100
100
  'condition.workbench_code': { contextAsString: CTX_WORKBENCH_CODE },
@@ -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
 
@@ -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 this.parseRunSqlException(e || {});
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
  }
@@ -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 || anyApi.status !== 1) {
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 || appInfo.status !== 1) {
98
+ if (!isEntityOK(appInfo)) {
99
99
  throw new BizException('应用不存在或已下线:' + appCode);
100
100
  }
101
101
 
@@ -279,7 +279,7 @@ export class CrudStdService extends ApiBaseService {
279
279
  */
280
280
  public async executeStdActionByReq(stdAction: ICrudStdActionParams, params: IRequestModelCrudProExt): Promise<ExecuteContext> {
281
281
  const appInfo = await this.getParsedCrudStdAppForSettingKey(stdAction);
282
- if (!appInfo || appInfo.status !== 1) {
282
+ if (!isEntityOK(appInfo)) {
283
283
  throw new BizException('应用不存在或已下线:' + stdAction.appCode);
284
284
  }
285
285
 
@@ -68,7 +68,7 @@ export class CurdMixByDictService implements IExecuteContextHandler {
68
68
  if (noCacheCodes.length > 0) {
69
69
  const res1 = await this.curdProService.executeCrudByCfg(
70
70
  {
71
- condition: { dict_code: { $in: noCacheCodes }, deleted_at: 0 },
71
+ condition: { dict_code: { $in: noCacheCodes }, deleted_at: 0, status: 1 },
72
72
  },
73
73
  {
74
74
  sqlTable: SystemTables.sys_data_dict_item,