[Python教程系列-20] REST API开发:使用Flask/Django创建RESTful API服务

108 阅读9分钟

引言

随着互联网的发展,Web服务已成为现代应用程序的重要组成部分。REST(Representational State Transfer)作为一种轻量级的架构风格,因其简洁性和可扩展性而成为构建Web API的事实标准。Python凭借其简洁的语法和丰富的生态系统,成为了开发REST API的理想选择。

在本章中,我们将深入探讨如何使用Python的两大主流Web框架——Flask和Django来构建RESTful API服务。我们将从基础概念入手,逐步介绍API的设计原则、路由处理、数据序列化、认证授权等关键主题,并通过实际案例展示如何构建一个功能完整的REST API。

学习目标

完成本章学习后,您将能够:

  1. 理解REST架构的基本原理和约束条件
  2. 掌握Flask和Django框架的基础知识
  3. 设计符合REST规范的API端点
  4. 实现数据的增删改查(CRUD)操作
  5. 处理API请求和响应的序列化
  6. 实现API的身份验证和授权机制
  7. 使用Swagger/OpenAPI文档化API接口
  8. 进行API测试和调试
  9. 部署REST API到生产环境

核心知识点讲解

REST架构风格

REST是一种基于资源的架构风格,它将网络上的所有事物都抽象为资源,并通过标准的HTTP方法对这些资源进行操作。REST的核心约束包括:

  1. 客户端-服务器分离:用户界面与数据存储分离,提高了跨平台的可移植性
  2. 无状态性:每个请求都包含足够的信息,服务器不需要保存客户端状态
  3. 缓存性:响应可以被标记为可缓存或不可缓存,提高系统效率
  4. 统一接口:通过标准化的接口操作资源,简化系统架构
  5. 分层系统:允许在客户端和服务器之间插入中间层
  6. 按需代码(可选):服务器可以临时向客户端传输可执行代码

HTTP方法与状态码

RESTful API使用标准的HTTP方法来操作资源:

  • GET:获取资源信息(幂等操作)
  • POST:创建新资源
  • PUT:更新整个资源(幂等操作)
  • PATCH:部分更新资源
  • DELETE:删除资源

常见的HTTP状态码:

  • 2xx:成功状态码(200 OK, 201 Created, 204 No Content)
  • 4xx:客户端错误(400 Bad Request, 401 Unauthorized, 404 Not Found)
  • 5xx:服务器错误(500 Internal Server Error, 503 Service Unavailable)

Flask框架基础

Flask是一个轻量级的Python Web框架,以其简洁性和灵活性著称。它采用微框架设计理念,核心简单但可通过扩展增强功能。

Flask核心特性:

  1. WSGI工具箱:基于Werkzeug WSGI工具箱
  2. 模板引擎:集成Jinja2模板引擎
  3. 路由系统:灵活的URL路由映射
  4. 请求处理:便捷的请求和响应对象
  5. 扩展机制:丰富的第三方扩展生态

Django框架基础

Django是一个全功能的Python Web框架,遵循"电池已包含"的理念,提供了构建Web应用所需的各种组件。

Django核心特性:

  1. ORM系统:强大的对象关系映射器
  2. Admin界面:自动生成的管理后台
  3. 认证系统:内置用户认证和权限管理
  4. URL路由:灵活的URL分发系统
  5. 模板系统:功能丰富的模板引擎
  6. 表单处理:自动化的表单验证和处理

API设计原则

良好的REST API设计应遵循以下原则:

  1. 资源导向:以名词而非动词命名资源
  2. 统一接口:使用标准HTTP方法操作资源
  3. 状态码语义:正确使用HTTP状态码表达操作结果
  4. 版本控制:通过URL或头部实现API版本管理
  5. 安全性:实现身份验证和授权机制
  6. 文档化:提供清晰的API文档

数据序列化

在REST API中,数据序列化是将对象转换为可传输格式(如JSON)的过程。常用的序列化方案包括:

  1. Flask-RESTful:Flask的扩展,提供简单的序列化功能
  2. Marshmallow:独立的序列化库,支持复杂的数据验证
  3. Django REST Framework Serializers:Django REST Framework提供的序列化器

认证与授权

API的安全性是至关重要的,常见的认证方式包括:

  1. Token认证:使用JWT或自定义Token进行身份验证
  2. OAuth 2.0:行业标准的授权框架
  3. API密钥:简单的密钥验证机制
  4. Session认证:基于会话的传统认证方式

代码示例与实战

实战一:使用Flask构建博客API

