Skip to content
nestjs各模块学习

跨域解决

安装

js
npm install cors
npm install @types/cors -D

src/main.ts里面引入一下

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  await app.listen(3000);
}
bootstrap();

验证码实现思路

安装

js
npm install svg-captcha --save

创建一个验证码模块

js
nest g res yanzhengma

// 回车后选择REST API
// 再输入yes
// 再次回车即可

在验证码模块的yanzhengma.controller.ts里面实现如下:

js
import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Request,
  Query,
  Headers,
  HttpCode,
  Res,
  Req,
} from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { CreateYanzhengmaDto } from './dto/create-yanzhengma.dto';
import { UpdateYanzhengmaDto } from './dto/update-yanzhengma.dto';
import * as svgCaptcha from 'svg-captcha';
@Controller('yanzhengma')
export class YanzhengmaController {
  constructor(private readonly yanzhengmaService: YanzhengmaService) {}
  // 浏览器访问:http://localhost:3000/yanzhengma/code 就可以看到验证码图片
  @Get('code')
  createCaptcha() {
    const captcha = svgCaptcha.create({
      size: 4, //生成几个验证码
      fontSize: 50, //文字大小 
      width: 100, //宽度
      height: 34, //高度
      background: '#cc9966', //背景颜色
    });
    // captcha 是一个对象,包含了验证码图片和验证码内容文字
    // res.type('image/svg+xml'); // 这样是直接返回验证码图片,不会经过下面的逻辑了
    console.log(captcha);
    
    return captcha; // 将验证码图片返回给前端
  }
}

共享模块

新建一个共享模块

js
nest g res 模块名

// 回车后选择REST API
// 再输入yes
// 再次回车即可

我这里模块名还是以yanzhengma为例

yanzhengma.module.ts里面将模块导出,内容如下:

js
import { Module } from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { YanzhengmaController } from './yanzhengma.controller';

@Module({
  controllers: [YanzhengmaController],
  providers: [YanzhengmaService],
  exports: [YanzhengmaService],
})
export class YanzhengmaModule {}

在其他模块就可以使用yanzhengma模块的方法了,如下在app.controller.ts里面引入yanzhengma.service,然后就可以直接调用其方法

js
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { YanzhengmaService } from './yanzhengma/yanzhengma.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService,private readonly yanzhengmaService: YanzhengmaService) {}

  @Get()
  getHello(): string {
    return this.yanzhengmaService.findAll();
  }
}

这时浏览器访问http://localhost:3000/就可以看到yanzhengma.service里面的findAll方法返回的信息了

中间件

创建中间件模版

js
nest g mi logger // 最后一个是模块名

模版内容如下:

js
import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    console.log('我是中间件');
    next();
  }
}

局部中间件

使用中间件

任意模块内引入中间件,例如我这里还是以yanzhengma模块为例

yanzhengma.module.ts里面进行引入

js
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { YanzhengmaController } from './yanzhengma.controller';
import { LoggerMiddleware } from 'src/logger/logger.middleware';
@Module({
  controllers: [YanzhengmaController],
  providers: [YanzhengmaService],
  exports: [YanzhengmaService],
})
export class YanzhengmaModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 方式一
    // consumer.apply(LoggerMiddleware).forRoutes('yanzhengma'); // yanzhengma为当前模块的controller的@Controller('yanzhengma')里面的参数,可以拦截/yanzhengma和子路径所有请求

    // 方式二
    // consumer.apply(LoggerMiddleware).forRoutes({path: 'yanzhengma', method: RequestMethod.GET}); // 只拦截验证码模块的get请求,并且访问路径只能是/yanzhengma,如/yanzhengma/code就不能被拦截
    // consumer.apply(LoggerMiddleware).forRoutes({path: 'yanzhengma/code', method: RequestMethod.POST}); // 只拦截验证码模块的get请求,并且访问路径只能是/yanzhengma/code

    // 方式三
    consumer.apply(LoggerMiddleware).forRoutes(YanzhengmaController); // 直接将当前控制器所有的请求都拦截了
  }
}

全局中间件

main.ts里面更换为如下代码:

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
import { Request,Response,NextFunction } from 'express';

// 定义全局中间件函数
function middleWareAll(req: Request, res: Response, next: NextFunction){
  console.log('当前访问的路由地址为',req.originalUrl); // 获取请求的路由地址
  next()
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  // 注册全局中间件
  app.use(middleWareAll)
  await app.listen(3000);
}
bootstrap();

响应拦截器

创建响应拦截器模块

js
nest g interceptor yanzhengma

src/yanzhengma/yanzhengma.interceptor.ts文件内加入以下代码

js
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable,map } from 'rxjs';

@Injectable()
export class YanzhengmaInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map((data) => {
        return {
          data,
          code: 0,
          msg: '接口请求成功',
        };
      }),
    );
  }
}

main.ts进行引入

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
import { Request,Response,NextFunction } from 'express';
// 引入验证码模块的响应拦截器
import {YanzhengmaInterceptor} from './yanzhengma/yanzhengma.interceptor';

// 定义全局中间件函数
function middleWareAll(req: Request, res: Response, next: NextFunction){
  console.log('当前访问的路由地址为',req.originalUrl); // 获取请求的路由地址
  next()
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  // 注册全局中间件
  app.use(middleWareAll)
  // 注册验证码模块的响应拦截器
  app.useGlobalInterceptors(new YanzhengmaInterceptor())
  await app.listen(3000);
}
bootstrap();

这样的话,所有的请求返回的1结构都是统一结构

异常过滤器

创建异常过滤器模块

js
nest g filter yanzhengma

src/yanzhengma/yanzhengma.filter.ts文件内加入以下代码

js
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';
import { request } from 'http';

@Catch(HttpException)
export class YanzhengmaFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取请求上下文
    const response = ctx.getResponse(); // 获取请求上下文中的 response对象
    const request = ctx.getRequest(); // 获取请求上下文中的 request对象
    const status = exception.getStatus(); // 获取异常状态码

    // 设置错误信息
    const message = exception.message
      ? exception.message
      : `${status >= 500 ? 'Service Error' : 'Client Error'}`;
    const errorResponse = {
      data: null, // 错误数据
      message: message, // 错误类型
      code: -1, // 状态码
      time: new Date().getTime(), // 发生的时间
      path: request.url, // 获取请求的接口路径
      status:status // 获取异常状态码
    };

    // 设置返回的状态码, 请求头,发送错误信息
    response.status(exception.getStatus()).json(errorResponse);
  }
}

main.ts进行引入

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
import { Request,Response,NextFunction } from 'express';
// 引入验证码模块的响应拦截器
import {YanzhengmaInterceptor} from './yanzhengma/yanzhengma.interceptor';
// 引入验证码模块的异常过滤器
import {YanzhengmaFilter} from './yanzhengma/yanzhengma.filter'
// 定义全局中间件函数
function middleWareAll(req: Request, res: Response, next: NextFunction){
  console.log('当前访问的路由地址为',req.originalUrl); // 获取请求的路由地址
  next()
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  // 注册全局中间件
  app.use(middleWareAll)
  // 注册验证码模块的响应拦截器
  app.useGlobalInterceptors(new YanzhengmaInterceptor())
  // 注册验证码模块的异常过滤器
  app.useGlobalFilters(new YanzhengmaFilter())
  await app.listen(3000);
}
bootstrap();

然后在yanzhengma模块内每个请求只要发生错误,即使404也能被捕获到返给前端

自定义抛出异常信息

js
import { HttpException } from '@nestjs/common';

// 使用如下:
throw new HttpException('未传入用户id,删除失败', 200);

管道验证

安装管道验证模块

js
// 以下两个是关于管道验证的模块
yarn add class-validator class-transformer

yanzhengma.controller.ts需要验证的接口如下:

js
import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Request,
  Query,
  Headers,
  HttpCode,
  Res,
  Req,
} from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { CreateYanzhengmaDto } from './dto/create-yanzhengma.dto';
import { UpdateYanzhengmaDto } from './dto/update-yanzhengma.dto';
import * as svgCaptcha from 'svg-captcha';
@Controller('yanzhengma')
export class YanzhengmaController {
  constructor(private readonly yanzhengmaService: YanzhengmaService) {}

  // 参数类型为CreateYanzhengmaDto的类型 
  @Post('codePost')
  codePost(@Body() body: CreateYanzhengmaDto) {
    console.log(body);
    
    return 'codePost';
  }
}

