当前位置: 首页 > 后端技术 > Node.js

Nestjs最佳实践教程:3模型关联和树嵌套

时间:2023-04-03 17:31:22 Node.js

视频地址:https://www.bilibili.com/video...如有疑问,请扫描视频中的QQ群二维码进行交流。另外,我正在找工作,希望有一个远程工作匹配(不能去其他地方)。有需要的老板可以看看我的个人介绍:pincman.com/aboutLearningobjectives本教程在上一节的基础上实现了一个简单的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├──实体│├──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.tscdsrc/modules/content&&\touchcontrollers/category.controller.TS\控制器/公司mment.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../../../Applicationcoding编码过程同上一节,entity->repository->dto->service->controller,最后注册模型类模型关联创建分类模型(CategoryEntity)和评论模型(CommentEntity),并将分类模型关联到PostEntity//src/modules/内容/实体/类别。entity.ts@Entity('content_categories')exportclassCategoryEntityextendsBaseEntity{...//类别和文章之间的多对多关联@ManyToMany((type)=>PostEntity,(post)=>post.categories)posts!:PostEntity[];}评论模型//src/modules/content/entities/comment.entity.ts@Entity('content_comments')exportclassCommentEntityextendsBaseEntity{...//评论和文章是多对一的,和触发`CASCADE`@ManyToOne(()=>PostEntity,(post)=>post.comments,{nullable:false,onDelete:'CASCADE',onUpdate:'CASCADE',})post!:PostEntity;}文章模型@Entity('content_posts')exportclassPostEntityextendsBaseEntity{//评论数//虚字段,通过Repository中的QueryBuilder设置commentCount!:number;//帖子和分类反向多对多关联@ManyToMany((type)=>CategoryEntity,(category)=>category.posts,{cascade:true,})@JoinTable()categories!:CategoryEntity[];//文章与评论的一对多关联@OneToMany(()=>CommentEntity,(comment)=>comment.post,{cascade:true,})comments!:CommentEntity[];}树嵌套的评论模型和分类modeltreeshape嵌套的实现基本相同,唯一不同的是删除父类时,子类不会被删除而是提升到顶层类,删除评论会删除其后代评论。一般来说,最好的是物化道。原因是邻接表的缺点是不能一次加载整棵树,闭包不能自动触发Cascade//src/modules/content/entities/category。entity.ts@Entity('content_categories')//物理路径嵌套树需要使用`@Tree`装饰器并将'materialized-path'作为参数传递@Tree('materialized-path')exportclassCategoryEntityextendsBaseEntity{...//子类别@TreeChildren({级联:真})孩子们!:CategoryEntity[];//父类别@TreeParent({onDelete:'SETNULL'})parent?:CategoryEntity|null;}//src/modules/content/entities/comment.entity.ts@Entity('content_comments')@Tree('materialized-path')exportclassCommentEntityextendsBaseEntity{...@TreeChildren({cascade:true})孩子们!:CommentEntity[];@TreeParent({onDelete:'CASCADE'})parent?:CommentEntity|null;}存储类创建一个空的CategoryRepository用于操作CategoryEntity模型。注意:树存储类必须通过getTreeRepository获取或者通过getCustomRepository加载继承自TreeRepository的类来加载。在[nestjs][]使用以下方法注入树模型的存储库。使用该模型的repository类是继承自TreeRepository类的自定义类,因此可以直接注入。如果没有存储库类,则需要在注入中使用TreeRepository作为类型提示。为了简单起见,CommentRepository暂时不需要创建,直接注入服务即可修改PostRepository增加buildBaseQuery用于Service查询,代码如下//src/modules/content/repositories/post.repository.tsbuildBaseQuery(){returnthis.createQueryBuilder('post')//加入分类关联.leftJoinAndSelect('post.categories','categories')//构建子查询查询评论数.addSelect((subQuery)=>{returnsubQuery.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一样。评论不需要更新,所以没有updateDTOcreate-category.dto.ts用于创建新分类update-category.dto.ts用于更新分类create-comment.dto.ts是用于添加评论。大家可以在代码中看到我这里添加了分类和评论的DTO。parent字段用于在创建和更新时设置其父级别@Transform装饰器用于转换数据,基于class-transformer类库的实现,这里的作用是将请求的值传入nullcharacter字符串父级的值被转换为真正的null类型。@ValidateIf的作用是只在请求的父字段不为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:'CategoryIDformaterror'})@IsOptional({总是:真})类别?:字符串[];在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.tsexportclassCategoryService{constructor(privateentityManager:EntityManager,privatecategoryRepository:CategoryRepository,){}异步findTrees(){returnthis.categoryRepository.findTrees();}...getParent方法用于根据请求的parent字段的ID值获取分类评论下的parentprotectedasyncgetParent(id?:string){letparent:CommentEntity|不明确的;if(id!==undefined){if(id===null)返回null;parent=awaitthis.commentRepository.findOne(id);if(!parent){thrownewNotFoundException(`父评论${id}不存在!`);}}返回父级;}PostService现在使用QueryBuilder来构建查询器,以便阅读和操作文章、类别和评论。在此之前,在core/types(新增)中定义一个方法type,用于额外传入查询回调参数//src/core/types.ts/***query*/exporttypeQueryHook添加查询回调函数接口=(hookQuery:SelectQueryBuilder,)=>Promise>;PostService更改评论的嵌套显示。在后续的教程中,我们会重新定义一个新的专用接口来实现通过findByIds创建新文章和查询关联分类更新时,通过addAn当dRemove更新与文章关联的类别查询时,使用.buildBaseQuery().leftJoinAndSelect将关联的评论控制器添加到文章数据中。添加两个新的控制器,用于处理分类和评论请求操作。CategoryContoller方法和PostController一样,index,show,store,update,destroy暂时使用findTrees直接查询树列表exportclassCategoryController{...@Get()asyncindex(){returnthis.categoryService.findTrees();}}CommentController目前是评论控制器上只有store和destroy两个方法,分别用于添加和删除评论。分别从entities、repositories、dtos、services、controllers等目录下的index.ts文件导出注册码,注册ContentModule。constentities=Object.values(EntityMaps);constrepositories=Object.values(RepositoryMaps);constdtos=Object.values(DtoMaps);constservices=Object.values(ServiceMaps);constcontrollers=Object.values(ControllerMaps);@Module({imports:[TypeOrmModule.forFeature(entities),//注册自定义RepositoryCoreModule.forRepository(repositories),],controllers,providers:[...dtos,...services],exports:[//export自定义Repository,其他模块使用CoreModule.forRepository(repositories),...services,],})exportclassContentModule{}