midway-fatcms 0.0.6 → 0.0.7

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.
@@ -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 = {
@@ -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;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ShardingCrudPro = void 0;
4
+ const OrderByUtils_1 = require("../../libs/crud-pro/utils/OrderByUtils");
4
5
  const keys_1 = require("../../libs/crud-pro/models/keys");
5
6
  const ShardingConfig_1 = require("./ShardingConfig");
6
7
  const ShardingRouter_1 = require("./ShardingRouter");
@@ -603,38 +604,26 @@ class ShardingCrudPro {
603
604
  return;
604
605
  }
605
606
  const orderBy = reqJson.orderBy;
606
- const expectedDesc = `${timeColumn} DESC`;
607
- const expectedAsc = `${timeColumn} ASC`;
608
607
  // 1. 必须传 orderBy
609
608
  if (!orderBy) {
610
609
  throw new Error(`[ShardingCrudPro] 查询操作必须传 orderBy 参数。` +
611
610
  `期望值: '${timeColumn} DESC' 或 '${timeColumn} ASC'`);
612
611
  }
613
- // 2. 校验 orderBy 格式(直接字符串比较,避免正则转义问题)
614
- if (typeof orderBy === 'string') {
615
- const upper = orderBy.trim().toUpperCase();
616
- if (upper !== expectedDesc.toUpperCase() && upper !== expectedAsc.toUpperCase()) {
617
- throw new Error(`[ShardingCrudPro] orderBy 参数必须是 '${timeColumn} DESC' 或 '${timeColumn} ASC',` +
618
- `当前值: '${orderBy}'`);
619
- }
612
+ // 2. 使用工具类解析并校验首个排序字段是否为 timeColumn
613
+ const firstOrderBy = OrderByUtils_1.OrderByUtils.getFirstOrderBy(orderBy);
614
+ if (!firstOrderBy) {
615
+ throw new Error(`[ShardingCrudPro] orderBy 参数格式错误,无法解析。` +
616
+ `期望值: '${timeColumn} DESC' 或 '${timeColumn} ASC'`);
620
617
  }
621
- else if (Array.isArray(orderBy)) {
622
- // 数组格式:首个排序字段必须是 timeColumn DESC/ASC,允许附加次级排序
623
- if (orderBy.length === 0) {
624
- throw new Error(`[ShardingCrudPro] orderBy 数组不能为空,首个排序字段必须为 '${timeColumn} DESC' 或 '${timeColumn} ASC'`);
625
- }
626
- const firstItem = orderBy[0];
627
- const firstItemStr = typeof firstItem === 'string'
628
- ? firstItem
629
- : `${firstItem.fieldName} ${firstItem.orderType || 'ASC'}`;
630
- const upper = firstItemStr.trim().toUpperCase();
631
- if (upper !== expectedDesc.toUpperCase() && upper !== expectedAsc.toUpperCase()) {
632
- throw new Error(`[ShardingCrudPro] orderBy 数组首个排序字段必须为 '${timeColumn} DESC' 或 '${timeColumn} ASC',` +
633
- `当前值: '${firstItemStr}'`);
634
- }
618
+ if (firstOrderBy.fieldName !== timeColumn) {
619
+ throw new Error(`[ShardingCrudPro] orderBy 首个排序字段必须为 '${timeColumn}',` +
620
+ `当前值: '${firstOrderBy.fieldName}'`);
635
621
  }
636
- else {
637
- throw new Error(`[ShardingCrudPro] orderBy 参数格式错误,期望字符串或数组。`);
622
+ // 校验排序方向是否为 ASC 或 DESC
623
+ const orderType = firstOrderBy.orderType.toUpperCase();
624
+ if (orderType !== 'ASC' && orderType !== 'DESC') {
625
+ throw new Error(`[ShardingCrudPro] orderBy 排序方向必须是 'ASC' 或 'DESC',` +
626
+ `当前值: '${firstOrderBy.orderType}'`);
638
627
  }
639
628
  }
640
629
  /**
@@ -646,20 +635,7 @@ class ShardingCrudPro {
646
635
  * @returns true 表示 ASC,false 表示 DESC
647
636
  */
648
637
  isAscOrderBy(reqJson) {
649
- const orderBy = reqJson.orderBy;
650
- if (!orderBy)
651
- return false;
652
- if (typeof orderBy === 'string') {
653
- return orderBy.trim().toUpperCase().endsWith('ASC');
654
- }
655
- if (Array.isArray(orderBy) && orderBy.length > 0) {
656
- const firstItem = orderBy[0];
657
- const firstItemStr = typeof firstItem === 'string'
658
- ? firstItem
659
- : `${firstItem.fieldName} ${firstItem.orderType || 'ASC'}`;
660
- return firstItemStr.trim().toUpperCase().endsWith('ASC');
661
- }
662
- return false;
638
+ return OrderByUtils_1.OrderByUtils.isFirstOrderByAsc(reqJson.orderBy);
663
639
  }
664
640
  /**
665
641
  * 根据排序方向对分表列表进行排序
@@ -55,8 +55,8 @@ export declare class ShardingMerger {
55
55
  * - orderBy 为 timeColumn DESC 或 timeColumn ASC
56
56
  *
57
57
  * 执行流程:
58
- * 1. 并行查询所有分表
59
- * 2. 按表顺序拼接结果(无需排序)
58
+ * 1. 串行查询分表,按表顺序拼接
59
+ * 2. 达到 maxRows 上限后立即停止,避免查询无关表
60
60
  *
61
61
  * @param crudPro CrudPro 实例
62
62
  * @param tables 分表列表(DESC: 新→旧,ASC: 旧→新)
@@ -75,8 +75,8 @@ class ShardingMerger {
75
75
  * - orderBy 为 timeColumn DESC 或 timeColumn ASC
76
76
  *
77
77
  * 执行流程:
78
- * 1. 并行查询所有分表
79
- * 2. 按表顺序拼接结果(无需排序)
78
+ * 1. 串行查询分表,按表顺序拼接
79
+ * 2. 达到 maxRows 上限后立即停止,避免查询无关表
80
80
  *
81
81
  * @param crudPro CrudPro 实例
82
82
  * @param tables 分表列表(DESC: 新→旧,ASC: 旧→新)
@@ -86,14 +86,16 @@ class ShardingMerger {
86
86
  * @returns 数据列表
87
87
  */
88
88
  async mergeQuery(crudPro, tables, reqJson, cfgJson, maxRows = 10000) {
89
- // 并行查询所有分表
90
- const dataPromises = tables.map(table => this.queryRowsSafe(crudPro, table, reqJson, cfgJson, maxRows));
91
- const dataResults = await Promise.all(dataPromises);
92
- // 按表顺序拼接(表顺序即数据顺序,无需排序)
89
+ // 串行查询分表,按表顺序拼接,达到上限即停
93
90
  let allRows = [];
94
- for (let i = 0; i < dataResults.length; i++) {
95
- allRows = allRows.concat(dataResults[i]);
96
- // 达到上限后停止
91
+ for (const table of tables) {
92
+ const needMore = maxRows - allRows.length;
93
+ if (needMore <= 0) {
94
+ break;
95
+ }
96
+ const rows = await this.queryRowsSafe(crudPro, table, reqJson, cfgJson, needMore);
97
+ allRows = allRows.concat(rows);
98
+ // 达到上限后停止,不再查询后续表
97
99
  if (allRows.length >= maxRows) {
98
100
  allRows = allRows.slice(0, maxRows);
99
101
  break;
@@ -824,7 +824,30 @@ await sharding.insert({
824
824
 
825
825
  ### linkColumnRelationDatas
826
826
 
827
- 独立于 CRUD 操作,直接对已有数据行执行关联填充:
827
+ 独立于 CRUD 操作,直接对已有数据行执行关联填充。适用于数据来自缓存、外部接口或其他非 CurdMixService 渠道,但仍需要字典翻译、用户信息等关联数据的场景。
828
+
829
+ > ⚠️ 该方法会**原地修改** `rows` 数组中的对象,不会返回新数组。
830
+
831
+ #### 方法签名
832
+
833
+ ```typescript
834
+ linkColumnRelationDatas(rows: any[], param: ILinkColumnRelationParam): Promise<void>
835
+ ```
836
+
837
+ #### 参数接口
838
+
839
+ ```typescript
840
+ interface ILinkColumnRelationParam {
841
+ columnsRelations: ColumnRelation[]; // 关联配置数组
842
+ }
843
+ ```
844
+
845
+ | 参数 | 类型 | 说明 |
846
+ |------|------|------|
847
+ | `rows` | `any[]` | 待填充的数据行数组;为空或非数组时直接返回 |
848
+ | `param.columnsRelations` | `ColumnRelation[]` | 关联规则配置,与 CRUD 配置中的 `columnsRelation` 格式一致 |
849
+
850
+ #### 基础用法
828
851
 
829
852
  ```typescript
830
853
  // 场景:从缓存或其他来源获取了数据行,仍需要关联填充
@@ -840,9 +863,85 @@ await curdMixService.linkColumnRelationDatas(rows, {
840
863
  },
841
864
  ],
842
865
  });
843
- // rows 中的每行会自动追加 sex_text 字段
866
+ // rows 中的每行会原地追加 sex_text 字段
844
867
  ```
845
868
 
869
+ #### 多关联类型组合
870
+
871
+ ```typescript
872
+ const rows = await redisCache.get('order_list');
873
+
874
+ await curdMixService.linkColumnRelationDatas(rows, {
875
+ columnsRelations: [
876
+ // 字典翻译
877
+ {
878
+ relatedType: 'dict',
879
+ relatedCode: 'OrderStatus',
880
+ sourceColumn: 'status',
881
+ targetColumns: [
882
+ { from: 'label', to: 'status_text' },
883
+ { from: 'color', to: 'status_color' },
884
+ ],
885
+ },
886
+ // 系统配置枚举
887
+ {
888
+ relatedType: 'sysCfgEnum',
889
+ relatedCode: 'PayMethod',
890
+ sourceColumn: 'pay_method',
891
+ targetColumns: [
892
+ { from: 'label', to: 'pay_method_info.label' },
893
+ { from: 'style', to: 'pay_method_info.style' },
894
+ ],
895
+ },
896
+ // 用户信息填充
897
+ {
898
+ relatedType: 'accountBasic',
899
+ sourceColumn: 'created_by',
900
+ targetColumns: [], // 空 = 使用默认映射
901
+ },
902
+ // 工作台信息
903
+ {
904
+ relatedType: 'workbenchBasic',
905
+ sourceColumn: 'workbench_code',
906
+ targetColumns: [
907
+ { from: 'workbench_domain', to: 'workbench_info.workbench_domain' },
908
+ { from: 'workbench_name', to: 'workbench_info.workbench_name' },
909
+ ],
910
+ },
911
+ ],
912
+ });
913
+ ```
914
+
915
+ #### 与 CrudProQuick 配合使用
916
+
917
+ ```typescript
918
+ const quick = curdProService.getQuickCrud('mydb', SqlDbType.mysql, 't_order');
919
+
920
+ // 先用 CrudProQuick 获取原始数据(不带关联)
921
+ const rows = await quick.getList({
922
+ condition: { status: 'paid' },
923
+ });
924
+
925
+ // 再用 linkColumnRelationDatas 补充关联数据
926
+ await curdMixService.linkColumnRelationDatas(rows, {
927
+ columnsRelations: [
928
+ {
929
+ relatedType: 'dict',
930
+ relatedCode: 'OrderStatus',
931
+ sourceColumn: 'status',
932
+ targetColumns: [{ from: 'label', to: 'status_text' }],
933
+ },
934
+ {
935
+ relatedType: 'accountBasic',
936
+ sourceColumn: 'created_by',
937
+ targetColumns: [],
938
+ },
939
+ ],
940
+ });
941
+ ```
942
+
943
+ > **提示**:如果 CRUD 操作本身就需要关联填充,建议直接使用 `CurdMixService.executeCrudByCfg()` 配置 `columnsRelation`,一步完成查询和填充。`linkColumnRelationDatas` 适用于数据已存在的二次填充场景。
944
+
846
945
  ## ColumnRelation 配置
847
946
 
848
947
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "midway-fatcms",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "This is a midway component sample",
5
5
  "main": "dist/index.js",
6
6
  "typings": "index.d.ts",
@@ -30,6 +30,7 @@
30
30
  "src/**/*.html",
31
31
  "src/**/*.ico",
32
32
  "src/**/*.md",
33
+ ".qoder/**/*.md",
33
34
  "index.d.ts",
34
35
  "tsconfig.json",
35
36
  ".prettierrc.js",