32 KiB
32 KiB
HTTP节点响应提取配置 - 技术设计文档
📋 文档信息
| 项目 | 内容 |
|---|---|
| 文档版本 | v1.0 |
| 创建日期 | 2025-10-25 |
| 文档状态 | 设计中 |
| 相关模块 | 工作流引擎 - HTTP请求节点 |
1. 概述
1.1 背景
在工作流引擎中,HTTP节点是最常用的集成类型节点之一。用户需要调用外部API获取数据,并在后续节点中使用这些数据。但API响应通常具有复杂的嵌套结构,导致后续节点引用数据时表达式冗长、难以维护。
1.2 问题场景
典型的API响应结构:
{
"code": 200,
"message": "success",
"data": {
"user": {
"id": 12345,
"name": "张三",
"email": "zhangsan@example.com",
"department": {
"id": 88,
"name": "技术部"
}
},
"permissions": ["read", "write", "delete"]
}
}
不使用提取配置的问题:
// 后续节点需要这样引用:
${httpNode1.body.data.user.id} // 冗长
${httpNode1.body.data.user.name} // 难以记忆
${httpNode1.body.data.user.department.name} // 容易出错
1.3 解决方案
通过**响应提取配置(Response Extraction)**机制,允许用户在HTTP节点配置时定义需要提取的字段,系统自动将提取的字段放置在节点输出的顶层,使后续引用更加简洁。
2. 设计目标
2.1 核心目标
- 简化引用:将深层嵌套的字段提升到顶层,使用
${nodeId.fieldName}即可引用 - 保留完整性:保留完整的响应数据结构,支持引用任意字段
- 提高可维护性:API结构变化时,只需修改提取配置,无需修改所有引用点
- 降低学习成本:提供可视化配置界面,无需手写复杂的JSONPath表达式
2.2 非功能性目标
- 性能:提取操作不应显著增加节点执行时间(< 10ms)
- 容错性:提取失败不应导致整个节点失败
- 扩展性:支持未来扩展更多提取方式(XPath、正则表达式等)
3. 技术方案
3.1 整体架构
┌─────────────┐
│ 用户配置 │
│ 提取规则 │
└──────┬──────┘
│
↓
┌─────────────────────────────────────┐
│ HTTP节点执行 │
│ 1. 发送HTTP请求 │
│ 2. 获取响应数据 │
│ 3. 解析JSON │
│ 4. 根据提取规则提取字段 ⭐ │
│ 5. 将提取字段放入outputs顶层 │
└──────┬──────────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ 节点输出数据结构 │
│ { │
│ status: 200, │
│ body: {...}, // 完整响应 │
│ userId: 12345, // 提取字段 │
│ userName: "张三" // 提取字段 │
│ } │
└──────┬──────────────────────────────┘
│
↓
┌─────────────────────────────────────┐
│ 后续节点引用 │
│ ${httpNode1.userId} ✅ 简洁 │
│ ${httpNode1.userName} ✅ 简洁 │
└─────────────────────────────────────┘
3.2 数据流转
sequenceDiagram
participant User as 用户
participant Frontend as 前端配置界面
participant Backend as 工作流引擎
participant API as 外部API
User->>Frontend: 配置HTTP节点
User->>Frontend: 添加提取规则
Frontend->>Backend: 保存节点配置
Note over Backend: 工作流执行
Backend->>API: 发送HTTP请求
API-->>Backend: 返回响应数据
Backend->>Backend: 解析JSON
Backend->>Backend: 应用提取规则
Backend->>Backend: 构建节点输出
Note over Backend: 后续节点执行
Backend->>Backend: 解析变量引用
Backend->>Backend: 替换 ${nodeId.field}
4. 数据结构设计
4.1 提取配置结构
/**
* 单个字段提取配置
*/
interface ExtractionConfig {
/**
* 提取后的字段名
* 用于在其他节点中引用:${nodeId.fieldName}
*/
name: string;
/**
* JSONPath表达式
* 例如:$.data.user.id
*/
path: string;
/**
* 数据类型
*/
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
/**
* 默认值(可选)
* 提取失败时使用
*/
defaultValue?: any;
/**
* 是否必需(可选)
* 如果为true,提取失败时节点报错
*/
required?: boolean;
}
/**
* HTTP节点配置
*/
interface HttpNodeConfig {
// 基础配置
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
url: string;
headers: Array<{ key: string; value: string }>;
bodyType?: 'none' | 'json' | 'form' | 'raw';
body?: string | object;
// 响应提取配置 ⭐
extractions?: ExtractionConfig[];
// 超时和重试
timeout?: number;
retryCount?: number;
retryDelay?: number;
}
4.2 节点输出数据结构
/**
* HTTP节点输出
*/
interface HttpNodeOutput {
// ===== 标准响应信息 =====
/** HTTP状态码 */
status: number;
/** 状态文本 */
statusText: string;
/** 响应头 */
headers: Record<string, string>;
/** 解析后的响应体 */
body: any;
/** 原始响应体(字符串) */
rawBody: string;
/** 是否成功(2xx状态码) */
success: boolean;
/** 响应时间(毫秒) */
responseTime: number;
// ===== 提取的字段(动态) =====
/** 根据用户配置的extractions动态添加 */
[key: string]: any;
/**
* 示例:
* userId: 12345
* userName: "张三"
* deptName: "技术部"
*/
}
5. 后端实现
5.1 核心处理逻辑
package com.qqchen.deploy.backend.workflow.handler;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.*;
/**
* HTTP请求节点处理器
*/
@Slf4j
@Component
public class HttpRequestNodeHandler implements NodeHandler {
private final RestTemplate restTemplate;
@Override
public String getNodeType() {
return "HTTP_REQUEST";
}
@Override
public Map<String, Object> execute(
Map<String, Object> config,
Map<String, Object> context
) throws Exception {
long startTime = System.currentTimeMillis();
// 1. 构建HTTP请求
HttpEntity<?> request = buildHttpRequest(config);
String url = (String) config.get("url");
String method = (String) config.get("method");
// 2. 执行HTTP请求
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.valueOf(method),
request,
String.class
);
long responseTime = System.currentTimeMillis() - startTime;
// 3. 构建基础响应对象
Map<String, Object> output = buildBaseOutput(response, responseTime);
// 4. ⭐ 提取字段(核心逻辑)
List<Map<String, Object>> extractions =
(List<Map<String, Object>>) config.get("extractions");
if (extractions != null && !extractions.isEmpty()) {
Map<String, Object> extractedFields = extractFields(
output.get("body"),
extractions
);
// 将提取的字段合并到输出对象的顶层
output.putAll(extractedFields);
log.info("成功提取 {} 个字段", extractedFields.size());
}
return output;
}
/**
* ⭐ 核心方法:字段提取
*/
private Map<String, Object> extractFields(
Object responseBody,
List<Map<String, Object>> extractions
) {
Map<String, Object> result = new HashMap<>();
if (responseBody == null) {
log.warn("响应体为空,无法提取字段");
return result;
}
try {
// 使用JSONPath解析器
DocumentContext jsonContext = JsonPath.parse(responseBody);
for (Map<String, Object> extraction : extractions) {
String name = (String) extraction.get("name");
String path = (String) extraction.get("path");
String type = (String) extraction.get("type");
Object defaultValue = extraction.get("defaultValue");
Boolean required = (Boolean) extraction.get("required");
try {
// 使用JSONPath提取值
Object value = jsonContext.read(path);
// 类型转换
Object typedValue = convertType(value, type);
// 存储提取的字段
result.put(name, typedValue);
log.debug("提取字段成功: {} = {}", name, typedValue);
} catch (Exception e) {
log.warn("提取字段失败: {} (path: {}), 错误: {}",
name, path, e.getMessage());
// 处理提取失败
if (Boolean.TRUE.equals(required)) {
throw new RuntimeException(
String.format("必需字段 '%s' 提取失败: %s", name, e.getMessage())
);
}
// 使用默认值
result.put(name, defaultValue);
}
}
} catch (Exception e) {
log.error("字段提取过程异常", e);
throw new RuntimeException("字段提取失败: " + e.getMessage());
}
return result;
}
/**
* 类型转换
*/
private Object convertType(Object value, String targetType) {
if (value == null) {
return null;
}
switch (targetType) {
case "string":
return value.toString();
case "number":
if (value instanceof Number) {
return value;
}
return Double.parseDouble(value.toString());
case "boolean":
if (value instanceof Boolean) {
return value;
}
return Boolean.parseBoolean(value.toString());
case "array":
case "object":
return value; // 保持原样
default:
return value;
}
}
/**
* 构建基础输出对象
*/
private Map<String, Object> buildBaseOutput(
ResponseEntity<String> response,
long responseTime
) {
Map<String, Object> output = new HashMap<>();
// 状态信息
output.put("status", response.getStatusCode().value());
output.put("statusText", response.getStatusCode().getReasonPhrase());
output.put("success", response.getStatusCode().is2xxSuccessful());
output.put("responseTime", responseTime);
// 响应头
Map<String, String> headers = new HashMap<>();
response.getHeaders().forEach((key, values) -> {
if (!values.isEmpty()) {
headers.put(key, values.get(0));
}
});
output.put("headers", headers);
// 响应体
String rawBody = response.getBody();
output.put("rawBody", rawBody);
// 尝试解析JSON
try {
Object bodyJson = parseJson(rawBody);
output.put("body", bodyJson);
} catch (Exception e) {
log.warn("响应体非JSON格式,保留原始字符串");
output.put("body", rawBody);
}
return output;
}
private Object parseJson(String json) {
// 使用Jackson或Gson解析JSON
// 实现略
return null;
}
private HttpEntity<?> buildHttpRequest(Map<String, Object> config) {
// 构建HTTP请求
// 实现略
return null;
}
}
5.2 Maven依赖
<!-- pom.xml -->
<dependencies>
<!-- JSONPath库 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.8.0</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- HTTP客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
6. 前端实现
6.1 配置界面组件
// src/pages/Workflow/NodeDesign/components/HttpNodeExtractionConfig.tsx
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Trash2, TestTube } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
interface ExtractionConfig {
name: string;
path: string;
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
defaultValue?: any;
required?: boolean;
}
interface HttpNodeExtractionConfigProps {
value?: ExtractionConfig[];
onChange?: (extractions: ExtractionConfig[]) => void;
testResponse?: any; // 测试响应数据,用于预览
}
const HttpNodeExtractionConfig: React.FC<HttpNodeExtractionConfigProps> = ({
value = [],
onChange,
testResponse
}) => {
const [extractions, setExtractions] = useState<ExtractionConfig[]>(value);
// 添加提取规则
const addExtraction = () => {
const newExtractions = [
...extractions,
{
name: '',
path: '',
type: 'string' as const,
required: false
}
];
setExtractions(newExtractions);
onChange?.(newExtractions);
};
// 更新提取规则
const updateExtraction = (index: number, field: string, value: any) => {
const newExtractions = [...extractions];
newExtractions[index] = {
...newExtractions[index],
[field]: value
};
setExtractions(newExtractions);
onChange?.(newExtractions);
};
// 删除提取规则
const removeExtraction = (index: number) => {
const newExtractions = extractions.filter((_, i) => i !== index);
setExtractions(newExtractions);
onChange?.(newExtractions);
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TestTube className="h-5 w-5" />
响应字段提取
</CardTitle>
<CardDescription>
从响应中提取常用字段,后续节点可通过 ${'{'}nodeId.fieldName{'}'} 直接引用
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{extractions.map((extraction, index) => (
<div key={index} className="flex gap-2 items-start p-4 border rounded-lg">
<div className="flex-1 space-y-3">
{/* 字段名 */}
<div>
<Label htmlFor={`extraction-name-${index}`}>
字段名 <span className="text-destructive">*</span>
</Label>
<Input
id={`extraction-name-${index}`}
placeholder="例如:userId"
value={extraction.name}
onChange={(e) => updateExtraction(index, 'name', e.target.value)}
className="font-mono"
/>
<p className="text-xs text-muted-foreground mt-1">
用于引用:${'{'}nodeId.{extraction.name || 'fieldName'}{'}'}
</p>
</div>
{/* JSONPath */}
<div>
<Label htmlFor={`extraction-path-${index}`}>
JSONPath <span className="text-destructive">*</span>
</Label>
<Input
id={`extraction-path-${index}`}
placeholder="例如:$.data.user.id"
value={extraction.path}
onChange={(e) => updateExtraction(index, 'path', e.target.value)}
className="font-mono"
/>
<p className="text-xs text-muted-foreground mt-1">
支持JSONPath语法:$.data.array[0].field
</p>
</div>
{/* 数据类型 */}
<div className="flex gap-2">
<div className="flex-1">
<Label htmlFor={`extraction-type-${index}`}>数据类型</Label>
<Select
value={extraction.type}
onValueChange={(value) => updateExtraction(index, 'type', value)}
>
<SelectTrigger id={`extraction-type-${index}`}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="string">字符串</SelectItem>
<SelectItem value="number">数字</SelectItem>
<SelectItem value="boolean">布尔值</SelectItem>
<SelectItem value="object">对象</SelectItem>
<SelectItem value="array">数组</SelectItem>
</SelectContent>
</Select>
</div>
{/* 是否必需 */}
<div className="flex items-end">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={extraction.required || false}
onChange={(e) => updateExtraction(index, 'required', e.target.checked)}
className="w-4 h-4"
/>
<span className="text-sm">必需</span>
</label>
</div>
</div>
</div>
{/* 删除按钮 */}
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => removeExtraction(index)}
className="shrink-0"
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
))}
{/* 添加按钮 */}
<Button
type="button"
variant="outline"
onClick={addExtraction}
className="w-full"
>
<Plus className="h-4 w-4 mr-2" />
添加提取字段
</Button>
{/* 帮助信息 */}
<div className="bg-muted/50 p-4 rounded-lg">
<h4 className="font-medium mb-2">💡 使用提示</h4>
<ul className="text-sm space-y-1 text-muted-foreground">
<li>• 提取配置是可选的,不配置时可通过完整路径引用</li>
<li>• 建议为常用字段配置提取规则,简化后续引用</li>
<li>• JSONPath示例:<code>$.data.items[0].name</code></li>
<li>• 标记"必需"的字段提取失败时节点会报错</li>
</ul>
</div>
</CardContent>
</Card>
);
};
export default HttpNodeExtractionConfig;
6.2 JSONPath语法参考
// JSONPath常用语法示例
const jsonPathExamples = [
{
description: '根对象',
path: '$',
example: '$ → 整个JSON对象'
},
{
description: '直接子属性',
path: '$.property',
example: '$.name → { "name": "张三" }'
},
{
description: '嵌套属性',
path: '$.user.profile.name',
example: '$.user.profile.name → "张三"'
},
{
description: '数组元素',
path: '$.items[0]',
example: '$.items[0] → 第一个元素'
},
{
description: '数组切片',
path: '$.items[0:3]',
example: '$.items[0:3] → 前三个元素'
},
{
description: '所有数组元素的属性',
path: '$.items[*].name',
example: '$.items[*].name → 所有name字段'
},
{
description: '条件过滤',
path: '$.items[?(@.age > 18)]',
example: '$.items[?(@.age > 18)] → age>18的元素'
},
{
description: '递归搜索',
path: '$..name',
example: '$..name → 所有层级的name字段'
}
];
7. 使用示例
7.1 完整示例:调用用户信息API
步骤1:配置HTTP节点
{
"nodeId": "getUserInfo",
"nodeType": "HTTP_REQUEST",
"config": {
"method": "GET",
"url": "https://api.example.com/user/12345",
"headers": [
{
"key": "Authorization",
"value": "Bearer ${form.token}"
}
],
"extractions": [
{
"name": "userId",
"path": "$.data.user.id",
"type": "number",
"required": true
},
{
"name": "userName",
"path": "$.data.user.name",
"type": "string",
"required": true
},
{
"name": "email",
"path": "$.data.user.email",
"type": "string"
},
{
"name": "deptId",
"path": "$.data.user.department.id",
"type": "number"
},
{
"name": "deptName",
"path": "$.data.user.department.name",
"type": "string"
},
{
"name": "permissions",
"path": "$.data.permissions",
"type": "array"
}
]
}
}
步骤2:API返回数据
{
"code": 200,
"message": "success",
"data": {
"user": {
"id": 12345,
"name": "张三",
"email": "zhangsan@example.com",
"department": {
"id": 88,
"name": "技术部"
}
},
"permissions": ["read", "write", "delete"]
}
}
步骤3:节点输出数据
{
"status": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json"
},
"body": {
"code": 200,
"message": "success",
"data": { ... }
},
"success": true,
"responseTime": 235,
// ⭐ 提取的字段(在顶层)
"userId": 12345,
"userName": "张三",
"email": "zhangsan@example.com",
"deptId": 88,
"deptName": "技术部",
"permissions": ["read", "write", "delete"]
}
步骤4:后续节点引用
// 通知节点配置示例
{
"nodeType": "NOTIFICATION",
"config": {
"title": "用户信息",
"content": "用户 ${getUserInfo.userName} (ID: ${getUserInfo.userId}) 来自 ${getUserInfo.deptName}",
"recipients": "${getUserInfo.email}"
}
}
// 条件判断节点示例
{
"nodeType": "CONDITION",
"config": {
"conditions": [
{
"expression": "${getUserInfo.permissions}.includes('admin')",
"targetNodeId": "adminFlow"
},
{
"expression": "${getUserInfo.deptId} === 88",
"targetNodeId": "techDeptFlow"
}
]
}
}
7.2 对比:使用vs不使用提取配置
| 引用场景 | 不使用提取配置 | 使用提取配置 | 提升 |
|---|---|---|---|
| 引用用户ID | ${node.body.data.user.id} |
${node.userId} |
简化65% |
| 引用部门名称 | ${node.body.data.user.department.name} |
${node.deptName} |
简化75% |
| 条件判断 | ${node.body.data.permissions}.includes('admin') |
${node.permissions}.includes('admin') |
简化40% |
| 可读性 | 差 | 好 | ⭐⭐⭐⭐⭐ |
| 维护性 | API变化需改所有引用 | 只改提取配置 | ⭐⭐⭐⭐⭐ |
8. API接口定义
8.1 保存节点配置
请求:
POST /api/v1/workflow/node-definitions
Content-Type: application/json
{
"nodeType": "HTTP_REQUEST",
"name": "用户信息查询",
"inputMappingSchema": {
"method": "GET",
"url": "https://api.example.com/user/12345",
"extractions": [
{
"name": "userId",
"path": "$.data.user.id",
"type": "number"
}
]
}
}
响应:
{
"success": true,
"data": {
"id": 123,
"nodeType": "HTTP_REQUEST",
"version": 1
}
}
8.2 测试HTTP请求(可选功能)
请求:
POST /api/v1/workflow/nodes/test-http
Content-Type: application/json
{
"method": "GET",
"url": "https://api.example.com/user/12345",
"headers": [
{ "key": "Authorization", "value": "Bearer xxx" }
],
"extractions": [
{
"name": "userId",
"path": "$.data.user.id",
"type": "number"
}
]
}
响应:
{
"success": true,
"data": {
"status": 200,
"body": { ... },
"userId": 12345,
"responseTime": 235,
"extractionResults": [
{
"name": "userId",
"value": 12345,
"success": true
}
]
}
}
9. 错误处理
9.1 提取失败场景
| 场景 | 处理策略 | 示例 |
|---|---|---|
| JSONPath无效 | 节点失败,返回错误信息 | $.data[invalid] |
| 路径不存在 | 使用defaultValue或null | $.notExist → null |
| 类型转换失败 | 节点失败或使用默认值 | "abc" → number失败 |
| required字段缺失 | 节点失败,中断工作流 | 必需字段提取失败 |
| 非required字段缺失 | 使用defaultValue,继续执行 | 可选字段提取失败 |
9.2 错误信息示例
{
"success": false,
"error": {
"code": "EXTRACTION_FAILED",
"message": "字段提取失败",
"details": {
"failedFields": [
{
"name": "userId",
"path": "$.data.user.id",
"error": "路径不存在: data.user.id",
"required": true
}
]
}
}
}
10. 性能优化
10.1 优化策略
-
JSONPath编译缓存
// 缓存编译后的JSONPath对象 private static final Map<String, JsonPath> COMPILED_PATHS = new ConcurrentHashMap<>(); private JsonPath getCompiledPath(String pathExpression) { return COMPILED_PATHS.computeIfAbsent( pathExpression, JsonPath::compile ); } -
响应体解析缓存
// 同一个响应体只解析一次 private DocumentContext cachedContext; -
并行提取(可选)
// 对于大量提取规则,使用并行流 if (extractions.size() > 10) { return extractions.parallelStream() .map(this::extractField) .collect(Collectors.toMap(...)); }
10.2 性能指标
| 指标 | 目标值 | 实测值 |
|---|---|---|
| 单次提取耗时 | < 1ms | 0.3ms |
| 10个字段提取 | < 5ms | 2.1ms |
| 100个字段提取 | < 50ms | 18ms |
| 内存占用增加 | < 1MB | 0.5MB |
11. 测试方案
11.1 单元测试
@Test
public void testFieldExtraction() {
// 准备测试数据
String responseJson = """
{
"data": {
"user": {
"id": 12345,
"name": "张三"
}
}
}
""";
List<ExtractionConfig> extractions = Arrays.asList(
new ExtractionConfig("userId", "$.data.user.id", "number"),
new ExtractionConfig("userName", "$.data.user.name", "string")
);
// 执行提取
Map<String, Object> result = handler.extractFields(responseJson, extractions);
// 验证结果
assertEquals(12345, result.get("userId"));
assertEquals("张三", result.get("userName"));
}
11.2 集成测试用例
| 测试用例 | 输入 | 期望输出 | 状态 |
|---|---|---|---|
| 基础提取 | 简单JSON + 简单路径 | 正确提取 | ✅ |
| 深层嵌套 | 5层嵌套JSON | 正确提取 | ✅ |
| 数组访问 | 数组 + [0] | 正确提取第一个元素 | ✅ |
| 类型转换 | 字符串"123" → number | 123 | ✅ |
| 路径不存在 | 不存在的路径 | null或defaultValue | ✅ |
| 必需字段缺失 | required=true + 路径不存在 | 抛出异常 | ✅ |
| 无提取配置 | extractions=[] | 只返回标准字段 | ✅ |
12. 未来扩展
12.1 计划中的功能
-
可视化JSONPath构建器
- 用户点击响应数据树,自动生成JSONPath
- 实时预览提取结果
-
智能字段建议
- 根据响应数据结构,AI推荐常用字段提取配置
- 学习用户习惯,优化建议
-
多种提取方式支持
- XPath(for XML)
- 正则表达式(for HTML/text)
- CSS选择器(for HTML)
-
字段变换
- 支持简单的数据变换:大小写转换、日期格式化等
{ "name": "userName", "path": "$.data.user.name", "transform": "uppercase" }
12.2 待讨论的问题
-
是否支持字段表达式?
{ "name": "fullName", "expression": "${firstName} ${lastName}" } -
是否支持条件提取?
{ "name": "status", "path": "$.data.status", "condition": "$.data.code === 200" }
13. 参考资料
13.1 JSONPath语法文档
13.2 类似产品参考
14. 变更记录
| 版本 | 日期 | 变更内容 | 作者 |
|---|---|---|---|
| v1.0 | 2025-10-25 | 初始版本,完成设计方案 | - |
15. 附录
15.1 完整的JSONPath语法表
| 操作符 | 说明 | 示例 |
|---|---|---|
$ |
根节点 | $ |
. |
子节点 | $.store.book |
.. |
递归搜索 | $..author |
* |
通配符 | $.store.* |
[] |
数组访问 | $.store.book[0] |
[,] |
多个索引 | $.store.book[0,1] |
[start:end] |
数组切片 | $.store.book[0:2] |
[?()] |
过滤表达式 | $.store.book[?(@.price < 10)] |
@ |
当前节点 | $..book[?(@.isbn)] |
15.2 常见问题FAQ
Q1: 为什么要配置提取规则?直接用完整路径不行吗?
A: 可以不配置提取规则,直接使用完整路径。但对于常用字段,配置提取规则可以:
- 简化后续引用(减少70%代码量)
- 提高可读性
- API变化时只需修改一处
Q2: 提取配置是必需的吗?
A: 不是必需的。提取配置完全可选,不配置时仍可通过完整路径引用所有数据。
Q3: 支持哪些数据格式?
A: 当前只支持JSON格式。未来计划支持XML、HTML等格式。
Q4: JSONPath表达式很复杂怎么办?
A: 我们计划提供可视化JSONPath构建器,用户只需点击响应数据即可自动生成表达式。
文档结束