Skip to content

Jsmond2016/nest-demo

Repository files navigation

笔记

本文主要参考资料为:

项目计划

  • CRUD 基本操作
  • 接口参数校验
  • 数据库操作:(注:暂不处理各种外键和关联)
    • ORM 操作数据库
    • 事务处理
  • 过滤器
  • 路由守卫
    • JWT 用户鉴权
    • 接口级权限点验证
  • 中间件
    • 错误捕获,错误统一返回格式
    • 正常接口返回格式统一化
  • 安全处理
  • 文件上传
    • 单个文件上传
    • 多文件上传
  • 文件下载
    • 单文件下载
    • 多文件下载
  • Excel 内容解析处理
  • 测试用例
  • Swagger 接口文档
  • Log 日志记录
  • Redis 缓存
  • Docker 部署,区分 dev,test,prod 环境

项目搭建过程

了解 Nest

nest -h

可以看到很多 alias 命令

# module
nest g mo

# controller
nest g co

# service
nest g s

项目初始化

  • 全局安装 nest,初始化项目:
npm i -g @nestjs/cli

nest new nest-demo
  • 核心组成部分:
    • module
    • controller
    • service
    • dto
    • entity
  • 创建模块:
# 创建模块
nest g module user

# 创建控制器
nest g controller user

# 创建服务
nest g service user

创建完成后,在 app.module.ts 导入新建的 user.module.ts ,代码如下:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserController } from './user/user.controller';
import { UserService } from './user/user.service';
import { UserModule } from './user/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

配置数据库

内容详细参考:Nestjs 中文网-数据库

  • 数据库定义

新建 /ormconfig.json 文件

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "root",
  "password": "root",
  "database": "nest_test",
  "entities": ["dist/**/*.entity{.ts,.js}"],
  "synchronize": true
}

主要关注 数据库的 账号、密码、数据库名、实体

  • 表定义

新建 user.entity.ts 文件,内容为

@Entity('user')
export class User {}
  • 字段定义 & 字段关联关系
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  OneToMany,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { Hobby } from '../hobby/hobby.entity';

@Entity('user')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ default: '', charset: 'utf8', comment: '用户名' })
  name: string;

  @Column({ default: '', length: 11, charset: 'utf8', comment: '用户电话' })
  phone: string;

  @CreateDateColumn()
  createdTime: Date;

  @UpdateDateColumn()
  updatedTime: Date;

  // 字段关联关系 
  @OneToMany(() => Hobby, (hobby) => hobby.user)
  hobby: Hobby[];
}

CRUD操作

TypeORM 中文文档

接口入参约定

  • 查询
// 单个查询
GET http://www.nest.com/api/user/get-one?id=123&token=asdf

// 批量查询
GET http://www.nest.com/api/user/get-list?token=asdf
  • 新增
// 单个新增
POST http://www.nest.com/api/user/create?token=asdf

{
    "name": "Nick",
    "password": "123456"
}

// 批量新增
POST http://www.nest.com/api/user/create?token=asdf

{
    "list": [
        {
            "name": "Nick",
            "password": "123456"
        },
        {
            "name": "Nick2",
            "password": "1234567"
        },
    ]
}
  • 删除
// 单个删除
DELETE http://www.nest.com/api/user/delete?token=asdf

{
    "id": 1234
}

// 批量删除
DELETE http://www.nest.com/api/user/batch-delete?token=asdf

{
    "list": [1234, 5678]
}
  • 修改
// 单个修改
PUT http://www.nest.com/api/user/delete?token=asdf

{
    "id": 1234,
    "name": "Nick",
    "password": "123456"
}

// 批量修改
PUT http://www.nest.com/api/user/batch-delete?token=asdf

{
    "list": [
        {
            "id": 1234,
            "name": "Nick",
            "password": "123456"
        },
        {
            "id": 4567,
            "name": "Nick",
            "password": "123456"
        }
    ]
}

接口返回约定

  • 成功
{
    "code": 200,
    "message": "操作成功",
    "data": {
        "id": 12345456,
        "name": "Nick"
    }
}
  • 失败
{
    "code": 400,
    "message": "用户不存在",
}
  • 入参异常
{
  "code": 400,
  "message": "id 不能为空",
}
  • 其他异常
{
   "code": 400,
  "message": "error ....",
}

配置 class-validator

注意,所有 dto 参数校验都需要使用 class 的形式,否则 无法校验

  • 定义接口,不同的接口入参要求不同
  • 定义 参数 dto
