- 视频地址: https://www.bilibili.com/vide...
有问题请扫描视频中qq群二维码交换
另,自己在找工作中,心愿能有近程工作匹配(无奈去当地),有须要的老板能够看一下我的集体介绍:pincman.com/about
学习指标
这次教程在上一节的根底上实现一个简略的CMS零碎,实现如下性能
- 文章与分类多对多关联
- 文章与评论一对多关联
- 分类与评论的树形有限级嵌套
文件构造
这次的更改集中于ContentModule
模块,编写好之后的目录构造如下
src/modules/content├── 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│ ├── 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
cd src/modules/content && \touch controllers/category.controller.ts \controllers/comment.controller.ts \dtos/create-category.dto.ts \dtos/create-comment.dto.ts \dtos/update-category.dto.ts \entities/category.entity.ts \entities/comment.entity.ts \repositories/category.repository.ts \services/category.service.ts \services/comment.service.ts \&& cd ../../../
利用编码
编码流程与上一节一样,entity->repository->dto->service->controller,最初注册
模型类
模型关联
别离创立分类模型(CategoryEntity
)和评论模型(CommentEntity
),并和PostEntity
进行关联
分类模型
// src/modules/content/entities/category.entity.ts@Entity('content_categories')export class CategoryEntity extends BaseEntity { ... // 分类与文章多对多关联 @ManyToMany((type) => PostEntity, (post) => post.categories) posts!: PostEntity[];}
评论模型
// src/modules/content/entities/comment.entity.ts@Entity('content_comments')export class CommentEntity extends BaseEntity { ... // 评论与文章多对一,并触发`CASCADE` @ManyToOne(() => PostEntity, (post) => post.comments, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) post!: PostEntity;}
文章模型
@Entity('content_posts')export class PostEntity extends BaseEntity { // 评论数量 // 虚构字段,在Repository中通过QueryBuilder设置 commentCount!: number; // 文章与分类反向多对多关联 @ManyToMany((type) => CategoryEntity, (category) => category.posts, { cascade: true, }) @JoinTable() categories!: CategoryEntity[]; // 文章与评论一对多关联 @OneToMany(() => CommentEntity, (comment) => comment.post, { cascade: true, }) comments!: CommentEntity[];}
树形嵌套
评论模型与分类模型的树形嵌套实现基本一致,惟一的区别在于在删除父分类时子分类不会删除而是晋升为顶级分类,而删除评论则连带删除其后辈评论
typeorm有三种计划实现树形嵌套模型,咱们应用综合来说最好用的一种,即物理门路(Materialized Path),起因在于Adjacency list的毛病是无奈一次加载整个树,而closure则无奈主动触发Cascade
// src/modules/content/entities/category.entity.ts@Entity('content_categories')// 物理门路嵌套树须要应用`@Tree`装璜器并以'materialized-path'作为参数传入@Tree('materialized-path')export class CategoryEntity extends BaseEntity { ... // 子分类 @TreeChildren({ cascade: true }) children!: CategoryEntity[]; // 父分类 @TreeParent({ onDelete: 'SET NULL' }) parent?: CategoryEntity | null;} // src/modules/content/entities/comment.entity.ts@Entity('content_comments')@Tree('materialized-path')export class CommentEntity extends BaseEntity { ... @TreeChildren({ cascade: true }) children!: CommentEntity[]; @TreeParent({ onDelete: 'CASCADE' }) parent?: CommentEntity | null;}
存储类
创立一个空的CategoryRepository
用于操作CategoryEntity
模型
留神:树形的存储类必须通过getTreeRepository
获取或者通过getCustomRepository
加载一个继承自TreeRepository
的类来获取
在[nestjs][]中注入树形模型的存储库应用以下办法
- 应用该模型的存储库类是继承自
TreeRepository
类的自定义类,则间接注入即可 - 如果没有存储库类就须要在注入的应用
TreeRepository<Entity>
作为类型提醒
为了简略,CommentRepository
临时不须要创立,间接注入服务即可
// src/modules/content/repositories/category.repository.ts@EntityRepository(CategoryEntity)export class CategoryRepository extends TreeRepository<CategoryEntity> {}
批改PostRepository
增加buildBaseQuery
用于服务查问,代码如下
// src/modules/content/repositories/post.repository.tsbuildBaseQuery() { return this.createQueryBuilder('post') // 退出分类关联 .leftJoinAndSelect('post.categories', 'categories') // 建设子查问用于查问评论数量 .addSelect((subQuery) => { return subQuery .select('COUNT(c.id)', 'count') .from(CommentEntity, 'c') .where('c.post.id = post.id'); }, 'commentCount') // 把评论数量赋值给虚构字段commentCount .loadRelationCountAndMap('post.commentCount', 'post.comments'); }
DTO验证
DTO类与后面的CreatePostDto
和UpdatePostDto
写法是一样的
评论无需更新所以没有update
的DTO
create-category.dto.ts
用于新建分类update-category.dto.ts
用于更新分类create-comment.dto.ts
用于增加评论
在代码中能够看到我这里对分类和评论的DTO增加了一个parent
字段用于在创立和更新时设置他们的父级
@Transform
装璜器是用于转换数据的,基于class-transformer
这个类库实现,此处的作用在于把申请中传入的值为null
字符串的parent
的值转换成实在的null
类型
@ValidateIf
的作用在于只在申请的parent
字段不为null
且存在值的时候进行验证,这样做的目标在于如果在更新时设置parent
为null
把以后分类设置为顶级分类,如果不传值则不扭转
// src/modules/content/dtos/create-category.dto.ts @IsUUID(undefined, { always: true, message: '父分类ID格局不正确' }) @ValidateIf((p) => p.parent !== null && p.parent) @IsOptional({ always: true }) @Transform(({ value }) => (value === 'null' ? null : value)) parent?: string;
在CreatePostDto
中增加分类IDS验证
// src/modules/content/dtos/create-post.dto.ts @IsUUID(undefined, { each: true, always: true, message: '分类ID格局谬误' }) @IsOptional({ always: true }) categories?: string[];
在CreateCommentDto
中增加一个文章ID验证
// src/modules/content/dtos/create-comment.dto.ts @IsUUID(undefined, { message: '文章ID格局谬误' }) @IsDefined({ message: '评论文章ID必须指定' }) post!: string;
服务类
Category/Comment
服务的编写根本与PostService
统一,咱们新增了以下几个服务
CategoryService
用于分类操作CommentService
用于评论操作
分类服务通过TreeRepository
自带的findTrees
办法可间接查问出树形构造的数据,然而此办法无奈增加查问条件和排序等,所以后续章节咱们须要本人增加这些
// src/modules/content/services/category.service.tsexport class CategoryService { constructor( private entityManager: EntityManager, private categoryRepository: CategoryRepository, ) {} async findTrees() { return this.categoryRepository.findTrees(); } ...
getParent
办法用于依据申请的parent
字段的ID
值获取分类和评论下的父级
protected async getParent(id?: string) { let parent: CommentEntity | undefined; if (id !== undefined) { if (id === null) return null; parent = await this.commentRepository.findOne(id); if (!parent) { throw new NotFoundException(`Parent comment ${id} not exists!`); } } return parent; }
PostService
当初为了读取和操作文章与分类和评论的关联,应用QueryBuilder
来构建查询器
在此之前,在core/types
(新增)中定义一个用于额定传入查问回调参数的办法类型
// src/core/types.ts/** * 为query增加查问的回调函数接口 */export type QueryHook<Entity> = ( hookQuery: SelectQueryBuilder<Entity>,) => Promise<SelectQueryBuilder<Entity>>;
PostService
更改
对于评论的嵌套展现在后续教程会从新定义一个新的专用接口来实现
create
时通过findByIds
为新增文章出查问关联的分类update
时通过addAndRemove
更新文章关联的分类- 查问时通过
.buildBaseQuery().leftJoinAndSelect
为文章数据增加上关联的评论
控制器
新增两个控制器,别离用于解决分类和评论的申请操作
CategoryContoller
办法与PostController
一样,index
,show
,store
,update
,destory
临时间接用findTrees
查问出树形列表即可
export class CategoryController { ... @Get() async index() { return this.categoryService.findTrees(); }}
CommentController
目前评论控制器只有两个办法store
和destory
,别离用于新增和删除评论
注册代码
别离在entities
,repositories
,dtos
,services
,controllers
等目录的index.ts
文件中导出新增代码以给ContentModule
进行注册
const entities = Object.values(EntityMaps);const repositories = Object.values(RepositoryMaps);const dtos = Object.values(DtoMaps);const services = Object.values(ServiceMaps);const controllers = Object.values(ControllerMaps);@Module({ imports: [ TypeOrmModule.forFeature(entities), // 注册自定义Repository CoreModule.forRepository(repositories), ], controllers, providers: [...dtos, ...services], exports: [ // 导出自定义Repository,以供其它模块应用 CoreModule.forRepository(repositories), ...services, ],})export class ContentModule {}