NestJS学习笔记

84 阅读6分钟

nestJS基本概念

  • 模块(Modules)

    1. 模块是用来组织代码的基本单位。
    2. 每个应用至少有一个根模块 AppModule
    3. 模块通过 @Module() 装饰器定义。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  imports: [TypeOrmModule.forFeature([User])], // 导入TypeORM模块并注册User实体
  providers: [UserService], // 提供UserService
  controllers: [UserController], // 注册UserController
  exports: [UserService], // 导出UserService以供其他模块使用
})
export class UserModule {}
  • 控制器(Controllers)

    1. 控制器负责处理传入的请求,并返回响应。
    2. 使用 @Controller() 装饰器定义路由路径。
    3. 通过装饰器定义路由处理方法(如 @Get()@Post() 等)。
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get('query') // cats/query
  findAll(): string {
    return this.catsService.findAll();
  }

  @Post('add') // cats/add
  create(@Body() createCatDto: CreateCatDto): string {
    return this.catsService.create(createCatDto);
  }
}

  • 服务(Providers)

    1. 服务用于处理业务逻辑,通常是注入到控制器或其他服务中。
    2. 使用 @Injectable() 装饰器标记为可注入的服务。
import { Injectable, HttpException, HttpStatus, Inject } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import * as bcrypt from 'bcryptjs';

@Injectable()
// 用户服务类,提供用户相关的业务逻辑
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  @Inject(JwtService)
  private jwtService: JwtService;

  // 创建用户,接受用户名和密码,返回创建的用户对象
  async createUser(username: string, password: string): Promise<User> {
    const hashedPassword = await bcrypt.hash(password, 10); // 对密码进行哈希加密
    const user = this.userRepository.create({
      username,
      password: hashedPassword,
    });
    return this.userRepository.save(user); // 保存用户到数据库
  }

  // 根据用户名查找用户,返回用户对象
  async findByUsername(username: string): Promise<User> {
    return this.userRepository.findOne({ where: { username } });
  }

  // 根据ID查找用户,返回用户对象
  async findById(id: number): Promise<User> {
    return this.userRepository.findOne({ where: { id } });
  }

  // 更新用户密码,接受用户ID和新密码
  async updatePassword(id: number, newPassword: string): Promise<void> {
    const hashedPassword = await bcrypt.hash(newPassword, 10); // 对新密码进行哈希加密
    await this.userRepository.update(id, { password: hashedPassword }); // 更新用户密码
  }
}

  • 中间件(Middleware)

    1. 中间件是在路由处理程序之前调用的函数。
    2. 使用 @Middleware() 装饰器定义。
    3. 在模块中应用中间件
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

  • 拦截器(Interceptors)

    1. 拦截器用于在处理请求前后添加额外的逻辑,如日志记录、缓存等。
    2. 使用 @Injectable() 装饰器定义。
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { Observable, map } from 'rxjs';

interface Data<T> {
  data: T;
}

@Injectable()
export class SuccessResponse<T> implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Data<T>> {
    return next.handle().pipe(
      map((data) => {
        return {
          data, // data即为 Service层或者Controller层的返回值
          code: 1,
          message: '请求成功',
          success: true,
        };
      }),
    );
  }
}

  • 管道(Pipes)

    1. 管道用于转换和验证请求数据。
    2. 内置的管道包括 ValidationPipeParseIntPipe 等。
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return `Cat ${id}`;
  }
}

  • 守卫(Guards)

    1. 守卫用于定义请求是否应该被处理的逻辑。
    2. 使用 @Injectable() 装饰器定义。
import { JwtService } from '@nestjs/jwt';
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';

@Injectable()
export class LoginGuard implements CanActivate {
  @Inject(JwtService)
  private jwtService: JwtService;

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();

    const token = request.header('authtoken') || '';

    try {
      const info = this.jwtService.verify(token);
      (request as any).user = info.user;
      return true;
    } catch (e) {
      throw new UnauthorizedException('登录已失效,请重新登录');
    }
  }
}
  • 异常过滤器(Exception Filters)

    1. 异常过滤器用于处理和转换异常响应。
    2. 使用 @Catch() 装饰器定义。
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';
import { Response, Request } from 'express';

@Catch()
// 接口异常拦截器
export class HttpFaild implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    console.log("🚀 ~ HttpFaild ~ exception:", exception)
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();
    const status = exception?.getStatus();
    console.log(status, 'status');

    response.status(status).json({
      success: false,
      time: new Date(),
      msg: exception.message,
      code: status,
      path: request.url,
    });
  }
}

常用功能

  • 数据库集成(TypeORM)

    1. 使用 @nestjs/typeorm 模块集成 TypeORM。
    2. 配置数据库连接并定义实体。
/*
 * app.module.ts
 */
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { MenuModule } from './menu/menu.module';
import { PostModule } from './post/post.module';
import { CrawlerService } from './crawler/crawler.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ScheduleModule } from '@nestjs/schedule';
import { JwtModule } from '@nestjs/jwt';
import { Menu } from './menu/menu.entity';
@Module({
  imports: [
    ScheduleModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'MuMu5217426',
      database: 'min-app',
      entities: ['dist/**/*.entity{.ts,.js}'], // 实体类
      synchronize: true, // 自动同步模式(开发用)
      charset: 'utf8mb4', // 设置字符集为 utf8mb4
      timezone: '+08:00', // 设置时区为北京时间
    }),
    TypeOrmModule.forFeature([Menu]),
    JwtModule.register({
      global: true,
      secret: 'syb-secret',
      signOptions: {
        expiresIn: '7d',
      },
    }),
    UserModule,
    MenuModule,
    PostModule,
  ],
  controllers: [AppController],
  providers: [AppService, CrawlerService],
})
export class AppModule {}