create-yanzhengma.dto文件内加入验证字段

js
import { IsNotEmpty, IsString, Length } from "class-validator";

export class CreateYanzhengmaDto {
    id: number;
    @IsNotEmpty({ message: '用户名不能为空' })
    @IsString()
    @Length(1, 10, { message: '用户名长度必须在1-10之间' })
    name: string;
    @IsNotEmpty({ message: '年龄不能为空' })
    age: string;
}

main.ts进行注册

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
import { Request,Response,NextFunction } from 'express';
// 引入验证码模块的响应拦截器
import {YanzhengmaInterceptor} from './yanzhengma/yanzhengma.interceptor';
// 引入验证码模块的异常过滤器
import {YanzhengmaFilter} from './yanzhengma/yanzhengma.filter'
import { ValidationPipe } from '@nestjs/common';
// 定义全局中间件函数
function middleWareAll(req: Request, res: Response, next: NextFunction){
  console.log('当前访问的路由地址为',req.originalUrl); // 获取请求的路由地址
  next()
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  // 注册全局中间件
  app.use(middleWareAll)
  // 注册管道验证器
  app.useGlobalPipes(new ValidationPipe());
  // 注册验证码模块的响应拦截器
  app.useGlobalInterceptors(new YanzhengmaInterceptor())
  // 注册验证码模块的异常过滤器
  // app.useGlobalFilters(new YanzhengmaFilter())
  await app.listen(3000);
}
bootstrap();

这里各位可能发现了,我注册完管道验证器后把异常过滤器注册注释掉了,因为验证失败也属于抛出异常,则只会显示上面定义的异常过滤器的显示规则,想看我们返回的message消息的话,就只能先把异常过滤器取消掉

守卫

创建守卫模块

js
nest g guard yanzhengma

全局使用

main.ts内导入注册

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入cors
import * as cors from 'cors'
import { Request,Response,NextFunction } from 'express';
// 引入验证码模块的响应拦截器
import {YanzhengmaInterceptor} from './yanzhengma/yanzhengma.interceptor';
// 引入验证码模块的异常过滤器
import {YanzhengmaFilter} from './yanzhengma/yanzhengma.filter'
import { ValidationPipe } from '@nestjs/common';
// 引入守卫
import { YanzhengmaGuard } from './yanzhengma/yanzhengma.guard';
// 定义全局中间件函数
function middleWareAll(req: Request, res: Response, next: NextFunction){
  console.log('当前访问的路由地址为',req.originalUrl); // 获取请求的路由地址
  next()
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册cors
  app.use(cors())
  // 注册全局中间件
  app.use(middleWareAll)
  // 注册管道验证器
  app.useGlobalPipes(new ValidationPipe());
  // 注册验证码模块的响应拦截器
  app.useGlobalInterceptors(new YanzhengmaInterceptor())
  // 注册验证码模块的异常过滤器
  app.useGlobalFilters(new YanzhengmaFilter())
  // 注册全局守卫
  app.useGlobalGuards(new YanzhengmaGuard())
  await app.listen(3000);
}
bootstrap();

单个模块使用

yanzhengma.controller.ts使用如下

js
import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Request,
  Query,
  Headers,
  HttpCode,
  Res,
  Req,
  HttpException,
  UseGuards,
  SetMetadata,
} from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { CreateYanzhengmaDto } from './dto/create-yanzhengma.dto';
import { UpdateYanzhengmaDto } from './dto/update-yanzhengma.dto';
import { YanzhengmaGuard } from './yanzhengma.guard';
@Controller('yanzhengma')
// 引入守卫
// @UseGuards(YanzhengmaGuard)在类上面加代表当前模块都会过守卫
// 在接口方法上面加代表只有该接口会使用守卫
@UseGuards(YanzhengmaGuard)
export class YanzhengmaController {
  constructor(private readonly yanzhengmaService: YanzhengmaService) {}
  @Get()
  @SetMetadata('role', ['admin']) // 定义权限,只能特定权限的用户才能访问接口
  findAll() {
    return '1234';  
  }
  @Post('codePost')
  codePost(@Body() body: CreateYanzhengmaDto) {
    console.log(body);
    return 'codePost';
  }
}

src/yanzhengma/yanzhengma.guard加入以下内容判断权限

