Nestjs 最佳实践教程:4 排序,分页与过滤 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lichnow
V2EX    Node.js

Nestjs 最佳实践教程:4 排序,分页与过滤

  •  
  •   lichnow 2022-07-10 09:45:39 +08:00 4168 次点击
    这是一个创建于 1241 天前的主题,其中的信息可能已经有所发展或是发生改变。

    另,本人在找工作中,希望能有远程工作匹配(无法去外地),有需要的老板可以看一下我的个人介绍: https://pincman.com/about

    学习目标

    • 重载 TreeRepository 自带方法来对树形结构的数据进行扁平化处理
    • 对 Typeorm 查询出的数据列表进行分页处理
    • 通过请求中的 query 查询对数据进行筛选处理,比如排序,过滤等
    • 实现发布文章和取消发布的功能
    • Typeorm 模型事件和 Subscriber(订阅者)的使用
    • 使用sanitize-html对文章内容进行防注入攻击处理

    预装依赖

    ~ pnpm add nestjs-typeorm-paginate sanitize-html deepmerge && pnpm add @types/sanitize-html -D 

    文件结构

    创建文件

    cd src/modules/content && \ mkdir subscribers && \ touch dtos/query-category.dto.ts \ dtos/query-post.dto.ts \ subscribers/post.subscriber.ts \ subscribers/index.ts \ services/sanitize.service.ts \ && cd ../../../ 

    与上一节一样,这一节的新增和修改集中于ContentModule

    src/modules/content ├── constants.ts ├── content.module.ts ├── controllers │ ├── category.controller.ts │ ├── comment.controller.ts │ ├── index.ts │ └── post.controller.ts ├── dtos │ ├── create-category.dto.ts │ ├── create-comment.dto.ts │ ├── create-post.dto.ts │ ├── index.ts │ ├── query-category.dto.ts │ ├── query-post.dto.ts │ ├── update-category.dto.ts │ └── update-post.dto.ts ├── entities │ ├── category.entity.ts │ ├── comment.entity.ts │ ├── index.ts │ └── post.entity.ts ├── repositories │ ├── category.repository.ts │ ├── comment.repository.ts │ ├── index.ts │ └── post.repository.ts ├── services │ ├── category.service.ts │ ├── comment.service.ts │ ├── index.ts │ ├── post.service.ts │ └── sanitize.service.ts └── subscribers ├── index.ts └── post.subscriber.ts 

    应用编码

    这节多了一个新的概念,即subscriber,具体请查阅typeorm文档,当然你也可以在模型中使用事件处理函数,效果没差别

    模型

    CategoryEntity

    代码:src/modules/content/entities/category.entity.ts

    • 添加order字段用于排序
    • 添加level属性(虚拟字段)用于在打平树形数据的时候添加当前项的等级

    PostEntity

    代码: src/modules/content/entities/post.entity.ts

    type字段的类型用enum枚举来设置,首先需要定义一个PostBodyTypeenum类型,可以添加一个constants.ts文件来统一定义这些enum和常量

    • 添加publishedAt字段用于控制发布时间和发布状态
    • 添加 type字段用于设置发布类型
    • 添加customOrder字段用于自定义排序

    存储类

    CategoryRepository

    代码: src/modules/content/repositories/category.repository.ts

    因为CategoryRepository继承自TreeRepository,所以我们在typeorm源码中找到这个类,并对部分方法进行覆盖,如此我们就可以对树形分类进行排序,覆盖的方法如下

    当然后面会讲到更加深入的再次封装,此处暂时先这么用

    • findRoots 为根分类列表查询添加排序
    • createDescendantsQueryBuilder 为子孙分类查询器添加排序
    • createAncestorsQueryBuilder 为祖先分类查询器添加排序

    DTO 验证

    新增QueryCategoryDtoQueryPostDto用于查询分类和文章时进行分页以及过滤数据和设置排序类型等

    在添加DTO之前,现在添加几个数据转义函数,以便把请求中的字符串改成需要的数据类型

    // src/core/helpers.ts // 用于请求验证中的 number 数据转义 export function tNumber(value?: string | number): string |number | undefined // 用于请求验证中的 boolean 数据转义 export function tBoolean(value?: string | boolean): string |boolean | undefined // 用于请求验证中转义 null export function tNull(value?: string | null): string | null | undefined 

    修改create-category.dto.tscreate-comment.dto.tsparent字段的@Transform装饰器

    export class CreateCategoryDto { ... @Transform(({ value }) => tNull(value)) parent?: string; } 

    添加一个通用的DTO接口类型

    // src/core/types.ts // 分页验证 DTO 接口 export interface PaginateDto { page: number; limit: number; } 

    QueryCategoryDto

    代码: src/modules/content/dtos/query-category.dto.ts

    • page属性设置当前分页
    • limit属性设置每页数据量

    QueryPostDto

    除了与QueryCateogryDto一样的分页属性外,其它属性如下

    • orderBy用于设置排序类型
    • isPublished根据发布状态过滤文章
    • category过滤出一下分类及其子孙分类下的文章

    orderBy字段是一个enum类型的字段,它的可取值如下

    • CREATED: 根据创建时间降序
    • UPDATED: 根据更新时间降序
    • PUBLISHED: 根据发布时间降序
    • COMMENTCOUNT: 根据评论数量降序
    • CUSTOM: 根据自定义的order字段升序

    服务类

    SanitizeService

    代码: src/modules/content/services/sanitize.service.ts

    此服务类用于clean html

    saniize方法用于对 HTML 数据进行防注入处理

    CategoryService

    代码:src/modules/content/services/category.service.ts

    添加一个辅助函数,用于对打平后的树形数据进行分页

    // src/core/helpers.ts export function manualPaginate<T extends ObjectLiteral>( { page, limit }: PaginateDto, data: T[], ): Pagination<T> 

    新增paginate(query: QueryCategoryDto)方法用于处理分页

    async paginate(query: QueryCategoryDto) { // 获取树形数据 const tree = await this.findTrees(); // 打平树形数据 const list = await this.categoryRepository.toFlatTrees(tree); // 调用手动分页函数进行分页 return manualPaginate(query, list); } 

    PostService

    代码:src/modules/content/services/post.service.ts

    • getListQuery: 用于构建过滤与排序以及通过分类查询文章数据等功能的query构建器
    • paginate: 调用getListQuery生成query,并作为nestjs-typeorm-paginate paginate的参数对数据进行分页
    async paginate(params: FindParams, options: IPaginationOptions) { const query = await this.getListQuery(params); return paginate<PostEntity>(query, options); } 

    订阅者

    PostSubscriber

    代码: src/modules/content/subscribers/post.subscriber.ts

    • beforeInsert(插入数据前事件): 如果在添加文章的同时发布文章,则设置当前时间为发布时间
    • beforeUpdate(更新数据前事件): 更改发布状态会同时更新发布时间的值,如果文章更新为未发布状态,则把发布时间设置为 null
    • afterLoad(加载数据后事件): 对 HTML 类型的文章内容进行去标签处理防止注入攻击

    一个需要注意的点是需要在subcriber类的构造函数中注入Connection才能获取链接

     constructor( connection: Connection, protected sanitizeService: SanitizeService, ) { connection.subscribers.push(this); } 

    注册订阅者

    把订阅者注册成服务后,由于在构造函数中注入了connection这个连接对象,所以typeorm会自动把它加载到这个默认连接的subscribers配置中

    // src/modules/content/subscribers/post.subscriber.ts import * as SubscriberMaps from './subscribers'; const subscribers = Object.values(SubscriberMaps); @Module({ .... providers: [...subscribers, ...dtos, ...services], }) 

    控制器

    CategoryController

    代码: src/modules/content/controllers/category.controller.ts

    • list: 通过分页来查找扁平化的分类列表
    • index: 把 url 设置成 @Get('tree')
     @Get() // 分页查询 async list( @Query( new ValidationPipe({ transform: true, forbidUnknownValues: true, validationError: { target: false }, }), ) query: QueryCategoryDto, ) { return this.categoryService.paginate(query); } // 查询树形分类 @Get('tree') async index() { return this.categoryService.findTrees(); } 

    PostController

    代码: src/modules/content/controllers/post.controller.ts

    修改index方法用于分页查询

    // 通过分页查询数据 async index( @Query( new ValidationPipe({ transform: true, forbidUnknownValues: true, validationError: { target: false }, }), ) { page, limit, ...params }: QueryPostDto, ) { return this.postService.paginate(params, { page, limit }); } 
    11 条回复    2022-07-11 13:19:37 +08:00
    putaozhenhaochi
        1
    putaozhenhaochi  
       2022-07-10 10:10:34 +08:00 via Android
    大哥 不在这搞 seo 行吗
    golangLover
        2
    golangLover  
       2022-07-10 10:17:54 +08:00 via Android
    支持一下,辛苦了
    dinjufen
        3
    dinjufen  
       2022-07-10 11:10:46 +08:00   1
    网站订阅有点贵,但是内容又不多
    lichnow
        4
    lichnow  
    OP
       2022-07-10 11:29:16 +08:00
    @dinjufen 还可以啦,教程每天都会更新,后面 react18 立马也上线了
    Kipp
        5
    Kipp  
       2022-07-10 11:59:30 +08:00
    昨晚看还是全免费,今早就订阅者免费了,建议发推广
    lichnow
        6
    lichnow  
    OP
       2022-07-10 12:10:44 +08:00
    @Kipp 没办法,跟合伙人闹掰辞职了,在找工作,缺钱啊
    chenzhe
        7
    chenzhe  
       2022-07-10 23:38:53 +08:00 via iPhone
    昨晚点开看了一下
    说实在的,对于初学者来说,讲得太浅显,对于真正后端开发的熟手来说,好像又犯不着看这样的教程。
    感觉定位有点儿尴尬。
    而且这样的帖子明显是推广贴,应该换一个节点发。

    真的做付费视频,希望能够讲的更加详细一些,带着新手一步一步把 nestjs 吃透。
    数据库操作这边,选 mongoose 或者 typeorm 详细的讲一下。
    lichnow
        8
    lichnow  
    OP
       2022-07-11 10:11:55 +08:00
    @chenzhe 视频才更到 5 啊,后面还有 30 集,很多东西得一步步深入不是?像 typeorm 5-10 是专门讲解的,用户系统 11-15 讲解,16-18 就讲 RABC,不可能几集就能全部讲完啊
    lichnow
        9
    lichnow  
    OP
       2022-07-11 10:20:15 +08:00
    @chenzhe 并且看个视频肯定是无法直接吃透的,需要跟着视频里的做,然后有无法理解的东西在提问这样才行
    chenzhe
        10
    chenzhe  
       2022-07-11 12:36:31 +08:00
    @lichnow 的确才更新了一点儿,但是看前五集就能知道你这个视频并不是针对初学者的。至于跟着视频做,看了视频,连为啥这么做都不知道。
    lichnow
        11
    lichnow  
    OP
       2022-07-11 13:19:37 +08:00
    @chenzhe 光看视频肯定不行,需要跟着源代码一步步实现,我每一集的源代码单独一个包,然后实现过程中有问题(比如不知道为啥这么做)可以群里或者问答频道提问,我会耐心解答
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1202 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 23:45 PVG 07:45 LAX 15:45 JFK 18:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86