12

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

 2 years ago
source link: https://www.v2ex.com/t/865211
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

V2EX  ›  Node.js

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

  lichnow · 12 小时 19 分钟前 · 674 次点击

另,本人在找工作中,希望能有远程工作匹配(无法去外地),有需要的老板可以看一下我的个人介绍: 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

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 });
    }

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK