请求、接口与鉴权
后端最基础的工作,是接收请求、确认身份、执行规则、读写数据并返回可解释的结果。框架只是把这些步骤组织起来。真正困难的是边界:什么算合法输入,谁有权执行,操作失败后应该留下什么状态,客户端怎样知道是否成功。
1. 一次请求的完整路径
跳转到“1. 一次请求的完整路径”客户端 │ URL、方法、头部、请求体 ▼DNS → TCP → TLS → 入口代理/负载均衡 │应用服务器 │ 解析 HTTP → 中间件 → 路由 ▼控制器 → 业务服务 → 数据访问 → 外部依赖 │ ▼响应:状态码 + 头部 + 响应体每一步都可能失败。域名没解析、连接被拒绝、证书过期、代理返回 502、路由不存在、鉴权失败、数据库超时,最终都可能表现为“接口不能用”,但处理方式完全不同。
1.1 中间件
跳转到“1.1 中间件”中间件位于请求进入业务代码之前,或响应离开之后,常负责:
- 生成请求 ID,建立日志上下文。
- 解析 Cookie、认证信息和内容类型。
- 限制请求体大小、限流和检查 IP/租户。
- 压缩、CORS、安全头部和缓存控制。
- 捕获异常,把内部错误转换成稳定响应。
中间件顺序会改变行为。例如鉴权必须在业务路由前,日志上下文要尽早建立,错误处理通常放在最外层。顺序错误可能让未认证请求先触发昂贵查询,或让错误响应缺少请求 ID。
1.2 控制器、服务与数据访问
跳转到“1.2 控制器、服务与数据访问”- 控制器负责 HTTP 边界:读取参数、调用服务、选择状态码。
- 服务层负责业务规则:权限、状态转换、事务和多个操作的组合。
- 数据访问层负责查询与持久化,不决定业务是否允许。
把数据库查询、权限判断和响应拼装全塞进控制器,代码短期能跑,但很难测试和复用。不是每个小接口都必须分三层;边界复杂时再拆分,但不要把不同职责混成一个不可解释的函数。
2. HTTP 的核心概念
跳转到“2. HTTP 的核心概念”HTTP 是请求/响应协议。请求包含方法、目标、版本、头部和可选请求体;响应包含状态码、头部和响应体。
POST /api/orders HTTP/1.1Content-Type: application/jsonAuthorization: Bearer <token>
{"items":[{"sku":"A-1","quantity":2}]}HTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /api/orders/ord_123
{"id":"ord_123","status":"pending"}2.1 方法
跳转到“2.1 方法”| 方法 | 语义 | 是否应幂等 |
|---|---|---|
| GET | 读取资源 | 是 |
| HEAD | 读取响应头 | 是 |
| POST | 创建资源或提交操作 | 不要求 |
| PUT | 整体替换目标资源 | 是 |
| PATCH | 部分修改资源 | 取决于设计 |
| DELETE | 删除资源 | 通常是 |
方法语义会影响缓存、重试、代理和客户端行为。不要把所有操作都塞进 GET;GET 不应产生副作用,也不应携带密码等敏感信息到 URL 和日志中。
2.2 状态码
跳转到“2.2 状态码”只需掌握分组和常用码:
2xx:成功。200一般成功,201已创建,204成功但无响应体。3xx:重定向或缓存相关。301/308永久重定向,302/307临时重定向。4xx:请求或权限问题。400参数错误,401未认证,403已认证但无权限,404不存在,409状态冲突,422语义校验失败,429限流。5xx:服务端未能完成。500未分类错误,502上游无效响应,503暂时不可用,504上游超时。
状态码不是装饰。错误分类正确,客户端才能决定重试、登录、提示用户还是停止。
2.3 头部
跳转到“2.3 头部”常见头部及用途:
| 头部 | 作用 |
|---|---|
Content-Type |
请求或响应的数据格式 |
Accept |
客户端希望接收的格式 |
Authorization |
凭据,如 Bearer token |
Cache-Control |
缓存策略 |
ETag / If-None-Match |
条件请求与版本校验 |
Idempotency-Key |
让重复写请求可安全重试 |
X-Request-Id |
跨服务追踪请求 |
Set-Cookie |
服务端设置 Cookie |
Retry-After |
限流或暂不可用时建议等待时间 |
自定义头部也要有明确语义,并考虑代理是否透传、日志是否会记录敏感值。
3. API 契约
跳转到“3. API 契约”接口文档不是“写给别人看的附件”,而是客户端与服务端的契约。契约至少包括:
- 路径、方法、鉴权方式和权限。
- 输入字段、类型、必填、范围和默认值。
- 成功响应结构与状态码。
- 稳定错误码、错误消息和字段错误。
- 分页、排序、过滤和幂等规则。
- 超时、重试、限流和长任务行为。
3.1 资源命名
跳转到“3.1 资源命名”REST 风格通常用名词表示资源:
GET /api/orders?status=paid&cursor=...GET /api/orders/{orderId}POST /api/ordersPATCH /api/orders/{orderId}DELETE /api/orders/{orderId}不要在 URL 中写动词式的 getOrder,除非该操作确实不是资源操作,例如 POST /api/orders/{id}/cancel 表示一次有业务语义的转换。
3.2 幂等
跳转到“3.2 幂等”幂等表示同一请求重复执行,最终业务效果应当一致。GET、PUT、DELETE 通常设计为幂等,POST 创建请求则容易制造重复。
客户端超时后不知道请求是否成功,可能重试。如果服务端每次重试都创建新订单,就会产生重复副作用。解决办法包括:
- 客户端生成
Idempotency-Key,服务端保存处理结果。 - 数据库使用唯一约束或业务幂等键。
- 写操作先检查状态,再决定是否执行。
- 对无法确认结果的场景返回可查询的操作 ID。
“重试三次”不能替代幂等设计。重试只会让重复更明显。
3.3 分页
跳转到“3.3 分页”偏移分页简单:
GET /api/orders?page=3&pageSize=20但数据频繁插入时会出现重复或跳过。游标分页用稳定排序字段和位置:
GET /api/orders?limit=20&cursor=eyJjcmVhdGVkQXQiOi...游标需要可验证、不可伪造成越权位置;排序字段要稳定,通常加上唯一 ID 作为最后比较项。
3.4 版本与兼容
跳转到“3.4 版本与兼容”版本变化不只是在路径里加 v2。字段新增、枚举扩展、默认值改变和错误结构变化都涉及兼容:
- 新增可选字段通常向后兼容。
- 删除字段、改类型、改语义通常是破坏性变更。
- 枚举增加新值可能让旧客户端崩溃。
- 时间、金额和大整数要明确格式与精度。
- 新客户端调用旧服务端时,服务端应忽略未知输入还是拒绝?
优先设计可演进的 schema,再决定是否需要版本化。版本化会带来多套逻辑和迁移成本,不是免费的隔离墙。
4. 常见 API 风格
跳转到“4. 常见 API 风格”| 风格 | 优势 | 代价 |
|---|---|---|
| REST/JSON | 通用、可读、生态广 | 灵活查询复杂时接口会膨胀 |
| GraphQL | 客户端按需取字段,减少多次请求 | 查询复杂度、缓存、权限与限流更复杂 |
| gRPC/Protobuf | 强 schema、二进制高效、适合内部调用 | 浏览器支持、调试和网关适配成本 |
| Webhook | 事件驱动,不用轮询 | 重复投递、签名、重试和顺序问题 |
| 文件/流 | 上传下载、实时结果 | 范围请求、背压、断点续传和资源释放 |
选型看调用方、数据规模和故障模式,不看向量图。公开集成优先考虑通用性,内部高频服务可能更重视 schema 与性能。
4.1 Webhook 也必须有契约
跳转到“4.1 Webhook 也必须有契约”Webhook 是“服务器主动调用你的服务器”。要处理:
- 签名验证和时间戳,防止伪造与重放。
- 事件 ID 与幂等处理。
- 超时、重试和死信。
- 乱序到达与重复投递。
- 版本字段与向后兼容。
接收方快速返回已接受不代表业务处理完成,异步任务状态需要可查询。
5. 序列化与数据格式
跳转到“5. 序列化与数据格式”序列化把内存对象变成可传输字节,反序列化把字节还原成对象。格式选择影响可读性、体积、schema、兼容性和安全。
| 格式 | 特点 | 适合 |
|---|---|---|
| JSON | 可读、通用、弱 schema | 公开 API、配置文件 |
| Protobuf | 紧凑、强 schema、代码生成 | 服务间 RPC、高吞吐 |
| MessagePack | 二进制、较紧凑 | 内部接口、特定客户端 |
| CSV | 表格简单、工具广 | 数据导入导出 |
| 自定义二进制 | 可控、极高效率 | 音视频、协议、底层系统 |
无论格式,都要处理:
- 未知字段是忽略、保留还是拒绝。
- 数字精度、时间格式、空值和默认值。
- 字符编码与最大长度。
- schema 版本和迁移。
- 反序列化不可信数据时的资源与安全风险。
JSON 没有原生日期和精确十进制类型;大整数可能超过 JavaScript 安全整数范围。对外传输大 ID 时可以使用字符串,金额使用最小货币单位整数或明确的字符串格式。
6. 认证与授权
跳转到“6. 认证与授权”认证回答“你是谁”,授权回答“你能做什么”。两者不能混在一起,也不能只在前端执行。
6.1 Session 与 Cookie
跳转到“6.1 Session 与 Cookie”服务端创建会话,浏览器保存 Cookie,请求时自动携带。关键属性:
HttpOnly:JavaScript 不能读取,降低 XSS 窃取风险。Secure:只通过 HTTPS 发送。SameSite:限制跨站携带,降低 CSRF。Path、Domain、Expires:控制有效范围。
Session 适合传统 Web 应用,服务端可以撤销会话,但要处理存储和水平扩展。
6.2 JWT / Bearer Token
跳转到“6.2 JWT / Bearer Token”JWT 把声明和签名放在令牌里,服务端可无状态验证。需要注意:
- 验证签名算法和密钥,不接受
none或错误算法。 - 检查过期时间、签发者、受众和用途。
- 不把敏感信息放进 payload,因为它只是编码,不是加密。
- 撤销困难时,使用短有效期、刷新令牌或服务端黑名单。
JWT 不是“更安全”的默认答案,只是把状态放到不同位置。
6.3 OAuth / OIDC
跳转到“6.3 OAuth / OIDC”OAuth 主要解决第三方授权,OIDC 在其上提供身份信息。授权码流程要使用 state 防止请求伪造,PKCE 防止授权码被截获。回调地址必须白名单匹配,scope 要最小化。
6.4 API Key
跳转到“6.4 API Key”API Key 适合机器调用和服务级配额,但要:
- 只在安全通道传输。
- 可以轮换、撤销和限制权限。
- 不放在前端、URL 或公开日志里。
- 与用户身份、租户和速率限制关联。
6.5 授权模型
跳转到“6.5 授权模型”| 模型 | 例子 |
|---|---|
| RBAC | 管理员、编辑、只读角色 |
| ABAC | 根据部门、资源标签、时间等属性判断 |
| 资源级权限 | 只有文档所有者或协作者可以编辑 |
| 租户隔离 | 用户只能访问所属租户的数据 |
每个受保护资源都要检查对象级权限。用户已登录不代表可以读取任意订单;修改 URL 中的 ID 后仍必须由后端决定是否允许。
7. 输入校验与错误契约
跳转到“7. 输入校验与错误契约”所有来自外部的数据都应视为不可信:请求体、查询参数、路径、头部、Cookie、上传文件名、第三方回调。校验至少包括:
- 类型、必填、长度、范围、格式。
- 业务状态是否允许此操作。
- 身份和对象级权限。
- 资源大小、分页上限和文件类型。
错误响应应稳定、机器可读,同时不泄露内部实现:
{ "error": { "code": "ORDER_NOT_PAYABLE", "message": "当前订单不能支付", "fields": {}, "requestId": "req_123" }}生产环境不要返回堆栈、SQL、内部主机名、密钥或完整第三方响应。日志可以记录更详细的原因,但要脱敏并关联 request ID。
8. 长请求、上传与流式响应
跳转到“8. 长请求、上传与流式响应”普通请求应尽快返回,避免连接长时间占用。耗时任务可以:
- 立即返回任务 ID。
- 把任务放入队列。
- 由工作进程处理并更新状态。
- 客户端轮询、SSE 或 WebSocket 获取进度。
- 提供取消、重试和结果过期策略。
文件上传要考虑:
- 请求体大小限制和流式处理。
- 文件类型、扩展名与实际内容校验。
- 对象存储直传与临时签名 URL。
- 断点续传、分片和范围请求。
- 文件名注入、病毒扫描、配额与清理。
流式响应要定义“已经发送了多少数据后失败”的语义。客户端收到部分内容后再重试,可能重复执行或拼接错乱。
9. 框架怎样选
跳转到“9. 框架怎样选”Web 框架通常提供路由、中间件、请求解析、响应序列化和依赖注入。选择时看:
- 团队语言经验与生态。
- 请求处理模型(同步、异步、事件循环)。
- 类型与 schema 支持。
- 中间件、认证、数据库和测试工具。
- 部署方式、内存与启动时间。
- 社区维护和安全更新。
不同语言框架的差异,通常不如清晰的输入校验、错误边界、权限模型和可测试性重要。框架能让第一个接口很快跑起来,但无法替你决定事务、幂等和权限。
检查清单
跳转到“检查清单”- 每个接口是否有明确输入、输出、错误码和权限。
- 写操作是否有幂等策略,重试不会制造重复副作用。
- 认证与授权是否都在服务端执行,是否检查对象级权限。
- Cookie、令牌、API Key 是否在正确位置,是否有轮换与撤销。
- 分页、排序、过滤是否有上限,是否可稳定遍历。
- 序列化格式是否处理精度、时间和 schema 兼容。
- 长请求、上传、流式响应和取消是否定义。
- 错误是否可定位,同时不泄露内部细节。
- 日志是否有 request ID,敏感字段是否脱敏。
- API 文档与真实行为是否一致。