接口基础:RESTful API 设计原则、HTTP 方法与状态码
在工程合同发票匹配与付款风险控制系统中,API 是前后端及第三方系统交互的桥梁。本章聚焦 RESTful API 的核心设计原则、HTTP 方法语义、状态码规范以及幂等性设计,为后续接口调用实战打下坚实基础。
2.1 RESTful API 六大设计原则
REST(Representational State Transfer)是一种架构风格,而非协议。遵循以下原则可构建出易维护、可扩展的工程财务接口:
| 原则 | 说明 | 工程合同场景示例 |
|---|---|---|
| 资源导向 | 每个 URL 代表一个资源(名词),而非操作(动词) | /api/contracts 表示合同集合,/api/invoices 表示发票集合 |
| 无状态 | 服务端不保存客户端上下文,每次请求包含全部必要信息 | 付款审批请求需携带 、合同 ID、发票 ID,服务端不依赖 session |
| 统一接口 | 使用标准 HTTP 方法操作资源,资源表述格式统一(JSON) | 所有增删改查均通过 GET/POST/PUT/PATCH/DELETE 完成 |
| 可缓存 | 响应隐式或显式标记为可缓存或不可缓存 | 合同基础信息(如合同编号、供应商名称)可缓存,付款状态不可缓存 |
| 分层系统 | 客户端无法直接感知是否与最终服务器或中间层通信 | API 网关、负载均衡、微服务之间透明调用 |
| 按需代码(可选) | 服务器可向客户端传输可执行脚本(如 JavaScript) | 工程财务系统中较少使用,通常保持纯数据接口 |
实战要点:在合同发票匹配系统中,资源命名应使用复数形式(如
/api/payment-schedules),避免使用动词(如 /api/getPayment)。URL 中仅包含名词,操作通过 HTTP 方法表达。
2.2 HTTP 方法语义与工程财务场景
每个 HTTP 方法对应一种标准操作。在付款风险控制接口中,正确使用方法可提升接口的可读性和安全性。
| 方法 | 语义 | 幂等 | 工程合同场景 | 请求体 | 响应体 |
|---|---|---|---|---|---|
| GET | 查询资源 | 是 | 查询合同列表、获取发票详情 | 无 | 资源对象或集合 |
| POST | 创建资源 | 否 | 创建新合同、提交发票匹配请求 | 创建所需数据 | 新创建的资源(含 ID) |
| PUT | 全量替换资源 | 是 | 更新合同全部字段(如合同总价变更) | 完整的资源数据 | 更新后的资源 |
| PATCH | 部分更新资源 | 否(需设计) | 仅更新发票匹配状态、修改付款比例 | 仅包含要修改的字段 | 更新后的资源 |
| DELETE | 删除资源 | 是 | 删除作废的发票记录、撤销付款申请 | 无 | 204 No Content |
常见误区:不要使用 GET 请求修改资源状态(如
GET /api/contracts/123/approve),这违反了 REST 语义且可能被缓存或爬虫触发。应使用 PATCH /api/contracts/123 并传入 {"status": "approved"}。
2.3 HTTP 状态码:精准表达接口结果
状态码是客户端判断请求结果的直接依据。在付款风险控制中,错误的状态码可能导致资金操作异常,必须严格遵循规范。
2xx 成功类
- 200 OK:GET、PUT、PATCH 请求成功,响应体包含资源数据。
- 201 Created:POST 请求成功创建资源,响应头
Location指向新资源 URL。 - 204 No Content:DELETE 请求成功,或某些无需返回体的操作(如批量确认)。
3xx 重定向类
- 301 Moved Permanently:资源 URL 已永久变更,客户端应更新缓存。
- 304 Not Modified:条件请求(如
If-None-Match)命中缓存,无需传输资源。
4xx 客户端错误类
- 400 Bad Request:请求参数错误(如发票金额格式不正确)。
- 401 Unauthorized:未提供或提供了无效的身份凭证。
- 403 Forbidden:已认证但无权限(如普通用户尝试审批付款)。
- 404 Not Found:资源不存在(如查询不存在的合同 ID)。
- 409 Conflict:资源当前状态冲突(如发票已被其他付款单锁定)。
- 422 Unprocessable Entity:请求体语义正确但业务校验失败(如合同金额与发票金额不匹配)。
- 429 Too Many Requests:超出 API 调用频率限制。
5xx 服务端错误类
- 500 Internal Server Error:服务器内部异常,需排查日志。
- 502 Bad Gateway:上游服务不可用(如数据库连接池耗尽)。
- 503 Service Unavailable:服务暂时过载或维护中。
工程财务最佳实践:对于付款接口,使用
409 Conflict 表示发票已被其他流程占用,使用 422 Unprocessable Entity 表示合同与发票金额不一致。避免将所有业务错误都返回 400 Bad Request,这会使客户端难以区分错误类型。
2.4 幂等性设计:保障付款安全的关键
幂等性(Idempotency)指同一操作执行多次与执行一次的结果相同。在付款场景中,网络重试可能导致重复扣款,幂等性设计是核心防线。
HTTP 方法的幂等性矩阵
| 方法 | 天然幂等 | 说明 |
|---|---|---|
| GET | 是 | 多次查询返回相同结果(不考虑并发修改) |
| PUT | 是 | 全量替换,多次执行结果一致 |
| DELETE | 是 | 第一次删除成功,后续返回 404 或 204,资源状态不变 |
| POST | 否 | 多次创建会生成多个资源(如重复提交付款申请) |
| PATCH | 否(需设计) | 若操作是“金额加 100”,多次执行结果不同 |
幂等性实现方案
- 幂等键(Idempotency-Key):客户端在 POST/PATCH 请求头中传入唯一键(如 UUID),服务端记录已处理的键,重复请求直接返回首次结果。
- 乐观锁(版本号):资源携带
version字段,更新时校验版本号,防止并发覆盖。 - 业务唯一约束:利用数据库唯一索引(如
contract_id + invoice_id + payment_seq联合唯一),重复插入直接报错。
付款场景示例:客户端发起
POST /api/payments 创建付款单,携带 Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000。若网络超时后重试,服务端检测到相同 key,直接返回已创建的付款单 ID,避免重复扣款。
2.5 接口设计综合示例:合同发票匹配
以下是一个完整的 RESTful 接口设计示例,涵盖合同与发票的匹配流程:
| 功能 | HTTP 方法 | URL | 请求体示例 | 成功状态码 | 幂等性 |
|---|---|---|---|---|---|
| 查询合同列表 | GET | /api/contracts?status=active |
无 | 200 | 是 |
| 创建合同 | POST | /api/contracts |
{"contractNo":"C2025-001","amount":500000} |
201 | 否(需幂等键) |
| 更新合同总价 | PUT | /api/contracts/123 |
{"contractNo":"C2025-001","amount":550000,"status":"active"} |
200 | 是 |
| 修改合同状态 | PATCH | /api/contracts/123 |
{"status":"approved"} |
200 | 否(需幂等键) |
| 删除作废发票 | DELETE | /api/invoices/456 |
无 | 204 | 是 |
| 提交发票匹配 | POST | /api/matchings |
{"contractId":123,"invoiceId":456,"amount":50000} |
201 | 否(需幂等键) |
代码示例:幂等键校验中间件(Node.js 伪代码)
// 幂等性中间件
const idempotencyStore = new Map(); // 生产环境应使用 Redis
function idempotencyMiddleware(req, res, next) {
if (['POST', 'PATCH'].includes(req.method)) {
const key = req.headers['idempotency-key'];
if (!key) {
return res.status(400).json({ error: 'Idempotency-Key header is required' });
}
if (idempotencyStore.has(key)) {
// 返回首次缓存的结果
return res.status(idempotencyStore.get(key).status)
.json(idempotencyStore.get(key).body);
}
// 保存原始 send 方法以缓存响应
const originalSend = res.send.bind(res);
res.send = function(body) {
idempotencyStore.set(key, { status: res.statusCode, body: JSON.parse(body) });
originalSend(body);
};
}
next();
}
2.6 常见设计反模式与避坑指南
- 反模式 1:在 URL 中使用动词 —— 如
/api/approveContract。应改为PATCH /api/contracts/123并传入{"status":"approved"}。 - 反模式 2:忽略幂等性 —— 付款接口未实现幂等键,网络重试导致重复扣款。必须为 POST/PATCH 添加幂等性支持。
- 反模式 3:状态码使用不当 —— 所有错误都返回 400 或 500。应区分 400(参数错误)、409(冲突)、422(业务校验失败)。
- 反模式 4:GET 请求带请求体 —— 某些客户端在 GET 中携带 JSON body,违反 HTTP 规范。查询参数应放在 URL query string 中。
- 反模式 5:嵌套资源过深 —— 如
/api/contracts/123/invoices/456/items/789。建议扁平化或使用查询参数:/api/items?contractId=123&invoiceId=456。
付款风险红线:任何涉及资金变动的接口(付款、退款、调账)必须实现幂等性校验,并在接口文档中明确标注幂等键的传递方式。未实现幂等性的付款接口不得上线。