现在位置: 首页 > FastAPI 教程 > 正文

关键词搜索与分页

本章你将学会用 SQLAlchemy 实现联合搜索,并用 offset/limit 实现分页。


ilike 大小写不敏感搜索

实例

# 单字段搜索
posts = db.query(Post).filter(Post.title.ilike(f'%{keyword}%')).all()

# 多字段联合搜索(OR 关系)
from sqlalchemy import or_
posts = db.query(Post).filter(
    or_(
        Post.title.ilike(f'%{keyword}%'),
        Post.summary.ilike(f'%{keyword}%')
    )
).all()

offset/limit 分页

Flask-SQLAlchemy 提供了便捷的 .paginate() 方法。

FastAPI 使用的是原生 SQLAlchemy,需要用 offset + limit 手动实现分页。

分页方式Flask-SQLAlchemyFastAPI(原生 SQLAlchemy)
获取当前页数据.paginate(page, per_page).items.offset(skip).limit(per_page).all()
总记录数pagination.totalquery.count()
总页数pagination.pages手动计算 math.ceil(total / per_page)

分页计算公式

实例

import math
from fastapi import Query

def get_pagination(page: int = 1, size: int = 6):
    """
    计算分页参数
    page:当前页码(从 1 开始)
    size:每页条数
    """

    skip = (page - 1) * size    # 跳过的记录数
    return skip, size

# 使用示例
skip, limit = get_pagination(page=2, size=6)
# skip = 6, limit = 6 → 跳过前 6 条,取第 7-12 条

完整的搜索 + 分页视图

实例

# 文件路径:routers/posts.py(扩展 index 路由)
from sqlalchemy import or_
from math import ceil
from fastapi import APIRouter, Depends, Request, Query

@router.get("/", name="index")
def index(
    request: Request,
    category: str | None = Query(None),
    q: str | None = Query(None, description="搜索关键词"),
    page: int = Query(1, ge=1, description="页码"),
    size: int = Query(6, ge=1, le=50, description="每页条数"),
    db: Session = Depends(get_db)
):
    """首页:分类筛选 + 关键词搜索 + 分页"""
    posts_query = db.query(Post).order_by(Post.created_at.desc())

    # 分类筛选
    if category:
        posts_query = posts_query.join(Category).filter(Category.slug == category)

    # 关键词搜索
    if q:
        keyword = f'%{q.strip()}%'
        posts_query = posts_query.filter(
            or_(
                Post.title.ilike(keyword),
                Post.summary.ilike(keyword)
            )
        )

    # 分页
    total = posts_query.count()
    total_pages = ceil(total / size)
    skip = (page - 1) * size
    posts = posts_query.offset(skip).limit(size).all()

    return templates.TemplateResponse("index.html", {
        "request": request,
        "posts": posts,
        "categories": db.query(Category).all(),
        "category_slug": category or "",
        "keyword": q or "",
        "page": page,
        "total_pages": total_pages,
        "total": total
    })

Query(1, ge=1) 中的 ge 是 greater than or equal 的缩写。FastAPI 的查询参数支持完整的数值约束:ge、le、gt、lt。如果用户传入 page=0,FastAPI 会自动返回 422 错误。


模板中的分页导航

实例

<!-- 在 index.html 的文章列表下方追加分页导航 -->
{% if total_pages > 1 %}
<div class="pagination">
    {% if page > 1 %}
    <a href="/?page={{ page - 1 }}&category={{ category_slug }}&q={{ keyword }}">← 上一页</a>
    {% endif %}

    <span class="page-info">第 {{ page }} 页 / 共 {{ total_pages }} 页</span>

    {% if page < total_pages %}
    <a href="/?page={{ page + 1 }}&category={{ category_slug }}&q={{ keyword }}">下一页 →</a>
    {% endif %}
</div>
{% endif %}

本章小结

本章你实现了列表页的实用功能:ilike 大小写不敏感搜索、or_() 多字段联合搜索、offset/limit 手动分页(替代 Flask 的 paginate)、查询参数用 Query() 约束范围。

搜索 + 分类筛选 + 分页可以任意组合,条件不会丢失。