nodejs企业级开发框架nest学习总结 - 5.NestJS入门使用mongoose、multer、验证、缓存、安全等

孔征
2023-12-01

mongoose、multer、验证、缓存、安全等

官方nestjsAPI地址

1.nestjs结合mongoose

1.1 安装mongoose和nest相关
cnpm i  @nestjs/mongoose mongoose --save

1.2 配置连接数据库

方式1,直接配置
// mongo.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
	imports: [MongooseModule.forRoot('mongodb://localhost/demo', { useNewUrlParser: true })],
})
export class MongoModule { }
方式2,异步配置
// mongo.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
	imports: [
		MongooseModule.forRootAsync({
	        // 1. 异步加载,使用useFactory方法返回参数对象配置
	        useFactory: () => ({
	            uri: 'mongodb://localhost/demo',
	            useNewUrlParser: true,
	        }),
	        // 2.使用useClass配置,传递一个类,实现MongooseOptionsFactory接口,重写方法
	        // useClass: MongooseConfigService,
	    }),
})
export class MongoModule { }
useClass: MongooseConfigService, 使用配置文件的方式配置
// config.service.ts
import { Injectable } from '@nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '@nestjs/mongoose';

@Injectable()
export class MongooseConfigService implements MongooseOptionsFactory {
  createMongooseOptions(): MongooseModuleOptions {
    return {
      uri: 'mongodb://localhost/demo',
      useNewUrlParser: true,
    };
  }
}
然后import { MongooseConfigService } from './config.service';引入使用
1.3 配置schema
// user/user.schema.ts
import { Schema } from 'mongoose';
// 创建一个文档架构
export const UserSchema = new Schema({
    name: String,
    password: String,
    phone: String,
    email: String,
    times: Number,
});
1.4 配置连接对应的文档集合的模块
// user/user.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './user.schema';
/**
 * 连接mongodb的用户模块,使用mongoose的模块并配置
 */
@Module({
    imports: [MongooseModule.forFeature([{ name: 'users', schema: UserSchema }])],
})
export class UserModule { }
name:'users'就是对应的mongodb的数据库里面的users集合,又因为forFeature接收的是一个数组,所以可以设置连接多个集合
1.5 配置mongoose查询数据库的服务
// user/user.service.ts
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { User } from './user.interface';
import { CreateUserDto } from './create-user.dto';

@Injectable()
export class UserService {
  constructor(@InjectModel('users') private readonly userModel: Model<User>) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const createdUser = new this.userModel(createUserDto); // 添加一个文档
    return await createdUser.save();
  }

  async findAll(): Promise<User[]> { // 查询全部文档
    return await this.userModel.find().exec();
  }
}
@InjectModel('users')中的users就是对应的集合 然后注入到对应的userModel中,之后就可以进行增删改查等
import { User } from './user.interface';
import { CreateUserDto } from './create-user.dto';
// 这两个就是对应的接口和dto
// user/user.interface.ts
export interface User {
    readonly name: string;
    readonly password: string;
    readonly phone: string;
    readonly email: string;
    readonly times: number;
}
// user/create-user.dto.ts
import { User } from './user.interface';

export class CreateUserDto implements User {
    public readonly name: string;
    public readonly phone: string;
    public readonly email: string;
    public readonly times: number;
    public readonly password: string;
}
1.6 开启一个控制器
// user/user.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { User } from './user.interface';
import { UserService } from './user.service';
import { CreateUserDto } from './create-user.dto';

@Controller('/mongo')
export class UserController {
    public constructor(private readonly userService: UserService) { }
    @Get()
    public getUsers(): Promise<User[]> {
        return this.userService.findAll();
    }
    @Post()
    public createUser(@Body() createUserDto: CreateUserDto): Promise<CreateUserDto> {
        return this.userService.create(createUserDto);
    }
}

这样就配置完成了一个简单的mongodb连接的demo了
配置mongo.module模块到app.module.ts中,然后使用http://localhost:5000/mongo,使用get请求就能获取集合,post请求就能添加一个数据,不过需要传递对应的CreateUserDto类型的参数

2.文件上传,使用multer

