midway-fatcms 0.0.6 → 0.0.8

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 (121) hide show
  1. package/.qoder/skills/midway-fatcms/01-quick-start.md +231 -0
  2. package/.qoder/skills/midway-fatcms/02-crud-quick.md +337 -0
  3. package/.qoder/skills/midway-fatcms/03-crud-sharding.md +488 -0
  4. package/.qoder/skills/midway-fatcms/04-condition-operators.md +93 -0
  5. package/.qoder/skills/midway-fatcms/05-configuration.md +290 -0
  6. package/.qoder/skills/midway-fatcms/06-builtin-functions.md +241 -0
  7. package/.qoder/skills/midway-fatcms/07-examples.md +500 -0
  8. package/.qoder/skills/midway-fatcms/SKILL.md +96 -0
  9. package/README.md +9 -9
  10. package/dist/controller/base/BaseApiController.d.ts +1 -2
  11. package/dist/controller/base/BaseApiController.js +0 -4
  12. package/dist/controller/gateway/DocGatewayController.js +1 -1
  13. package/dist/controller/manage/FlowConfigManageApi.js +4 -2
  14. package/dist/controller/manage/SysConfigMangeApi.js +6 -1
  15. package/dist/controller/manage/UserAccountManageApi.js +7 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +2 -2
  18. package/dist/libs/crud-pro/CrudPro.d.ts +23 -2
  19. package/dist/libs/crud-pro/CrudPro.js +53 -2
  20. package/dist/libs/crud-pro/interfaces.d.ts +82 -12
  21. package/dist/libs/crud-pro/models/CrudResult.d.ts +115 -0
  22. package/dist/libs/crud-pro/models/CrudResult.js +126 -0
  23. package/dist/libs/crud-pro/models/RequestModel.d.ts +2 -38
  24. package/dist/libs/crud-pro/models/RequestModel.js +2 -99
  25. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +36 -2
  26. package/dist/libs/crud-pro/services/CrudProGenSqlCondition.js +8 -4
  27. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +1 -2
  28. package/dist/libs/crud-pro/utils/OrderByUtils.d.ts +70 -0
  29. package/dist/libs/crud-pro/utils/OrderByUtils.js +158 -0
  30. package/dist/libs/crud-pro-quick/CrudProQuick.d.ts +295 -0
  31. package/dist/libs/crud-pro-quick/CrudProQuick.js +529 -0
  32. package/dist/libs/crud-pro-quick/fixSoftDelete.d.ts +30 -0
  33. package/dist/{service/curd → libs/crud-pro-quick}/fixSoftDelete.js +3 -6
  34. package/dist/libs/crud-pro-quick/index.d.ts +36 -0
  35. package/dist/libs/crud-pro-quick/index.js +49 -0
  36. package/dist/libs/crud-pro-quick/models.d.ts +33 -0
  37. package/dist/libs/crud-pro-quick/models.js +2 -0
  38. package/dist/libs/crud-sharding/ShardingConfig.d.ts +15 -2
  39. package/dist/libs/crud-sharding/ShardingConfig.js +2 -2
  40. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +119 -274
  41. package/dist/libs/crud-sharding/ShardingCrudPro.js +559 -379
  42. package/dist/libs/crud-sharding/ShardingMerger.d.ts +12 -20
  43. package/dist/libs/crud-sharding/ShardingMerger.js +36 -51
  44. package/dist/libs/crud-sharding/ShardingResult.d.ts +33 -0
  45. package/dist/libs/crud-sharding/ShardingResult.js +16 -0
  46. package/dist/libs/crud-sharding/ShardingRouter.d.ts +1 -0
  47. package/dist/libs/crud-sharding/ShardingRouter.js +25 -6
  48. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +21 -4
  49. package/dist/libs/crud-sharding/ShardingTableCreator.js +193 -59
  50. package/dist/libs/crud-sharding/ShardingUtils.d.ts +48 -0
  51. package/dist/libs/crud-sharding/ShardingUtils.js +122 -1
  52. package/dist/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  53. package/dist/libs/crud-sharding/index.d.ts +4 -3
  54. package/dist/libs/crud-sharding/index.js +14 -2
  55. package/dist/models/bizmodels.d.ts +2 -6
  56. package/dist/service/SysAppService.d.ts +2 -2
  57. package/dist/service/SysAppService.js +16 -5
  58. package/dist/service/SysConfigService.d.ts +1 -1
  59. package/dist/service/SysConfigService.js +7 -2
  60. package/dist/service/SysDictDataService.js +14 -4
  61. package/dist/service/SysMenuService.js +7 -2
  62. package/dist/service/curd/CurdMixService.d.ts +6 -4
  63. package/dist/service/curd/CurdMixService.js +16 -2
  64. package/dist/service/curd/CurdProService.d.ts +43 -27
  65. package/dist/service/curd/CurdProService.js +32 -33
  66. package/dist/service/flow/FlowConfigService.js +7 -2
  67. package/dist/service/flow/FlowInstanceCrudService.js +22 -19
  68. package/package.json +2 -1
  69. package/src/controller/base/BaseApiController.ts +0 -5
  70. package/src/controller/gateway/DocGatewayController.ts +1 -1
  71. package/src/controller/manage/CrudStandardDesignApi.ts +4 -3
  72. package/src/controller/manage/FlowConfigManageApi.ts +4 -2
  73. package/src/controller/manage/SysConfigMangeApi.ts +6 -1
  74. package/src/controller/manage/UserAccountManageApi.ts +7 -2
  75. package/src/index.ts +2 -2
  76. package/src/libs/crud-pro/CrudPro.ts +62 -4
  77. package/src/libs/crud-pro/interfaces.ts +110 -15
  78. package/src/libs/crud-pro/models/CrudResult.ts +178 -0
  79. package/src/libs/crud-pro/models/RequestModel.ts +4 -110
  80. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +41 -2
  81. package/src/libs/crud-pro/services/CrudProGenSqlCondition.ts +11 -7
  82. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +1 -2
  83. package/src/libs/crud-pro/utils/OrderByUtils.ts +169 -0
  84. package/src/libs/crud-pro-quick/CrudProQuick.ts +594 -0
  85. package/src/{service/curd → libs/crud-pro-quick}/fixSoftDelete.ts +23 -13
  86. package/src/libs/crud-pro-quick/index.ts +52 -0
  87. package/src/libs/crud-pro-quick/models.ts +35 -0
  88. package/src/libs/crud-sharding/ShardingConfig.ts +18 -2
  89. package/src/libs/crud-sharding/ShardingCrudPro.ts +689 -440
  90. package/src/libs/crud-sharding/ShardingMerger.ts +47 -73
  91. package/src/libs/crud-sharding/ShardingResult.ts +29 -0
  92. package/src/libs/crud-sharding/ShardingRouter.ts +27 -6
  93. package/src/libs/crud-sharding/ShardingTableCreator.ts +214 -71
  94. package/src/libs/crud-sharding/ShardingUtils.ts +137 -0
  95. package/src/libs/crud-sharding/TIME_COLUMN_CLEAN_SPEC.md +488 -0
  96. package/src/libs/crud-sharding/index.ts +14 -3
  97. package/src/models/bizmodels.ts +4 -7
  98. package/src/service/SysAppService.ts +18 -7
  99. package/src/service/SysConfigService.ts +8 -3
  100. package/src/service/SysDictDataService.ts +14 -4
  101. package/src/service/SysMenuService.ts +7 -2
  102. package/src/service/crudstd/CrudStdService.ts +2 -2
  103. package/src/service/curd/CurdMixService.ts +26 -5
  104. package/src/service/curd/CurdProService.ts +58 -39
  105. package/src/service/flow/FlowConfigService.ts +7 -2
  106. package/src/service/flow/FlowInstanceCrudService.ts +23 -20
  107. package/dist/libs/crud-pro/README.md +0 -809
  108. package/dist/libs/crud-pro/README_FUNC.md +0 -193
  109. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  110. package/dist/models/StandardColumns.d.ts +0 -71
  111. package/dist/models/StandardColumns.js +0 -28
  112. package/dist/service/curd/CrudProQuick.d.ts +0 -190
  113. package/dist/service/curd/CrudProQuick.js +0 -319
  114. package/dist/service/curd/README.md +0 -1001
  115. package/dist/service/curd/fixSoftDelete.d.ts +0 -20
  116. package/src/libs/crud-pro/README.md +0 -809
  117. package/src/libs/crud-pro/README_FUNC.md +0 -193
  118. package/src/libs/crud-sharding/ROUTING_LOGIC.md +0 -944
  119. package/src/models/StandardColumns.ts +0 -76
  120. package/src/service/curd/CrudProQuick.ts +0 -360
  121. package/src/service/curd/README.md +0 -1001
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestModel = void 0;
4
4
  const _ = require("lodash");
