whistle.mock-plugins 1.1.0 → 1.1.1-beta.2

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,113 @@ const path = require('path');
3
3
  const url = require('url');
4
4
  const Mock = require('mockjs');
5
5
 
6
+ // 获取嵌套对象属性值的工具函数
7
+ const getNestedValue = (obj, path) => {
8
+ return path.split('.').reduce((current, key) => {
9
+ return current && current[key] !== undefined ? current[key] : undefined;
10
+ }, obj);
11
+ };
12
+
13
+ // 参数匹配功能
14
+ const isParamMatch = (req, paramMatchers, log) => {
15
+ if (!paramMatchers || !Array.isArray(paramMatchers) || paramMatchers.length === 0) {
16
+ return true; // 如果没有参数匹配规则,视为匹配成功
17
+ }
18
+
19
+ try {
20
+ // 合并所有可能的参数来源
21
+ let requestParams = {};
22
+
23
+ // URL查询参数
24
+ if (req.query) {
25
+ requestParams = { ...requestParams, ...req.query };
26
+ }
27
+
28
+ // POST请求体参数
29
+ if (req.body && typeof req.body === 'object') {
30
+ requestParams = { ...requestParams, ...req.body };
31
+ }
32
+
33
+ // URL路径参数(如果有的话)
34
+ if (req.params) {
35
+ requestParams = { ...requestParams, ...req.params };
36
+ }
37
+
38
+ if (log) {
39
+ log(`参数匹配检查 - 请求参数: ${JSON.stringify(requestParams)}`);
40
+ log(`参数匹配检查 - 匹配规则: ${JSON.stringify(paramMatchers)}`);
41
+ }
42
+
43
+ // 检查每个匹配规则
44
+ for (const matcher of paramMatchers) {
45
+ const { paramPath, paramValue, matchType = 'exact' } = matcher;
46
+
47
+ if (!paramPath) {
48
+ continue; // 跳过无效的匹配规则
49
+ }
50
+
51
+ // 获取实际参数值
52
+ const actualValue = getNestedValue(requestParams, paramPath);
53
+
54
+ if (log) {
55
+ log(`参数匹配检查 - 路径: ${paramPath}, 期望值: ${paramValue}, 实际值: ${actualValue}, 匹配类型: ${matchType}`);
56
+ }
57
+
58
+ // 如果参数不存在,视为不匹配
59
+ if (actualValue === undefined || actualValue === null) {
60
+ if (log) {
61
+ log(`参数匹配失败 - 参数 ${paramPath} 不存在`);
62
+ }
63
+ return false;
64
+ }
65
+
66
+ // 根据匹配类型进行比较
67
+ let isMatch = false;
68
+ const actualStr = String(actualValue);
69
+ const expectedStr = String(paramValue);
70
+
71
+ switch (matchType) {
72
+ case 'exact':
73
+ isMatch = actualStr === expectedStr;
74
+ break;
75
+ case 'contains':
76
+ isMatch = actualStr.includes(expectedStr);
77
+ break;
78
+ case 'regex':
79
+ try {
80
+ const regex = new RegExp(expectedStr);
81
+ isMatch = regex.test(actualStr);
82
+ } catch (e) {
83
+ if (log) {
84
+ log(`参数匹配失败 - 正则表达式无效: ${expectedStr}`);
85
+ }
86
+ return false;
87
+ }
88
+ break;
89
+ default:
90
+ isMatch = actualStr === expectedStr;
91
+ }
92
+
93
+ if (!isMatch) {
94
+ if (log) {
95
+ log(`参数匹配失败 - ${paramPath}: ${actualStr} 不匹配 ${expectedStr} (${matchType})`);
96
+ }
97
+ return false;
98
+ }
99
+ }
100
+
101
+ if (log) {
102
+ log('参数匹配成功 - 所有规则都匹配');
103
+ }
104
+ return true;
105
+ } catch (error) {
106
+ if (log) {
107
+ log(`参数匹配检查出错: ${error.message}`);
108
+ }
109
+ return false;
110
+ }
111
+ };
112
+
6
113
  /**
7
114
  * 规则管理模块,负责处理 Whistle 规则和代理请求
8
115
  */