/*
 * user.entity.ts
 */
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToOne,
  JoinColumn,
  OneToMany,
} from 'typeorm';
import { Exclude } from 'class-transformer';
import { Post } from '../post/post.entity';

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

  @Column({ unique: true })
  username: string;

  @Column()
  @Exclude({ toPlainOnly: true })
  password: string;

  @OneToOne(() => User, { nullable: true })
  @JoinColumn()
  relatedUser: User;

  @Column({ nullable: true })
  relatedUserId: number; // 新增关联用户ID字段
  @OneToMany(() => Post, (post) => post.user)
  posts: Post[];
}
  • 身份验证(Passport.js)

    1. 使用 @nestjs/passport 模块集成 Passport.js。
    2. 配置 JWT 策略。
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: 'secretKey',
      signOptions: { expiresIn: '7d' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

NestJS 中与 MySQL 交互

  • 安装依赖

    1. @nestjs/typeorm
    2. typeorm
    3. mysql2
  • 配置数据库连接

    1. 在 AppModule 中使用 TypeOrmModule.forRoot 方法配置数据库连接。
/*
 * @Description:
 * @Author: muqingkun
 * @Date: 2024-06-28 17:42:40
 * @LastEditTime: 2024-07-04 09:26:53
 * @LastEditors: muqingkun
 * @Reference:
 */
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { MenuModule } from './menu/menu.module';
import { PostModule } from './post/post.module';
import { CrawlerService } from './crawler/crawler.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ScheduleModule } from '@nestjs/schedule';
import { JwtModule } from '@nestjs/jwt';
import { Menu } from './menu/menu.entity';
@Module({
  imports: [
    ScheduleModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'MuMu5217426',
      database: 'min-app',
      entities: ['dist/**/*.entity{.ts,.js}'],
      synchronize: true, // 自动同步模式(开发用)
      charset: 'utf8mb4', // 设置字符集为 utf8mb4
      timezone: '+08:00', // 设置时区为北京时间
    }),
    TypeOrmModule.forFeature([Menu]),
    JwtModule.register({
      global: true,
      secret: 'syb-secret',
      signOptions: {
        expiresIn: '7d',
      },
    }),
    UserModule,
    MenuModule,
    PostModule,
  ],
  controllers: [AppController],
  providers: [AppService, CrawlerService],
})
export class AppModule {}

  • 创建实体

    1. 使用 @Entity() 装饰器定义实体类。
    2. 使用 @PrimaryGeneratedColumn()@Column(), 等装饰器定义实体字段。
/*
 * @Description:
 * @Author: muqingkun
 * @Date: 2024-06-28 20:47:13
 * @LastEditTime: 2024-07-02 10:28:46
 * @LastEditors: muqingkun
 * @Reference:
 */
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  OneToOne,
  JoinColumn,
  OneToMany,
} from 'typeorm';
import { Exclude } from 'class-transformer';
import { Post } from '../post/post.entity';

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

  @Column({ unique: true })
  username: string;

  @Column()
  @Exclude({ toPlainOnly: true })
  password: string;

  @OneToOne(() => User, { nullable: true })
  @JoinColumn()
  relatedUser: User;

  @Column({ nullable: true })
  relatedUserId: number; // 新增关联用户ID字段
  @OneToMany(() => Post, (post) => post.user)
  posts: Post[];
}

  • 使用 Repository 进行数据库操作

    1. 使用 @InjectRepository 注入实体的 Repository。
    2. 使用 repository 的方法如 findfindOnesavedelete 等进行数据库操作。
/*
 * @Description:
 * @Author: muqingkun
 * @Date: 2024-06-28 19:41:59
 * @LastEditTime: 2024-07-01 15:45:40
 * @LastEditors: muqingkun
 * @Reference:
 */
import { Injectable, Inject } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { CreateMenuDto } from './dto/create-menu.dto';
import { UpdateMenuDto } from './dto/update-menu.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Menu } from './menu.entity';

@Injectable()
export class MenuService {
  constructor(
    @InjectRepository(Menu)
    private menuRepository: Repository<Menu>,
  ) {}

  @Inject(JwtService)
  // private jwtService: JwtService;
  // private logger = new Logger();
  async create(createMenuDto: CreateMenuDto) {
    return await this.menuRepository.save({
      ...createMenuDto,
    });
  }

  async update(id: number, updateMenuDto: UpdateMenuDto) {
    const qb = await this.menuRepository.createQueryBuilder();
    return await qb.update().set(updateMenuDto).where({ id }).execute();
  }

  async findAll() {
    return await this.menuRepository.find();
  }

  async findOne(id: number) {
    return await this.menuRepository.find({
      where: { id },
    });
  }
  async findByCategory(category: number) {
    return await this.menuRepository.find({
      where: { category },
    });
  }

  async remove(id: number) {
    const qb = await this.menuRepository.createQueryBuilder();
    return await qb.delete().where({ id }).execute();
  }
}