让我们从零开始构建一个简单的博客API,包含文章的增删改查功能。

# requirements.txt
flask==2.3.2
flask-sqlalchemy==3.0.5
flask-marshmallow==0.15.0
marshmallow-sqlalchemy==0.29.0
flask-jwt-extended==4.5.2

# app.py
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
from datetime import datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['JWT_SECRET_KEY'] = 'your-secret-key'

db = SQLAlchemy(app)
ma = Marshmallow(app)
jwt = JWTManager(app)

# 数据模型定义
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

# 序列化器定义
class UserSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = User
        load_instance = True
        exclude = ('password',)

class PostSchema(ma.SQLAlchemyAutoSchema):
    class Meta:
        model = Post
        load_instance = True
        include_fk = True
    
    author = ma.Nested(UserSchema)

# 初始化序列化器实例
user_schema = UserSchema()
users_schema = UserSchema(many=True)
post_schema = PostSchema()
posts_schema = PostSchema(many=True)

# 用户注册
@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if User.query.filter_by(username=username).first():
        return jsonify({'message': '用户名已存在'}), 400
    
    user = User(username=username, password=password)
    db.session.add(user)
    db.session.commit()
    
    return jsonify({'message': '用户注册成功'}), 201

# 用户登录
@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    user = User.query.filter_by(username=username, password=password).first()
    if not user:
        return jsonify({'message': '用户名或密码错误'}), 401
    
    access_token = create_access_token(identity=user.id)
    return jsonify(access_token=access_token), 200

# 获取所有文章
@app.route('/posts', methods=['GET'])
def get_posts():
    page = request.args.get('page', 1, type=int)
    per_page = min(request.args.get('per_page', 10, type=int), 100)
    
    posts = Post.query.paginate(
        page=page, per_page=per_page, error_out=False)
    
    return posts_schema.jsonify(posts.items), 200

# 获取单篇文章
@app.route('/posts/<int:post_id>', methods=['GET'])
def get_post(post_id):
    post = Post.query.get_or_404(post_id)
    return post_schema.jsonify(post), 200

# 创建新文章
@app.route('/posts', methods=['POST'])
@jwt_required()
def create_post():
    current_user_id = get_jwt_identity()
    data = request.get_json()
    
    post = Post(
        title=data['title'],
        content=data['content'],
        user_id=current_user_id
    )
    
    db.session.add(post)
    db.session.commit()
    
    return post_schema.jsonify(post), 201

# 更新文章
@app.route('/posts/<int:post_id>', methods=['PUT'])
@jwt_required()
def update_post(post_id):
    current_user_id = get_jwt_identity()
    post = Post.query.get_or_404(post_id)
    
    # 检查文章作者权限
    if post.user_id != current_user_id:
        return jsonify({'message': '权限不足'}), 403
    
    data = request.get_json()
    post.title = data.get('title', post.title)
    post.content = data.get('content', post.content)
    
    db.session.commit()
    return post_schema.jsonify(post), 200

# 删除文章
@app.route('/posts/<int:post_id>', methods=['DELETE'])
@jwt_required()
def delete_post(post_id):
    current_user_id = get_jwt_identity()
    post = Post.query.get_or_404(post_id)
    
    # 检查文章作者权限
    if post.user_id != current_user_id:
        return jsonify({'message': '权限不足'}), 403
    
    db.session.delete(post)
    db.session.commit()
    
    return jsonify({'message': '文章删除成功'}), 200

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

实战二:使用Django构建任务管理系统API

接下来我们使用Django构建一个任务管理系统API,展示Django REST Framework的强大功能。

# requirements.txt
django==4.2.3
djangorestframework==3.14.0
django-cors-headers==4.2.0

# settings.py (部分配置)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'tasks',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20
}

# models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class Task(models.Model):
    PRIORITY_CHOICES = [
        ('low', '低'),
        ('medium', '中'),
        ('high', '高'),
    ]
    
    STATUS_CHOICES = [
        ('pending', '待处理'),
        ('in_progress', '进行中'),
        ('completed', '已完成'),
        ('cancelled', '已取消'),
    ]
    
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default='medium')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    due_date = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(auto_now=True)
    assigned_to = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assigned_tasks')
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='created_tasks')
    
    def __str__(self):
        return self.title

# serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Task

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name']

