第30天:RESTful API设计
“好的API设计就像好的用户界面,让使用者感到直观和愉悦!”
📚 今日目标
- 理解REST架构风格
- 掌握RESTful API设计原则
- 学习HTTP方法的正确使用
- 理解资源和URI设计
- 掌握API版本控制和错误处理
- 实现一个简单的RESTful API
1. REST基础概念
1.1 什么是REST?
REST (Representational State Transfer)
表述性状态转移
核心思想:
- 资源(Resource)为中心
- 使用标准HTTP方法
- 无状态通信
- 统一接口
REST不是协议,而是一种架构风格
类比:
REST API = 图书馆管理系统
- 资源 = 书籍
- URI = 书籍编号
- HTTP方法 = 操作(借、还、查、删)
- 状态码 = 操作结果(成功、失败、未找到)
例如:
GET /books/123 → 查询123号书
POST /books → 新增一本书
PUT /books/123 → 更新123号书
DELETE /books/123 → 删除123号书
1.2 REST的六大约束
1. 客户端-服务器分离 (Client-Server)
- 客户端和服务器独立演化
- 前后端分离
2. 无状态 (Stateless)
- 每个请求包含所有必要信息
- 服务器不保存客户端状态
- 易于扩展和负载均衡
3. 可缓存 (Cacheable)
- 响应可以被缓存
- 提高性能
4. 分层系统 (Layered System)
- 客户端不知道直接连接的是服务器还是中间层
- 可以加入负载均衡、缓存等
5. 统一接口 (Uniform Interface)
- 资源标识(URI)
- 通过表述操作资源
- 自描述消息
- 超媒体驱动(HATEOAS)
6. 按需代码(可选)(Code on Demand)
- 服务器可以返回可执行代码
- 如JavaScript
2. RESTful API设计原则
2.1 资源和URI设计
资源设计原则:
1. 使用名词,不使用动词
✅ 好的设计:
GET /users # 获取用户列表
GET /users/123 # 获取ID为123的用户
POST /users # 创建新用户
❌ 不好的设计:
GET /getUsers
POST /createUser
GET /user/delete/123
2. 使用复数形式
✅ 好: /users, /products, /orders
❌ 差: /user, /product, /order
3. 使用小写字母和连字符
✅ 好: /user-profiles, /product-categories
❌ 差: /UserProfiles, /product_categories
4. 资源嵌套表示关系
✅ 好: /users/123/orders # 用户123的订单
/orders/456/items # 订单456的商品
但不要嵌套太深(最多2-3层)
❌ 差: /users/123/orders/456/items/789/reviews
5. 使用查询参数进行过滤、排序、分页
/users?role=admin # 过滤
/users?sort=created_at&order=desc # 排序
/users?page=2&limit=20 # 分页
/users?fields=id,name,email # 字段选择
2.2 HTTP方法使用
标准HTTP方法及其用途:
┌────────┬──────────┬─────────┬─────────┬──────────┐
│ 方法 │ 用途 │ 安全性 │ 幂等性 │ 可缓存 │
├────────┼──────────┼─────────┼─────────┼──────────┤
│ GET │ 获取资源 │ 是 │ 是 │ 是 │
│ POST │ 创建资源 │ 否 │ 否 │ 否 │
│ PUT │ 更新资源 │ 否 │ 是 │ 否 │
│ PATCH │ 部分更新 │ 否 │ 否 │ 否 │
│ DELETE │ 删除资源 │ 否 │ 是 │ 否 │
│ HEAD │ 获取头部 │ 是 │ 是 │ 是 │
│ OPTIONS│ 获取选项 │ 是 │ 是 │ 否 │
└────────┴──────────┴─────────┴─────────┴──────────┘
详细说明:
GET - 获取资源
GET /users → 获取所有用户
GET /users/123 → 获取ID为123的用户
特点:安全、幂等、可缓存
POST - 创建新资源
POST /users
Body: {"name": "张三", "email": "zhang@example.com"}
特点:不安全、不幂等
PUT - 完整更新资源
PUT /users/123
Body: {"name": "张三", "email": "new@example.com", "age": 30}
特点:幂等(多次执行结果相同)
要求:必须提供完整的资源数据
PATCH - 部分更新资源
PATCH /users/123
Body: {"email": "new@example.com"}
特点:只更新提供的字段
更灵活,推荐使用
DELETE - 删除资源
DELETE /users/123
特点:幂等(删除多次结果相同)
HEAD - 获取资源元数据
HEAD /users/123
返回:只返回响应头,不返回响应体
用途:检查资源是否存在、获取资源大小等
OPTIONS - 获取资源支持的方法
OPTIONS /users
返回:Allow: GET, POST, PUT, DELETE
用途:CORS预检请求
2.3 状态码使用
HTTP状态码分类:
1xx - 信息性
100 Continue # 继续请求
2xx - 成功
200 OK # 成功(GET, PUT, PATCH)
201 Created # 创建成功(POST)
202 Accepted # 已接受,处理中(异步操作)
204 No Content # 成功,无返回内容(DELETE)
3xx - 重定向
301 Moved Permanently # 永久重定向
302 Found # 临时重定向
304 Not Modified # 未修改(缓存有效)
4xx - 客户端错误
400 Bad Request # 请求参数错误
401 Unauthorized # 未认证
403 Forbidden # 无权限
404 Not Found # 资源不存在
405 Method Not Allowed # 方法不允许
409 Conflict # 资源冲突
422 Unprocessable Entity # 验证失败
429 Too Many Requests # 请求过多
5xx - 服务器错误
500 Internal Server Error # 服务器内部错误
502 Bad Gateway # 网关错误
503 Service Unavailable # 服务不可用
504 Gateway Timeout # 网关超时
最常用的状态码:
┌──────────────────┬─────────────────────────┐
│ 操作 │ 状态码 │
├──────────────────┼─────────────────────────┤
│ GET成功 │ 200 OK │
│ POST创建成功 │ 201 Created │
│ PUT/PATCH成功 │ 200 OK │
│ DELETE成功 │ 204 No Content │
│ 参数错误 │ 400 Bad Request │
│ 未登录 │ 401 Unauthorized │
│ 无权限 │ 403 Forbidden │
│ 资源不存在 │ 404 Not Found │
│ 验证失败 │ 422 Unprocessable Entity│
│ 服务器错误 │ 500 Internal Server Error│
└──────────────────┴─────────────────────────┘
2.4 响应数据格式
标准JSON响应格式:
成功响应:
{
"status": "success",
"data": {
"id": 123,
"name": "张三",
"email": "zhang@example.com"
},
"message": "操作成功"
}
列表响应(带分页):
{
"status": "success",
"data": [
{"id": 1, "name": "张三"},
{"id": 2, "name": "李四"}
],
"pagination": {
"total": 100,
"page": 1,
"limit": 20,
"pages": 5
},
"message": "获取成功"
}
错误响应:
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "password",
"message": "密码长度至少8位"
}
]
}
}
统一字段说明:
- status: 状态(success/error)
- data: 数据内容
- message: 提示信息
- error: 错误详情
- pagination: 分页信息
3. 高级特性
3.1 版本控制
API版本控制的三种方式:
1. URI版本控制(推荐)
https://api.example.com/v1/users
https://api.example.com/v2/users
优点:清晰、直观
缺点:URI变化
2. 请求头版本控制
GET /users
Headers:
Accept: application/vnd.example.v1+json
优点:URI不变
缺点:不够直观
3. 参数版本控制
GET /users?version=1
优点:简单
缺点:容易忽略,不够规范
推荐做法:
- 使用URI版本控制
- 主版本号(v1, v2)
- 保持向后兼容
- 明确废弃策略
3.2 分页、过滤、排序
分页参数:
GET /users?page=2&limit=20
或使用offset和limit:
GET /users?offset=20&limit=20
响应包含分页信息:
{
"data": [...],
"pagination": {
"total": 1000, # 总记录数
"page": 2, # 当前页
"limit": 20, # 每页数量
"pages": 50, # 总页数
"prev": 1, # 上一页
"next": 3 # 下一页
}
}
---
过滤参数:
GET /users?role=admin&status=active
GET /products?category=electronics&price_min=100&price_max=1000
GET /orders?created_after=2024-01-01&created_before=2024-12-31
---
排序参数:
GET /users?sort=created_at&order=desc
GET /products?sort=-price # 减号表示降序
GET /users?sort=name,created_at # 多字段排序
---
字段选择(稀疏字段集):
GET /users?fields=id,name,email
只返回指定的字段,减少数据传输
---
搜索:
GET /users?search=张三
GET /products?q=手机
3.3 认证和授权
常见认证方式:
1. Basic Auth(基础认证)
Authorization: Basic base64(username:password)
优点:简单
缺点:不够安全(需HTTPS)
2. API Key
GET /users?api_key=your_api_key
或
Headers: X-API-Key: your_api_key
优点:简单、易于管理
缺点:密钥泄露风险
3. JWT (JSON Web Token)(推荐)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
优点:无状态、可扩展、信息丰富
缺点:令牌较大
4. OAuth 2.0
用于第三方授权
流程:
1. 客户端请求授权
2. 用户同意授权
3. 获取访问令牌
4. 使用令牌访问资源
JWT结构:
Header.Payload.Signature
{
"alg": "HS256", # 算法
"typ": "JWT" # 类型
}
.
{
"sub": "123", # 用户ID
"name": "张三",
"iat": 1516239022, # 签发时间
"exp": 1516242622 # 过期时间
}
.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
3.4 HATEOAS
HATEOAS (Hypermedia as the Engine of Application State)
超媒体驱动的应用状态
原则:
API响应中包含相关操作的链接
示例:
GET /users/123
响应:
{
"id": 123,
"name": "张三",
"email": "zhang@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"
}
]
}
优点:
- API自解释
- 客户端不需要硬编码URL
- 易于演化
缺点:
- 增加响应大小
- 实现复杂
4. 实战项目:用户管理API
4.1 API设计
用户管理API设计:
资源:Users(用户)
端点设计:
┌────────┬─────────────────┬──────────────────┬─────────┐
│ 方法 │ URI │ 描述 │ 状态码 │
├────────┼─────────────────┼──────────────────┼─────────┤
│ GET │ /api/v1/users │ 获取用户列表 │ 200 │
│ GET │ /api/v1/users/1 │ 获取指定用户 │ 200/404 │
│ POST │ /api/v1/users │ 创建新用户 │ 201 │
│ PUT │ /api/v1/users/1 │ 更新用户(完整) │ 200/404 │
│ PATCH │ /api/v1/users/1 │ 更新用户(部分) │ 200/404 │
│ DELETE │ /api/v1/users/1 │ 删除用户 │ 204/404 │
└────────┴─────────────────┴──────────────────┴─────────┘
查询参数:
- ?page=1&limit=20 # 分页
- ?role=admin # 按角色过滤
- ?search=张 # 搜索
- ?sort=created_at # 排序
4.2 代码实现
# day30_restful_api.py
# 功能:简单的RESTful API实现
from flask import Flask, request, jsonify
from datetime import datetime
import uuid
app = Flask(__name__)
# 模拟数据库
users_db = {}
# 初始化一些测试数据
def init_data():
"""初始化测试数据"""
users = [
{"name": "张三", "email": "zhang@example.com", "role": "admin"},
{"name": "李四", "email": "li@example.com", "role": "user"},
{"name": "王五", "email": "wang@example.com", "role": "user"},
]
for user_data in users:
user_id = str(uuid.uuid4())
users_db[user_id] = {
"id": user_id,
**user_data,
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
init_data()
def success_response(data=None, message="操作成功", status_code=200):
"""
成功响应格式
参数:
data: 返回的数据
message: 提示信息
status_code: HTTP状态码
"""
response = {
"status": "success",
"message": message
}
if data is not None:
response["data"] = data
return jsonify(response), status_code
def error_response(message="操作失败", code="ERROR", details=None, status_code=400):
"""
错误响应格式
参数:
message: 错误信息
code: 错误代码
details: 错误详情
status_code: HTTP状态码
"""
response = {
"status": "error",
"error": {
"code": code,
"message": message
}
}
if details:
response["error"]["details"] = details
return jsonify(response), status_code
# ============================================================
# 用户管理API
# ============================================================
@app.route('/api/v1/users', methods=['GET'])
def get_users():
"""
获取用户列表
查询参数:
page: 页码(默认1)
limit: 每页数量(默认20)
role: 角色过滤
search: 搜索(姓名或邮箱)
sort: 排序字段
order: 排序顺序(asc/desc)
"""
# 获取查询参数
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
role = request.args.get('role')
search = request.args.get('search')
sort_field = request.args.get('sort', 'created_at')
sort_order = request.args.get('order', 'desc')
# 获取所有用户
users_list = list(users_db.values())
# 过滤
if role:
users_list = [u for u in users_list if u.get('role') == role]
if search:
users_list = [
u for u in users_list
if search.lower() in u.get('name', '').lower()
or search.lower() in u.get('email', '').lower()
]
# 排序
reverse = (sort_order == 'desc')
users_list.sort(key=lambda x: x.get(sort_field, ''), reverse=reverse)
# 分页
total = len(users_list)
start = (page - 1) * limit
end = start + limit
users_page = users_list[start:end]
# 构造响应
response_data = {
"users": users_page,
"pagination": {
"total": total,
"page": page,
"limit": limit,
"pages": (total + limit - 1) // limit
}
}
return success_response(response_data, "获取用户列表成功")
@app.route('/api/v1/users/<user_id>', methods=['GET'])
def get_user(user_id):
"""
获取指定用户
路径参数:
user_id: 用户ID
"""
user = users_db.get(user_id)
if not user:
return error_response(
message=f"用户不存在",
code="USER_NOT_FOUND",
status_code=404
)
return success_response(user, "获取用户成功")
@app.route('/api/v1/users', methods=['POST'])
def create_user():
"""
创建新用户
请求体:
{
"name": "用户名",
"email": "邮箱",
"role": "角色"
}
"""
data = request.get_json()
# 验证必填字段
required_fields = ['name', 'email']
missing_fields = [f for f in required_fields if f not in data]
if missing_fields:
return error_response(
message="缺少必填字段",
code="VALIDATION_ERROR",
details=[{"field": f, "message": "此字段为必填项"} for f in missing_fields],
status_code=422
)
# 验证邮箱唯一性
email = data.get('email')
if any(u['email'] == email for u in users_db.values()):
return error_response(
message="邮箱已存在",
code="EMAIL_EXISTS",
status_code=409
)
# 创建用户
user_id = str(uuid.uuid4())
user = {
"id": user_id,
"name": data['name'],
"email": data['email'],
"role": data.get('role', 'user'),
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat()
}
users_db[user_id] = user
return success_response(user, "创建用户成功", 201)
@app.route('/api/v1/users/<user_id>', methods=['PUT'])
def update_user(user_id):
"""
完整更新用户
路径参数:
user_id: 用户ID
请求体:
{
"name": "用户名",
"email": "邮箱",
"role": "角色"
}
"""
if user_id not in users_db:
return error_response(
message="用户不存在",
code="USER_NOT_FOUND",
status_code=404
)
data = request.get_json()
# 验证必填字段
required_fields = ['name', 'email']
missing_fields = [f for f in required_fields if f not in data]
if missing_fields:
return error_response(
message="缺少必填字段",
code="VALIDATION_ERROR",
details=[{"field": f, "message": "此字段为必填项"} for f in missing_fields],
status_code=422
)
# 更新用户
users_db[user_id].update({
"name": data['name'],
"email": data['email'],
"role": data.get('role', 'user'),
"updated_at": datetime.now().isoformat()
})
return success_response(users_db[user_id], "更新用户成功")
@app.route('/api/v1/users/<user_id>', methods=['PATCH'])
def patch_user(user_id):
"""
部分更新用户
路径参数:
user_id: 用户ID
请求体:可以只包含要更新的字段
{
"name": "新用户名"
}
"""
if user_id not in users_db:
return error_response(
message="用户不存在",
code="USER_NOT_FOUND",
status_code=404
)
data = request.get_json()
# 只更新提供的字段
allowed_fields = ['name', 'email', 'role']
for field in allowed_fields:
if field in data:
users_db[user_id][field] = data[field]
users_db[user_id]['updated_at'] = datetime.now().isoformat()
return success_response(users_db[user_id], "更新用户成功")
@app.route('/api/v1/users/<user_id>', methods=['DELETE'])
def delete_user(user_id):
"""
删除用户
路径参数:
user_id: 用户ID
"""
if user_id not in users_db:
return error_response(
message="用户不存在",
code="USER_NOT_FOUND",
status_code=404
)
del users_db[user_id]
# DELETE成功通常返回204 No Content
return '', 204
# ============================================================
# API文档和健康检查
# ============================================================
@app.route('/api/v1/health', methods=['GET'])
def health_check():
"""健康检查"""
return success_response({
"status": "healthy",
"timestamp": datetime.now().isoformat()
})
@app.route('/api/v1/docs', methods=['GET'])
def api_docs():
"""API文档"""
docs = {
"version": "1.0.0",
"title": "用户管理API",
"endpoints": [
{
"path": "/api/v1/users",
"method": "GET",
"description": "获取用户列表",
"parameters": {
"page": "页码",
"limit": "每页数量",
"role": "角色过滤",
"search": "搜索",
"sort": "排序字段",
"order": "排序顺序"
}
},
{
"path": "/api/v1/users/<id>",
"method": "GET",
"description": "获取指定用户"
},
{
"path": "/api/v1/users",
"method": "POST",
"description": "创建新用户",
"body": {
"name": "用户名(必填)",
"email": "邮箱(必填)",
"role": "角色(可选)"
}
},
{
"path": "/api/v1/users/<id>",
"method": "PUT",
"description": "完整更新用户"
},
{
"path": "/api/v1/users/<id>",
"method": "PATCH",
"description": "部分更新用户"
},
{
"path": "/api/v1/users/<id>",
"method": "DELETE",
"description": "删除用户"
}
]
}
return success_response(docs)
if __name__ == '__main__':
print("""
╔══════════════════════════════════════════════════════════╗
║ RESTful API 服务器 ║
║ User Management API ║
╚══════════════════════════════════════════════════════════╝
API地址: http://127.0.0.1:5000/api/v1
可用端点:
- GET /api/v1/users 获取用户列表
- GET /api/v1/users/<id> 获取指定用户
- POST /api/v1/users 创建新用户
- PUT /api/v1/users/<id> 更新用户(完整)
- PATCH /api/v1/users/<id> 更新用户(部分)
- DELETE /api/v1/users/<id> 删除用户
- GET /api/v1/health 健康检查
- GET /api/v1/docs API文档
测试命令:
# 获取所有用户
curl http://127.0.0.1:5000/api/v1/users
# 创建用户
curl -X POST http://127.0.0.1:5000/api/v1/users \\
-H "Content-Type: application/json" \\
-d '{"name":"测试用户","email":"test@example.com"}'
# 获取单个用户
curl http://127.0.0.1:5000/api/v1/users/<user_id>
# 更新用户
curl -X PATCH http://127.0.0.1:5000/api/v1/users/<user_id> \\
-H "Content-Type: application/json" \\
-d '{"name":"新名字"}'
# 删除用户
curl -X DELETE http://127.0.0.1:5000/api/v1/users/<user_id>
""")
app.run(debug=True, host='0.0.0.0', port=5000)
5. API测试
5.1 使用curl测试
# 1. 获取所有用户
curl http://127.0.0.1:5000/api/v1/users
# 2. 分页和过滤
curl "http://127.0.0.1:5000/api/v1/users?page=1&limit=10&role=admin"
# 3. 创建用户
curl -X POST http://127.0.0.1:5000/api/v1/users \
-H "Content-Type: application/json" \
-d '{
"name": "新用户",
"email": "new@example.com",
"role": "user"
}'
# 4. 获取单个用户
curl http://127.0.0.1:5000/api/v1/users/<user_id>
# 5. 更新用户(PUT - 完整更新)
curl -X PUT http://127.0.0.1:5000/api/v1/users/<user_id> \
-H "Content-Type: application/json" \
-d '{
"name": "更新的用户",
"email": "updated@example.com",
"role": "admin"
}'
# 6. 更新用户(PATCH - 部分更新)
curl -X PATCH http://127.0.0.1:5000/api/v1/users/<user_id> \
-H "Content-Type: application/json" \
-d '{"name": "只更新名字"}'
# 7. 删除用户
curl -X DELETE http://127.0.0.1:5000/api/v1/users/<user_id>
# 8. 健康检查
curl http://127.0.0.1:5000/api/v1/health
6. 最佳实践总结
RESTful API设计最佳实践:
1. 使用名词表示资源,用HTTP方法表示操作
✅ GET /users
❌ GET /getUsers
2. 使用复数形式
✅ /users, /products
❌ /user, /product
3. 使用正确的HTTP状态码
200: 成功
201: 创建成功
204: 删除成功
400: 请求错误
401: 未认证
403: 无权限
404: 未找到
500: 服务器错误
4. 提供版本控制
/api/v1/users
5. 支持分页、过滤、排序
?page=1&limit=20&sort=name
6. 使用统一的响应格式
{
"status": "success",
"data": {...}
}
7. 提供清晰的错误信息
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "详细错误信息"
}
}
8. 使用HTTPS保护API
9. 实现认证和授权(JWT推荐)
10. 提供完善的API文档
11. 实现请求限流(Rate Limiting)
12. 使用缓存提高性能
13. 记录完整的日志
14. 编写完善的测试
7. 今日练习
练习1:设计博客API
设计一个博客系统的RESTful API,包含:
- 文章(articles)
- 评论(comments)
- 标签(tags)
要求:
- 设计合理的URI
- 选择正确的HTTP方法
- 考虑资源之间的关系
练习2:实现产品API
扩展示例代码,实现产品管理API:
- 产品CRUD操作
- 分类过滤
- 价格区间查询
- 库存管理
练习3:API测试
使用Postman或curl测试今天的用户管理API:
- 测试所有端点
- 测试边界情况
- 测试错误处理
8. 扩展阅读
- REST架构论文(Roy Fielding)
- HTTP/1.1规范(RFC 7231-7235)
- JSON API规范
- OpenAPI/Swagger文档规范
进度:30/120天(25.0%)
恭喜完成第30天学习!你已经掌握了RESTful API设计的核心原则。明天我们将学习WebSocket实时通信!