js
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
@Injectable()
export class YanzhengmaGuard implements CanActivate {
  // 加上反射(可以拿到接口对应相关的一些信息)
  constructor(private reflector: Reflector) {}
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    console.log('经过了守卫');
    // 下面的role与controller接口的@SetMetadata('role', ['access1','access2'])第一个参数保持一致
    const roles = this.reflector.get<string[]>('role', context.getHandler());
    console.log('roles', roles); // 打印的roles就是['access1','access2'],刚刚的第二个参数
    const req = context.switchToHttp().getRequest<Request>();
    console.log('request', req.query.role); 
    if (roles.includes(req.query.role as string)) {
      return true;
    } else {
      return false; // 前面的异常拦截器会返回403错误信息,代表没有权限
    }
  }
}

接口请求要携带参数role,如http://localhost:3000/yanzhengma?role=admin 并且role的值跟接口匹配的规则对应上才能通过

接口文档编写

安装接口文档使用模块swagger

js
npm install  @nestjs/swagger swagger-ui-express

main.ts里面进行配置

js
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入swagger相关
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 初始化接口文档的配置项
  const options = new DocumentBuilder()
  	.addBearerAuth() // 会让文档右上角有个权限,里面存放token信息(需要在每个模块里面也要加一下这个装饰器)
    .setTitle('接口文档')
    .setDescription('接口文档描述')
    .setVersion('1.0')
    .addTag('接口文档')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('/api-docs', app, document);
  await app.listen(3000);
}
bootstrap();

浏览器访问:http://localhost:3000/api-docs

上面就会有一个基本的文档骨架了

在每个模块的controller里面进行分组

这里还是以yanzhengma为例

yanzhengma.controller.ts里面改造如下

js
import { Controller, Get, Post, Body } from '@nestjs/common';
import { YanzhengmaService } from './yanzhengma.service';
import { CreateYanzhengmaDto } from './dto/create-yanzhengma.dto';
// 导入接口文档相关
import {
  ApiTags,
  ApiOperation,
  ApiBearerAuth,
  ApiParam,
  ApiQuery,
  ApiResponse,
} from '@nestjs/swagger';
@Controller('yanzhengma')
@ApiTags('验证码模块接口') // 给接口文档分组
@ApiBearerAuth()
export class YanzhengmaController {
  constructor(private readonly yanzhengmaService: YanzhengmaService) {}
  @Get()
  @ApiOperation({ summary: '获取验证码', description: '该接口用于获取验证码' }) // 给接口文档添加描述
  @ApiParam({ name: '用户姓名', description: '用户描述', required: true,type:'number' }) // 给接口文档添加参数
  @ApiParam({ name: 'param2', description: '第二个路径参数', required: true }) // 有更多参数信息继续往下拼就行
  @ApiQuery({ name: 'param3', description: '第三个路径参数', required: true }) // ApiQuery功能和参数跟上面一样
  @ApiResponse({status:403,description:'暂无权限'}) // 自定义返回的描述信息
  @ApiResponse({status:404,description:'路径错误'}) // 自定义返回的描述信息
  @ApiResponse({status:500,description:'服务器发生错误'}) // 自定义返回的描述信息
  findAll() {
    return '1234';
  }
  @Post('codePost')
  codePost(@Body() body: CreateYanzhengmaDto) {
    console.log(body);
    return 'codePost';
  }
}

也可以在dto层进行限制,如:在create-yanzhengma.dto.ts内容如下

js
import { ApiProperty } from "@nestjs/swagger";

export class CreateYanzhengmaDto {
    @ApiProperty({example:'用户id',description:'id'})
    id: number
    @ApiProperty({example:'用户姓名',description:'name'})
    name: string
    @ApiProperty({example:'用户年龄',description:'age'})
    age: string
}

dto里面的字段在哪个接口使用到了就去哪个接口看

JWT认证

初始化结构

安装如下包

js
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
npm install @nestjs/passport
npm install md5

创建认证模块auth,这个认证模块只用到了module,servicestrategy模块,第三个文件需要手动创建

js
nest g mo auth
nest g s auth

在上面创建完以后找到auth文件夹,在下面创建jwt.strategy.ts文件

创建登录模块login,用于登录返回token

js
nest g res login

创建用户模块user,需要携带token才能访问

