D12 要做的训练内容是去创建一个标签模块,标签在应用里也是一种实体,每个标签可以有自己的名字、别名等等,你也可以加上标签的描述、缩略图之类的东西。
这次训练的重点是要理解多对多的关系,我们可以在每个文章内容上面打上多个标签,每个标签都可以关联一组文章内容。
先在标签实体上定义它跟文章内容的关系:
@ManyToMany(type => Post, post => post.tags) posts: Post[];
标签上的这个关系的名字叫 posts,它的类型是一组 Post 实体。在 Post 实体上对应的关系应该叫 tags 。
在文章内容实体上定义与标签的关系:
@ManyToMany(type => Tag, tag => tag.posts) @JoinTable() tags: Tag[];
同样使用 @ManyToMany 这个装饰器去定义多对多的关系,这个关系的名字叫 tags,类型是一组 Tag 实体。注意这里定义关系的时候用了一个 @JoinTable 装饰器,它可以创建一个中间表来保存内容与标签之间的多对多的关系。
发布或更新内容时存储与标签的关系
在发布或更新文章内容的时候,可以存储一下内容与标签之间的关系。我们需要改进内容服务上的两个方法:
src/modules/post/post.service.ts
async store(data: PostDto, user: User) { const { tags } = data; if (tags) { data.tags = await this.beforeTag(tags); } const entity = await this.postRepository.create(data); await this.postRepository.save({ ...entity, user }); return entity; }
async update(id: string, data: Partial) { const { tags } = data; delete data.tags; await this.postRepository.update(id, data); const entity = await this.postRepository .findOne(id, { relations: ['category', 'tags'] }); if (tags) { entity.tags = await this.beforeTag(tags); } return await this.postRepository.save(entity); }
async beforeTag(tags: Partial[]) { const _tags = tags.map(async item => { const { id, name } = item; if (id) { const _tag = await this.tagRepository.findOne(id); if (_tag) { return _tag; } return; } if (name) { const _tag = await this.tagRepository.findOne({ name }); if (_tag) { return _tag; } return await this.tagRepository.save(item); } }); return Promise.all(_tags); }
在发布或更新内容的时候,存储内容与标签的关系,用了一个帮手方法叫 beforeTag,这个方法的作用是按标签的 id 找到对应的标签实体。如果打标签的时候提供的是标签的名字,就按名字去查找对应的标签实体,如果没找到就去创建一个同样名字的标签,最终方法返回需要给内容打上的所有标签实体。发布与更新内容用的方法会利用返回的这组标签实体,存储内容与标签之间的关系。
利用内容与标签的关系
比如在查询内容列表的时候,可以在地址的查询参数里提供一组标签,对应的服务方法可以得到这组标签内容,用它们作为查询文章列表时的一个查询条件。
async index(options: ListOptionsInterface) { const { categories, tags } = options; const queryBuilder = await this.postRepository .createQueryBuilder('post'); queryBuilder.leftJoinAndSelect('post.user', 'user'); queryBuilder.leftJoinAndSelect('post.category', 'category'); queryBuilder.leftJoinAndSelect('post.tags', 'tag'); if (categories) { queryBuilder.where('category.alias IN (:...categories)', { categories }); } if (tags) { queryBuilder.andWhere('tag.name In (:...tags)', { tags }); } const entities = queryBuilder.getMany(); return entities; }