Nestjs 最佳实践教程 Nodejs 排序,分页与过滤
Nestjs 最佳实践教程:4 Nodejs 排序,分页与过滤
- Nestjs教程地址: https://www.itying.com/goods-1139.html
- Nestjs视频学习地址: https://www.bilibili.com/video/BV1NW421c7kz
学习目标
- 重载 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](/user/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
枚举来设置,首先需要定义一个PostBodyType
的enum
类型,可以添加一个constants.ts
文件来统一定义这些enum
和常量
- 添加
publishedAt
字段用于控制发布时间和发布状态 - 添加
type
字段用于设置发布类型 - 添加
customOrder
字段用于自定义排序
存储类
CategoryRepository
代码: src/modules/content/repositories/category.repository.ts
因为CategoryRepository
继承自TreeRepository
,所以我们在typeorm
源码中找到这个类,并对部分方法进行覆盖,如此我们就可以对树形分类进行排序,覆盖的方法如下
当然后面会讲到更加深入的再次封装,此处暂时先这么用
findRoots
为根分类列表查询添加排序createDescendantsQueryBuilder
为子孙分类查询器添加排序createAncestorsQueryBuilder
为祖先分类查询器添加排序
DTO 验证
新增QueryCategoryDto
和QueryPostDto
用于查询分类和文章时进行分页以及过滤数据和设置排序类型等
在添加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.ts
和create-comment.dto.ts
的parent
字段的[@Transform](/user/Transform)
装饰器
export class CreateCategoryDto {
...
[@Transform](/user/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
(更新数据前事件): 更改发布状态会同时更新发布时间的值,如果文章更新为未发布状态,则把发布时间设置为 nullafterLoad
(加载数据后事件): 对 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](/user/Module)({
....
providers: [...subscribers, ...dtos, ...services],
})
控制器
CategoryController
代码: src/modules/content/controllers/category.controller.ts
list
: 通过分页来查找扁平化的分类列表index
: 把 url 设置成[@Get](/user/Get)('tree')
[@Get](/user/Get)()
// 分页查询
async list(
@Query(
new ValidationPipe({
transform: true,
forbidUnknownValues: true,
validationError: { target: false },
}),
)
query: QueryCategoryDto,
) {
return this.categoryService.paginate(query);
}
// 查询树形分类
[@Get](/user/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 });
}
大哥 不在这搞 seo 行吗
支持一下,辛苦了
网站订阅有点贵,但是内容又不多
还可以啦,教程每天都会更新,后面 react18 立马也上线了
昨晚看还是全免费,今早就订阅者免费了,建议发推广
没办法,跟合伙人闹掰辞职了,在找工作,缺钱啊😄
昨晚点开看了一下
说实在的,对于初学者来说,讲得太浅显,对于真正后端开发的熟手来说,好像又犯不着看这样的教程。
感觉定位有点儿尴尬。
而且这样的帖子明显是推广贴,应该换一个节点发。
真的做付费视频,希望能够讲的更加详细一些,带着新手一步一步把 nestjs 吃透。
数据库操作这边,选 mongoose 或者 typeorm 详细的讲一下。
视频才更到 5 啊,后面还有 30 集,很多东西得一步步深入不是?像 typeorm 5-10 是专门讲解的,用户系统 11-15 讲解,16-18 就讲 RABC,不可能几集就能全部讲完啊
并且看个视频肯定是无法直接吃透的,需要跟着视频里的做,然后有无法理解的东西在提问这样才行
的确才更新了一点儿,但是看前五集就能知道你这个视频并不是针对初学者的。至于跟着视频做,看了视频,连为啥这么做都不知道。
光看视频肯定不行,需要跟着源代码一步步实现,我每一集的源代码单独一个包,然后实现过程中有问题(比如不知道为啥这么做)可以群里或者问答频道提问,我会耐心解答
在NestJS中实现排序、分页与过滤是构建RESTful API时的常见需求。以下是一个使用NestJS和TypeORM实现这些功能的最佳实践教程的简要示例。
首先,确保你已经安装了NestJS和TypeORM。然后,创建一个实体(例如,User
)并配置数据库连接。
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
}
接下来,在你的服务中实现排序、分页和过滤逻辑。
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(page: number, limit: number, sort: string, filter: { [key: string]: any }): Promise<User[]> {
const queryBuilder = this.usersRepository.createQueryBuilder('user');
if (filter) {
Object.keys(filter).forEach(key => {
queryBuilder.andWhere(`user.${key} = :${key}`, { [key]: filter[key] });
});
}
if (sort) {
queryBuilder.orderBy(sort);
}
return queryBuilder
.skip((page - 1) * limit)
.take(limit)
.getMany();
}
}
在控制器中调用服务方法并处理请求。
这个示例展示了如何在NestJS中使用TypeORM实现基本的排序、分页和过滤功能。你可以根据需要进一步扩展和优化这些功能。