2.1 安装依赖
cnpm i multer -S
2.2 配置upload.module.ts
同步配置
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
/**
 * 文件上传控制器
 */
@Module({
    imports: [
     	MulterModule.register({
            dest: './uploadFile', // 文件存储位置,项目根目录算起
            // preservePath: ,// 保留路径
            // fileFilter: , // 文件过滤器,函数,可以控制可上传的文件类型
            // storage: , // 文件存储位置,特点是可以配置一个multer的diskStorage执行函数
            // limits: , // 限制文件上传的大小
        }),
    ],
})
export class UploadModule { }
异步配置
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { MulterConfigService } from './multerConfig.service';
/**
 * 文件上传控制器
 */
@Module({
    imports: [
        // 异步配置
        MulterModule.registerAsync({
            // 1. 直接useFactory配置
            // useFactory: () => ({
            //     dest: './uploadFile',
            // }),
            // 服务配置
            useClass: MulterConfigService,
        }),
    ],
})
export class UploadModule { }
multerConfig.service.ts ,也是配置对应的文件
import { Injectable } from '@nestjs/common';
import { MulterOptionsFactory, MulterModuleOptions } from '@nestjs/platform-express';
import { Request } from 'express';
import { diskStorage } from 'multer';
/**
 * 上传的文件配置服务
 */
@Injectable()
export class MulterConfigService implements MulterOptionsFactory {
    createMulterOptions(): MulterModuleOptions {
        return {
            // dest: './uploadFile',
            fileFilter(req: Request, file: any, cb: (error: Error, acceptFile: boolean) => void): void {
                // 需要调用回调函数 `cb`,
                // 并在第二个参数中传入一个布尔值,用于指示文件是否可接受
                // 如果要拒绝文件,上传则传入 `false`。如:
                // cb(null, false);
                // 如果接受上传文件,则传入 `true`。如:
                cb(null, true);
                // 出错后,可以在第一个参数中传入一个错误:
                // cb(new Error('I don\'t have a clue!'));
                // console.log(file.filename);
                // cb(null, false);
            },
            storage : diskStorage({
                destination: (req, file, cb) => {
                    cb(null, './uploadFile');
                },
                filename: (req, file, cb) => {
                    cb(null, file.originalname);
                },
            }),
        };
    }
}
fileFilter:配置过滤,可以阻止文件上传
storge:比dest的好处是,可以配置一个multer的diskStorage,可以让上传的文件拥有文件名和文件后缀
destination:就是对应的文件路径
filename:就是对应的文件名和文件后缀的设置
2.3 配置控制器文件,用于上传文件
单个文件上传
import { Controller, Post, UseInterceptors, UploadedFile, UploadedFiles } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
/**
 * 文件获取流
 */
@Controller('/upload')
export class UploadController {
    /**  单个文件上传 */
    @Post('/one')
    // FileInterceptor()采用两个参数,一个fieldName(从保持一个文件的HTML表单指向场)和可选的options对象。这些MulterOptions等同于传递给multer构造函数
    @UseInterceptors(FileInterceptor('file', { // FileInterceptor 第二个参数的storage 可以配置文件的保存文件名等
        // storage: diskStorage({
        //     // destination: (req, file, cb) => {
        //     //     cb(null, '/Users/liu/Desktop/test/');
        //     // },
        //     filename: (req, file, cb) => {
        //         cb(null, file.originalname);
        //     },
        // }),
    }))
    public uploadFile(@UploadedFile() file: any): any {
        console.log(file);
        return file;
        // return 'file 对应参数名的单个文件上传';
    }
}
FileInterceptor:文件流拦截器,拦截单个文件,第一个参数是字符串,用于拦截的文件的参数名,第二个参数就是一个可配置项,可以配置和upload.module.ts里面的MulterModule.register里面的对象参数一致的参数
UploadedFile装饰器,则是接收拦截到的文件流,正常在有multer的配置下,拦截到的只是一个保存后的文件的信息对象例如:
{
    "fieldname": "file",
    "originalname": "a.jpg",
    "encoding": "7bit",
    "mimetype": "image/jpeg",
    "destination": "./uploadFile",
    "filename": "ajpg",
    "path": "uploadFile/a.jpg",
    "size": 11145
}

