midway-fatcms 0.0.5 → 0.0.6

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 (126) hide show
  1. package/README.md +377 -134
  2. package/dist/controller/manage/CrudStandardDesignApi.d.ts +0 -2
  3. package/dist/controller/manage/CrudStandardDesignApi.js +11 -85
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/libs/crud-pro/CrudPro.d.ts +9 -1
  7. package/dist/libs/crud-pro/CrudPro.js +15 -0
  8. package/dist/libs/crud-pro/README.md +809 -0
  9. package/dist/libs/crud-pro/README_FUNC.md +193 -0
  10. package/dist/libs/crud-pro/exceptions.d.ts +2 -0
  11. package/dist/libs/crud-pro/exceptions.js +2 -0
  12. package/dist/libs/crud-pro/interfaces.d.ts +34 -1
  13. package/dist/libs/crud-pro/models/ExecuteContext.d.ts +3 -3
  14. package/dist/libs/crud-pro/models/ExecuteContext.js +2 -0
  15. package/dist/libs/crud-pro/models/RequestModel.d.ts +41 -1
  16. package/dist/libs/crud-pro/models/RequestModel.js +103 -39
  17. package/dist/libs/crud-pro/models/ResModel.d.ts +6 -4
  18. package/dist/libs/crud-pro/models/ServiceHub.d.ts +1 -0
  19. package/dist/libs/crud-pro/models/keys.d.ts +6 -1
  20. package/dist/libs/crud-pro/models/keys.js +5 -0
  21. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.d.ts +52 -0
  22. package/dist/libs/crud-pro/services/CrudProDataTypeConvertService.js +158 -0
  23. package/dist/libs/crud-pro/services/CrudProExecuteSqlService.js +20 -1
  24. package/dist/libs/crud-pro/services/CrudProFieldValidateService.d.ts +7 -0
  25. package/dist/libs/crud-pro/services/CrudProFieldValidateService.js +32 -0
  26. package/dist/libs/crud-pro/services/CrudProGenSqlService.d.ts +13 -0
  27. package/dist/libs/crud-pro/services/CrudProGenSqlService.js +44 -7
  28. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.d.ts +43 -0
  29. package/dist/libs/crud-pro/services/CrudProOriginToExecuteSql.js +132 -1
  30. package/dist/libs/crud-pro/services/CrudProTableMetaService.d.ts +15 -1
  31. package/dist/libs/crud-pro/services/CrudProTableMetaService.js +107 -0
  32. package/dist/libs/crud-pro/services/CurdProServiceHub.d.ts +5 -1
  33. package/dist/libs/crud-pro/services/CurdProServiceHub.js +11 -0
  34. package/dist/libs/crud-pro/utils/DateTimeUtils.d.ts +1 -0
  35. package/dist/libs/crud-pro/utils/DateTimeUtils.js +3 -0
  36. package/dist/libs/crud-pro/utils/MixinUtils.d.ts +32 -0
  37. package/dist/libs/crud-pro/utils/MixinUtils.js +85 -1
  38. package/dist/libs/crud-pro/utils/ValidateUtils.js +1 -1
  39. package/dist/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  40. package/dist/libs/crud-sharding/ShardingConfig.d.ts +218 -0
  41. package/dist/libs/crud-sharding/ShardingConfig.js +32 -0
  42. package/dist/libs/crud-sharding/ShardingCountCache.d.ts +69 -0
  43. package/dist/libs/crud-sharding/ShardingCountCache.js +160 -0
  44. package/dist/libs/crud-sharding/ShardingCrudPro.d.ts +363 -0
  45. package/dist/libs/crud-sharding/ShardingCrudPro.js +699 -0
  46. package/dist/libs/crud-sharding/ShardingMerger.d.ts +130 -0
  47. package/dist/libs/crud-sharding/ShardingMerger.js +280 -0
  48. package/dist/libs/crud-sharding/ShardingRouter.d.ts +69 -0
  49. package/dist/libs/crud-sharding/ShardingRouter.js +377 -0
  50. package/dist/libs/crud-sharding/ShardingTableCreator.d.ts +146 -0
  51. package/dist/libs/crud-sharding/ShardingTableCreator.js +805 -0
  52. package/dist/libs/crud-sharding/ShardingUtils.d.ts +38 -0
  53. package/dist/libs/crud-sharding/ShardingUtils.js +77 -0
  54. package/dist/libs/crud-sharding/index.d.ts +45 -0
  55. package/dist/libs/crud-sharding/index.js +55 -0
  56. package/dist/models/StandardColumns.d.ts +71 -0
  57. package/dist/models/StandardColumns.js +28 -0
  58. package/dist/service/SysAppService.js +2 -2
  59. package/dist/service/SysConfigService.js +1 -1
  60. package/dist/service/SysDictDataService.js +2 -2
  61. package/dist/service/SysMenuService.js +1 -1
  62. package/dist/service/UserAccountService.d.ts +1 -1
  63. package/dist/service/crudstd/CrudStdService.d.ts +0 -1
  64. package/dist/service/crudstd/CrudStdService.js +0 -27
  65. package/dist/service/curd/CrudProQuick.d.ts +134 -4
  66. package/dist/service/curd/CrudProQuick.js +155 -3
  67. package/dist/service/curd/CurdMixService.d.ts +2 -1
  68. package/dist/service/curd/CurdMixService.js +5 -1
  69. package/dist/service/curd/CurdProService.d.ts +44 -2
  70. package/dist/service/curd/CurdProService.js +53 -1
  71. package/dist/service/curd/README.md +1001 -0
  72. package/dist/service/curd/fixSoftDelete.d.ts +14 -0
  73. package/dist/service/curd/fixSoftDelete.js +29 -11
  74. package/dist/service/flow/FlowConfigService.js +1 -1
  75. package/dist/service/flow/FlowInstanceCrudService.js +1 -1
  76. package/package.json +3 -1
  77. package/src/controller/gateway/AsyncTaskController.ts +1 -1
  78. package/src/controller/manage/CrudStandardDesignApi.ts +16 -100
  79. package/src/index.ts +3 -0
  80. package/src/libs/crud-pro/CrudPro.ts +19 -1
  81. package/src/libs/crud-pro/README.md +809 -0
  82. package/src/libs/crud-pro/README_FUNC.md +193 -0
  83. package/src/libs/crud-pro/exceptions.ts +2 -0
  84. package/src/libs/crud-pro/interfaces.ts +38 -1
  85. package/src/libs/crud-pro/models/ExecuteContext.ts +6 -3
  86. package/src/libs/crud-pro/models/RequestModel.ts +108 -44
  87. package/src/libs/crud-pro/models/ResModel.ts +10 -4
  88. package/src/libs/crud-pro/models/ServiceHub.ts +2 -0
  89. package/src/libs/crud-pro/models/keys.ts +5 -0
  90. package/src/libs/crud-pro/services/CrudProDataTypeConvertService.ts +171 -0
  91. package/src/libs/crud-pro/services/CrudProExecuteSqlService.ts +24 -1
  92. package/src/libs/crud-pro/services/CrudProFieldValidateService.ts +53 -1
  93. package/src/libs/crud-pro/services/CrudProGenSqlService.ts +51 -7
  94. package/src/libs/crud-pro/services/CrudProOriginToExecuteSql.ts +159 -2
  95. package/src/libs/crud-pro/services/CrudProTableMetaService.ts +139 -1
  96. package/src/libs/crud-pro/services/CurdProServiceHub.ts +16 -1
  97. package/src/libs/crud-pro/utils/DateTimeUtils.ts +3 -0
  98. package/src/libs/crud-pro/utils/MixinUtils.ts +97 -1
  99. package/src/libs/crud-pro/utils/ValidateUtils.ts +1 -1
  100. package/src/libs/crud-sharding/ROUTING_LOGIC.md +944 -0
  101. package/src/libs/crud-sharding/ShardingConfig.ts +240 -0
  102. package/src/libs/crud-sharding/ShardingCountCache.ts +200 -0
  103. package/src/libs/crud-sharding/ShardingCrudPro.ts +856 -0
  104. package/src/libs/crud-sharding/ShardingMerger.ts +382 -0
  105. package/src/libs/crud-sharding/ShardingRouter.ts +512 -0
  106. package/src/libs/crud-sharding/ShardingTableCreator.ts +1007 -0
  107. package/src/libs/crud-sharding/ShardingUtils.ts +84 -0
  108. package/src/libs/crud-sharding/index.ts +64 -0
  109. package/src/models/StandardColumns.ts +76 -0
  110. package/src/service/FileCenterService.ts +1 -1
  111. package/src/service/SysAppService.ts +2 -2
  112. package/src/service/SysConfigService.ts +1 -1
  113. package/src/service/SysDictDataService.ts +2 -2
  114. package/src/service/SysMenuService.ts +2 -2
  115. package/src/service/WorkbenchService.ts +1 -1
  116. package/src/service/anyapi/AnyApiService.ts +1 -1
  117. package/src/service/asyncTask/AsyncTaskRunnerService.ts +1 -1
  118. package/src/service/crudstd/CrudStdService.ts +0 -32
  119. package/src/service/curd/CrudProQuick.ts +164 -5
  120. package/src/service/curd/CurdMixService.ts +7 -2
  121. package/src/service/curd/CurdProService.ts +62 -3
  122. package/src/service/curd/README.md +1001 -0
  123. package/src/service/curd/fixCfgModel.ts +1 -2
  124. package/src/service/curd/fixSoftDelete.ts +38 -16
  125. package/src/service/flow/FlowConfigService.ts +1 -1
  126. package/src/service/flow/FlowInstanceCrudService.ts +1 -1
