3. 接口契约总览
报表导出服务作为企业级数据平台的核心能力之一,其接口契约的规范性直接决定了上下游系统的集成效率与运维成本。本章从架构视角出发,围绕 RESTful 设计原则、资源路径规划与 HTTP 方法语义三个维度,构建一套可落地、可扩展的导出服务接口契约。
3.1 RESTful 设计原则在导出场景中的落地
报表导出并非简单的文件下载,而是一个包含提交 → 处理 → 查询 → 下载的异步生命周期。RESTful 原则在此场景下的核心映射如下:
| REST 原则 | 导出场景映射 | 关键约束 |
|---|---|---|
| 资源导向 | 将每次导出请求视为一个 Export 资源 |
每个导出任务拥有唯一标识 exportId |
| 无状态交互 | 服务端不保存客户端会话状态 | 所有请求携带认证令牌,状态通过资源属性表达 |
| 统一接口 | 使用标准 HTTP 方法操作导出资源 | POST 创建、GET 查询、DELETE 取消 |
| 可缓存性 | 已完成导出的元数据可缓存 | 响应头显式标注 Cache-Control |
| 分层系统 | 客户端不直接感知后端处理引擎 | 通过网关统一暴露,隐藏内部队列与计算节点 |
架构提示:导出服务天然具有“命令查询职责分离(CQRS)”的特征——提交导出是命令,查询状态与下载文件是查询。RESTful 设计恰好通过不同的 HTTP 方法天然支持这种分离,无需额外引入消息层。
3.2 资源路径规划:/api/v1/exports 层级设计
资源路径是接口契约的骨架。我们采用 集合资源 + 子资源 的层级结构,确保路径语义清晰、版本可控。
# 基础路径结构
/api/v1/exports # 导出任务集合
/api/v1/exports/{exportId} # 单个导出任务
/api/v1/exports/{exportId}/file # 导出文件下载端点
/api/v1/exports/{exportId}/logs # 导出执行日志
路径规划遵循以下设计规则:
- 版本前缀:
/api/v1/作为稳定版本标识,避免破坏性变更影响存量客户端。 - 名词复数:使用
exports而非export,符合 RESTful 集合资源惯例。 - 子资源明确:
file和logs作为导出资源的附属子资源,通过路径层级表达从属关系。 - 无动词路径:避免
/createExport、/downloadFile等动词式路径,动作由 HTTP 方法表达。
避坑指南:不要将文件格式或参数编码到路径中(如
/api/v1/exports/csv),应通过请求体或查询参数传递。路径只表达资源标识,不表达资源形态。
3.3 HTTP 方法语义与状态同步
每个 HTTP 方法对应导出任务生命周期中的一个特定操作。方法语义必须与资源状态严格对齐,避免语义混淆。
| HTTP 方法 | 目标路径 | 语义说明 | 状态转换 | 幂等性 |
|---|---|---|---|---|
| POST | /api/v1/exports |
提交新的导出请求,服务端创建任务并返回 exportId |
无 → PENDING |
否(每次创建新资源) |
| GET | /api/v1/exports/{exportId} |
查询单个导出任务的当前状态与元数据 | 不改变状态 | 是 |
| GET | /api/v1/exports |
查询当前用户/租户的导出任务列表,支持分页与过滤 | 不改变状态 | 是 |
| GET | /api/v1/exports/{exportId}/file |
下载已完成的导出文件,支持 Range 头实现断点续传 |
不改变状态 | 是 |
| DELETE | /api/v1/exports/{exportId} |
取消正在处理或待处理的导出任务,清理资源 | PENDING/PROCESSING → CANCELLED |
是 |
设计警告:不要使用
PATCH 或 PUT 来“更新”导出任务的状态。导出任务的状态由服务端内部驱动,客户端不应直接修改。如果业务需要“重新导出”,应通过 POST 创建新的导出任务,而非复用已有资源。
3.4 请求与响应契约规范
接口契约的最终落地体现在具体的请求/响应结构上。以下以创建导出任务为例,展示完整的契约定义。
3.4.1 创建导出请求(POST /api/v1/exports)
// 请求头
Content-Type: application/json
Authorization: Bearer <token>
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000
// 请求体
{
"reportType": "sales_summary",
"format": "xlsx",
"params": {
"startDate": "2025-01-01",
"endDate": "2025-03-31",
"region": "华东",
"includeCharts": true
},
"notify": {
"email": "user@company.com",
"webhook": "https://callback.company.com/export-complete"
}
}
3.4.2 创建导出响应(201 Created)
// 响应头
Location: /api/v1/exports/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Content-Type: application/json
// 响应体
{
"exportId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "PENDING",
"createdAt": "2025-04-01T10:30:00Z",
"estimatedCompletion": "2025-04-01T10:32:00Z",
"_links": {
"self": { "href": "/api/v1/exports/a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
"status": { "href": "/api/v1/exports/a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
}
}
HATEOAS 实践:响应中包含
_links 字段,引导客户端发现后续可执行的操作。例如,当任务状态变为 COMPLETED 时,_links 中会动态添加 download 链接,指向 /api/v1/exports/{exportId}/file。
3.5 接口契约检查清单
在交付接口契约前,请逐项核对以下清单:
- 路径一致性:所有导出相关端点是否统一以
/api/v1/exports为前缀? - 方法语义正确:是否严格遵循 POST 创建、GET 查询、DELETE 取消的语义映射?
- 状态机完整:是否覆盖了
PENDING → PROCESSING → COMPLETED/FAILED/CANCELLED所有路径? - 错误响应规范:是否统一使用
Problem JSON(RFC 7807)格式返回错误? - 幂等性保障:GET 和 DELETE 是否保证幂等?POST 是否在文档中明确标注非幂等?
- 版本策略:是否通过 URL 前缀(
/v1/)而非请求头管理版本?