下面都是UploadedFiles从@nestjs/common导出

文件数组上传,公用一个参数名
    @Post('/many')
    // 为了上传文件数组,我们使用FilesInterceptor()。这个拦截器有三个参数。fieldName(保持不变),maxCount即可以同时上载的最大文件数,以及可选MulterOptions对象。
    @UseInterceptors(FilesInterceptor('files', 10))
    public uploadFileArray(@UploadedFiles() files: any): any {
        // console.log(files);
        return files;
    }

FilesInterceptor同样从@nestjs/platform-express导出

多文件上传,不同参数名
/** 不同参数名的多个文件 */
    @Post('/manyFiles')
    @UseInterceptors(FileFieldsInterceptor([
        { name: 'avatar', maxCount: 3 },
        { name: 'background', maxCount: 1 },
    ]))
    public uploadFiles(@UploadedFiles() files: any): any {
        // console.log(files);
        // return 'files 不同参数名的多个文件';
        return files;
    }

name就是对应的参数名,maxCount就是这个参数名的最大的上传数
UseInterceptors同样从@nestjs/platform-express导出

任意文件上传,不限制参数名,无需配置name maxCount
    /**
     * 任何文件
     */
    @Post('/any')
    @UseInterceptors(AnyFilesInterceptor())
    public anyUploadFile(@UploadedFiles() files) {
        // console.log(files);
        // return 'files 任何文件';
        return files;
    }

3.验证,使用class-validator

安装
cnpm i class-validator 
使用参考:class-validator(Github地址)
只需在main.ts中配置 全局验证
  app.useGlobalPipes(new ValidationPipe({
    disableErrorMessages: true, // 错误消息对于理解通过网络发送的数据有什么问题有很大帮助。但是,在某些生产环境中,您可能希望禁用详细错误。
    transform: true, // 该ValidationPipe不会自动将您的有效载荷到相应的DTO类。
  })); // 全局验证
或者在module中的providers中直接配置一个
  {
      provide: APP_PIPE, // APP_PIPE 常量,可以自己配置
      useClass: ValidationPipe,
  },
最后,只要在dto中使用验证即可,验证不通过会自动返回对应的验证不通过的信息给前端,当disableErrorMessages设置为ture的话,就不返回对应的错误信息

4.高速缓存cache-manager

缓存是一种非常简单的技术,有助于提高应用程序的性能。它充当临时数据存储,提供高性能数据访问。

4.1 安装
cnpm i --save cache-manager
4.2配置缓存管理的模块
import { CacheModule, Module } from '@nestjs/common';
@Module({
  imports: [CacheModule.register()],
})
export class CacheConfigModule { }
4.3 需要使用缓存的地方,配置拦截器,或者配置整个module
import { CacheInterceptor } from '@nestjs/common';
// @UseInterceptors(new CacheInterceptor(cacheManager,reflector)) // 使用缓存 接收两个参数,还未深究
// 在模块中使用
 {
      provide: APP_INTERCEPTOR,
      useClass: CacheInterceptor,
  },

5. 序列化serialization

5.1 排除需要返回的数据的一些参数

Exclude 装饰器,被装饰后的属性会被排除

import { Exclude } from 'class-transformer';

export class UserEntity {
  id: number;
  firstName: string;
  lastName: string;

  @Exclude()
  password: string;

  constructor(partial: Partial<UserEntity>) {
    Object.assign(this, partial);
  }
}
5.2暴露属性 @Expose()

Expose装饰器,装饰后的对应的get方法会成为一个参数一样被暴露出去

import { Expose } from 'class-transformer';
@Expose()
get fullName(): string {
  return `${this.firstName} ${this.lastName}`;
}

5.3 Transform 选择暴露出去的对象

您可以使用@Transform()装饰器执行其他数据转换。例如,您要选择一个名称RoleEntity而不是返回整个对象。如下,暴露出name和age,不暴露其它的属性

@Transform(role => ({name:role.name,age:role.age}))
role: RoleEntity;
Transform可能因某些因素而异。要覆盖默认设置,请使用@SerializeOptions()装饰器。