js
nest g res user

上述全部都选择REST风格,然后输入yes

认证模块实现

auth.service.ts这里面主要写一个方法返回token,主要给登陆模块返回一个token

js
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as md5 from 'md5';
@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}

  async login(username: any, passport: string) {
    // 模拟下注册时使用md5加密密码,然后与传来的密码进行比对
    let zhucepassword = md5('123456')
    
    if(username == 'admin' && md5(passport) == zhucepassword){
        const payload = {
          username: username,
          passport: passport,
        };
        return {
          access_token: this.jwtService.sign(payload,{
            secret: 'ababab', // 秘钥
          }),
        };
    }else{
        return false
    }
  }
}

auth/jwt.strategy.ts主要用于校验token信息是否存在和过期,内容如下

js
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      // jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 这样默认就是取请求头的token的策略
      jwtFromRequest: (req) => {
        // 这里可以取到前端传来的token信息,只需要把指定的token返回出去即可,例如做登录时使用token,但是同样的人在另一台设备上也可以实现登录,那当前账户就应该从redis取出最新token,使用最新token,这样就保证了一个账号只能登陆一次
        let token = req.headers.authorization.split(' ')[1];
        console.log('token',token); // 用于校验的token
        return token;
      },
      ignoreExpiration: false,
      secretOrKey:'ababab', // 秘钥
    });
  }
  async validate(payload) {
  	// 这里面就是封装的token的参数信息
    return { passport: payload.passport, username: payload.username };
  }
}

auth.module.ts内容如下:

js
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [JwtModule.register({
    secret: 'ababab',  // 秘钥
    signOptions: { expiresIn: '30s' } // 过期时间
  })],
  providers: [AuthService,JwtStrategy], // 将验证策略注入进来
  exports: [AuthService] // 将模块导出去,以便于登录模块调用得到返回的token返给前端
})
export class AuthModule {}

登录模块实现

login.controller.ts里面代码如下:

js
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { LoginService } from './login.service';
import { CreateLoginDto } from './dto/create-login.dto';
import { UpdateLoginDto } from './dto/update-login.dto';
import { AuthService } from 'src/auth/auth.service';
@Controller('login')
export class LoginController {
  constructor(
    private readonly loginService: LoginService,
    private readonly authService: AuthService
  ) {}

  @Post()
  create() {
  	// 调用验证模块的方法,得到token值返回出去
    return this.authService.login('admin','123456');
  }
}

login.module.ts引入验证模块,否则上面的AuthService就不能用了

js
import { Module } from '@nestjs/common';
import { LoginService } from './login.service';
import { LoginController } from './login.controller';
import { AuthModule } from 'src/auth/auth.module';

@Module({
  imports: [AuthModule],  // 注入认证模块的AuthModule,以便于controller能够访问认证模块内的方法
  controllers: [LoginController],
  providers: [LoginService], 
})
export class LoginModule {}

用户模块实现

该模块需要携带token才能访问

user.controller.ts里面的接口规则如下

js
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { AuthGuard } from '@nestjs/passport';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  @UseGuards(AuthGuard('jwt')) // 使用jwt认证
  create(@Body() createUserDto: CreateUserDto) {
    return '1';
  }
}

使用接口请求工具访问post请求:http://127.0.0.1:3000/login 会得到一个token 请求:http://127.0.0.1:3000/user 并且在headers里面加个参数Authorization Bearer token.....然后访问即可,token有效期上面定的是30秒过期

使用.env文件配置全局常量

根目录创建一个.env文件,内容如下

js
XIAOJITEST = "你好哈哈"
NUMBERTEST = 123456
BOOLEANTEST = true

安装模块

js
yarn add @nestjs/config -D

app.module.ts进行全局引入插件

js
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoginModule } from './login/login.module';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
// 引入插件
import { ConfigModule } from '@nestjs/config';

@Module({
  controllers: [AppController],
  providers: [AppService],
  // 在这里引入 ConfigModule.forRoot() 就是全局引入插件了,这个配置了全局访问.env文件
  imports: [ConfigModule.forRoot(),AuthModule, LoginModule, UserModule],
})
export class AppModule {}

接下来在任意模块任意文件内均可以使用下面方式打印出.env文件内容,process无需引入,此常量在全局引入过了