5
5
  const MixinUtils_1 = require("../utils/MixinUtils");
6
+ const OrderByUtils_1 = require("../utils/OrderByUtils");
6
7
  const exceptions_1 = require("../exceptions");
7
8
  const defaultConfigs_1 = require("../defaultConfigs");
8
9
  class RequestModel {
@@ -10,109 +11,11 @@ class RequestModel {
10
11
  Object.assign(this, req);
11
12
  this.visitor = visitor;
12
13
  this.columns = MixinUtils_1.MixinUtils.parseColumns(req.columns);
13
- this.orderBys = this.parseOrderBys(req.orderBy);
14
+ this.orderBys = OrderByUtils_1.OrderByUtils.parseOrderBys(req.orderBy);
14
15
  const limitOffset = this.parseOffsetList(req);
15
16
  this.limit = limitOffset.limit;
16
17
  this.offset = limitOffset.offset;
17
18
  }
18
- /**
19
- * 解析 orderBy 参数为 IOrderByItem 数组
20
- *
21
- * 支持的格式:
22
- * 1. 字符串格式(逗号分隔多个字段):
23
- * - 标准 SQL 格式:'created_at DESC, amount ASC'
24
- * - 简写 +/- 格式:'created_at-, amount+'('-' 表示 DESC,'+' 或省略表示 ASC)
25
- * - 默认升序:'order_id'(无后缀时默认为 ASC)
26
- *
27
- * 2. 数组格式:
28
- * - 纯对象数组:[{ fieldName: 'created_at', orderType: 'desc' }]
29
- * - 混合数组1(字符串+对象):['order_id', { fieldName: 'amount', orderType: 'asc' }]
30
- * - 混合数组2(字符串+对象):['order_id+', { fieldName: 'amount', orderType: 'asc' }]
31
- * - 混合数组3(字符串+对象):['order_id DESC', { fieldName: 'amount', orderType: 'asc' }]
32
- * 注:数组中的字符串元素支持标准 SQL 格式和简写格式,与字符串参数格式一致。
33
- *
34
- * SQL 注入防护:
35
- * - 所有字段名必须通过 MixinUtils.isValidFieldName() 校验
36
- * - 只允许 ASC/DESC 作为排序方向
37
- *
38
- * @param orderByStr 排序参数,可以是字符串或数组
39
- * @returns IOrderByItem[] 解析后的排序项数组
40
- */
41
- parseOrderBys(orderByStr) {
42
- if (MixinUtils_1.MixinUtils.isEmpty(orderByStr)) {
43
- return [];
44
- }
45
- // 数组格式:支持字符串和对象混合
46
- if (Array.isArray(orderByStr)) {
47
- return orderByStr
48
- .map(item => this.parseOrderByItem(item))
49
- .filter((o) => !!o);
50
- }
51
- // 字符串格式:逗号分隔多个字段
52
- return orderByStr
53
- .split(',')
54
- .map(s => s.trim())
55
- .filter(s => !!s)
56
- .map(item => this.parseOrderByString(item))
57
- .filter((o) => !!o);
58
- }
59
- /**
60
- * 解析单个排序项(数组元素)
61
- */
62
- parseOrderByItem(item) {
63
- // 字符串格式:解析标准 SQL 格式或简写格式
64
- if (typeof item === 'string') {
65
- return this.parseOrderByString(item);
66
- }
67
- // 对象格式:提取 fieldName 和 orderType
68
- const { fieldName, orderType = 'asc' } = item || {};
69
- if (!fieldName) {
70
- return null;
71
- }
72
- this.validateFieldName(fieldName, fieldName);
73
- return { fieldName, orderType };
74
- }
75
- /**
76
- * 解析字符串格式的排序项(支持标准 SQL 和简写格式)
77
- */
78
- parseOrderByString(orderByStr) {
79
- let orderType = 'asc';
80
- let fieldName = orderByStr;
81
- // 检查是否为空格分隔的标准 SQL 格式(如 'created_at DESC')
82
- const spaceIndex = orderByStr.lastIndexOf(' ');
83
- if (spaceIndex > 0) {
84
- const beforeSpace = orderByStr.substring(0, spaceIndex).trim();
85
- const afterSpace = orderByStr.substring(spaceIndex + 1).trim().toUpperCase();
86
- if (afterSpace === 'ASC' || afterSpace === 'DESC') {
87
- fieldName = beforeSpace;
88
- orderType = afterSpace.toLowerCase();
89
- }
90
- else {
91
- throw new exceptions_1.CommonException(exceptions_1.Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, orderByStr);
92
- }
93
- }
94
- else if (orderByStr.endsWith('+')) {
95
- // 简写格式:+ 表示升序
96
- fieldName = orderByStr.slice(0, -1);
97
- orderType = 'asc';
98
- }
99
- else if (orderByStr.endsWith('-')) {
100
- // 简写格式:- 表示降序
101
- fieldName = orderByStr.slice(0, -1);
102
- orderType = 'desc';
103
- }
104
- fieldName = fieldName.trim();
105
- this.validateFieldName(fieldName, orderByStr);
106
- return { fieldName, orderType };
107
- }
108
- /**
109
- * SQL 注入防护:校验字段名格式
110
- */
111
- validateFieldName(fieldName, originalValue) {
112
- if (MixinUtils_1.MixinUtils.isEmpty(fieldName) || !MixinUtils_1.MixinUtils.isValidFieldName(fieldName)) {
113
- throw new exceptions_1.CommonException(exceptions_1.Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, originalValue);
114
- }
115
- }
116
19
  parseOffsetList(req) {
117
20
  const { limit, offset, pageSize, pageNo } = req;
118
21
  const limitOffset = {
@@ -16,6 +16,7 @@ class CrudProExecuteSqlService extends CrudProServiceBase_1.CrudProServiceBase {
16
16
  async executeSqlCfgModels() {
17
17
  const exeCtx = this.getExecuteContext();
18
18
  const sqlCfgModels = exeCtx.getSqlCfgModels();
19
+ console.log("CrudProExecuteSqlService===> " + JSON.stringify(sqlCfgModels));
19
20
  for (let i = 0; i < sqlCfgModels.length; i++) {
20
21
  const sqlCfgModel = sqlCfgModels[i];
21
22
  const checkResult = this.executeSqlCfgModelPreCheck(sqlCfgModel);
@@ -73,9 +74,42 @@ class CrudProExecuteSqlService extends CrudProServiceBase_1.CrudProServiceBase {
73
74
  // 允许空SQL,没有需要执行的正常。
74
75
  return false;
75
76
  }
77
+ const errorMsg = `禁止执行的SQL: ${sqlCfgModel.executeSql}`;
76
78
  if (keys_1.KeyOfCrudTypes.NOT_CRUD === crudType) {
77
- // 只支持增删改查,其他语句不支持。
78
- throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_NOT_SUPPORT_THE_SQL, sqlCfgModel.executeSql);
79
+ // 在 isNativeSQL 模式下,允许特定的 DDL 操作(CREATE TABLE、CREATE INDEX 等),但禁止 DROP TABLE
80
+ if (sqlCfgModel.isNativeSQL) {
81
+ // 禁止的 DDL 操作(破坏性操作)
82
+ const forbiddenPatterns = [
83
+ /^\s*DROP\s+TABLE/i,
84
+ /^\s*DROP\s+DATABASE/i,
85
+ /^\s*DROP\s+SCHEMA/i,
86
+ /^\s*TRUNCATE\s+TABLE/i,
87
+ ];
88
+ for (const pattern of forbiddenPatterns) {
89
+ if (pattern.test(sqlCfgModel.executeSql)) {
90
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
91
+ }
92
+ }
93
+ // 允许的 DDL 操作白名单
94
+ const allowedPatterns = [
95
+ /^\s*CREATE\s+TABLE/i,
96
+ /^\s*CREATE\s+INDEX/i,
97
+ /^\s*CREATE\s+UNIQUE\s+INDEX/i,
98
+ /^\s*ALTER\s+TABLE/i,
99
+ ];
100
+ const isAllowed = allowedPatterns.some(pattern => pattern.test(sqlCfgModel.executeSql));
101
+ if (isAllowed) {
102
+ // 允许的 DDL 操作,继续执行后续校验
103
+ }
104
+ else {
105
+ // 非允许的 DDL 操作,仍然抛出异常
106
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
107
+ }
108
+ }
109
+ else {
110
+ // 非 isNativeSQL 模式下,只支持增删改查,其他语句不支持。
111
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.CFG_NOT_SUPPORT_THE_SQL, errorMsg);
112
+ }
79
113
  }
80
114
  // 此SQL不需要执行
81
115
  if (!this.isNeedExecute(sqlCfgModel)) {
@@ -174,12 +174,16 @@ class CrudProGenSqlCondition {
174
174
  const value0 = map[compare];
175
175
  let tmpSql = '';
176
176
  if (equalsIgnoreCase(keys_1.KeysOfConditions.$NULL, compare)) {
177
- tmpSql = `${toSqlColumnName(key)} is null `;
178
- hasNoArgSql = true;
177
+ if (value0 === true) {
178
+ tmpSql = `${toSqlColumnName(key)} is null `;
179
+ hasNoArgSql = true;
180
+ }
179
181
  }
180
182
  else if (equalsIgnoreCase(keys_1.KeysOfConditions.$NOT_NULL, compare)) {
181
- tmpSql = `${toSqlColumnName(key)} is not null `;
182
- hasNoArgSql = true;
183
+ if (value0 === true) {
184
+ tmpSql = `${toSqlColumnName(key)} is not null `;
185
+ hasNoArgSql = true;
186
+ }
183
187
  }
184
188
  else if (equalsIgnoreCase(keys_1.KeysOfConditions.$GT, compare)) {
185
189
  tmpArgList.push(value0);
@@ -71,8 +71,7 @@ class CrudProTableMetaService extends CrudProServiceBase_1.CrudProServiceBase {
71
71
  const result = await this.loadAllTableInfos(query);
72
72
  // 3. 更新缓存
73
73
  if (!skipCache || refreshCache) {
74
- const { tableMetaCacheTime } = this.getContextCfg();
75
- metaCache.setTableInfos(cacheKey, result, tableMetaCacheTime || 3600000);
74
+ metaCache.setTableInfos(cacheKey, result, 1000 * 5);
76
75
  }
77
76
  return result;
78
77
  }
@@ -0,0 +1,70 @@
1
+ import { IOrderByItem } from '../interfaces';
2
+ /**
3
+ * OrderBy 解析工具类
4
+ *
5
+ * 提供统一的 orderBy 参数解析功能,支持多种格式:
6
+ * 1. 字符串格式(逗号分隔多个字段):
7
+ * - 标准 SQL 格式:'created_at DESC, amount ASC'
8
+ * - 简写 +/- 格式:'created_at-, amount+'('-' 表示 DESC,'+' 或省略表示 ASC)
9
+ * - 默认升序:'order_id'(无后缀时默认为 ASC)
10
+ *
11
+ * 2. 数组格式:
12
+ * - 纯对象数组:[{ fieldName: 'created_at', orderType: 'desc' }]
13
+ * - 混合数组(字符串+对象):['order_id+', { fieldName: 'amount', orderType: 'asc' }]
14
+ *
15
+ * SQL 注入防护:
16
+ * - 所有字段名必须通过 MixinUtils.isValidFieldName() 校验
17
+ * - 只允许 ASC/DESC 作为排序方向
18
+ */
19
+ export declare class OrderByUtils {
20
+ /**
21
+ * 解析 orderBy 参数为 IOrderByItem 数组
22
+ *
23
+ * @param orderByStr 排序参数,可以是字符串或数组
24
+ * @returns IOrderByItem[] 解析后的排序项数组
25
+ */
26
+ static parseOrderBys(orderByStr: any): IOrderByItem[];
27
+ /**
28
+ * 获取第一个排序项
29
+ *
30
+ * @param orderByStr 排序参数
31
+ * @returns IOrderByItem | null 第一个排序项,无则返回 null
32
+ */
33
+ static getFirstOrderBy(orderByStr: any): IOrderByItem | null;
34
+ /**
35
+ * 判断是否为 ASC 升序排序
36
+ *
37
+ * @param orderByStr 排序参数
38
+ * @returns true 表示 ASC,false 表示 DESC 或无排序
39
+ */
40
+ static isFirstOrderByAsc(orderByStr: any): boolean;
41
+ /**
42
+ * 判断是否为 DESC 降序排序
43
+ *
44
+ * @param orderByStr 排序参数
45
+ * @returns true 表示 DESC,false 表示 ASC 或无排序
46
+ */
47
+ static isFirstOrderByDesc(orderByStr: any): boolean;
48
+ /**
49
+ * 判断排序字段是否匹配指定的时间字段
50
+ *
51
+ * 用于分表查询时校验排序字段是否为分表字段
52
+ *
53
+ * @param orderByStr 排序参数
54
+ * @param timeColumn 时间字段名
55
+ * @returns true 表示匹配,false 表示不匹配或无排序
56
+ */
57
+ static isOrderByTimeColumn(orderByStr: any, timeColumn: string): boolean;
58
+ /**
59
+ * 解析单个排序项(数组元素)
60
+ */
61
+ private static parseOrderByItem;
62
+ /**
63
+ * 解析字符串格式的排序项(支持标准 SQL 和简写格式)
64
+ */
65
+ private static parseOrderByString;
66
+ /**
67
+ * SQL 注入防护:校验字段名格式
68
+ */
69
+ private static validateFieldName;
70
+ }
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderByUtils = void 0;
4
+ const MixinUtils_1 = require("./MixinUtils");
5
+ const exceptions_1 = require("../exceptions");
6
+ /**
7
+ * OrderBy 解析工具类
8
+ *
9
+ * 提供统一的 orderBy 参数解析功能,支持多种格式:
10
+ * 1. 字符串格式(逗号分隔多个字段):
11
+ * - 标准 SQL 格式:'created_at DESC, amount ASC'
12
+ * - 简写 +/- 格式:'created_at-, amount+'('-' 表示 DESC,'+' 或省略表示 ASC)
13
+ * - 默认升序:'order_id'(无后缀时默认为 ASC)
14
+ *
15
+ * 2. 数组格式:
16
+ * - 纯对象数组:[{ fieldName: 'created_at', orderType: 'desc' }]
17
+ * - 混合数组(字符串+对象):['order_id+', { fieldName: 'amount', orderType: 'asc' }]
18
+ *
19
+ * SQL 注入防护:
20
+ * - 所有字段名必须通过 MixinUtils.isValidFieldName() 校验
21
+ * - 只允许 ASC/DESC 作为排序方向
22
+ */
23
+ class OrderByUtils {
24
+ /**
25
+ * 解析 orderBy 参数为 IOrderByItem 数组
26
+ *
27
+ * @param orderByStr 排序参数,可以是字符串或数组
28
+ * @returns IOrderByItem[] 解析后的排序项数组
29
+ */
30
+ static parseOrderBys(orderByStr) {
31
+ if (MixinUtils_1.MixinUtils.isEmpty(orderByStr)) {
32
+ return [];
33
+ }
34
+ // 数组格式:支持字符串和对象混合
35
+ if (Array.isArray(orderByStr)) {
36
+ return orderByStr
37
+ .map(item => this.parseOrderByItem(item))
38
+ .filter((o) => !!o);
39
+ }
40
+ // 字符串格式:逗号分隔多个字段
41
+ return orderByStr
42
+ .split(',')
43
+ .map(s => s.trim())
44
+ .filter(s => !!s)
45
+ .map(item => this.parseOrderByString(item))
46
+ .filter((o) => !!o);
47
+ }
48
+ /**
49
+ * 获取第一个排序项
50
+ *
51
+ * @param orderByStr 排序参数
52
+ * @returns IOrderByItem | null 第一个排序项,无则返回 null
53
+ */
54
+ static getFirstOrderBy(orderByStr) {
55
+ const orderBys = this.parseOrderBys(orderByStr);
56
+ return orderBys.length > 0 ? orderBys[0] : null;
57
+ }
58
+ /**
59
+ * 判断是否为 ASC 升序排序
60
+ *
61
+ * @param orderByStr 排序参数
62
+ * @returns true 表示 ASC,false 表示 DESC 或无排序
63
+ */
64
+ static isFirstOrderByAsc(orderByStr) {
65
+ const first = this.getFirstOrderBy(orderByStr);
66
+ if (!first) {
67
+ return false;
68
+ }
69
+ return first.orderType.toUpperCase() === 'ASC';
70
+ }
71
+ /**
72
+ * 判断是否为 DESC 降序排序
73
+ *
74
+ * @param orderByStr 排序参数
75
+ * @returns true 表示 DESC,false 表示 ASC 或无排序
76
+ */
77
+ static isFirstOrderByDesc(orderByStr) {
78
+ const first = this.getFirstOrderBy(orderByStr);
79
+ if (!first) {
80
+ return false;
81
+ }
82
+ return first.orderType.toUpperCase() === 'DESC';
83
+ }
84
+ /**
85
+ * 判断排序字段是否匹配指定的时间字段
86
+ *
87
+ * 用于分表查询时校验排序字段是否为分表字段
88
+ *
89
+ * @param orderByStr 排序参数
90
+ * @param timeColumn 时间字段名
91
+ * @returns true 表示匹配,false 表示不匹配或无排序
92
+ */
93
+ static isOrderByTimeColumn(orderByStr, timeColumn) {
94
+ const first = this.getFirstOrderBy(orderByStr);
95
+ if (!first) {
96
+ return false;
97
+ }
98
+ return first.fieldName === timeColumn;
99
+ }
100
+ /**
101
+ * 解析单个排序项(数组元素)
102
+ */
103
+ static parseOrderByItem(item) {
104
+ // 字符串格式:解析标准 SQL 格式或简写格式
105
+ if (typeof item === 'string') {
106
+ return this.parseOrderByString(item);
107
+ }
108
+ // 对象格式:提取 fieldName 和 orderType
109
+ const { fieldName, orderType = 'asc' } = item || {};
110
+ if (!fieldName) {
111
+ return null;
112
+ }
113
+ this.validateFieldName(fieldName, fieldName);
114
+ return { fieldName, orderType };
115
+ }
116
+ /**
117
+ * 解析字符串格式的排序项(支持标准 SQL 和简写格式)
118
+ */
119
+ static parseOrderByString(orderByStr) {
120
+ let orderType = 'asc';
121
+ let fieldName = orderByStr;
122
+ // 检查是否为空格分隔的标准 SQL 格式(如 'created_at DESC')
123
+ const spaceIndex = orderByStr.lastIndexOf(' ');
124
+ if (spaceIndex > 0) {
125
+ const beforeSpace = orderByStr.substring(0, spaceIndex).trim();
126
+ const afterSpace = orderByStr.substring(spaceIndex + 1).trim().toUpperCase();
127
+ if (afterSpace === 'ASC' || afterSpace === 'DESC') {
128
+ fieldName = beforeSpace;
129
+ orderType = afterSpace.toLowerCase();
130
+ }
131
+ else {
132
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, orderByStr);
133
+ }
134
+ }
135
+ else if (orderByStr.endsWith('+')) {
136
+ // 简写格式:+ 表示升序
137
+ fieldName = orderByStr.slice(0, -1);
138
+ orderType = 'asc';
139
+ }
140
+ else if (orderByStr.endsWith('-')) {
141
+ // 简写格式:- 表示降序
142
+ fieldName = orderByStr.slice(0, -1);
143
+ orderType = 'desc';
144
+ }
145
+ fieldName = fieldName.trim();
146
+ this.validateFieldName(fieldName, orderByStr);
147
+ return { fieldName, orderType };
148
+ }
149
+ /**
150
+ * SQL 注入防护:校验字段名格式
151
+ */
152
+ static validateFieldName(fieldName, originalValue) {
153
+ if (MixinUtils_1.MixinUtils.isEmpty(fieldName) || !MixinUtils_1.MixinUtils.isValidFieldName(fieldName)) {
154
+ throw new exceptions_1.CommonException(exceptions_1.Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, originalValue);
155
+ }
156
+ }
157
+ }
158
+ exports.OrderByUtils = OrderByUtils;