该@SerializeOptions()装饰器从进口@nestjs/common包。

@SerializeOptions({
  // excludePrefixes: ['_'], // 排除前缀为_
})
@Get()
findOne(): UserEntity {
  return {};
}

6.logger日志

Nest带有一个internal的默认实现,Logger它在实例化过程中以及几种不同的情况下使用,例如发生的异常,等等。但是,有时您可能希望完全禁用日志记录或提供自定义实现并自行处理消息。
启用某些类型的日志记录
const app = await NestFactory.create(ApplicationModule, {
  logger: ['error', 'warn'],
});
await app.listen(3000);
或者使用自定义并实现父类的的日志
import { LoggerService } from '@nestjs/common';

export class MyLogger implements LoggerService {
  log(message: string) {}
  error(message: string, trace: string) {}
  warn(message: string) {}
  debug(message: string) {}
  verbose(message: string) {}
}
使用自定义的日志
// logger:['error', 'warn']
logger:new MyLogger()
扩展日志器
// 只需扩展内置Logger类以部分覆盖默认实现,并使用super委托将调用委托给父类。
import { Logger } from '@nestjs/common';

export class MyLogger extends Logger {
  error(message: string, trace: string) {
    // add your tailored logic here
    super.error(message, trace);
  }
}
// 使用
// logger:new MyLogger()
或者直接app实例配置使用
app.useLogger(app.get(MyLogger)); // 解决方案的唯一缺点是您的第一个初始化消息将不会由您的记录器实例处理,但此时它并不重要。

7.安全

7.1安装对应的安全配置
cnpm i helmet csurf express-rate-limit -S

helmet:保护您的应用免受一些众所周知的Web漏洞的影响。通常,Helmet只是12个较小的中间件函数的集合,它们设置与安全相关的HTTP头
csurf:跨站点请求伪造(称为CSRF或XSRF)是一种恶意利用网站,其中未经授权的命令从Web应用程序信任的用户传输。要缓解此类攻击,您可以使用csurf软件包。
express-rate-limit :防止过度请求的一个库,限制规定时间内的请求次数等

7.2 使用
import * as helmet from 'helmet';
import * as csurf from 'csurf';
import * as rateLimit from 'express-rate-limit';
	 // 全局配置跨域
	 app.enableCors(); // 这个nest自带
	// 通过适当地设置HTTP标头,头盔可以帮助保护您的应用免受一些众所周知的Web漏洞的影响。通常,Helmet只是12个较小的中间件函数的集合,它们设置与安全相关的HTTP头(阅读更多)。
	 app.use(helmet());
	 // 跨站点请求伪造(称为CSRF或XSRF)是一种恶意利用网站,其中未经授权的命令从Web应用程序信任的用户传输。要缓解此类攻击,您可以使用csurf软件包。
	 // app.use(csurf()); // 跨站点请求伪造,当前后端分离的项目,别乱使用
	 // 为了保护您的应用程序免受暴力攻击,您必须实现某种速率限制。幸运的是,NPM上已经有很多各种中间件可用。其中一个是快速限额。
	 app.use(
	   rateLimit({
	     windowMs: 15 * 60 * 1000, // 15 minutes
	     max: 100, // limit each IP to 100 requests per windowMs
	   }),
	 );

8.压缩,用于减少请求响应的体积

cnpm i --save compression
使用
import * as compression from 'compression';
// somewhere in your initialization file
app.use(compression());

9.使用Fastify

Fastify为Nest提供了一个很好的替代框架,因为它以类似于Express的方式解决了设计问题。然而,fastify 比Express 快得多,实现了几乎两倍的基准测试结果。

9.1 安装
	cnpm i --save @nestjs/platform-fastify
9.2 使用fastify并配置

第二个参数变成了new FastifyAdapter(),这时候对应的对象配置直接传递给FastifyAdapter对象

// src/main.ts
import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({ logger: false, })
  );
  await app.listen(3000);
}
bootstrap();
9.3 Fastify处理重定向响应的方式与Express略有不同。

@Get()
index (@Res() res) {
  // send 302 redirect to /login
  res.status(302).redirect('/login');
}
 类似资料: