作者 | 周周酱
本文写于2021年5月13日。首发于周周酱个人博客,转载请注明出处。
博客后端选用的是Nest框架,Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。风格类似java spring,在服务器端提供开箱即用的应用架构,让前端人员也能够快速创建可扩展、松耦合、易维护的应用。
关于Nest的一些概念,需移步官网查看Nest官方文档
创建应用
npm i -g @nestjs/cli
nest new nest-blog-api
初始项目
数据库模型
数据库存储:mysql ORM框架:typeorm nest接入typeorm typrorm文档
搭建mysql
首先搭建mysql服务,创建数据库并获取数据库连接以及用户名密码
项目配置文件
nest连接数据库,我使用环境变量的方式,在项目中创建了.env文件,将数据库连接配置以及后续(oss上传相关配置)都放在该文件中
nest连接数据库
添加@nestjs/typeorm,@nestjs/config(用于读取项目配置),修改app.module.ts
我的博客站点分为前台和后台管理系统
后台管理系统主要功能
- 用户鉴权登录(目前只有单一管理员)
- 文章管理:列表,编辑,创建,详情
- 项目管理:列表,编辑,创建,详情
- 标签管理:为文章和项目添加标签,标签的编辑创建
- 分类:为文章和项目添加分类,分类的编辑创建
前台页面功能
- 文章列表,详情展示
- 项目列表,详情展示
- 评论功能(暂时没做)
数据库设计
以及一些数据统计,用于辅助的表
定义模型
接下来使用typeorm定义entity article模型定义如下,其中涉及到关联关系的创建,请看typeorm关联关系
import {
BeforeUpdate,
Column,
Entity,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn
} from 'typeorm';
import { CategoryEntity } from './category.entity';
import { CommentEntity } from './comment.entity';
import { TagEntity } from './tag.entity';
import { UserEntity } from './user.entity';
@Entity('article')
export class ArticleEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
slug: string;
@Column()
title: string;
@Column({ default: '' })
image: string;
@Column('text')
description: string;
@Column('text')
content: string;
@Column('text', { nullable: true })
config: string;
/**
* 1 已上架 2 已下架
*/
@Column({ default: 2 })
state: number;
@Column({ default: false })
isDeleted: boolean;
@Column({
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP'
})
createdAt: Date;
@Column({
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP'
})
updatedAt: Date;
@BeforeUpdate()
updateTimestamp() {
this.updatedAt = new Date();
}
@ManyToOne(
type => UserEntity,
user => user.articles
)
author: UserEntity;
@ManyToOne(
type => CategoryEntity,
category => category.articles
)
category: CategoryEntity;
@ManyToMany(
type => TagEntity,
tag => tag.articles
)
@JoinTable({
name: 'article_tag'
})
tags: TagEntity[];
@OneToMany(
type => CommentEntity,
comment => comment.article
)
comments: CommentEntity[];
}
同理完成其他实体的定义
启动服务之后,实体自动映射,会将数据模型同步到数据库。
接下来可以愉快地打业务代码了。
功能模块
接下来按业务对象区分功能模块,组织应用程序结构。 一般一个功能模块下会包含 controller、service、module三个文件。
service
复杂的任务应该委托给 providers,我们这里的service就是一个provider,该服务负责数据存储和检索,然后在controller中调用,service类声明之前带有 @Injectable()装饰器,可以在controller中通过 constructor 注入依赖关系。下述代码是相对较典型的一个增删查改操作
//category.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PaginationData } from 'core/models/common';
import { CategoryEntity } from 'entity/category.entity';
import { In, Repository } from 'typeorm';
@Injectable()
export class CategoryService {
constructor(
@InjectRepository(CategoryEntity)
private readonly categoryRepository: Repository<CategoryEntity>
) {}
/**
* 获取分类列表
*
*/
async findAll(
index: number,
size: number,
module?: string
): Promise<PaginationData<CategoryEntity>> {
let res = null;
if (module) {
res = await this.categoryRepository.findAndCount({
where: { module: In([module, 'common']), isDeleted: false },
take: size,
skip: (index - 1) * size
});
} else {
res = await this.categoryRepository.findAndCount({
where: { isDeleted: false },
take: size,
skip: (index - 1) * size
});
}
return { index, size, list: res[0], total: res[1] };
}
/**
* 获取分类详情
*
*/
async findOne(id: number): Promise<CategoryEntity> {
const res = await this.categoryRepository.findOne({ id });
return res;
}
/**
* 创建
*
*/
async create(
title: string,
description: string,
module: string
): Promise<number> {
const category = new CategoryEntity();
category.title = title;
category.description = description;
category.module = module;
const newCategory = await this.categoryRepository.save(category);
return newCategory.id;
}
/**
* 修改
*
*/
async update(
id: number,
title: string,
description: string,
module: string
): Promise<number> {
const category = await this.categoryRepository.findOne({ id });
if (!category) {
throw new HttpException('该分类不存在', HttpStatus.BAD_REQUEST);
}
category.title = title;
category.description = description;
category.module = module;
await this.categoryRepository.save(category);
return id;
}
/**
* 删除
*
*/
async delete(id: number): Promise<void> {
const category = await this.categoryRepository.findOne({ id });
if (!category) {
throw new HttpException('该分类不存在', HttpStatus.BAD_REQUEST);
}
category.isDeleted = true;
await this.categoryRepository.save(category);
}
}
controller
controller控制器负责处理传入的请求和向客户端返回响应 。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。控制器使用@Controller装饰器,上面定义的categoryService 是通过类构造函数注入的,意味着我们已经在controller中创建并初始化了 categoryService 成员。
//category.controller.ts
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query
} from '@nestjs/common';
import { CategoryService } from './category.service';
@Controller('category')
export class CategoryController {
constructor(private readonly categoryService: CategoryService) {}
/**
* 获取分类列表
* @param index
* @param size
* @param module
*/
@Get('all')
async findAll(
@Query('index') index: number,
@Query('size') size: number,
@Query('module') module?: string
) {
return this.categoryService.findAll(
Number(index) || 0,
Number(size),
module
);
}
/**
* 获取分类详情
* @param id
*/
@Get(':id')
async findOne(@Param('id') id: number) {
return this.categoryService.findOne(id);
}
/**
* 创建分类
* @param title
* @param description
* @param module
*/
@Post()
async create(
@Body('title') title: string,
@Body('description') description: string,
@Body('module') module: string
) {
return this.categoryService.create(title, description, module);
}
/**
* 编辑分类
* @param id
* @param title
* @param description
* @param module
*/
@Put(':id')
async update(
@Param('id') id: number,
@Body('title') title: string,
@Body('description') description: string,
@Body('module') module: string
) {
return this.categoryService.update(id, title, description, module);
}
/**
* 删除
* @param id
*/
@Delete(':id')
async delete(@Param('id') id: number) {
return this.categoryService.delete(id);
}
}
module
模块是具有 @Module() 装饰器的类,每个 Nest 应用程序都一定会有一个根模块即app.module。然后根据应用的实际功能划分出多个功能模块,比如category就是一个相对独立的功能模块,category.module.ts可以说是这个功能模块的一个出口。
//category.module.ts
import { AuthMiddleware } from '@/common/middleware/auth';
import { UserModule } from '@/user/user.module';
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod
} from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CategoryEntity } from 'core/entity/category.entity';
import { CategoryController } from './category.controller';
import { CategoryService } from './category.service';
@Module({
imports: [TypeOrmModule.forFeature([CategoryEntity]), UserModule],
providers: [CategoryService],
controllers: [CategoryController]
})
export class CategoryModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes(
{
path: 'category/all',
method: RequestMethod.GET
},
{
path: 'category/:id',
method: RequestMethod.GET
},
{
path: 'category',
method: RequestMethod.POST
},
{
path: 'category/:id',
method: RequestMethod.PUT
},
{
path: 'category/:id',
method: RequestMethod.DELETE
}
);
}
}
@module() 装饰器接受一个描述模块属性的对象:
providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享 |
---|---|
controllers | 必须创建的一组控制器 |
imports | 导入模块的列表,这些模块导出了此模块中所需提供者 |
exports | 由本模块提供并应在其他模块中可用的提供者的子集。在模块共享的时候需要用到,比如这里的userModule,因为在所有模块中都需要依赖user模块的用户鉴权公共方法,所以需要在userModule中导出userService,然后在其他模块的imports 数组中增加userModule,这样其他模块就可以访问userService。 |
同时需要在跟模块app.module.ts中注册所有的功能模块
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ArticleModule } from './article/article.module';
import { CategoryModule } from './category/category.module';
import { DashboardModule } from './dashboard/dashboard.module';
import { LogModule } from './log/log.module';
import { ProjectModule } from './project/project.module';
import { ShareModule } from './share/share.module';
import { StatisModule } from './statis/statis.module';
import { TagModule } from './tag/tag.module';
import { TaskModule } from './task/task.module';
import { UserModule } from './user/user.module';
@Module({
imports: [
TypeOrmModule.forRoot(),
ConfigModule.forRoot({
envFilePath: '.env',
isGlobal: true
}),
ScheduleModule.forRoot(),
ArticleModule,
ProjectModule,
TagModule,
CategoryModule,
UserModule,
ShareModule,
DashboardModule,
LogModule,
StatisModule,
TaskModule
],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
这样一个最基本的功能模块就跑通了,接下来就按照业务功能去实现业务代码,同时完善基础建设,比如增加用户统一鉴权控制器,全局错误过滤器,全局接口返回结果处理器,完善swagger定义等。
完整代码请访问 zzj-admin-api