← 返回 Day 66 正文

Day 66 · 练习与清单

Day 066 — Flask 进阶:练习与检查表

✅ 今日完成清单


📝 练习题

基础题

题 1:SQLAlchemy 基础模型

定义以下数据模型:

作者(Author):
  - id: 整数,主键
  - name: 字符串(50),非空
  - email: 字符串(120),唯一

文章(Article):
  - id: 整数,主键
  - title: 字符串(200),非空
  - content: 文本
  - published: 布尔值,默认 False
  - author_id: 外键 → author.id
  - created_at: 日期时间,默认当前时间

标签(Tag):
  - id: 整数,主键
  - name: 字符串(30),唯一

一篇文章可以有多个标签,一个标签可以被多篇文章使用。

要求: 1. 用 Flask-SQLAlchemy 定义这三个模型 2. 设置 Author ↔ Article 的一对多关系 3. 设置 Article ↔ Tag 的多对多关系 4. 每个模型添加 to_dict() 序列化方法

题 2:认证系统实现

基于现有框架实现以下功能:

python
# 补全以下代码
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
import jwt

app = Flask(__name__)
app.config['SECRET_KEY'] = 'test-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)
    
    # TODO: 添加 set_password 和 check_password 方法

# TODO: 实现 token_required 装饰器
# TODO: 实现 login 路由
# TODO: 实现 register 路由
# TODO: 实现一个受保护的路由 /api/protected

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

要求: - set_password() 使用 generate_password_hash - check_password() 使用 check_password_hash - token_required 装饰器从 Authorization: Bearer <token> 提取并验证 JWT - /api/register 接收 username 和 password,返回 token - /api/login 验证凭据,返回 token - /api/protected 返回 {"message": "Hello, {username}!"}

题 3:RESTful 接口设计

为以下资源设计 RESTful API 端点:

资源:图书(Book)

字段:id, title, author, isbn, published_year, available (是否可借)

要求: 1. 列出所有图书(支持按作者过滤、分页、排序) 2. 获取单本图书详情 3. 添加新图书 4. 更新图书信息 5. 借出/归还图书(PATCH 局部更新 available 字段) 6. 删除图书

请写出: - 每个端点的 HTTP 方法 + URL 路径 - 请求体/参数格式 - 成功和错误状态的预期响应 - 适当的 HTTP 状态码


进阶题

题 4:带分页和过滤的 TODO API

扩展 TODO API,实现一个"智能列表"端点:

GET /api/v1/todos/smart-list

查询参数: - today: 如果为 true,只返回今天的任务(包括过期未完成的) - overdue: 如果为 true,只返回超过截止日期但未完成的任务 - week: 如果为 true,返回本周(周一到周日)的任务

返回格式:

json
{
    "data": [...],
    "meta": {
        "total": 42,
        "sections": {
            "overdue": 2,
            "today": 5,
            "upcoming": 35
        }
    }
}

要求: - 实现日期范围的正确计算(注意时区) - overdue 的任务即使在 today 列表中也要标记出来 - 按紧急程度排序:过期 > 今天到期 > 优先级高 > 创建时间新

题 5:Flask-Login 集成

将 Flask-Login 集成到已有的 TODO API 中,替换 JWT 认证:

python
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user

login_manager = LoginManager()
login_manager.init_app(app)

# TODO: 实现 UserMixin 用户模型
# TODO: 实现 user_loader 回调
# TODO: 实现基于 session 的登录/登出
# TODO: 将 @token_required 替换为 @login_required

要求: - 使用 UserMixin 混入类 - 实现 login_manager.user_loader - 登录路由调用 login_user() - 登出路由调用 logout_user() - 保护路由使用 @login_required - 对比 JWT 和 Session 两种方案的优劣


💡 挑战题

挑战 1:完整 Blog API

设计并实现一个完整的博客 API 系统:

功能需求:

  1. 用户系统 - 注册(支持用户名、邮箱、密码) - 登录(返回 JWT) - 个人资料查看/修改

  2. 文章系统 - CRUD(用户只能操作自己的文章) - 文章有标题、内容、摘要、封面图 URL - 文章可以发布/草稿两种状态 - 支持按标签分类 - 支持搜索(标题和内容全文搜索)

  3. 评论系统 - 用户可以对已发布的文章评论 - 评论支持回复(嵌套评论) - 文章作者可以删除评论

  4. 交互功能 - 点赞/取消点赞文章 - 统计文章阅读量(每次 GET 详情 +1) - 热门文章排行(按阅读量或点赞数排序)

技术要求: - 至少 5 个数据库模型 - RESTful 风格设计 - 完整的错误码体系 - 分页、过滤、排序支持 - 请求参数校验

示例数据模型:

python
class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    summary = db.Column(db.String(500))
    cover_url = db.Column(db.String(500))
    status = db.Column(db.String(20), default='draft')  # draft / published
    view_count = db.Column(db.Integer, default=0)
    
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = ...
    updated_at = ...
    published_at = db.Column(db.DateTime, nullable=True)

挑战 2:生产级错误处理中间件

实现一个完善的错误处理系统:

python
# 自定义异常类
class APIException(Exception):
    """API 异常基类"""
    def __init__(self, message, code, status_code=400):
        self.message = message
        self.code = code
        self.status_code = status_code

class NotFound(APIException):
    def __init__(self, message='资源不存在'):
        super().__init__(message, 'NOT_FOUND', 404)

class ValidationError(APIException):
    def __init__(self, message, details=None):
        super().__init__(message, 'VALIDATION_ERROR', 422)
        self.details = details or {}

class Unauthorized(APIException):
    def __init__(self, message='请先登录'):
        super().__init__(message, 'UNAUTHORIZED', 401)

class Forbidden(APIException):
    def __init__(self, message='权限不足'):
        super().__init__(message, 'FORBIDDEN', 403)

# TODO: 实现全局错误处理函数
# @app.errorhandler(APIException)
# def handle_api_exception(error):
#     ...

# @app.errorhandler(404)
# def handle_404(error):
#     ...

# @app.errorhandler(500)
# def handle_500(error):
#     ...

# @app.before_request
# def validate_content_type():
#     # 对 POST/PUT/PATCH 请求,检查 Content-Type
#     ...

要求: - 所有 API 错误通过 raise APIException 统一抛出 - 全局 errorhandler 捕获并返回统一格式的 JSON 错误 - 500 错误要记录日志并回滚数据库事务 - before_request 检查 JSON API 的 Content-Type


📚 参考资料