杰瑞科技汇

Tornado分页如何实现?数据查询怎么优化?

  1. 准备工作:安装 Tornado 并创建一个基本的应用结构。
  2. 模拟数据:创建一个模拟的数据源。
  3. 后端分页逻辑:编写 Tornado 处理器,实现分页查询和返回。
  4. 前端模板:创建一个 HTML 模板来展示分页数据和导航链接。
  5. 运行与测试:启动应用并查看效果。

准备工作

确保你已经安装了 Tornado,如果没有,可以通过 pip 安装:

Tornado分页如何实现?数据查询怎么优化?-图1
(图片来源网络,侵删)
pip install tornado

项目结构如下:

tornado_pagination/
├── main.py          # Tornado 应用入口
└── templates/
    └── index.html   # 前端页面模板

模拟数据

为了演示,我们不需要连接真实的数据库,在 main.py 中,我们创建一个包含 100 个条目的列表作为我们的数据源。

# main.py
# 模拟一个包含100个条目的数据源
# 每个条目是一个字典,包含 id 和 name
ALL_DATA = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]
# 每页显示多少条数据
ITEMS_PER_PAGE = 10

后端分页逻辑

这是实现分页的核心部分,我们将创建一个 Tornado RequestHandler 来处理分页请求。

分页逻辑要点

Tornado分页如何实现?数据查询怎么优化?-图2
(图片来源网络,侵删)
  1. 从 URL 的查询参数中获取当前页码 pagepage 不存在或无效,则默认为第 1 页。
  2. 根据每页显示的数量 ITEMS_PER_PAGE 计算出数据的起始索引和结束索引。
  3. 使用切片从总数据 ALL_DATA 中提取当前页的数据。
  4. 计算总页数,用于生成分页导航。
  5. 将当前页数据、当前页码、总页数等信息渲染到模板中。

下面是完整的 main.py 代码:

# main.py
import tornado.ioloop
import tornado.web
import tornado.escape
# 模拟一个包含100个条目的数据源
ALL_DATA = [{"id": i, "name": f"Item {i}"} for i in range(1, 101)]
# 每页显示多少条数据
ITEMS_PER_PAGE = 10
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        # 1. 获取页码参数,默认为1
        try:
            page = int(self.get_argument("page", 1))
        except ValueError:
            page = 1
        # 确保页码是正整数
        if page < 1:
            page = 1
        # 2. 计算切片的起始和结束索引
        start_index = (page - 1) * ITEMS_PER_PAGE
        end_index = start_index + ITEMS_PER_PAGE
        # 3. 获取当前页的数据
        page_data = ALL_DATA[start_index:end_index]
        # 4. 计算总页数
        total_items = len(ALL_DATA)
        total_pages = (total_items + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE
        # 5. 将数据传递给模板
        self.render(
            "index.html",
            page_data=page_data,
            current_page=page,
            total_pages=total_pages,
            items_per_page=ITEMS_PER_PAGE
        )
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ], template_path="templates")
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    print("Server is running on http://localhost:8888")
    tornado.ioloop.IOLoop.current().start()

代码解释

  • self.get_argument("page", 1): 从 URL 的查询参数中获取 page 的值,访问 http://localhost:8888/?page=3page 的值就是 3,如果参数不存在,则使用默认值 1
  • total_pages = (total_items + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE: 这是一个计算总页数的常用技巧,可以正确处理不能被整除的情况,避免了浮点数运算。
  • self.render(...): 将变量传递给 HTML 模板进行渲染。

前端模板

templates/index.html 中,我们将展示数据和生成分页导航链接。

<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">Tornado Pagination Example</title>
    <style>
        body { font-family: sans-serif; margin: 2em; }
        table { border-collapse: collapse; width: 50%; }
        th, td { border: 1px solid #dddddd; text-align: left; padding: 8px; }
        thead { background-color: #f2f2f2; }
        .pagination { margin-top: 20px; }
        .pagination a, .pagination span {
            padding: 8px 16px;
            text-decoration: none;
            border: 1px solid #ddd;
            color: #007bff;
        }
        .pagination a:hover { background-color: #ddd; }
        .pagination .current-page {
            background-color: #007bff;
            color: white;
            border-color: #007bff;
        }
        .pagination .disabled {
            color: #ccc;
            pointer-events: none;
            border-color: #eee;
        }
    </style>
</head>
<body>
    <h1>Item List</h1>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            {% for item in page_data %}
            <tr>
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
            </tr>
            {% end %}
        </tbody>
    </table>
    <div class="pagination">
        <!-- 上一页链接 -->
        {% if current_page > 1 %}
            <a href="/?page={{ current_page - 1 }}">« Previous</a>
        {% else %}
            <span class="disabled">« Previous</span>
        {% end %}
        <!-- 页码链接 -->
        {% for i in range(1, total_pages + 1) %}
            {% if i == current_page %}
                <span class="current-page">{{ i }}</span>
            {% else %}
                <a href="/?page={{ i }}">{{ i }}</a>
            {% end %}
        {% end %}
        <!-- 下一页链接 -->
        {% if current_page < total_pages %}
            <a href="/?page={{ current_page + 1 }}">Next »</a>
        {% else %}
            <span class="disabled">Next »</span>
        {% end %}
    </div>
</body>
</html>

模板语法解释

Tornado分页如何实现?数据查询怎么优化?-图3
(图片来源网络,侵删)
  • {% for item in page_data %} ... {% end %}: Tornado 的模板循环语法,用于遍历 page_data
  • {{ item.id }}: Tornado 的模板变量插入语法,用于显示变量值。
  • {% if current_page > 1 %} ... {% else %} ... {% end %}: 条件判断,我们用它来决定是否显示“上一页”链接,以及是否禁用它。
  • href="/?page={{ i }}": 动态生成指向不同页码的链接。

运行与测试

  1. 确保你的文件结构正确。

  2. 打开终端,进入 tornado_pagination 目录。

  3. 运行 main.py

    python main.py
  4. 打开你的浏览器,访问 http://localhost:8888

你应该能看到第一页的数据(1-10条),以及底部的分页导航,点击 "Next »" 或页码 "2",URL 会变成 http://localhost:8888/?page=2,页面会显示第2条数据(11-20条)。

总结与扩展

这个例子提供了一个标准的、易于理解的分页实现,在实际项目中,你可以根据需要进行扩展:

  • URL 友好化:可以使用 tornado.web.urlspec 的正则表达式来美化 URL,/page/3 而不是 /?page=3

    # 在 make_app 中
    (r"/page/(\d+)", MainHandler),

    然后在 get 方法中通过 self.request.path_args[0] 获取页码。

  • ORM 集成:如果你的数据来自数据库(如 SQLAlchemy, Peewee),逻辑是相通的,你只需要将 ALL_DATA[start_index:end_index] 替换为数据库查询,例如使用 offset()limit()

    # 伪代码,以 SQLAlchemy 为例
    page_data = session.query(MyModel).offset(start_index).limit(ITEMS_PER_PAGE).all()
    total_items = session.query(MyModel).count()
  • AJAX 分页:为了提升用户体验,可以使用 JavaScript (如 Fetch API) 来异步获取数据,然后动态更新页面内容,而无需刷新整个页面,后端可以只返回 JSON 数据,而不是完整的 HTML。

  • 更复杂的分页组件:当数据量非常大时(例如成千上万页),可以只显示当前页附近的几个页码,而不是所有页码,以避免导航栏过长。

分享:
扫描分享到社交APP
上一篇
下一篇