js
console.log('process.env',process.env.XIAOJITEST);
console.log('process.env',process.env.NUMBERTEST);
console.log('process.env',process.env.BOOLEANTEST);

使用Redis

安装如下模块包

js
npm i @nestjs/microservices ioredis -S

安装redis模块

js
nest g mo redis
nest g s redis

在生成的模块redis/redis.module.ts里面做如下配置

js
import { Module } from '@nestjs/common';
import { RedisService } from './redis.service';
import { Transport, ClientsModule } from '@nestjs/microservices';
import { Redis } from 'ioredis';

@Module({
  imports: [
    // 初始化redis,redis参数建议配置到外部配置文件引入
    ClientsModule.register([
      {
        name: 'MATH_SERVICE',
        transport: Transport.REDIS,
        options: {
          host: '127.0.0.1', // 域名
          port: 6379, // 端口号
          db:2, // 使用db2的redis
        }
      }
    ]),
  ],
  providers: [RedisService,Redis],
  exports: [RedisService]
})
export class RedisModule {}

redis/redis.service.ts

js
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
@Injectable()
export class RedisService {  
    
    private readonly redis: Redis;

    constructor() {
      // 初始化redis及其配置
      this.redis = new Redis({
        host: '127.0.0.1', // 域名
        port: 6379, // 端口号
        db:2, // 使用db2的redis
      });
    }
 
  // 获取redis
  async get(key) {
    const res = await this.redis.get(key);
    return JSON.parse(res);
  }
 
  // 设置redis
  async set(key, value) {
    return await this.redis.set(key, JSON.stringify(value));
  }
}

在其他模块使用(哪个模块需要redis存储就引入哪个模块就可以了)

这里以登录模块login为例

新建登录的模块

js
nest g res login

// 回车后选择REST API
// 再输入yes
// 再次回车即可

login/login.module.ts引入redis模块

js
import { Module } from '@nestjs/common';
import { LoginService } from './login.service';
import { LoginController } from './login.controller';
import { RedisModule } from 'src/redis/redis.module';

@Module({
  imports: [RedisModule],
  controllers: [LoginController],
  providers: [LoginService],
})
export class LoginModule {}

login/login.service.ts使用redis模块

js
import { Injectable } from '@nestjs/common';
import { CreateLoginDto } from './dto/create-login.dto';
import { UpdateLoginDto } from './dto/update-login.dto';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class LoginService {
  // 这里使用一下RedisService
  constructor(private readonly redisService: RedisService) {}
  async setRedis() {
    // 设置redis的键和值
    this.redisService.set('test1', '1234');
    return 1;
  }

  async getRedis() {
    // 根据键读取值
    let data =await this.redisService.get('test');
    return data;
  }
}

login.controller.ts发请求测试即可

js
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { LoginService } from './login.service';
import { CreateLoginDto } from './dto/create-login.dto';
import { UpdateLoginDto } from './dto/update-login.dto';
@Controller('login')
export class LoginController {
  constructor(
    private readonly loginService: LoginService
  ) {}

  // 发请求设置redis
  @Get('setdata')
  setdata() {
    return this.loginService.setRedis();
  }

  // 发请求读取redis
  @Get('getdata')
  getdata() {
    return this.loginService.getRedis();
  }
}

单设备登陆思路

利用上面学习的守卫jwtredis实现单设备登陆

生成jwt时将jwt存入redis

auth.service.ts里面生成token,生成完毕存入redis

如下

js
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as md5 from 'md5';
import axios from 'axios';
import { RedisService } from '../redis/redis.service';

interface wx_code_jwt {
  openid: number;
  session_key: string;
  errcode: boolean;
}

@Injectable()
export class AuthService {
  constructor(
    private jwtService: JwtService,
    private readonly redisService: RedisService,
  ) {}

  async loginweb(phone: any, passport: string) {
      const payload = {
        phone: phone,
        passport: passport,
      };
      let jwt = this.jwtService.sign(payload, {
        secret: process.env.JWT_SECRET, // 秘钥
      });
      // 存redis
      await this.redisService.set('web_'+phone, jwt);
      console.log('jwt_web',jwt);
      
      return {
        token: jwt,
        phone:phone,
        msg:'登录成功'
      };
  };
}

