乐趣区

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

  • 视频地址: https://www.bilibili.com/vide…

有问题请扫描视频中 qq 群二维码交换

另,自己在找工作中,心愿能有近程工作匹配(无奈去当地),有须要的老板能够看一下我的集体介绍:pincman.com/about

学习指标

  • 重载 TreeRepository 自带办法来对树形构造的数据进行扁平化解决
  • 对 Typeorm 查问出的数据列表进行分页解决
  • 通过申请中的 query 查问对数据进行筛选解决, 比方排序, 过滤等
  • 实现公布文章和勾销公布的性能
  • Typeorm 模型事件和 Subscriber(订阅者)的应用
  • 应用 sanitize-html 对文章内容进行防注入攻打解决

预装依赖

  • nestjs-typeorm-paginate 实现分页
  • sanitize-html 过滤 html 标签, 防注入攻打
  • deepmerge 深度合并对象
~ 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

sanitize办法用于对 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});
    }
退出移动版