4

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

 1 year ago
source link: https://www.cnblogs.com/deali/p/16992573.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

在之前的第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": [{...},{...},{...},{...},{...}]
}

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK