51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2790|回复: 0
打印 上一主题 下一主题

[原创] 如何优雅的对列表接口进行分页?

[复制链接]
  • TA的每日心情
    擦汗
    昨天 08:46
  • 签到天数: 981 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-9-30 11:19:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
     最近,在做测试开发平台的时候,需要对测试用例的列表进行后端分页,在实际去写代码和测试的过程中,发现这里面还是有些细节的,故想复盘一下,所以有了这篇文章。
      分页的目的
      做个假设,加入用例库有 1W 条数据,如果想要以列表形式展示,一次性返回 1W 条数据;这样做有两个弊端:
      1.这样写出来的接口会慢。想一下如果随着时间的发展,这个数量变成了 10W、100W 该怎么办?
      2.对前端不友好。用前端渲染 1W 条数据,有理智的开发都不会这么做。
      所以,我们做分页的目的,主要是为了性能的提高,提高使用体验。
      我这边开发语言是Python,Web框架用的是 Tornado,后台数据库用的是 Mongodb。
      接口设计
      首先,我们做一下接口的设计,如下:
    1. HTTP URL: /api/admin/v1/case/list
    2. HTTP Method: GET
    3. Request Params
    4. Reponse Data
    5. {
    6.     "code": 0,
    7.     "message": null,
    8.     "data": {
    9.         "cases": [
    10.             {
    11.                 "method": str,
    12.                 "url": str,
    13.                 "request_data"?: str,
    14.                 "request_params"?: str,
    15.                 "header": dict,
    16.                 "reponse_data"?: str
    17.             },
    18.             ...
    19.         ],
    20.         "count": int
    21.     }
    22. }
    复制代码

    返回数据结构解析:
      cases 的值是包含多个 case 对象的列表;
      count 指的是 case 的总数,这样前端就可以通过 count 来判断一共有多少页,然后通过点击的页数直接请求到对应页的数据做展示,这样做可以有效的控制返回的数据量以及返回时间,提高用户体验感。
      根据上述的接口约定,接口实现如下(仅展示关键代码):
    1. class CaseList(APIHandler):
    2.     async def get_handler(self):
    3.         page = self.input.page or 1
    4.         limit = self.input.limit or 12
    5.         page, limit = int(page), int(limit)
    6.         skip = limit * (page - 1)
    7.         return {
    8.             'customers': await CaseService.get_case_list(filter_={}, skip=skip, limit=limit),
    9.             'count': await CaseService.get_count(filter_={})
    10.         }
    复制代码

    接口解读
      下面我们逐行来解读这个接口。
      1.根据 API 约定,page和limit是不必传且有默认值,所以获取时可以直接写为self.input.page or 1,其中当page不传时self.input.page为None。
      2.后端获取到的GET请求的请求参数是String,所以需要int()转为整型,这里直接int(page)即可,因为经过了前面的处理,page必然是有值的。
      3.通过limit * (page - 1),计算出跳过多少个;白话描述skip的作用就是,从第skip个对象开始,往后取limit个,返回。
      4.get_case_list方法和get_count方法的filter_参数的值应该保持一致,因为他们都是对case这个对象做操作,只不过count是取总数而已。

      CaseService.get_case_list方法
    1. async def get_case_list(filter_: dict, skip: int = None, limit: int = None) -> Optional[List[dict]]:
    2.     """ 获取 case 列表 """
    3.         sort = [('create_time', -1)]
    4.     if skip is not None and limit is not None:
    5.         ret = await cls.find('case', filter=filter_, sort=sort, limit=limit, skip=skip)
    6.     else:
    7.         ret = await cls.find('case', filter=filter_, sort=sort)
    8.     return ret
    复制代码

    1.对skip和limit的空值判断要用is not None,因为skip可能为,如果使用if skip来判断,skip=0的时候也会进入分支。
      2.sort = [(‘create_time’, -1)]的传入使返回的列表按照创建时间倒叙排列,如果你不做这个,相信负责人的测试同学会给你提一个“易用性问题”。
      3.sort + skip + limit是有执行优先级的,他们优先级依次是sort→skip→limit,skip+limit的优先级为skip→limit。
      这里大家不要去考虑pymogo执行的时候会不会对sort、skip、limit自动给按照优先级执行,传参的时候按照优先级去传参就好了,养成良好的习惯,不必纠结其他的。

      CaseService.get_count方法
    1. async def get_count(cls, filter_: dict) -> int:
    2.     """ 获取 case 总数 """
    3.     return await ModelHandler.count('case', filter=filter_)
    复制代码

    这里用count来获取数据,可以直接拿到总数,而不是取出list然后去求length,避免了内存空间的浪费。
      以上,就是对列表接口进行分页的整个过程啦,谢谢阅读,这不是一篇喂饭教程,是带你熟悉了写这个接口的整个过程及其中的细节,具体的实现还需要大家动手去实践。

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏1
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-7-9 06:53 , Processed in 0.059115 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表