RESTful API
restful本质上是一种设计风格,没有特别的规定,主要是为了创建高效,安全,易于使用和维护的API
RESTful API 全面详解
什么是 RESTful API?
RESTful API 是一种基于 REST(Representational State Transfer,表述性状态转移)架构风格设计的 API。它不是标准或协议,而是一种设计风格,用于创建可扩展、可靠且易于维护的 Web 服务。
核心概念
资源(Resource):网络上的任何事物都是资源,如用户、订单、产品等
表述(Representation):资源的具体表现形式,如 JSON、XML 等
状态转移(State Transfer):通过操作资源的表述来实现资源状态的改变
REST 的 6 大约束条件
1. 客户端-服务器架构(Client-Server)
关注点分离:客户端负责用户界面,服务器负责数据处理和存储
允许两者独立演进
2. 无状态(Stateless)
每个请求必须包含处理该请求所需的所有信息
服务器不保存客户端的状态信息
优点:可扩展性、可靠性、简化服务器设计
3. 可缓存(Cacheable)
响应必须明确表明是否可缓存
减少客户端-服务器交互,提高性能
4. 统一接口(Uniform Interface)
资源标识:使用 URI 标识资源
通过表述操作资源:使用标准 HTTP 方法
自描述消息:消息包含足够信息说明如何处理
超媒体作为应用状态引擎(HATEOAS):响应中包含相关操作的链接
5. 分层系统(Layered System)
客户端不知道是否直接连接到最终服务器
中间层(如负载均衡器、代理)可以提高可扩展性和安全性
6. 按需代码(Code-On-Demand,可选)
服务器可以临时扩展客户端功能,如发送 JavaScript 代码
RESTful API 设计规范
1. 资源命名规范
使用名词而非动词
http
# 正确 - 使用名词 GET /users GET /users/123 POST /users# 错误 - 使用动词 GET /getUsers POST /createUser
使用复数形式
http
# 推荐使用复数 GET /users GET /users/123/orders# 单数形式(不推荐) GET /user GET /user/123/order
资源层次结构
http
# 表示层次关系 GET /users/123/orders # 用户123的所有订单 GET /users/123/orders/456 # 用户123的订单456
过滤、排序、分页
http
# 使用查询参数 GET /users?role=admin&active=true # 过滤 GET /users?sort=name,-age # 排序(name升序,age降序) GET /users?page=2&limit=20 # 分页 GET /users?fields=id,name,email # 字段选择
2. HTTP 方法使用规范
HTTP 方法 | 描述 | 幂等性 | 安全性 |
---|---|---|---|
GET | 获取资源 | 是 | 是 |
POST | 创建资源 | 否 | 否 |
PUT | 更新或创建资源 | 是 | 否 |
PATCH | 部分更新资源 | 否 | 否 |
DELETE | 删除资源 | 是 | 否 |
具体使用示例
http
# 获取资源 GET /users/123# 创建资源 POST /users Content-Type: application/json {"name": "John Doe","email": "john@example.com" }# 完整更新资源 PUT /users/123 Content-Type: application/json {"name": "John Smith","email": "john.smith@example.com" }# 部分更新资源 PATCH /users/123 Content-Type: application/json {"email": "new.email@example.com" }# 删除资源 DELETE /users/123
Content-Type(内容类型)与 Accept(接受类型)
特性 | Content-Type | Accept |
---|---|---|
方向 | 主要描述请求体格式 | 描述期望的响应格式 |
请求中 | 声明客户端发送数据的格式 | 声明客户端希望接收数据的格式 |
响应中 | 声明服务器返回数据的格式 | 一般不用于响应 |
必需性 | 有请求体时通常必需 | 可选,但有最佳实践价值 |
实际开发中的应用
1. 正确的 API 调用示例
javascript
// 前端代码调用 API async function updateUser(userId, userData) {const response = await fetch(`/api/users/${userId}`, {method: 'PUT',headers: {'Content-Type': 'application/json', // 我发送的是 JSON'Accept': 'application/json' // 我希望接收 JSON},body: JSON.stringify(userData)});return response.json(); }
3. HTTP 状态码使用
2xx 成功
200 OK:请求成功
201 Created:资源创建成功
202 Accepted:请求已接受处理,但尚未完成
204 No Content:请求成功,但无返回内容
3xx 重定向
301 Moved Permanently:资源已永久移动
302 Found:资源临时移动
304 Not Modified:资源未修改(缓存相关)
4xx 客户端错误
400 Bad Request:请求格式错误
401 Unauthorized:需要身份验证
403 Forbidden:无权限访问
404 Not Found:资源不存在
405 Method Not Allowed:HTTP方法不允许
409 Conflict:资源状态冲突
429 Too Many Requests:请求过于频繁
5xx 服务器错误
500 Internal Server Error:服务器内部错误
501 Not Implemented:功能未实现
503 Service Unavailable:服务不可用
4. 版本管理
API 版本管理是 RESTful API 设计中至关重要的一环,它允许 API 在演进过程中引入破坏性变更,同时保持对旧客户端的兼容性。
URI 路径版本控制(最常用)
http
GET /api/v1/users GET /api/v2/users
简单直观:URI 中明确显示版本号,易于理解和调试
易于实现:服务器可以根据路径路由到不同版本的处理器
缓存友好:不同版本的资源有不同 URI,可以分别缓存
浏览器友好:可以直接在浏览器中测试不同版本的 API
缺点
URI 污染:版本信息污染了干净的 URI 设计
破坏 REST 原则:理论上,资源的 URI 不应该随时间变化
维护成本:需要维护多个版本的端点
实际应用示例
python
# Flask 框架中的实现示例 @app.route('/api/v1/users') def get_users_v1():# 版本1的实现return jsonify({"version": "v1", "data": [...]})@app.route('/api/v2/users') def get_users_v2():# 版本2的实现return jsonify({"version": "v2", "data": [...], "metadata": {...}})
公司中的实际使用
大多数公司选择这种方式,因为:
开发工具和监控系统更容易处理
可以在负载均衡器级别进行版本路由
便于 A/B 测试和灰度发布
请求头版本控制(更 RESTful 的方式)
http
GET /users Accept: application/vnd.example.v1+json
查询参数版本控制
http
GET /users?version=1
实现方式
http
# 使用 Accept 头指定版本 GET /users Accept: application/vnd.example.v1+jsonGET /users Accept: application/vnd.example.v2+json# 或者使用自定义头 GET /users X-API-Version: 1
媒体类型版本控制(推荐)
http
GET /users/123 Accept: application/vnd.company.user.v1+jsonGET /users/123 Accept: application/vnd.company.user.v2+json
优点
干净的 URI:URI 不包含版本信息,更符合 REST 原则
内容协商:符合 HTTP 的内容协商机制
渐进式升级:客户端可以逐步迁移到新版本
缺点
复杂性:服务器需要解析请求头来确定版本
调试困难:不能在浏览器中直接测试,需要特殊工具
缓存挑战:相同 URI 的不同版本需要不同的缓存键
3. 查询参数版本控制
实现方式
http
# 使用查询参数指定版本 GET /users?version=1 GET /users/123?version=1GET /users?version=2 GET /users/123?version=2
优点
简单易用:只需添加查询参数,易于理解和实现
渐进升级:客户端可以逐个请求迁移到新版本
调试方便:可以在浏览器中直接测试
缺点
URI 不一致:相同资源有多个不同的 URI
缓存问题:需要确保缓存系统正确处理查询参数
不够 RESTful:查询参数通常用于过滤,而不是版本控制
实际应用示例
python
@app.route('/users') def get_users():version = request.args.get('version', '1') # 默认为版本1if version == '1':return jsonify({"version": "v1", "data": [...]})elif version == '2':return jsonify({"version": "v2", "data": [...], "metadata": {...}})
版本管理策略比较
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
URI 路径 | 简单直观,易于缓存 | URI 污染,破坏 REST 原则 | 大多数企业应用 |
请求头 | 干净的 URI,符合 REST | 复杂,调试困难 | 对 REST 原则要求严格的项目 |
查询参数 | 简单易用,渐进升级 | URI 不一致,缓存问题 | 简单的 API,快速原型 |
最佳实践建议
选择一种策略并保持一致:不要在同一个 API 中混用多种版本控制策略
提供默认版本:当客户端没有指定版本时,提供一个合理的默认版本
5. 响应格式
响应内容都是JSON体(body),但没有状态码字段——这是REST风格API常见做法。HTTP状态码(如200、400、404、500等)不是写在JSON里,而是由HTTP协议的响应头(header)直接返回的。
下面只是HTTP响应体(body)的一部分,不包含状态码。
- 前端/客户端收到响应时,先看HTTP头里的状态码,再解析body里的JSON内容。
- 这样能区分:
- 传输层/协议层的异常(如网络错误状态码)
- 业务内容的异常/返回信息(如{"error": ...})
成功响应
json
{"data": {"id": 123,"name": "John Doe","email": "john@example.com"} }
列表响应
json
{"data": [{"id": 123,"name": "John Doe"},{"id": 124,"name": "Jane Smith"}],"pagination": {"total": 2,"page": 1,"limit": 20} }
错误响应
json
{"error": {"code": "invalid_email","message": "邮箱格式不正确","details": {"email": "invalid-email"}} }
实际返回流程举例
成功响应
HTTP
HTTP/1.1 200 OK
Content-Type: application/json{"data": {"id": 123,"name": "John Doe","email": "john@example.com"}
}
业务错误响应
HTTP
HTTP/1.1 400 Bad Request
Content-Type: application/json{"error": {"code": "invalid_email","message": "邮箱格式不正确","details": {"email": "invalid-email"}}
}
6. HATEOAS(超媒体作为应用状态引擎)
json
{"data": {"id": 123,"name": "John Doe","email": "john@example.com","links": [{"rel": "self","href": "/users/123","method": "GET"},{"rel": "update","href": "/users/123","method": "PUT"},{"rel": "delete","href": "/users/123","method": "DELETE"},{"rel": "orders","href": "/users/123/orders","method": "GET"}]} }
实际开发中的重要考虑
1. 认证和授权
认证与授权在实际开发中的实现细节,尤其是JWT(JSON Web Token)认证和OAuth 2.0授权。
JWT(JSON Web Token)认证
JWT是一种无状态的令牌认证机制,常用于Web API和微服务。
- 令牌本身就是用户身份和权限的信息,服务端不用存session,直接验证令牌即可。
- 令牌内容是一个经过签名的JSON字符串,通常包括用户ID、角色、过期时间等。
2. 认证流程
步骤一:用户登录,获取JWT令牌
HTTP
POST /auth/login
Content-Type: application/json{"username": "user@example.com","password": "password123"
}
- 客户端提交用户名和密码,服务端验证是否正确。
步骤二:服务端返回JWT令牌
JSON
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
- 令牌内容由三部分组成:Header(头)、Payload(载荷)、Signature(签名)。
- 服务端用密钥对令牌进行签名。
步骤三:客户端保存令牌(通常存localStorage或Cookie)
步骤四:客户端访问受保护接口时,带上令牌
HTTP
GET /users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
- 在请求头加
Authorization: Bearer <token>
,服务端验证令牌即可。
3. 服务端如何验证JWT?
- 解析令牌,验证签名是否正确(确保没被伪造)。
- 检查令牌是否过期。
- 从令牌中读取用户信息和权限,决定是否允许访问。
4. JWT的优缺点
优点:
- 无状态,服务端不用保存session,扩展性强。
- 可扩展到微服务、分布式架构。
- 可以直接存放权限、角色等信息,API可以快速做权限判断。
缺点:
- 一旦令牌泄露,别人可以伪造身份。
- 令牌不能主动失效(只能等到过期或用黑名单)。
- 刷新机制要自己实现。
5. 实际开发要点
- 令牌要设置合理的过期时间,比如15分钟~2小时。
- 不要把敏感信息(如密码)放在JWT里。
- 密钥要妥善保管,否则令牌验证就失效了。
- HTTPS必须开启,防止令牌被窃听。
- 注意跨域和CSRF问题,存储方式要选对。
http
POST /auth/login Content-Type: application/json {"username": "user@example.com","password": "password123" }# 响应 {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
http
GET /users/me Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
OAuth 2.0
http
# 授权码流程 GET /oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code# 获取访问令牌 POST /oauth/token Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
1. OAuth 2.0是什么?
OAuth 2.0是第三方授权协议,常用于“用微信/QQ/Google登录”,或让第三方APP安全访问你的API。
- 用户可以授权第三方应用访问自己的资源,而不用把密码给第三方。
2. 授权码流程(最常用)
步骤一:客户端跳转到授权页面
HTTP
GET /oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code
- 用户看到授权页面,输入账号密码并同意授权。
client_id
:第三方应用的标识redirect_uri
:授权完成后回跳地址
步骤二:授权服务器回跳带授权码
HTTP
GET REDIRECT_URI?code=AUTHORIZATION_CODE
- 授权码是一次性的短期凭证。
步骤三:客户端用授权码换取访问令牌
HTTP
POST /oauth/token
Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code
code=AUTHORIZATION_CODE
redirect_uri=REDIRECT_URI
client_id=CLIENT_ID
client_secret=CLIENT_SECRET
- 服务端验证授权码有效后,返回access_token(访问令牌)。
步骤四:客户端带令牌访问资源
HTTP
GET /api/resource
Authorization: Bearer ACCESS_TOKEN
- 令牌代表用户授权给第三方的权限,服务端验证后决定是否允许访问。
3. OAuth 2.0的优缺点
优点:
- 用户不用把密码给第三方,安全性高。
- 可细粒度控制授权范围(只开放部分权限)。
- 支持令牌刷新,长期授权。
缺点:
- 实现复杂,涉及回跳、授权码、令牌管理。
- 配置繁琐,需要注册client_id、client_secret。
4. 实际开发要点
- 登录和授权最好分离,用户体验更好。
- 回跳地址要严格校验,防止钓鱼和劫持。
- 授权码和令牌要设置合理的有效期和权限范围。
- 日志和审计要完善,方便追踪授权行为。
三、JWT vs OAuth 2.0
- JWT适用于你自己的用户认证和API鉴权(比如你的Web后端、微服务、移动APP)。
- OAuth 2.0适用于“第三方授权”,比如开放平台、社交登录,让别人安全地访问你的数据。
- 两者可以结合,OAuth 2.0获取到token后,令牌本身可以是JWT格式。
五、工程实战建议
- 用JWT做自己的系统认证,简单高效。
- 对外开放API时,用OAuth 2.0,不要暴露用户密码。
- 所有敏感操作(如支付、转账),二次认证+短期令牌。
- 令牌管理、黑名单机制、刷新机制要设计好。
2. 速率限制(Rate Limiting)
速率限制就是API服务器每单位时间允许客户端发起的最大请求数。
用来限制客户端请求频率,保护服务器资源,防止滥用或攻击。
当你访问RESTful API时,服务器通常会在HTTP响应头里加入速率限制相关字段,最典型的就是:
http
# 响应头中包含速率限制信息 X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 999 X-RateLimit-Reset: 1627833600
1. X-RateLimit-Limit
- 含义:你每个时间窗口(比如1小时、1分钟)最多能请求多少次API。
- 例子:
1000
表示你每小时最多能发1000次请求。
2. X-RateLimit-Remaining
- 含义:你在当前时间窗口内还剩下多少次可用请求。
- 例子:
999
表示你还有999次可用(刚刚用了1次)。
3. X-RateLimit-Reset
- 含义:时间窗口什么时候重置,单位通常是Unix时间戳(秒)。
- 例子:
1627833600
表示到这个时间点后,速率限制会重置,你又有1000次可用。 - 可以用
date -d @1627833600
转换为人类可读时间。
比如你的API速率限制是每小时最多1000次:
- 你第一次请求,响应头里显示:
- X-RateLimit-Remaining: 999
- 你又请求了10次,就变成:
- X-RateLimit-Remaining: 989
- 一小时内你请求满了1000次,下一次再请求时:
- 有的API会返回429 Too Many Requests状态码,表示超出速率限制。
- 响应头里可能显示剩余为0,reset时间戳告诉你什么时候可以重新请求。
- API客户端每次请求都应该解析这些响应头
- 动态判断还有多少额度,是否要等待
- 当剩余为0或快到0时,要主动限流或延迟请求,避免被封禁
- 遇到429 Too Many Requests时,可以用
X-RateLimit-Reset
字段等待重试
代码示例:
Python
import requests
import timeresp = requests.get("https://api.example.com/data")
limit = int(resp.headers.get("X-RateLimit-Limit", "0"))
remaining = int(resp.headers.get("X-RateLimit-Remaining", "0"))
reset = int(resp.headers.get("X-RateLimit-Reset", "0"))if remaining == 0:print("Rate limit exceeded, wait until:", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(reset)))time.sleep(max(0, reset - int(time.time())))
else:print(f"You have {remaining}/{limit} requests left")
五、速率限制的常见策略
- 固定窗口(Fixed Window):每个时间窗口限制请求数,窗口到期重置。
- 滑动窗口(Sliding Window):更细致,按时间间隔移动窗口。
- 令牌桶/漏桶算法(Token Bucket/Leaky Bucket):可平滑突发流量。
- 按用户/按IP/按API Key限流:不同身份不同额度。
六、工程注意事项
- 速率限制通常是后端统一配置,前端只需要遵守和解析头信息。
- 超出速率限制时,API一般返回429 Too Many Requests,要处理重试逻辑。
- 有些API会在错误响应的body里写明超额信息,但头部字段是标准做法。
七、总结
X-RateLimit-Limit
:本窗口最大允许请求数X-RateLimit-Remaining
:本窗口剩余可用请求数X-RateLimit-Reset
:下一个窗口的起始时间(Unix时间戳)- 客户端应主动解析这些头,合理限流、避免封禁、提升体验
3. API 文档
OpenAPI/Swagger 规范
yaml
openapi: 3.0.0 info:title: User APIversion: 1.0.0 paths:/users:get:summary: 获取用户列表parameters:- name: pagein: queryschema:type: integerdescription: 页码responses:'200':description: 成功content:application/json:schema:type: objectproperties:data:type: arrayitems:$ref: '#/components/schemas/User'pagination:$ref: '#/components/schemas/Pagination'
一、什么是 OpenAPI/Swagger?
- OpenAPI(前身Swagger)规范是描述RESTful API的标准格式,通常用YAML或JSON书写。
- 它可以被自动工具识别,生成可交互的API文档(如 Swagger UI),还可用于代码生成、测试和mock服务。
- 让API描述规范化、自动化、可读性强,极大提升开发效率和协作体验。
二、这段API文档的结构详解
YAML
openapi: 3.0.0
- 标明使用的是 OpenAPI 3.0.0 版本。
YAML
info:title: User APIversion: 1.0.0
- info部分:API的基本信息
title
:API名称version
:API版本号
YAML
paths:/users:get:summary: 获取用户列表parameters:- name: pagein: queryschema:type: integerdescription: 页码responses:'200':description: 成功content:application/json:schema:type: objectproperties:data:type: arrayitems:$ref: '#/components/schemas/User'pagination:$ref: '#/components/schemas/Pagination'
1. paths
paths
:API的所有路由(URL)定义区- 每个path对应一个API资源或接口
2. /users 路径
/users
:表示一个用户相关的资源集合,通常用于管理用户列表
3. GET 方法
get
:HTTP GET方法,对应“查询用户列表”
3.1 summary
summary: 获取用户列表
- 简要说明接口用途,Swagger UI会展示这里的内容。
3.2 parameters
- 参数定义区,说明本接口支持哪些参数
YAML
parameters:- name: pagein: queryschema:type: integerdescription: 页码
name: page
:参数名叫 pagein: query
:参数在查询字符串(如 /users?page=2)type: integer
:参数类型为整数description: 页码
:参数说明
3.3 responses
responses
:定义请求的所有响应类型(通常有200、400、404等)
YAML
'200':description: 成功content:application/json:schema:type: objectproperties:data:type: arrayitems:$ref: '#/components/schemas/User'pagination:$ref: '#/components/schemas/Pagination'
'200'
:HTTP状态码 200,表示成功description: 成功
:响应说明
3.3.1 content
content: application/json
:响应体格式为JSON
3.3.2 schema
schema
:定义响应JSON的结构(数据模型)
YAML
type: object
properties:data:type: arrayitems:$ref: '#/components/schemas/User'pagination:$ref: '#/components/schemas/Pagination'
- 响应是一个对象(即JSON的 {} 结构)
- 有两个属性:
data
:一个数组,每个元素是一个 User 对象(引用了 User 模型)pagination
:分页信息,引用了 Pagination 模型
4. $ref 语法
$ref
是OpenAPI的引用语法,指向文档下方components/schemas
定义的数据模型。- 比如
#/components/schemas/User
表示引用名为User的数据结构。 - 这让模型可以复用,结构清晰。
三、实际工程意义
前后端协作标准
- API接口结构、字段、参数、响应都规范化,前后端无需反复确认,减少沟通成本。
自动化文档和测试
- 用Swagger UI等工具加载OpenAPI文档,自动生成可交互的API页面,开发、测试、产品、运维都可用。
- 可直接在线调试接口,输入参数,查看响应。
代码自动生成
- 后端可根据OpenAPI文档生成接口框架代码,前端可生成API调用代码,减少重复劳动。
Mock和自动化测试
- 可用文档自动生成Mock服务,前端可在后端未完成时提前开发和测试。
四、扩展:components/schemas
通常会在文档后面补充数据模型定义:
YAML
components:schemas:User:type: objectproperties:id:type: integername:type: stringemail:type: stringPagination:type: objectproperties:total:type: integerpage:type: integerlimit:type: integer
五、总结
- OpenAPI/Swagger文档是描述RESTful API最权威的标准
- 明确接口路径、参数、响应结构、数据模型
- 支持自动化文档、代码生成、测试和Mock
- 前后端协作、API治理、API扩展都极为高效
4. 数据验证
json
// 请求体验证 {"type": "object","required": ["name", "email"],"properties": {"name": {"type": "string","minLength": 1,"maxLength": 100},"email": {"type": "string","format": "email"},"age": {"type": "integer","minimum": 0,"maximum": 150}} }
一、数据验证(Validation)
1. 为什么要做数据验证?
- 防止不合法、恶意、脏数据进入系统
- 避免后端业务/数据库报错
- 前端用户体验友好,第一时间给出清晰提示
- 提升系统安全性和健壮性
2. 请求体验证规范解析
你给的JSON是典型的JSON Schema,很多API框架(如Node.js的express-validator、Python的pydantic/FastAPI、Java的Hibernate Validator)都支持类似规范。
JSON
{"type": "object","required": ["name", "email"],"properties": {"name": {"type": "string","minLength": 1,"maxLength": 100},"email": {"type": "string","format": "email"},"age": {"type": "integer","minimum": 0,"maximum": 150}}
}
逐项解释
"type": "object"
请求体必须是一个对象(即{}
结构),不是数组或其他类型。"required": ["name", "email"]
name
和email
字段是必填项,缺少即报错。"properties"
对每个字段进行详细约束:"name"
- 必须是字符串
- 长度至少1,最多100字符
- 空字符串或超长字符串都不合法
"email"
- 必须是字符串
format: "email"
要求有正确的邮箱格式- 比如
haojiubudaqiu@github.com
是合法,hello@
或abc
是不合法
"age"
- 必须是整数
- 最小0,最大150
- 负数或超过150都不合法
- 非必填,可以为空
3. 实际开发怎么用?
- 后端收到请求体后,先用这些规则做校验
- 不通过则立即返回错误响应,不进入业务处理
- 很多API框架都能自动根据JSON Schema校验
示例:Python FastAPI
Python
from pydantic import BaseModel, EmailStr, conint, constrclass UserRequest(BaseModel):name: constr(min_length=1, max_length=100)email: EmailStrage: conint(ge=0, le=150) = None
示例:Node.js express-validator
JavaScript
const { body } = require('express-validator');
app.post('/user',[body('name').isString().isLength({ min: 1, max: 100 }),body('email').isEmail(),body('age').optional().isInt({ min: 0, max: 150 })],(req, res) => { ... }
);
5. 错误处理最佳实践
json
{"error": {"code": "validation_error","message": "输入数据验证失败","details": [{"field": "email","message": "邮箱格式不正确","value": "invalid-email"},{"field": "age","message": "年龄必须大于0","value": -5}],"documentation_url": "https://api.example.com/docs/errors#validation_error"} }
你给的是一个标准错误响应体,讲究结构清晰、可扩展、便于前端解析和自动化处理。
JSON
{"error": {"code": "validation_error","message": "输入数据验证失败","details": [{"field": "email","message": "邮箱格式不正确","value": "invalid-email"},{"field": "age","message": "年龄必须大于0","value": -5}],"documentation_url": "https://api.example.com/docs/errors#validation_error"}
}
1. 结构逐项解释
"error"
:错误信息主对象"code"
:错误代码,唯一标识类型,便于前端/客户端做自动化处理(如validation_error
)"message"
:人类可读的提示,给前端/用户展示"details"
:详细的错误列表,每个字段的具体问题"field"
:哪个字段出错- 示
"message"
:该字段的详细错误提 "value"
:实际输入的非法值,方便前端定位和修正
"documentation_url"
:指向API文档的错误说明,便于开发者查阅和快速定位问题
2. 为什么要这么设计?
- 前端用户能一眼知道哪里错了、怎么改
- 自动化客户端(如App、SDK)可以根据
code
自动处理(比如弹窗、定位输入框) - 具体错误列表(
details
)支持一次返回多个错误,用户不用反复提交修正 documentation_url
方便开发者自查,降低沟通成本
3. 实际开发建议
- 错误代码要有统一规范和枚举,比如
validation_error
、not_found
、unauthorized
等 - 错误信息要本地化,支持多语言
- 错误详情要结构化,方便前端自动处理
- 文档链接要维护好,便于开发者自助查找
- 返回HTTP状态码要和错误体对应,比如400 Bad Request对应校验错误
4. 代码实现示例
Python FastAPI
Python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationErrorapp = FastAPI()@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):details = []for err in exc.errors():details.append({"field": err['loc'][-1],"message": err['msg'],"value": err.get('ctx', {}).get('value', None)})return JSONResponse(status_code=400,content={"error": {"code": "validation_error","message": "输入数据验证失败","details": details,"documentation_url": "https://api.example.com/docs/errors#validation_error"}})
Node.js express-validator
JavaScript
const { validationResult } = require('express-validator');
app.post('/user', [...], (req, res) => {const errors = validationResult(req);if (!errors.isEmpty()) {return res.status(400).json({error: {code: "validation_error",message: "输入数据验证失败",details: errors.array().map(e => ({field: e.param,message: e.msg,value: e.value})),documentation_url: "https://api.example.com/docs/errors#validation_error"}});}// 业务逻辑...
});
三、工程总结
- 数据验证是API安全和用户体验的基础,建议所有输入都做结构化验证。
- 错误处理要结构清晰、信息丰富、代码标准化,便于前端和开发者自动处理。
- 推荐用JSON Schema、统一错误码、详细details、文档链接等设计模式,提升API质量。
公司工作中的实际应用
1. 微服务架构中的 API 设计
http
# 用户服务 GET /users/{id}# 订单服务 GET /orders?user_id={userId}# 支付服务 POST /payments# 使用 API 网关统一入口 GET /api/users/{id} GET /api/orders POST /api/payments
2. 前后端分离架构
javascript
// 前端代码调用 RESTful API async function getUserProfile(userId) {try {const response = await fetch(`/api/users/${userId}`, {headers: {'Authorization': `Bearer ${getAuthToken()}`,'Content-Type': 'application/json'}});if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const data = await response.json();return data;} catch (error) {console.error('获取用户信息失败:', error);throw error;} }
3. 第三方集成
http
# 提供 webhook 支持 POST /webhooks/order-created Content-Type: application/json X-Webhook-Signature: sha256=...{"event": "order.created","data": {"order_id": "12345","amount": 99.99,"currency": "USD"} }
面试常见问题及详解
1. REST 和 RESTful 的区别是什么?
答:
REST 是一种架构风格,包含一组约束条件
RESTful 是指符合 REST 架构风格的 API 或服务
简单说:REST 是理论,RESTful 是实践
2. 什么是幂等性?为什么重要?
答:
幂等性:多次执行相同操作产生的结果与执行一次相同
重要原因:
网络不稳定时客户端可以安全重试
简化错误处理和恢复机制
幂等方法:GET、PUT、DELETE、HEAD、OPTIONS
非幂等方法:POST、PATCH
3. PUT 和 PATCH 的区别?
答:
PUT:完整更新资源,客户端提供完整资源表述
PATCH:部分更新资源,客户端只提供需要修改的字段
示例:
http
# PUT - 更新整个用户资源 PUT /users/123 {"name": "New Name","email": "new@example.com","age": 30 }# PATCH - 只更新邮箱 PATCH /users/123 {"email": "new@example.com" }
4. 如何设计一个好的 RESTful API?
答:
使用名词而非动词:
/users
而不是/getUsers
使用合适的HTTP方法:GET、POST、PUT、DELETE、PATCH
使用正确的HTTP状态码:200、201、400、404等
提供清晰的错误信息:包含错误代码和详细信息
版本控制:在URI或请求头中包含版本信息
支持过滤、排序、分页:使用查询参数
提供HATEOAS支持:响应中包含相关操作链接
良好的文档:使用OpenAPI等标准
5. 如何保证 RESTful API 的安全性?
答:
HTTPS:所有通信使用加密连接
认证:JWT、OAuth 2.0、API密钥等
授权:基于角色的访问控制(RBAC)
输入验证:验证所有输入数据,防止注入攻击
速率限制:防止滥用和DDoS攻击
日志和监控:记录所有API调用,监控异常行为
CORS配置:正确配置跨域资源共享
6. 什么是 HATEOAS?为什么重要?
答:
HATEOAS:Hypermedia as the Engine of Application State
核心思想:响应中包含相关操作的链接,客户端通过这些链接发现和操作资源
重要性:
减少客户端与服务器的耦合
使API更易于发现和使用
支持服务器的演进,不影响现有客户端
7. 如何处理 API 版本升级?
答:
URI版本控制:
/api/v1/users
,/api/v2/users
请求头版本控制:
Accept: application/vnd.example.v1+json
向后兼容:尽量保持向后兼容,不破坏现有客户端
弃用策略:明确标记已弃用的端点,提供迁移指南
版本支持周期:明确每个版本的支持时间
8. REST 和 GraphQL 的区别?
答:
特性 | REST | GraphQL |
---|---|---|
数据获取 | 多个端点,可能过度获取或获取不足 | 单个端点,精确获取所需数据 |
版本控制 | 通过URI或请求头 | 通过Schema演进 |
错误处理 | HTTP状态码 | 总是返回200,错误在响应体中 |
实时数据 | 需要WebSocket或轮询 | 支持订阅(Subscriptions) |
学习曲线 | 相对简单 | 需要学习GraphQL语法和类型系统 |
9. 如何设计分页?
答:
http
# 基于偏移量的分页 GET /users?page=2&limit=20# 响应 {"data": [...],"pagination": {"page": 2,"limit": 20,"total": 100,"pages": 5} }# 基于游标的分页(更适合大数据集) GET /users?cursor=abc123&limit=20# 响应 {"data": [...],"pagination": {"next_cursor": "def456","has_more": true} }
分页(Pagination)在RESTful API和数据库开发中非常重要,直接影响到性能、用户体验和可扩展性。
下面我会详细讲解分页的概念、两大主流方案(偏移量分页、游标分页)、它们的工作原理、优缺点、响应结构以及实际开发的工程要点,让你彻底理解并能灵活应用。
一、什么是分页?为什么要分页?
分页就是把大量数据分成若干页,每次只返回一部分,比如列表、搜索结果、日志等。
目的:
- 提升性能:一次只查一页数据,避免一次性拉取全量数据,降低服务器和网络压力。
- 改善体验:前端/客户端可以“翻页”浏览数据,响应更快。
- 支持无限滚动/加载更多:比如App里的“下拉加载更多”,都是分页技术。
二、主流分页设计方案
1. 基于偏移量分页(Offset Pagination)
原理
- API参数中指定
page
和limit
,服务端根据这两个参数从数据库查第几页、每页多少条。 - 适合数据总量不是特别巨大的场景。
请求示例
HTTP
GET /users?page=2&limit=20
page=2
:请求第2页limit=20
:每页20条
响应示例
JSON
{"data": [ ... ], // 本页的数据"pagination": {"page": 2,"limit": 20,"total": 100, // 总数据条数"pages": 5 // 总页数(total/limit取整)}
}
data
:当前页的数据pagination
:分页元数据,前端可以据此渲染页码、"上一页/下一页"按钮等
优点
- 简单易懂,前后端都好实现
- 适合中小型数据集、管理后台
缺点
- 大数据集性能差,每次都要数据库跳过大量数据(如MySQL的OFFSET),会变慢
- 数据变动时可能重复或漏掉数据,比如有人插入/删除了数据,页码就会错乱
2. 基于游标的分页(Cursor Pagination)
原理
- 不用page/offset,而是用某个唯一标识(如ID、时间戳)做"游标",每次请求告知服务端从哪继续查。
- 适合大量数据/实时流场景,比如社交消息流、日志系统。
请求示例
HTTP
GET /users?cursor=abc123&limit=20
cursor=abc123
:上一次请求最后一条数据的游标,下次从这里继续查limit=20
:每次查20条
响应示例
JSON
{"data": [ ... ],"pagination": {"next_cursor": "def456", // 下一页的游标"has_more": true // 是否还有更多数据}
}
next_cursor
:下一页继续请求时要传的游标has_more
:是否还有未返回的数据
优点
- 对大数据、实时流性能好,不需要数据库OFFSET跳过数据
- 数据插入/删除不会影响分页顺序,不会漏数据/重复数据
- 适合无限加载/滚动等现代应用场景
缺点
- 前端不能直接跳到第N页,只能“下一页/上一页”翻
- API和前端逻辑稍复杂,需要管理游标
三、分页响应结构设计要点
无论哪种分页,响应里都建议带分页元数据:
- 对于偏移量分页:
- page, limit, total, pages
- 对于游标分页:
- next_cursor, has_more
这样前端可以根据这些信息渲染"更多"按钮、页码导航、判断是否到底等。
四、实际开发注意事项
- 偏移量分页适合后台管理、小型列表,比如运营后台、报表、订单列表
- 游标分页适合大数据/高并发/社交消息流,比如微博、聊天、日志、评论流
- 游标可以用ID、时间戳、复合键等,确保唯一和顺序
- 响应里建议带足够元信息,便于前端分页体验优化
- 分页参数要有默认值和最大值,防止一次拉取太多数据
- 错误处理要清楚,如游标无效、参数越界等
五、代码/数据库实现举例
偏移量分页(SQL举例)
SQL
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 20; -- 第2页,每页20条
游标分页(SQL举例)
SQL
-- 假设cursor是最后一条id
SELECT * FROM users WHERE id > 123 ORDER BY id LIMIT 20;
- 取到数据后,把最后一条的id作为next_cursor返回给前端
六、工程总结
- 分页让API高效、安全、可扩展,是列表型数据必备
- 偏移量分页简单,适合小数据
- 游标分页强大,适合大数据和无限滚动
- 响应格式要有详细的分页信息,前端才能做好的体验
10. 如何测试 RESTful API?
答:
单元测试:测试单个端点或功能
集成测试:测试多个组件的协作
端到端测试:测试完整流程
性能测试:测试API的响应时间和吞吐量
安全测试:测试认证、授权、注入等安全问题
工具:Postman、curl、Jest、Supertest、LoadRunner等
总结
RESTful API 是现代Web开发的核心技术,良好的API设计能够提高系统的可维护性、可扩展性和可用性。在实际工作中,需要综合考虑资源设计、HTTP方法使用、状态码选择、版本管理、安全性等多个方面。在面试中,除了理解基本概念外,还需要能够结合实际场景分析问题和提出解决方案。
掌握RESTful API不仅需要理论知识,更需要在实际项目中不断实践和总结经验。随着技术的发展,虽然出现了GraphQL等替代方案,但RESTful API仍然是目前最广泛使用的API设计风格,深入理解其原理和最佳实践对于任何Web开发者都是必不可少的技能。