可以看到我们的键是用用户手机号作为的,值为jwt

创建一个中间件模块

js
nest g mi logger

logger.middleware.ts里面放入以下代码

主要是在jwt.strategy.ts进行token验证时必须是同步的,不然过不了验证,而取redis数据是异步操作,这里利用中间件在请求时去获取redis的对应的值,并把它携带到header头里面

js
import { Injectable, NestMiddleware } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  constructor(private readonly redisService: RedisService) {}
  async use(req: any, res: any, next: () => void) {
    const token = req.headers.token;
    if (token) {
      req.headers.redisToken = await this.redisService.get(token); // 取redis数据
    }
    next();
  }
}

比对校验

jwt.strategy.ts里面加入以下代码

这里将传入的token与redis作对比,如果不一致就返回null校验失败

js
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: (req) => {
        let token = req.headers.redisToken;
        let jwt = req.headers?.authorization?.split(' ')[1];
        if (token === jwt) {
          // 当前用户请求的token和jwt存储的一致
          return token;
        }else{
          // 当前用户请求的token和jwt存储的不一致
          return null;
        }
      },
      ignoreExpiration: false,
      secretOrKey:process.env.JWT_SECRET, // 秘钥
    });
  }
  async validate(payload) {
  	// 这里面就是封装的token的参数信息
    return { passport: payload.passport, username: payload.username };
  }
}

最后,例如我们要在需要进行token校验的模块注入中间件即可,不然中间件不会执行,设置哪些接口需要进行jwt校验就在哪里加个@UseGuards(AuthGuard('jwt'))即可

例如我要在login/a这个接口进行token校验

首先在login.module.ts注入中间件

js
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { LoginService } from './login.service';
import { LoginController } from './login.controller';
import { AuthModule } from 'src/common/auth/auth.module';
import { RedisModule } from '../redis/redis.module';
import { LoggerMiddleware } from '../logger/logger.middleware';

@Module({
  imports: [AuthModule,RedisModule],
  controllers: [LoginController],
  providers: [LoginService],
})
export class LoginModule implements NestModule{
  configure(consumer: MiddlewareConsumer) {
    // 注入中间件
    consumer.apply(LoggerMiddleware).forRoutes(LoginController);
  }
}

login.controller.ts找到login/a接口标识jwt校验

js
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
import { LoginService } from './login.service';
import { CreateLoginDto } from './dto/create-login.dto';
import { UpdateLoginDto } from './dto/update-login.dto';
import { AuthService } from 'src/common/auth/auth.service';
import { AuthGuard } from '@nestjs/passport';

@Controller('login')
export class LoginController {
  constructor(
    private readonly loginService: LoginService,
    private readonly authService: AuthService
  ) {}
  @Post('a')
  @UseGuards(AuthGuard('jwt')) // 使用jwt校验
  login1(@Body() createLoginDto: CreateLoginDto) {
    return '1';
  }
}

整个流程大概就这样,被注入中间件的模块都会经过中间件转化,查找到该用户在redis的token在请求头添加进去,如果该模块的某个接口标识了使用jwt校验,会在jwt校验的地方比对redis存的和用户携带的是否一致,不一致的话就校验失败,这样就实现了任意一方登陆某个账号,其他地方的账号就会退出

文件上传

安装模块

js
npm i -D @types/multer

使用模块

js
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  UseInterceptors,
  UploadedFile,
} from '@nestjs/common';
import { ImageChangeService } from './image-change.service';
import { CreateImageChangeDto } from './dto/create-image-change.dto';
import { UpdateImageChangeDto } from './dto/update-image-change.dto';
import { FileInterceptor } from '@nestjs/platform-express';
// 创建接口
@Controller('image')
export class ImageChangeController {
  constructor(private readonly imageChangeService: ImageChangeService) {}

  @Post('uploadImage')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    // 这里可以拿到file对象
    // 将文件内容的 Buffer 转换为 Base64(例如我这边要base64图片,就可以看下面进行转换)
    const base64File = file.buffer.toString('base64');
    return base64File;
  }

  
  @Post('updateImage')
  @UseInterceptors(FileInterceptor('file'))
  updateFile(@UploadedFile() file: Express.Multer.File,@Body() body:any) {
    // body可以拿到除file字段以外的其它参数信息
    return '1';
  }
}