3. 导出请求链路(一):请求接收与参数校验——RESTful API 设计、参数模型与异步/同步模式选择
报表导出请求的入口是整个链路的“守门员”。一个设计良好的请求接收层,能够在流量进入核心处理逻辑之前,完成格式校验、语义校验与模式决策,从而避免无效请求消耗后端资源。本节聚焦于 RESTful API 端点设计、参数模型定义以及同步/异步模式的选择策略。
3.1 RESTful API 端点设计
报表导出通常设计为 POST 请求,因为请求体包含复杂的过滤条件与配置参数,且操作具有副作用(创建导出任务或生成文件)。
| 端点路径 | 请求方法 | 适用场景 | 说明 |
|---|---|---|---|
/api/v1/reports/export |
POST | 同步导出(小数据量) | 直接返回文件流或 Base64 编码内容 |
/api/v1/reports/export/async |
POST | 异步导出(大数据量) | 返回任务 ID,客户端轮询或回调获取结果 |
/api/v1/reports/export/tasks/{taskId} |
GET | 异步任务状态查询 | 返回任务进度、状态、下载链接 |
设计原则:端点路径应包含版本号(v1),避免未来接口变更时破坏现有客户端。建议将同步与异步端点分离,而非通过单个参数控制,以提升 API 的可读性与路由匹配效率。
3.2 参数模型定义
导出请求的参数模型需要兼顾灵活性与校验严格性。以下是一个典型的 JSON 请求体结构:
{
"reportCode": "SALES_DAILY", // 必填,报表模板编码
"format": "xlsx", // 必填,导出格式:xlsx | csv | pdf
"fields": ["date", "revenue", "region"], // 可选,指定导出字段,为空则导出全部字段
"filters": { // 可选,过滤条件
"dateRange": {
"start": "2025-01-01",
"end": "2025-01-31"
},
"region": ["华东", "华南"],
"productCategory": "电子产品"
},
"sort": { // 可选,排序规则
"field": "revenue",
"order": "desc"
},
"pageSize": 10000, // 可选,每页记录数,默认 5000
"maxRows": 500000, // 可选,最大导出行数,默认 100000
"async": true // 可选,是否异步,默认 false
}
3.3 参数校验清单
后端必须在反序列化后立即执行分层校验,避免脏数据进入导出引擎。
- 格式校验:
reportCode是否存在于报表注册表中;format是否在允许列表(xlsx, csv, pdf)内。 - 字段校验:
fields数组中的每个字段名是否属于该报表模板的合法字段集合;若传入非法字段,直接拒绝请求。 - 过滤条件校验:日期范围
start不能晚于end;枚举型过滤值(如 region)必须在预定义枚举列表中。 - 边界校验:
pageSize不得超过系统上限(如 50000);maxRows不得超过单次导出配额(如 100 万行)。 - 权限校验:当前用户是否拥有该报表的导出权限;过滤条件中的维度是否在用户数据权限范围内。
常见陷阱:仅依赖前端校验,忽略后端重复校验。攻击者可以绕过前端直接调用 API,因此后端必须对所有参数进行独立、完整的校验,尤其是过滤条件中的 SQL 注入风险字段。
3.4 同步 vs 异步模式选择
模式选择直接影响用户体验与系统资源分配。下表对比了两种模式的核心差异:
| 维度 | 同步模式 | 异步模式 |
|---|---|---|
| 响应时间 | 毫秒~秒级(受数据量影响) | 立即返回任务 ID,实际耗时分钟级 |
| 适用数据量 | < 10 万行 | > 10 万行 或 复杂报表 |
| 连接占用 | 长连接,占用 HTTP 线程 | 短连接,释放线程资源 |
| 错误处理 | 直接返回 HTTP 4xx/5xx | 通过任务状态接口查询错误信息 |
| 重试机制 | 客户端需重新发起请求 | 服务端可自动重试失败任务 |
推荐策略:在网关层或业务层根据
maxRows 自动切换模式。当请求的预估行数低于阈值(如 5 万行)时走同步路径,否则自动转为异步。同时允许客户端通过 async 参数强制指定模式。
3.5 请求接收与校验流程(SVG)
下图展示了从请求到达至进入导出引擎前的完整校验链路:
3.6 异步模式下的任务模型
当请求被判定为异步模式时,系统会立即创建一个导出任务并返回任务 ID。任务模型的核心字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
taskId |
UUID | 全局唯一任务标识 |
status |
枚举 | PENDING / RUNNING / SUCCESS / FAILED |
progress |
Integer | 0~100 的进度百分比 |
downloadUrl |
String | 导出完成后的文件下载链接(临时签名 URL) |
errorMessage |
String | 失败时的详细错误信息 |
createdAt |
DateTime | 任务创建时间 |
最佳实践:任务状态应存储在 Redis 或数据库中以支持持久化查询。下载链接建议使用对象存储的预签名 URL,并设置过期时间(如 30 分钟),避免文件长期暴露。
3.7 参数校验失败响应规范
统一的错误响应格式有助于客户端快速定位问题。推荐使用以下结构:
{
"code": 40001,
"message": "参数校验失败",
"details": [
{
"field": "filters.dateRange.start",
"message": "开始日期不能晚于结束日期"
},
{
"field": "fields[2]",
"message": "字段 'profit' 不在报表模板 'SALES_DAILY' 的合法字段列表中"
}
]
}
通过 details 数组逐条列出所有校验失败项,避免客户端因第一个错误修复后再次请求时遇到第二个错误。