@@ -60,7 +167,7 @@ const ruleManager = {
60
167
  const requestPath = parsedUrl.pathname;
61
168
 
62
169
  // 查找匹配的接口
63
- const matchedInterface = this.findMatchingInterface(enabledInterfaces, requestPath, req.method);
170
+ const matchedInterface = this.findMatchingInterface(enabledInterfaces, requestPath, req.method, req);
64
171
 
65
172
  if (!matchedInterface) {
66
173
  this.log(`[规则处理器] 未找到匹配的接口,请求将透传: ${req.method} ${requestPath}`);
@@ -93,9 +200,10 @@ const ruleManager = {
93
200
  * @param {Array} interfaces 接口列表
94
201
  * @param {string} requestPath 请求路径
95
202
  * @param {string} method 请求方法
203
+ * @param {object} req 完整的请求对象,用于参数匹配
96
204
  * @returns {object|null} 匹配的接口对象,如果没有匹配则返回 null
97
205
  */
98
- findMatchingInterface(interfaces, requestPath, method) {
206
+ findMatchingInterface(interfaces, requestPath, method, req) {
99
207
  // 简化日志,只记录正在进行匹配的操作
100
208
  this.log(`[规则处理器] 尝试匹配接口,路径: ${requestPath}, 方法: ${method}`);
101
209
 
@@ -111,17 +219,16 @@ const ruleManager = {
111
219
  return null;
112
220
  }
113
221
 
114
- // 先尝试完全匹配,效率更高
115
- let matchedInterface = interfaces.find(intf => {
116
- // 检查接口对象是否有效
222
+ // 收集所有URL匹配的接口
223
+ const urlMatchedInterfaces = [];
224
+
225
+ // 先收集所有URL匹配的接口(包括精确匹配和正则匹配)
226
+ for (const intf of interfaces) {
117
227
  if (!intf || !intf.urlPattern) {
118
- return false;
228
+ continue;
119
229
  }
120
230
 
121
- // 检查URL和方法是否匹配
122
- const urlMatches = intf.urlPattern === requestPath;
123
-
124
- // 修复: 兼容不同的方法名称和值
231
+ // 检查HTTP方法是否匹配
125
232
  const methodField = intf.httpMethod || intf.method;
126
233
  const methodValue = methodField && methodField.toUpperCase();
127
234
  const methodMatches = !methodValue ||
@@ -129,32 +236,24 @@ const ruleManager = {
129
236
  methodValue === 'ANY' ||
130
237
  methodValue === method;
131
238
 
132
- // 移除中间匹配过程的日志
133
- return urlMatches && methodMatches;
134
- });
135
-
136
- if (matchedInterface) {
137
- this.log(`[规则处理器] 找到完全匹配的接口: ${matchedInterface.name}`);
138
- return matchedInterface;
139
- }
140
-
141
- // 如果没有完全匹配,尝试正则表达式匹配
142
- this.log(`[规则处理器] 未找到完全匹配,尝试正则表达式匹配...`);
143
-
144
- // 使用正则表达式缓存优化性能
145
- if (!this.regexCache) {
146
- this.regexCache = new Map();
147
- }
148
-
149
- try {
150
- matchedInterface = interfaces.find(intf => {
239
+ if (!methodMatches) {
240
+ continue;
241
+ }
242
+
243
+ // 检查URL是否匹配
244
+ let urlMatches = false;
245
+
246
+ // 精确匹配
247
+ if (intf.urlPattern === requestPath) {
248
+ urlMatches = true;
249
+ } else {
250
+ // 正则表达式匹配
151
251
  try {
152
- // 验证接口对象
153
- if (!intf || !intf.urlPattern) {
154
- return false;
252
+ // 从缓存获取或创建正则表达式
253
+ if (!this.regexCache) {
254
+ this.regexCache = new Map();
155
255
  }
156
256
 
157
- // 从缓存获取或创建正则表达式
158
257
  let regex;
159
258
  if (this.regexCache.has(intf.urlPattern)) {
160
259
  regex = this.regexCache.get(intf.urlPattern);
@@ -166,37 +265,48 @@ const ruleManager = {
166
265
  this.regexCache.set(intf.urlPattern, regex);
167
266
  }
168
267
 
169
- // 获取方法值,兼容不同的字段名
170
- const methodField = intf.httpMethod || intf.method;
171
- const methodValue = methodField && methodField.toUpperCase();
172
-
173
- // 检查URL和方法是否匹配
174
- const urlMatches = regex.test(requestPath);
175
- const methodMatches = !methodValue ||
176
- methodValue === 'ALL' ||
177
- methodValue === 'ANY' ||
178
- methodValue === method;
179
-
180
- return urlMatches && methodMatches;
268
+ urlMatches = regex.test(requestPath);
181
269
  } catch (err) {
182
- // 记录特定接口的错误,但继续处理其他接口
183
- this.log(`[规则处理器] 匹配接口 ${intf?.name || 'unknown'} 时出错: ${err.message}`);
184
- return false;
270
+ this.log(`[规则处理器] 正则匹配接口 ${intf.name} 时出错: ${err.message}`);
271
+ continue;
185
272
  }
186
- });
187
- } catch (err) {
188
- // 记录整体匹配过程的错误
189
- this.log(`[规则处理器] 正则匹配过程出错: ${err.message}`);
190
- return null;
273
+ }
274
+
275
+ if (urlMatches) {
276
+ urlMatchedInterfaces.push(intf);
277
+ }
191
278
  }
192
-
193
- if (matchedInterface) {
194
- this.log(`[规则处理器] 找到正则匹配的接口: ${matchedInterface.name}`);
195
- } else {
279
+
280
+ this.log(`[规则处理器] 找到 ${urlMatchedInterfaces.length} 个URL匹配的接口`);
281
+
282
+ if (urlMatchedInterfaces.length === 0) {
196
283
  this.log(`[规则处理器] 未找到匹配的接口`);
284
+ return null;
285
+ }
286
+
287
+ // 在URL匹配的接口中,查找参数也匹配的接口
288
+ for (const intf of urlMatchedInterfaces) {
289
+ // 检查参数匹配
290
+ if (isParamMatch(req, intf.paramMatchers, this.log)) {
291
+ this.log(`[规则处理器] 找到完全匹配的接口: ${intf.name} (URL + 参数匹配)`);
292
+ return intf;
293
+ } else {
294
+ this.log(`[规则处理器] 接口 ${intf.name} URL匹配但参数不匹配,继续查找...`);
295
+ }
296
+ }
297
+
298
+ // 如果没有参数匹配的接口,返回第一个没有参数匹配规则的接口
299
+ const fallbackInterface = urlMatchedInterfaces.find(intf =>
300
+ !intf.paramMatchers || !Array.isArray(intf.paramMatchers) || intf.paramMatchers.length === 0
301
+ );
302
+
303
+ if (fallbackInterface) {
304
+ this.log(`[规则处理器] 使用回退接口: ${fallbackInterface.name} (无参数匹配规则)`);
305
+ return fallbackInterface;
197
306
  }
198
307
 
199
- return matchedInterface;
308
+ this.log(`[规则处理器] 所有URL匹配的接口都有参数匹配规则且都不满足,未找到合适的接口`);
309
+ return null;
200
310
  },
201
311
 
202
312
  /**
package/lib/server.js CHANGED
@@ -170,6 +170,99 @@ if (!fs.existsSync(INTERFACES_FILE)) {
170
170
  const RULE_VALUE_HEADER = 'x-whistle-rule-value';
171
171
  const MOCK_PREFIX = 'mock://';
172
172
 
173
+ // 获取嵌套对象属性值的工具函数
174
+ const getNestedValue = (obj, path) => {
175
+ return path.split('.').reduce((current, key) => {
176
+ return current && current[key] !== undefined ? current[key] : undefined;
177
+ }, obj);
178
+ };
179
+
180
+ // 参数匹配功能
181
+ const isParamMatch = (req, paramMatchers) => {
182
+ if (!paramMatchers || !Array.isArray(paramMatchers) || paramMatchers.length === 0) {
183
+ return true; // 如果没有参数匹配规则,视为匹配成功
184
+ }
185
+
186
+ try {
187
+ // 合并所有可能的参数来源
188
+ let requestParams = {};
189
+
190
+ // URL查询参数
191
+ if (req.query) {
192
+ requestParams = { ...requestParams, ...req.query };
193
+ }
194
+
195
+ // POST请求体参数
196
+ if (req.body && typeof req.body === 'object') {
197
+ requestParams = { ...requestParams, ...req.body };
198
+ }
199
+
200
+ // URL路径参数(如果有的话)
201
+ if (req.params) {
202
+ requestParams = { ...requestParams, ...req.params };
203
+ }
204
+
205
+ logMessage(`参数匹配检查 - 请求参数: ${JSON.stringify(requestParams)}`);
206
+ logMessage(`参数匹配检查 - 匹配规则: ${JSON.stringify(paramMatchers)}`);
207
+
208
+ // 检查每个匹配规则
209
+ for (const matcher of paramMatchers) {
210
+ const { paramPath, paramValue, matchType = 'exact' } = matcher;
211
+
212
+ if (!paramPath) {
213
+ continue; // 跳过无效的匹配规则
214
+ }
215
+
216
+ // 获取实际参数值
217
+ const actualValue = getNestedValue(requestParams, paramPath);
218
+
219
+ logMessage(`参数匹配检查 - 路径: ${paramPath}, 期望值: ${paramValue}, 实际值: ${actualValue}, 匹配类型: ${matchType}`);
220
+
221
+ // 如果参数不存在,视为不匹配
222
+ if (actualValue === undefined || actualValue === null) {
223
+ logMessage(`参数匹配失败 - 参数 ${paramPath} 不存在`);
224
+ return false;
225
+ }
226
+
227
+ // 根据匹配类型进行比较
228
+ let isMatch = false;
229
+ const actualStr = String(actualValue);
230
+ const expectedStr = String(paramValue);
231
+
232
+ switch (matchType) {
233
+ case 'exact':
234
+ isMatch = actualStr === expectedStr;
235
+ break;
236
+ case 'contains':
237
+ isMatch = actualStr.includes(expectedStr);
238
+ break;
239
+ case 'regex':
240
+ try {
241
+ const regex = new RegExp(expectedStr);
242
+ isMatch = regex.test(actualStr);
243
+ } catch (e) {
244
+ logMessage(`参数匹配失败 - 正则表达式无效: ${expectedStr}`);
245
+ return false;
246
+ }
247
+ break;
248
+ default:
249
+ isMatch = actualStr === expectedStr;
250
+ }
251
+
252
+ if (!isMatch) {
253
+ logMessage(`参数匹配失败 - ${paramPath}: ${actualStr} 不匹配 ${expectedStr} (${matchType})`);
254
+ return false;
255
+ }
256
+ }
257
+
258
+ logMessage('参数匹配成功 - 所有规则都匹配');
259
+ return true;
260
+ } catch (error) {
261
+ logMessage(`参数匹配检查出错: ${error.message}`);
262
+ return false;
263
+ }
264
+ };
265
+
173
266
  // 创建 Express 应用
174
267
  const app = express();
175
268
 
@@ -577,6 +670,33 @@ const handleResponse = (interfaceItem, req, res) => {
577
670
  logMessage(`匹配到接口: ${interfaceItem.name}, ID: ${interfaceItem.id}`);
578
671
  logMessage(`URL匹配规则: ${interfaceItem.urlPattern}`);
579
672
 
673
+ // 检查参数匹配规则
674
+ if (!isParamMatch(req, interfaceItem.paramMatchers)) {
675
+ logMessage(`参数匹配失败,跳过接口: ${interfaceItem.name}`);
676
+
677
+ // 记录参数匹配失败的日志
678
+ addToJsonLog({
679
+ eventType: 'param_mismatch',
680
+ url: req.url,
681
+ method: req.method,
682
+ message: `参数匹配失败: ${interfaceItem.name}`,
683
+ pattern: interfaceItem.urlPattern,
684
+ status: 'skipped',
685
+ paramMatchers: interfaceItem.paramMatchers || []
686
+ });
687
+
688
+ // 返回404,表示没有匹配的接口
689
+ return res.status(404).json({
690
+ code: 404,
691
+ message: '参数匹配失败,未找到符合条件的接口',
692
+ data: {
693
+ interfaceName: interfaceItem.name,
694
+ urlPattern: interfaceItem.urlPattern,
695
+ paramMatchers: interfaceItem.paramMatchers || []
696
+ }
697
+ });
698
+ }
699
+
580
700
  // 记录请求详情
581
701
  logMessage(`请求方法: ${req.method}`);
582
702
  logMessage(`原始URL: ${req.url}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.1.0",
3
+ "version": "1.1.1-beta.2",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -166,7 +166,8 @@ const InterfaceManagement = () => {
166
166
  responses: defaultResponses,
167
167
  activeResponseId: defaultResponseId,
168
168
  httpMethod: 'ALL',
169
- headerItems: [] // 初始化为空数组
169
+ headerItems: [], // 初始化为空数组
170
+ paramMatchers: [] // 初始化为空数组
170
171
  });
171
172
 
172
173
  setCurrentResponseId(defaultResponseId);
@@ -187,6 +188,16 @@ const InterfaceManagement = () => {
187
188
  }));
188
189
  }
189
190
 
191
+ // 处理参数匹配规则,转换为数组格式
192
+ let paramMatchersArray = [];
193
+ if (record.paramMatchers && Array.isArray(record.paramMatchers)) {
194
+ paramMatchersArray = record.paramMatchers.map(matcher => ({
195
+ paramPath: matcher.paramPath || '',
196
+ paramValue: matcher.paramValue || '',
197
+ matchType: matcher.matchType || 'exact'
198
+ }));
199
+ }
200
+
190
201
  // 处理多响应数据
191
202
  let initialResponses = [];
192
203
  let initialResponseId = null;
@@ -233,7 +244,8 @@ const InterfaceManagement = () => {
233
244
  activeResponseId: initialResponseId,
234
245
  httpMethod: record.httpMethod || 'ALL',
235
246
  targetUrl: record.targetUrl || '',
236
- headerItems: headersArray // 使用数组存储表单项
247
+ headerItems: headersArray, // 使用数组存储表单项
248
+ paramMatchers: paramMatchersArray
237
249
  });
238
250
  setModalVisible(true);
239
251
  };
@@ -373,6 +385,22 @@ const InterfaceManagement = () => {
373
385
  }
374
386
  }
375
387
 
388
+ // 处理参数匹配规则,清理无效的规则
389
+ let paramMatchers = [];
390
+ if (values.proxyType === 'response' && values.paramMatchers && values.paramMatchers.length > 0) {
391
+ paramMatchers = values.paramMatchers
392
+ .filter(item => item && item.paramPath && item.paramPath.trim() && item.paramValue !== undefined)
393
+ .map(item => ({
394
+ paramPath: item.paramPath.trim(),
395
+ paramValue: item.paramValue,
396
+ matchType: item.matchType || 'exact'
397
+ }));
398
+ }
399
+
400
+ console.log('=== 参数匹配规则处理 ===');
401
+ console.log('原始参数匹配规则:', values.paramMatchers);
402
+ console.log('清理后的参数匹配规则:', paramMatchers);
403
+
376
404
  const interfaceData = {
377
405
  name: values.name,
378
406
  featureId: selectedFeatureId,
@@ -385,6 +413,7 @@ const InterfaceManagement = () => {
385
413
  responseContent: activeResponse ? activeResponse.content : '',
386
414
  targetUrl: (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') ? values.targetUrl : '',
387
415
  customHeaders: (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') ? customHeaders : {},
416
+ paramMatchers: paramMatchers, // 添加参数匹配规则
388
417
  httpStatus: parseInt(values.statusCode, 10), // 转换为数字
389
418
  contentType: values.contentType,
390
419
  responseDelay: 0,
@@ -584,6 +613,43 @@ const InterfaceManagement = () => {
584
613
  );
585
614
  }
586
615
  },
616
+ {
617
+ title: '参数匹配',
618
+ dataIndex: 'paramMatchers',
619
+ key: 'paramMatchers',
620
+ width: 100,
621
+ render: (_, record) => {
622
+ if (record.proxyType !== 'response') {
623
+ return '-';
624
+ }
625
+
626
+ const matchers = record.paramMatchers || [];
627
+ const count = matchers.length;
628
+
629
+ if (count === 0) {
630
+ return '-';
631
+ }
632
+
633
+ return (
634
+ <Tooltip title={
635
+ <div>
636
+ {matchers.map((matcher, index) => (
637
+ <div key={index}>
638
+ {matcher.paramPath}: {matcher.paramValue}
639
+ <span style={{ color: '#52c41a', marginLeft: 4 }}>
640
+ ({matcher.matchType === 'exact' ? '精确' : matcher.matchType === 'contains' ? '包含' : '正则'})
641
+ </span>
642
+ </div>
643
+ ))}
644
+ </div>
645
+ }>
646
+ <span style={{ color: '#1890ff' }}>
647
+ {count}条规则
648
+ </span>
649
+ </Tooltip>
650
+ );
651
+ }
652
+ },
587
653
  {
588
654
  title: '请求方法',
589
655
  dataIndex: 'httpMethod',
@@ -711,7 +777,8 @@ const InterfaceManagement = () => {
711
777
  responses: [],
712
778
  httpMethod: 'ALL',
713
779
  targetUrl: '',
714
- headerItems: []
780
+ headerItems: [],
781
+ paramMatchers: []
715
782
  }}
716
783
  >
717
784
  {/* 基础信息表单项保持原样 */}
@@ -900,6 +967,87 @@ const InterfaceManagement = () => {
900
967
  </div>
901
968
  </div>
902
969
 
970
+ {/* 请求入参匹配设置 */}
971
+ <Form.Item
972
+ label="请求入参匹配"
973
+ tooltip="设置请求参数的匹配条件,只有当请求参数满足条件时才返回对应的响应。支持嵌套属性路径,如:a.b.c"
974
+ >
975
+ <Form.List name="paramMatchers">
976
+ {(fields, { add, remove }) => (
977
+ <>
978
+ {fields.map(({ key, name, ...restField }) => (
979
+ <div key={key} style={{ display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '8px' }}>
980
+ <Form.Item
981
+ {...restField}
982
+ name={[name, 'paramPath']}
983
+ rules={[
984
+ { required: true, message: '请输入参数路径' },
985
+ {
986
+ pattern: /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/,
987
+ message: '请输入有效的参数路径,如:userId 或 data.user.id'
988
+ }
989
+ ]}
990
+ style={{ flex: 1, margin: 0 }}
991
+ >
992
+ <Input placeholder="参数路径(如:userId 或 data.user.id)" />
993
+ </Form.Item>
994
+ <Form.Item
995
+ {...restField}
996
+ name={[name, 'paramValue']}
997
+ rules={[{ required: true, message: '请输入期望值' }]}
998
+ style={{ flex: 1, margin: 0 }}
999
+ >
1000
+ <Input placeholder="期望值(如:123 或 admin)" />
1001
+ </Form.Item>
1002
+ <Form.Item
1003
+ {...restField}
1004
+ name={[name, 'matchType']}
1005
+ style={{ width: 100, margin: 0 }}
1006
+ initialValue="exact"
1007
+ >
1008
+ <Select placeholder="匹配类型">
1009
+ <Option value="exact">精确匹配</Option>
1010
+ <Option value="contains">包含</Option>
1011
+ <Option value="regex">正则</Option>
1012
+ </Select>
1013
+ </Form.Item>
1014
+ <Button
1015
+ type="text"
1016
+ danger
1017
+ icon={<DeleteOutlined />}
1018
+ onClick={() => remove(name)}
1019
+ />
1020
+ </div>
1021
+ ))}
1022
+ <Button
1023
+ type="dashed"
1024
+ onClick={() => add()}
1025
+ icon={<PlusOutlined />}
1026
+ style={{ width: '100%', marginTop: '8px' }}
1027
+ >
1028
+ 添加参数匹配规则
1029
+ </Button>
1030
+ {fields.length > 0 && (
1031
+ <div style={{
1032
+ fontSize: '12px',
1033
+ color: '#666',
1034
+ marginTop: '8px',
1035
+ padding: '8px',
1036
+ backgroundColor: '#f5f5f5',
1037
+ borderRadius: '4px'
1038
+ }}>
1039
+ <div><strong>说明:</strong></div>
1040
+ <div>• <strong>精确匹配</strong>:参数值完全相等</div>
1041
+ <div>• <strong>包含</strong>:参数值包含指定内容</div>
1042
+ <div>• <strong>正则</strong>:参数值符合正则表达式</div>
1043
+ <div>• <strong>嵌套路径</strong>:使用点号分隔,如 data.user.id</div>
1044
+ </div>
1045
+ )}
1046
+ </>
1047
+ )}
1048
+ </Form.List>
1049
+ </Form.Item>
1050
+
903
1051
  {/* 隐藏的表单字段用于存储响应数据 */}
904
1052
  <Form.Item
905
1053
  name="responses"