大厂技术 高级前端 Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
考拉🐨 的 Nest.js 系列文章(系列会持续更新):
学完这篇 Nest.js 实战,还没入门的来锤我!(长文预警)
Nest.js 实战系列第二篇-实现注册、扫码登陆、jwt认证等
这篇文章是上篇实现登录、注册的后续, 本来是和上一篇文章写在一起的, 考虑篇幅问题,就拆了一个下篇出来。
文章主要内容:

有的小伙伴可能觉得文章不就增删改查嘛,没什么好写的吧!
其实在我整体写下来,觉得文章模块还是涉及到很多知识点的,比如分类表与文章表的一对多以及文章表与标签表多对多处理、文件上传等,还有一些实现的小细节:关于文章摘要的提取方式,Markdown
转html
等,都会在这篇文章中给大家介绍清楚。
前置说明
首先我们说一下文章设计的需求,文章基本信息:标题、封面、摘要、阅读量、点赞量等;文章有分类,一篇只能选择一个分类;一篇文章可以选择多个标签,文章的状态分为草稿和已发布,考虑到后期文章的展示,还给文章设置了推荐标识。
数据表关系
前面文章中已经说了TypeORM
建表时,是通过@Entity()
装饰的class 映射为数据表, 所以实体中的关系也就是表关系。接下来探索一下如何用TypeORM
创建一对一、一对多和多对多的关系。
一对一
一对一指的是表中一条数据仅关联另外一个表中的另一条数据。例如用户表和用户档案表, 一个用户只有一份档案。我们在TypeORM
中如何实现user
表和info
之间这种对一对的关系呢?
// user.entity.ts
@Entity('user')
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@OneToOne(type =>InfoEntity, info => info.user)
@JoinColumn()
info: InformationEntity;
}
info
是 InfoEntity
类型的,但是存入在数据库中类型却是 info.id 的类型。从上面代码可以看出, 是通过@OneToOne
装饰器来修饰的, 在装饰器中需要指定对方entity
的类型,以及指定对方entity
的外键。
@JoinColumn
必须在且只在关系的一侧的外键上, 你设置@JoinColumn
的哪一方,哪一方的表将包含一个relation id
和目标实体表的外键。记住,不能同时在二者entity
中。
看一下info
实体如何实现:
@Entity('info')
export class InfoEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
idcard: string;
@Column()
gender: string;
...
@OneToOne(type =>UserEntity, user => user.info)
user: UserEntity;
}
以上两个实体映射的数据表如下:
| user表 |
+--------+--------------+-----+-----------------+----------------------+
| Field | Type | Key | Default | Extra |
+--------+--------------+-----+-----------------+----------------------+
| id | int(11) | PRI | NULL | auto_increment |
| name | varchar(255) | | NULL | |
| infoId | int(11) | MUL | NULL | |
+--------+--------------+-----+-----------------+----------------------+
| info表 |
+--------+--------------+-----+-----------------+----------------------+
| Field | Type | Key | Default | Extra |
+--------+--------------+-----+-----------------+----------------------+
| id | int(11) | PRI | NULL | auto_increment |
| idcard | varchar(255) | | NULL | |
| gender | varchar(255) | | NULL | |
+--------+--------------+-----+-----------------+----------------------+
生成的从数据表可以看出,默认生成的"relation id
格式为xxId
, 如果你是数据表中希望对其进行重名名, 可以通过@JoinColumn
配置,在一对多例子中会实践一下。
一对多
在一对多关系中,表A中的一条记录,可以关联表B中的一条或多条记录。比如:每一个文章分类都可以对应多篇文章,反过来一篇文章只能属于一个分类,这种文章表和分类表的关系就是一对多的关系。
同样我们用代码看看TypeOrm
中如何实现这种关系的:
// category.entity.ts
import {PostEntity} from "../../post/post.entity"
@Entity('category')
export class CategoryEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => PostEntity, post => post.category)
post: PostEntity[];
}
将@OneToMany
添加到post
属性中, 并且在@OneToMany
中指定对方的类型为PostEntity
, 接下来定义文章实体:
// posts.entity.ts
...
import { CategoryEntity } from './../category/entities/category.entity';
@Entity('post')
export class PostsEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50 })
title: string;
...
// 分类
@Exclude()
@ManyToOne(() => CategoryEntity, (category) => category.posts)
@JoinColumn({name: "category_id"})
category: CategoryEntity;
}
@ JoinColumn
不仅定义了关系的哪一侧包含带有外键的连接列,还允许自定义连接列名和引用的列名。上边文章entity
中,就自定义了列名为category_id
, 如果不自定义, 默认生成的列名为categoryId
。
TypeORM在处理“一对多”的关系时, 将一
的主键作为多
的外键,即@ManyToOne
装饰的属性;这样建表时有最少的数据表操作代价,避免数据冗余,提高效率, 上面的实体关系会生成以下表:
| category表 |
+--------+--------------+-----+-----------------+----------------------+
| Field | Type | Key | Default | Extra |
+--------+--------------+-----+-----------------+----------------------+
| id | int(11) | PRI | NULL | auto_increment |
| name | varchar(255) | | NULL | |
+--------+--------------+-----+-----------------+----------------------+
| post表 |
+-------------+--------------+-----+------------+----------------------+
| Field | Type | Key | Default | Extra |
+-------------+--------------+-----+------------+----------------------+
| id | int(11) | PRI | NULL | auto_increment |
| title