// user.dto.ts
import { IsNotEmpty, Length, ArrayNotEmpty, IsArray } from 'class-validator';
import { Hobby } from 'src/modules/hobby/hobby.entity';
export class CreateUserDto {
  @IsNotEmpty({ message: 'name不能为空' })
  readonly name: string;

  @IsNotEmpty({ message: 'phone不能为空' })
  @Length(6, 20, { message: 'phone长度不合法' })
  readonly phone: string;

  @IsArray()
  readonly hobby: Hobby[];
}

export class CreateUserResDto {
  @IsNotEmpty({ message: '用户id不能为空' })
  readonly id: string;

  @IsNotEmpty({ message: 'name不能为空' })
  readonly name: string;

  @IsNotEmpty({ message: 'phone不能为空' })
  @Length(6, 20, { message: 'phone长度不合法' })
  readonly phone: string;
}

export class UpdateUserDto extends CreateUserDto {
  @IsNotEmpty({ message: '用户id不能为空' })
  readonly id: number;
}

export class GetOneUserDto {
  @IsNotEmpty({ message: 'id不能为空' })
  readonly id: number;
}

export class GetUserListDto {
  @IsNotEmpty({ message: 'pageIndex不能为空' })
  pageIndex: number;

  @IsNotEmpty({ message: 'pageSize不能为空' })
  pageSize: number;
}

export class DeleteUserDto {
  @IsNotEmpty({ message: 'id不能为空' })
  readonly id: number;
}
export class BatchDeleteUserDto {

  @ArrayNotEmpty({ message: 'id不能为空' })
  readonly userIds: number[];
}

接口例子

  • controller 层
// user.controller.ts

@Controller('/user')
export class UserController {
  @Inject()
  private readonly userService: UserService;

  @Post('/create')
  create(@Body() user: CreateUserDto) {
    return this.userService.save(user);
  }

  @Put('/update')
  async update(@Body() user: UpdateUserDto) {
    const user1 = await this.userService.getOne(user.id);
    if (!user1) {
      throw new HttpException('用户不存在', HttpStatus.BAD_REQUEST);
    }
    const newUser = { ...user1, ...user };
    return this.userService.save(newUser);
  }

  @Get('/get-by-id')
  async get(@Query() params: GetOneUserDto) {
    const { id } = params;
    const user = await this.userService.getOne(id);
    return user || {};
  }
}
  • service 层
// user.service.ts

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
    private connection: Connection,
  ) {}

   // 使用事务
  async save(user: CreateUserDto): Promise<any> {
    const { hobby, ...userInfo } = user;
    const queryRunner = this.connection.createQueryRunner();

    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      const { id } = await queryRunner.manager.save(User, userInfo);
      const hobbies = hobby.map((h) => ({ ...h, userId: id }));
      await queryRunner.manager.save(Hobby, hobbies);
      await queryRunner.commitTransaction();
    } catch (err) {
      //如果遇到错误,可以回滚事务
      await queryRunner.rollbackTransaction();
    } finally {
      //你需要手动实例化并部署一个queryRunner
      await queryRunner.release();
    }
    // return this.usersRepository.save(user);
    // return Promise.resolve();
  }

  getList(params: GetUserListDto): Promise<User[]> {
    console.log('params: ', params);
    return this.usersRepository.find({ relations: ['hobby'] });
  }

  getOne(id: number): Promise<User> {
    return this.usersRepository.findOne(id, { relations: ['hobby'] });
  }

  async delete(id: number | number[]): Promise<void> {
    await this.usersRepository.delete(id);
  }
}

对 一个数据库多个表进行操作,当其中一个表操作失败的时候 ,能够全部回滚。

async createMany(users: User[]) {
  const queryRunner = this.connection.createQueryRunner();

  await queryRunner.connect();
  await queryRunner.startTransaction();
  try {
    await queryRunner.manager.save(users[0]);
    await queryRunner.manager.save(users[1]);

    await queryRunner.commitTransaction();
  } catch (err) {
    //如果遇到错误,可以回滚事务
    await queryRunner.rollbackTransaction();
  } finally {
    //你需要手动实例化并部署一个queryRunner
    await queryRunner.release();
  }
}

配置 swagger

参考:swagger

其他:

  • 自定义 logger 中间件
  • 异常过滤
  • 参数类型转换管道
  • 角色守卫
  • 邮件服务
  • 配置集中管理
  • 服务监控
  • jwt 鉴权
  • 定时任务
  • 任务队列
  • 文件上传和下载

参考资料

相关资料:

视频

swagger

项目参考

About

🚀 初学 nest.js demo

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published