基于.NetCore开发博客项目 StarBlog – (23) 文章列表接口分页、过滤、搜索、排序

前言

上一篇留的坑,火速补上。

在之前的第6篇中,已经有初步介绍,本文做一些补充,已经搞定这部分的同学可以快速跳过,基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表

对标准的WebApi来说,分页、过滤、搜索、排序是很常见的功能,既可以方便用户查看数据,又可以提升程序性能。

通用请求参数

定义一个类来作为通用的请求参数

列表接口通用的参数是这几个:PageSize, Page, Search, SortBy

反映到URL上,就是 Blog/?pageSize=10&page=1&search=关键词 这样的形式

public class QueryParameters {     /// <summary>     /// 最大页面条目     /// </summary>     public const int MaxPageSize = 50;      private int _pageSize = 10;      /// <summary>     /// 页面大小     /// </summary>     public int PageSize {         get => _pageSize;         set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;     }      /// <summary>     /// 当前页码     /// </summary>     public int Page { get; set; } = 1;      /// <summary>     /// 搜索关键词     /// </summary>     public string? Search { get; set; }      /// <summary>     /// 排序字段     /// </summary>     public string? SortBy { get; set; } } 

文章列表请求参数

在通用请求参数 QueryParameters 的基础上,派生出文章列表的请求参数类 PostQueryParameters

public class PostQueryParameters : QueryParameters {     /// <summary>     /// 仅请求已发布文章     /// </summary>     public bool OnlyPublished { get; set; } = false;      /// <summary>     /// 文章状态     /// </summary>     public string? Status { get; set; }          /// <summary>     /// 分类ID     /// </summary>     public int CategoryId { get; set; } = 0;      /// <summary>     /// 排序字段     /// </summary>     public new string? SortBy { get; set; } = "-LastUpdateTime"; } 

在通用请求参数的基础上,增加文章相关的筛选字段。

SortBy 字段使用 new 关键词覆盖基类属性,设置为默认排序是最后更新时间,前面加个减号表示倒序。

service

StarBlog.Web/Services/PostService.cs 中封装获取分页列表的方法

代码里有注释,比较容易,根据 PostQueryParameters 中的各种参数来做过滤筛选

public IPagedList<Post> GetPagedList(PostQueryParameters param) {     var querySet = _postRepo.Select;      // 是否发布     if (param.OnlyPublished) {         querySet = _postRepo.Select.Where(a => a.IsPublish);     }      // 状态过滤     if (!string.IsNullOrEmpty(param.Status)) {         querySet = querySet.Where(a => a.Status == param.Status);     }      // 分类过滤     if (param.CategoryId != 0) {         querySet = querySet.Where(a => a.CategoryId == param.CategoryId);     }      // 关键词过滤     if (!string.IsNullOrEmpty(param.Search)) {         querySet = querySet.Where(a => a.Title.Contains(param.Search));     }      // 排序     if (!string.IsNullOrEmpty(param.SortBy)) {         // 是否升序         var isAscending = !param.SortBy.StartsWith("-");         var orderByProperty = param.SortBy.Trim('-');          querySet = querySet.OrderByPropertyName(orderByProperty, isAscending);     }      return querySet.Include(a => a.Category).ToList()         .ToPagedList(param.Page, param.PageSize); } 

搜索的实现

在上面 service 的代码中

可以看到搜索只是简单的“关键词过滤”

使用 Title.Contains(param.Search) ,转换成SQL就是

select * from post where title like '%关键词%' 

单纯判断标题字符串中是否包含有关键词的子串。

这对于简单搜索一下文章是够用的,如果要像谷歌、百度这类搜索引擎一样能搜到文章的内容,需要用上全文检索。

现在主流的就是 ElasticSearch 和 Solr,后续可以考虑把这个功能加入本项目~

PS:关于全文检索,我之前写过一篇文章:全文检索引擎原理以及Lucene简单介绍

同时开源了一个玩具级的全文检索引擎,https://github.com/Deali-Axy/CloverSearch

分页的实现

本项目使用 X.PagedList 来实现分页功能

这个组件在结合MVC使用很方便,如果纯WebApi的话,用数据库自带的分页是更好的选择,性能更好。

这个分页组件是在 IEnumerable<T> 上添加了扩展方法 ToPagedList,所以在用的时候要先把数据都读取出来,再执行分页,性能不如在数据库里做好分页再读出来,很多ORM都支持这个功能,FreeSQL也不例外。

用法例子:

var list = fsql.Select<Topic>()     .Where(a => a.Id > 10)     .Count(out var total) //总记录数量     .Page(1, 20)     .Tolist(); 

详情请查看FreeSQL官方文档:https://freesql.net/guide/paging.html

用上 X.PagedList 这个组件后,在任意 IEnumerable<T> 对象上执行 ToPagedList 方法,可以得到 IPagedList<T> 对象

这个对象处理当前页面的列表数据,还有分页信息。

为了让前端可以方便的使用这部分信息,我又写了个扩展方法。

StarBlog.Web/Extensions/PagedListExt.cs

public static class PagedListExt {     public static PaginationMetadata ToPaginationMetadata(this IPagedList page) {         return new PaginationMetadata {             PageCount = page.PageCount,             TotalItemCount = page.TotalItemCount,             PageNumber = page.PageNumber,             PageSize = page.PageSize,             HasNextPage = page.HasNextPage,             HasPreviousPage = page.HasPreviousPage,             IsFirstPage = page.IsFirstPage,             IsLastPage = page.IsLastPage,             FirstItemOnPage = page.FirstItemOnPage,             LastItemOnPage = page.LastItemOnPage         };     }      public static string ToPaginationMetadataJson(this IPagedList page) {         return JsonSerializer.Serialize(ToPaginationMetadata(page));     } } 

这样就可以在分页后得到的 IPagedList 对象上执行 ToPaginationMetadata 得到分页元数据了。

这个 PaginationMetadata 也是本项目里定义的 ViewModel,StarBlog.Web/ViewModels/PaginationMetadata.cs

代码如下

public class PaginationMetadata {     public int PageCount { get; set; }     public int TotalItemCount { get; set; }     public int PageNumber { get; set; }     public int PageSize { get; set; }     public bool HasPreviousPage { get; set; }     public bool HasNextPage { get; set; }     public bool IsFirstPage { get; set; }     public bool IsLastPage { get; set; }     public int FirstItemOnPage { get; set; }     public int LastItemOnPage { get; set; } } 

搞定

controller与最终效果

代码如下

[AllowAnonymous] [HttpGet] public ApiResponsePaged<Post> GetList([FromQuery] PostQueryParameters param) {     var pagedList = _postService.GetPagedList(param);     return new ApiResponsePaged<Post> {         Message = "Get posts list",         Data = pagedList.ToList(),         Pagination = pagedList.ToPaginationMetadata()     }; } 

获取到分页数据之后,输出 ApiResponsePaged<T> 类型的返回值

这个也是我封装的接口返回值类型,下一篇文章会详细介绍

Data 属性就是列表数据,Pagination 属性是分页的信息。

请求这个接口返回的效果如下

{   "pagination": {     "pageCount": 40,     "totalItemCount": 394,     "pageNumber": 1,     "pageSize": 10,     "hasPreviousPage": false,     "hasNextPage": true,     "isFirstPage": true,     "isLastPage": false,     "firstItemOnPage": 1,     "lastItemOnPage": 10   },   "statusCode": 200,   "successful": true,   "message": "Get posts list",   "data": [{...},{...},{...},{...},{...}] } 

系列文章

发表评论

相关文章