class TaskSerializer(serializers.ModelSerializer):
    assigned_to = UserSerializer(read_only=True)
    created_by = UserSerializer(read_only=True)
    assigned_to_id = serializers.IntegerField(write_only=True)
    
    class Meta:
        model = Task
        fields = '__all__'
        read_only_fields = ('created_at', 'updated_at', 'created_by')
    
    def create(self, validated_data):
        validated_data['created_by'] = self.context['request'].user
        return super().create(validated_data)

# views.py
from rest_framework import generics, permissions, filters
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Task
from .serializers import TaskSerializer

class TaskListCreateView(generics.ListCreateAPIView):
    serializer_class = TaskSerializer
    permission_classes = [permissions.IsAuthenticated]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['status', 'priority', 'assigned_to']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at', 'due_date', 'priority']
    ordering = ['-created_at']
    
    def get_queryset(self):
        return Task.objects.filter(
            models.Q(assigned_to=self.request.user) | 
            models.Q(created_by=self.request.user)
        ).distinct()
    
    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)

class TaskDetailView(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = TaskSerializer
    permission_classes = [permissions.IsAuthenticated]
    
    def get_queryset(self):
        return Task.objects.filter(
            models.Q(assigned_to=self.request.user) | 
            models.Q(created_by=self.request.user)
        ).distinct()

# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TaskListCreateView, TaskDetailView

urlpatterns = [
    path('tasks/', TaskListCreateView.as_view(), name='task-list-create'),
    path('tasks/<int:pk>/', TaskDetailView.as_view(), name='task-detail'),
]

# urls.py (项目根目录)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('tasks.urls')),
    path('api-auth/', include('rest_framework.urls')),
]

API文档化

良好的API文档对于开发者使用API至关重要。我们可以使用Swagger UI来自动生成交互式API文档。

# Flask + Flasgger示例
from flasgger import Swagger, swag_from

app.config['SWAGGER'] = {
    'title': '博客API文档',
    'uiversion': 3
}

swagger = Swagger(app)

@app.route('/posts', methods=['GET'])
@swag_from({
    'responses': {
        200: {
            'description': '获取文章列表',
            'schema': {
                'type': 'array',
                'items': {
                    '$ref': '#/definitions/Post'
                }
            }
        }
    }
})
def get_posts():
    # 实现代码...

# Django + drf-yasg示例
# settings.py
INSTALLED_APPS = [
    # ...
    'drf_yasg',
]

# urls.py
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
   openapi.Info(
      title="任务管理API",
      default_version='v1',
      description="任务管理系统的API文档",
   ),
   public=True,
   permission_classes=[permissions.AllowAny],
)

urlpatterns = [
    # ...
    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
]

小结与回顾

在本章中,我们深入探讨了REST API开发的核心概念和实践方法:

  1. REST架构理解:掌握了REST的六大约束和设计原则,理解了资源导向的API设计理念。

  2. 框架对比:比较了Flask和Django两种框架的特点,Flask适合轻量级、灵活的应用,而Django适合功能丰富、快速开发的企业级应用。

  3. 核心功能实现:通过两个完整的实战案例,学会了如何实现API的增删改查操作、用户认证、数据序列化等功能。

  4. 安全考虑:了解了JWT Token认证机制,掌握了API安全防护的基本方法。

  5. 文档化实践:学会了使用Swagger/OpenAPI工具自动生成API文档,提升API的可用性。

  6. 最佳实践:掌握了API版本控制、错误处理、分页查询等最佳实践。

练习与挑战

基础练习

  1. 扩展博客API

    • 添加文章分类功能
    • 实现文章评论系统
    • 添加文章点赞功能
  2. 完善任务管理系统

    • 添加任务标签功能
    • 实现任务提醒机制
    • 添加任务统计报表

进阶挑战

  1. 构建电商API

    • 设计商品、订单、用户等核心资源
    • 实现购物车功能
    • 添加支付接口集成
  2. 社交网络API

    • 实现用户关注/粉丝系统
    • 构建动态发布和评论功能
    • 添加消息通知系统

性能优化挑战

  1. 缓存优化

    • 实现Redis缓存机制
    • 添加CDN静态资源加速
    • 优化数据库查询性能
  2. 并发处理

    • 实现异步任务处理
    • 添加负载均衡配置
    • 优化API响应时间

扩展阅读

  1. 官方文档

  2. 书籍推荐

    • 《Flask Web开发》- Miguel Grinberg
    • 《Django企业开发实战》- 胡阳
    • 《RESTful Web APIs》- Leonard Richardson
  3. 在线资源

  4. 工具推荐

    • Postman:API测试工具
    • Insomnia:现代化API客户端
    • Docker:容器化部署工具