@@ -0,0 +1,193 @@
1
+ # CrudPro 内置函数参考
2
+
3
+ CrudPro 的 `IFuncCfgModel` 支持 `functionName` 方式调用内置函数。内置函数由四个工具类注册:`DateTimeUtils`、`CompareUtils`、`MixinUtils`、`TypeUtils`。
4
+
5
+ > 源码位置:`src/libs/crud-pro/services/CrudProServiceBase.ts` → `createBuildInExecuteFuncMap()`
6
+
7
+ ## 调用方式
8
+
9
+ `functionName` 是 `IFuncCfgModel` 的属性之一,可在 `updateCfg`、`validateCfg`、`executeWhen`、`validate` 等配置中使用。
10
+
11
+ ### 无参调用
12
+
13
+ ```typescript
14
+ 'data.created_at': { functionName: 'getCurrentTimeString' }
15
+ ```
16
+
17
+ ### 带参调用
18
+
19
+ ```typescript
20
+ 'data.order_date': { functionName: 'getCurrentDateFormat', functionParams: ['YYYYMMDD'] }
21
+ ```
22
+
23
+ `functionParams` 中的每个元素可以是基础类型(string / number / boolean),也可以嵌套 `IFuncCfgModel` 对象:
24
+
25
+ ```typescript
26
+ {
27
+ functionName: 'eq',
28
+ functionParams: [
29
+ { contextAsBool: 'res.is_exist' },
30
+ { constBool: false }
31
+ ]
32
+ }
33
+ ```
34
+
35
+ ## DateTimeUtils — 日期时间
36
+
37
+ | 函数名 | 参数 | 返回值 | 说明 |
38
+ |--------|------|--------|------|
39
+ | `getCurrentTimeStampMs` | 无 | `number` | 当前时间戳(毫秒) |
40
+ | `getCurrentTimeStampSecond` | 无 | `number` | 当前时间戳(秒) |
41
+ | `getCurrentTimeString` | 无 | `string` | 当前时间,格式 `YYYY-MM-DD HH:mm:ss` |
42
+ | `getCurrentDateFormat` | `format: string` | `string` | 按指定格式返回当前时间 |
43
+ | `getYesterdayDateFormat` | `format: string` | `string` | 按指定格式返回昨天时间 |
44
+
45
+ **示例:**
46
+
47
+ ```typescript
48
+ updateCfg: {
49
+ 'data.created_at': { functionName: 'getCurrentTimeString' },
50
+ 'data.created_ts': { functionName: 'getCurrentTimeStampMs' },
51
+ 'data.order_date': { functionName: 'getCurrentDateFormat', functionParams: ['YYYYMMDD'] },
52
+ 'data.yesterday': { functionName: 'getYesterdayDateFormat', functionParams: ['YYYY-MM-DD'] },
53
+ }
54
+ ```
55
+
56
+ ## CompareUtils — 比较运算
57
+
58
+ | 函数名 | 参数 | 返回值 | 说明 |
59
+ |--------|------|--------|------|
60
+ | `eq` | `a: any, b: any` | `boolean` | 相等比较(`==`,兼容类型转换) |
61
+ | `ne` | `a: any, b: any` | `boolean` | 不相等 |
62
+ | `gt` | `a: any, b: any` | `boolean` | 大于 |
63
+ | `gte` | `a: any, b: any` | `boolean` | 大于等于 |
64
+ | `lt` | `a: any, b: any` | `boolean` | 小于 |
65
+ | `lte` | `a: any, b: any` | `boolean` | 小于等于 |
66
+
67
+ **示例:**
68
+
69
+ ```typescript
70
+ // executeWhen:仅当 is_exist 为 false 时执行插入
71
+ executeWhen: {
72
+ functionName: 'eq',
73
+ functionParams: [{ contextAsBool: 'res.is_exist' }, { constBool: false }]
74
+ }
75
+
76
+ // validate:确保数量大于 0
77
+ validate: {
78
+ functionName: 'gt',
79
+ functionParams: [{ context: '$current' }, { constNumber: 0 }],
80
+ message: '数量必须大于0'
81
+ }
82
+ ```
83
+
84
+ ## MixinUtils — 通用工具
85
+
86
+ | 函数名 | 参数 | 返回值 | 说明 |
87
+ |--------|------|--------|------|
88
+ | `isNil` | `obj: any` | `boolean` | 是否为 null 或 undefined |
89
+ | `isNotNil` | `obj: any` | `boolean` | 不为 null 且不为 undefined |
90
+ | `isEmpty` | `obj: any` | `boolean` | 是否为空(null / undefined / 空字符串 / 空数组 / 空对象) |
91
+ | `isNotEmpty` | `obj: any` | `boolean` | 是否非空 |
92
+ | `equals` | `obj1: any, obj2: any` | `boolean` | 严格相等(`===`) |
93
+ | `equalsIgnoreCase` | `str1: string, str2: string` | `boolean` | 忽略大小写比较字符串 |
94
+ | `startsWith` | `str1: string, str2: string` | `boolean` | 字符串前缀判断 |
95
+ | `isValidFieldName` | `s: string` | `boolean` | 合法字段名(字母开头,仅含字母/数字/下划线) |
96
+ | `selectNotEmpty` | `a: any, b: any` | `any` | 返回第一个非空值 |
97
+ | `hasAny` | `a: string[], b: string[]` | `boolean` | 集合 A 是否包含集合 B 中的任意元素 |
98
+ | `parseValueByType` | `value: any, targetType: string` | `any` | 按类型转换值(string / number / boolean / array / JSONObject) |
99
+ | `sleepMs` | `ms: number` | `Promise` | 延时等待 |
100
+ | `uuid` | 无 | `string` | 生成 UUID v4(优先 `crypto.getRandomValues`,降级 `Math.random`) |
101
+ | `generateSnowflakeId` | `machineId?: number` | `string` | 生成雪花ID(64位整数字符串) |
102
+
103
+ **uuid 和雪花ID 示例:**
104
+
105
+ ```typescript
106
+ updateCfg: {
107
+ // UUID v4:适合需要标准唯一标识的场景
108
+ 'data.id': { functionName: 'uuid' },
109
+
110
+ // 雪花ID:适合需要趋势递增、时间有序的场景
111
+ 'data.order_id': { functionName: 'generateSnowflakeId' },
112
+
113
+ // 雪花ID:指定机器ID(0-1023),多实例部署时区分
114
+ 'data.trace_id': { functionName: 'generateSnowflakeId', functionParams: [1] },
115
+ }
116
+ ```
117
+
118
+ > **雪花ID machineId 获取优先级**:函数参数 > 环境变量 `SNOWFLAKE_MACHINE_ID` > `process.pid` 低10位。多实例部署时建议设置环境变量 `SNOWFLAKE_MACHINE_ID` 为 0-1023 的唯一值。
119
+
120
+ ## TypeUtils — 类型判断
121
+
122
+ | 函数名 | 参数 | 返回值 | 说明 |
123
+ |--------|------|--------|------|
124
+ | `isBasicType` | `value: any` | `boolean` | 基础类型(boolean / string / number / null) |
125
+ | `isBoolean` | `value: any` | `boolean` | 布尔值 |
126
+ | `isString` | `value: any` | `boolean` | 字符串 |
127
+ | `isInteger` | `value: any` | `boolean` | 整数 |
128
+ | `isNumber` | `value: any` | `boolean` | 数字 |
129
+ | `isNumeric` | `value: any` | `boolean` | 数字或可转换数字的字符串 |
130
+ | `isValidName` | `value: any` | `boolean` | 合法命名(字母开头,字母/数字/下划线) |
131
+ | `isChinesePhone` | `value: string` | `boolean` | 中文手机号 |
132
+ | `isEmailStrValid` | `value: string` | `boolean` | 合法邮箱地址 |
133
+ | `pickNumber` | `s: string` | `number \| null` | 从字符串中提取数字部分 |
134
+
135
+ ## 自定义函数扩展
136
+
137
+ 除内置函数外,可通过覆写 `IExecuteContextFunc.getFunctionMap()` 注册自定义函数。
138
+
139
+ 函数查找顺序:`getFunctionMap()` → `buildInExecuteFuncMap`(内置函数),自定义函数优先级更高,可覆盖内置函数。
140
+
141
+ ### 注册方式
142
+
143
+ 继承 `BaseExecuteContextFunc`,覆写 `getFunctionMap()` 方法返回函数映射表:
144
+
145
+ ```typescript
146
+ import { BaseExecuteContextFunc } from './libs/crud-pro/models/ExecuteContextFunc';
147
+
148
+ class MyContextFunc extends BaseExecuteContextFunc {
149
+
150
+ // 覆写此方法,返回自定义函数映射表
151
+ getFunctionMap(): any {
152
+ return {
153
+ // 无参函数
154
+ generateOrderNo: (prefix: string) => {
155
+ return prefix + Date.now().toString(36).toUpperCase();
156
+ },
157
+
158
+ // 带参函数:参数来自 functionParams 配置
159
+ formatAmount: (amount: number, currency: string) => {
160
+ return currency + ' ' + amount.toFixed(2);
161
+ },
162
+
163
+ // 可访问 FuncContext 的函数(通过 this 关键字)
164
+ getVisitorName: function(this: any) {
165
+ return this.reqModel?.visitor?.nickName || 'anonymous';
166
+ },
167
+ };
168
+ }
169
+ }
170
+
171
+ // 创建 CrudPro 实例时注入
172
+ const crudPro = new CrudPro();
173
+ crudPro.contextFunc = new MyContextFunc(/* 构造参数 */);
174
+ ```
175
+
176
+ ### 在配置中使用
177
+
178
+ 注册后即可在 `updateCfg`、`validateCfg`、`functionCfg` 等场景中通过 `functionName` 调用:
179
+
180
+ ```typescript
181
+ const cfgJson: IRequestCfgModel = {
182
+ method: 'order.create',
183
+ sqlSimpleName: KeysOfSimpleSQL.SIMPLE_INSERT,
184
+ updateCfg: {
185
+ 'data.order_no': { functionName: 'generateOrderNo', functionParams: ['PRE'] },
186
+ },
187
+ };
188
+ ```
189
+
190
+ > **注意**:`functionParams` 中配置的参数会按顺序传入函数。如果函数需要访问 `FuncContext`(如 `reqModel`、`resModel`、`currentValue`),
191
+ > 需使用普通 `function`(非箭头函数)并通过 `this` 访问,框架会以 `func.apply(funcContext, params)` 方式调用。
192
+
193
+ > **注意**:`generateOrderNo` 等并非内置函数,需通过 `contextFunc` 扩展注册后才可使用,否则运行时抛出 `RUN_FUNCTION_NOT_FOUND` 异常。
@@ -49,6 +49,7 @@ export enum Exceptions {
49
49
  CFG_UNIQUE_COLUMN_COUNT_MUST_ONE = 'CFG_UNIQUE_COLUMN_COUNT_MUST_ONE',
50
50
  CFG_ORDER_BY_TYPE_ERROR = 'CFG_ORDER_BY_TYPE_ERROR',
51
51
  CFG_ERROR_POSTGRES_COLUMN_NAME_INVALID = 'CFG_ERROR_POSTGRES_COLUMN_NAME_INVALID',
52
+ CFG_INVALID_DATA_TYPE = 'CFG_INVALID_DATA_TYPE',
52
53
 
53
54
  /**
54
55
  * 数据传错
@@ -58,6 +59,7 @@ export enum Exceptions {
58
59
  DATA_GET_DATA_EMPTY_ON_UPDATE = 'DATA_GET_DATA_EMPTY_ON_UPDATE',
59
60
  DATA_GET_DATA_EMPTY_ON_INSERT_KEYS = 'DATA_GET_DATA_EMPTY_ON_INSERT_KEYS',
60
61
  DATA_GET_DATA_EMPTY_ON_INSERT_VALUES = 'DATA_GET_DATA_EMPTY_ON_INSERT_VALUES',
62
+ BATCH_INSERT_KEYS_MISMATCH = 'BATCH_INSERT_KEYS_MISMATCH', // 批量插入时字段不一致
61
63
 
62
64
  /**
63
65
  * 请求的Method字段为空
@@ -54,7 +54,7 @@ export interface IRequestModel {
54
54
  method?: string;
55
55
  columns?: string | string[]; // "columns": "id,name,age,sex,addr",
56
56
  condition?: Record<string, any>; // 拼接查询条件
57
- data?: Record<string, any>; //拼接update/insert语句的数据部分
57
+ data?: Record<string, any> | Record<string, any>[]; // 单条或批量插入/更新的数据部分
58
58
  orderBy?: string | IOrderByItem[];
59
59
  limit?: number;
60
60
  offset?: number;
@@ -182,6 +182,7 @@ export interface IRequestCfgModel extends IBaseCfgModel {
182
182
  transactionIsolation?: number; // 事务隔离级别
183
183
  sqlSimpleName?: KeysOfSimpleSQL; // 二选一 sqlSimpleName sqlCfgList;; sqlCfgList 优先级高于sqlSimpleName
184
184
  sqlCfgList?: ISqlCfgModel[]; // 二选一 sqlSimpleName sqlCfgList;; sqlCfgList 优先级高于sqlSimpleName
185
+ uniqueColumn?: string[]; // PostgreSQL/SQL Server 的唯一键列,用于 INSERT ON CONFLICT / MERGE
185
186
  updateCfg?: Record<string, IFuncCfgModel>;
186
187
  allowCfg?: Record<string, string[]>;
187
188
  rejectCfg?: Record<string, string[]>;
@@ -223,3 +224,39 @@ export interface ITableMetaQuery {
223
224
  sqlDbType: SqlDbType;
224
225
  sqlSchema?: string;
225
226
  }
227
+
228
+ /**
229
+ * 表名列表查询参数
230
+ */
231
+ export interface ITableNamesQuery {
232
+ sqlDatabase: string;
233
+ sqlDbType: SqlDbType;
234
+ sqlSchema?: string;
235
+ }
236
+
237
+ /**
238
+ * 表名查询缓存选项
239
+ */
240
+ export interface ITableNamesOptions {
241
+ /** 是否跳过缓存,直接查询数据库 */
242
+ skipCache?: boolean;
243
+ /** 查询后是否更新缓存 */
244
+ refreshCache?: boolean;
245
+ }
246
+
247
+ /**
248
+ * 表信息项(包含表名和类型)
249
+ */
250
+ export interface ITableInfo {
251
+ name: string; // 表名
252
+ tableType: string; // 类型:'table' | 'view'
253
+ }
254
+
255
+ /**
256
+ * 表信息查询结果
257
+ */
258
+ export interface ITableListResult {
259
+ tables: ITableInfo[]; // 所有表和视图
260
+ tableNames: string[]; // 仅表名(不含视图)
261
+ viewNames: string[]; // 仅视图名
262
+ }
@@ -7,7 +7,7 @@ import { Transaction } from './Transaction';
7
7
  import { ColumnRelation, ICrudProCfg, ILogger, IVisitor } from '../interfaces';
8
8
  import { IExecuteContextFunc } from './ExecuteContextFunc';
9
9
  import { defaultCrudProCfg } from '../defaultConfigs';
10
- import { ResModelFlexible, ResModelPageQuery } from './ResModel';
10
+ import { ResModelFlexible, ResModelPageQuery, ResModelRowEntity } from './ResModel';
11
11
 
12
12
  class ExecuteContext {
13
13
  // 运行前设置的
@@ -66,11 +66,14 @@ class ExecuteContext {
66
66
  return this.resModel;
67
67
  }
68
68
 
69
- getOneObj() {
69
+
70
+ // 获取单条数据对象
71
+ getOneObj(): ResModelRowEntity {
70
72
  return this.resModel['row'];
71
73
  }
72
74
 
73
- getResRows(): any[] {
75
+ // 获取多条数据对象
76
+ getResRows(): ResModelRowEntity[] {
74
77
  return this.resModel['rows'];
75
78
  }
76
79
 
@@ -10,7 +10,7 @@ class RequestModel {
10
10
  method: string;
11
11
  columns?: string[]; // "columns": "id,name,age,sex,addr",
12
12
  condition?: Record<string, any>;
13
- data?: Record<string, any>;
13
+ data?: Record<string, any> | Record<string, any>[]; // 支持单条或批量数据
14
14
  limit: number;
15
15
  offset: number;
16
16
  orderBys?: IOrderByItem[];
@@ -25,67 +25,111 @@ class RequestModel {
25
25
  this.offset = limitOffset.offset;
26
26
  }
27
27
 
28
+ /**
29
+ * 解析 orderBy 参数为 IOrderByItem 数组
30
+ *
31
+ * 支持的格式:
32
+ * 1. 字符串格式(逗号分隔多个字段):
33
+ * - 标准 SQL 格式:'created_at DESC, amount ASC'
34
+ * - 简写 +/- 格式:'created_at-, amount+'('-' 表示 DESC,'+' 或省略表示 ASC)
35
+ * - 默认升序:'order_id'(无后缀时默认为 ASC)
36
+ *
37
+ * 2. 数组格式:
38
+ * - 纯对象数组:[{ fieldName: 'created_at', orderType: 'desc' }]
39
+ * - 混合数组1(字符串+对象):['order_id', { fieldName: 'amount', orderType: 'asc' }]
40
+ * - 混合数组2(字符串+对象):['order_id+', { fieldName: 'amount', orderType: 'asc' }]
41
+ * - 混合数组3(字符串+对象):['order_id DESC', { fieldName: 'amount', orderType: 'asc' }]
42
+ * 注:数组中的字符串元素支持标准 SQL 格式和简写格式,与字符串参数格式一致。
43
+ *
44
+ * SQL 注入防护:
45
+ * - 所有字段名必须通过 MixinUtils.isValidFieldName() 校验
46
+ * - 只允许 ASC/DESC 作为排序方向
47
+ *
48
+ * @param orderByStr 排序参数,可以是字符串或数组
49
+ * @returns IOrderByItem[] 解析后的排序项数组
50
+ */
28
51
  private parseOrderBys(orderByStr: any): IOrderByItem[] {
29
52
  if (MixinUtils.isEmpty(orderByStr)) {
30
53
  return [];
31
54
  }
32
55
 
56
+ // 数组格式:支持字符串和对象混合
33
57
  if (Array.isArray(orderByStr)) {
34
58
  return orderByStr
35
- .map(obj => {
36
- if (typeof obj === 'string') {
37
- return { fieldName: obj, orderType: 'asc' };
38
- }
39
-
40
- const { fieldName, orderType = 'asc' } = obj;
41
- if (fieldName) {
42
- return { fieldName, orderType };
43
- }
44
-
45
- return null;
46
- })
47
- .filter(o => {
48
- return !!o;
49
- });
59
+ .map(item => this.parseOrderByItem(item))
60
+ .filter((o): o is IOrderByItem => !!o);
50
61
  }
51
62
 
52
- const orderByResult: IOrderByItem[] = [];
53
- const orderByArray = orderByStr
63
+ // 字符串格式:逗号分隔多个字段
64
+ return orderByStr
54
65
  .split(',')
55
- .map(s => {
56
- return s.trim();
57
- })
58
- .filter(s => !!s);
66
+ .map(s => s.trim())
67
+ .filter(s => !!s)
68
+ .map(item => this.parseOrderByString(item))
69
+ .filter((o): o is IOrderByItem => !!o);
70
+ }
59
71
 
60
- if (MixinUtils.isEmpty(orderByArray)) {
61
- return orderByResult;
72
+ /**
73
+ * 解析单个排序项(数组元素)
74
+ */
75
+ private parseOrderByItem(item: any): IOrderByItem | null {
76
+ // 字符串格式:解析标准 SQL 格式或简写格式
77
+ if (typeof item === 'string') {
78
+ return this.parseOrderByString(item);
62
79
  }
63
80
 
64
- for (let i = 0; i < orderByArray.length; i++) {
65
- const orderByItem = orderByArray[i];
66
- let orderType = 'asc'; //默认升序
67
- let fieldName = orderByItem;
68
-
69
- if (orderByItem.endsWith('+')) {
70
- // 升序
71
- fieldName = orderByItem.substring(0, orderByItem.length - 1);
72
- orderType = 'asc'; //升序
73
- } else if (orderByItem.endsWith('-')) {
74
- //降序
75
- fieldName = orderByItem.substring(0, orderByItem.length - 1);
76
- orderType = 'desc'; //降序
77
- }
81
+ // 对象格式:提取 fieldName orderType
82
+ const { fieldName, orderType = 'asc' } = item || {};
83
+ if (!fieldName) {
84
+ return null;
85
+ }
78
86
 
79
- fieldName = fieldName.trim();
87
+ this.validateFieldName(fieldName, fieldName);
88
+ return { fieldName, orderType };
89
+ }
80
90
 
81
- if (MixinUtils.isEmpty(fieldName) || !MixinUtils.isValidFieldName(fieldName)) {
82
- throw new CommonException(Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, orderByItem);
91
+ /**
92
+ * 解析字符串格式的排序项(支持标准 SQL 和简写格式)
93
+ */
94
+ private parseOrderByString(orderByStr: string): IOrderByItem | null {
95
+ let orderType = 'asc';
96
+ let fieldName = orderByStr;
97
+
98
+ // 检查是否为空格分隔的标准 SQL 格式(如 'created_at DESC')
99
+ const spaceIndex = orderByStr.lastIndexOf(' ');
100
+ if (spaceIndex > 0) {
101
+ const beforeSpace = orderByStr.substring(0, spaceIndex).trim();
102
+ const afterSpace = orderByStr.substring(spaceIndex + 1).trim().toUpperCase();
103
+
104
+ if (afterSpace === 'ASC' || afterSpace === 'DESC') {
105
+ fieldName = beforeSpace;
106
+ orderType = afterSpace.toLowerCase() as 'asc' | 'desc';
107
+ } else {
108
+ throw new CommonException(Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, orderByStr);
83
109
  }
84
-
85
- orderByResult.push({ fieldName, orderType });
110
+ } else if (orderByStr.endsWith('+')) {
111
+ // 简写格式:+ 表示升序
112
+ fieldName = orderByStr.slice(0, -1);
113
+ orderType = 'asc';
114
+ } else if (orderByStr.endsWith('-')) {
115
+ // 简写格式:- 表示降序
116
+ fieldName = orderByStr.slice(0, -1);
117
+ orderType = 'desc';
86
118
  }
87
119
 
88
- return orderByResult;
120
+ fieldName = fieldName.trim();
121
+ this.validateFieldName(fieldName, orderByStr);
122
+
123
+ return { fieldName, orderType };
124
+ }
125
+
126
+ /**
127
+ * SQL 注入防护:校验字段名格式
128
+ */
129
+ private validateFieldName(fieldName: string, originalValue: string): void {
130
+ if (MixinUtils.isEmpty(fieldName) || !MixinUtils.isValidFieldName(fieldName)) {
131
+ throw new CommonException(Exceptions.REQUEST_MODEL_PARSE_ORDER_BY_FAILED, originalValue);
132
+ }
89
133
  }
90
134
 
91
135
  private parseOffsetList(req: IRequestModel): ILimitOffset {
@@ -129,6 +173,26 @@ class RequestModel {
129
173
  throw new CommonException(Exceptions.RUN_GET_COND_OR_DATA_JSONOBJECT, attrName);
130
174
  }
131
175
 
176
+ /**
177
+ * 获取数组类型的数据(用于批量插入)
178
+ * @param attrName
179
+ */
180
+ getCondOrDataAsArray(attrName: string): Record<string, any>[] {
181
+ if (attrName.startsWith('data')) {
182
+ const dataValue = _.get(this, attrName);
183
+ if (!dataValue) {
184
+ return null;
185
+ }
186
+ // 如果是数组直接返回
187
+ if (Array.isArray(dataValue)) {
188
+ return dataValue;
189
+ }
190
+
191
+ throw new CommonException(Exceptions.RUN_GET_COND_OR_DATA_JSONOBJECT, attrName);
192
+ }
193
+ throw new CommonException(Exceptions.RUN_GET_COND_OR_DATA_JSONOBJECT, attrName);
194
+ }
195
+
132
196
  /**
133
197
  * 获取基本数据类型
134
198
  * @param attrName
@@ -3,17 +3,23 @@ interface ResModelAffected {
3
3
  affectedRows: number;
4
4
  }
5
5
 
6
+
7
+ // 单条数据对象
8
+ type ResModelRowEntity = Record<string, any>;
9
+
10
+
6
11
  interface ResModelStandard {
7
- row?: any;
8
- rows?: any[];
12
+ row?: ResModelRowEntity;
13
+ rows?: ResModelRowEntity[];
9
14
  total_count?: number;
15
+ is_exist?: boolean;
10
16
  affected?: ResModelAffected;
11
17
  insert_affected?: ResModelAffected;
12
18
  update_affected?: ResModelAffected;
13
19
  }
14
20
 
15
21
  interface ResModelPageQuery {
16
- rows: any[];
22
+ rows: ResModelRowEntity[];
17
23
  total_count: number;
18
24
  }
19
25
 
@@ -21,4 +27,4 @@ type ResModelFlexible = ResModelStandard & {
21
27
  [key: string]: any;
22
28
  };
23
29
 
24
- export { ResModelAffected, ResModelStandard, ResModelFlexible, ResModelPageQuery };
30
+ export { ResModelAffected, ResModelStandard, ResModelFlexible, ResModelPageQuery, ResModelRowEntity };
@@ -29,4 +29,6 @@ export interface ICurdProServiceHub {
29
29
  executeFuncCfg(tmpFunCfg: IFuncCfgModel, exeFunCtx: FuncContext): string;
30
30
 
31
31
  getTableMeta(query: ITableMetaQuery): Promise<ITableMeta>;
32
+
33
+ convertDataTypeByTableMeta(reqModel: RequestModel, cfgModel: RequestCfgModel): Promise<void>;
32
34
  }
@@ -57,8 +57,10 @@ export enum KeysOfSimpleSQL {
57
57
  SIMPLE_QUERY_ONE = 'SIMPLE_QUERY_ONE', //只查询一条 select * from xx where @condition limit 1,
58
58
  SIMPLE_QUERY_PAGE = 'SIMPLE_QUERY_PAGE', //分页查询 select * from xx where @condition limit 100 offset 100,
59
59
  SIMPLE_QUERY_COUNT = 'SIMPLE_QUERY_COUNT', //查询数量 select count(0) as totalCount from xx where @condition,
60
+ SIMPLE_QUERY_EXIST = 'SIMPLE_QUERY_EXIST', //判断一个元素是否存在
60
61
  SIMPLE_UPDATE = 'SIMPLE_UPDATE', // update xx set @data where @condition limit 1
61
62
  SIMPLE_INSERT = 'SIMPLE_INSERT', // insert into xx values @datas
63
+ SIMPLE_BATCH_INSERT = 'SIMPLE_BATCH_INSERT', // 批量插入 insert into xx values (...),(...),(...)
62
64
  SIMPLE_DELETE = 'SIMPLE_DELETE', // delete from xx values where @condition limit 1
63
65
  SIMPLE_INSERT_ON_DUPLICATE_UPDATE = 'SIMPLE_INSERT_ON_DUPLICATE_UPDATE',
64
66
  SIMPLE_INSERT_OR_UPDATE = 'SIMPLE_INSERT_OR_UPDATE',
@@ -70,6 +72,7 @@ export enum KeysOfSqlResPicker {
70
72
  RESULT_FIRST_ROW = 'RESULT_FIRST_ROW', // $ResultSet[0], 只取第一行的数据返回。返回JSONObject
71
73
  RESULT_TOTAL_COUNT = 'RESULT_TOTAL_COUNT', // $ResultSet[0].total_count , 只取第一行的的total_count。返回long
72
74
  RESULT_ONE_VALUE = 'RESULT_ONE_VALUE', // $ResultSet[0].total_count , 只取第一行的的total_count。返回long
75
+ RESULT_IS_EXIST = 'RESULT_IS_EXIST', // 返回boolean
73
76
  }
74
77
 
75
78
  export const KeysOfCustomSQL = {
@@ -82,6 +85,8 @@ export const KeysOfCustomSQL = {
82
85
  SQL_AS_UPDATE: '@@asUpdate:', // @@asUpdate:data, @@asWhere:data_zhangsan
83
86
  SQL_AS_INSERT_KEYS: '@@asInsertKeys:', // @@asInsertKeys:data, @@asInsertKeys:data_zhangsan
84
87
  SQL_AS_INSERT_VALUES: '@@asInsertValues:', // @@asInsertValues:data, @@asInsertValues:data_zhangsan
88
+ SQL_AS_BATCH_INSERT_KEYS: '@@asBatchInsertKeys:', // 批量插入:列名部分,取第一条数据的keys
89
+ SQL_AS_BATCH_INSERT_VALUES: '@@asBatchInsertValues:', // 批量插入:值部分,生成 (?,?,?),(?,?),...
85
90
 
86
91
  // 从Req中取值:单独取Data中的某个字段:获取单个值。
87
92
  SQL_GET_DATA_ATTR_START: '@@data', // // select * from fa_lang_faq where id = @@data.id