nestjs各模块学习
跨域解决
安装
npm install cors
npm install @types/cors -D
在src/main.ts
里面引入一下
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();
验证码实现思路
安装
npm install svg-captcha --save
创建一个验证码模块
nest g res yanzhengma
// 回车后选择REST API
// 再输入yes
// 再次回车即可
在验证码模块的yanzhengma.controller.ts
里面实现如下:
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; // 将验证码图片返回给前端
}
}
共享模块
新建一个共享模块
nest g res 模块名
// 回车后选择REST API
// 再输入yes
// 再次回车即可
我这里模块名还是以yanzhengma
为例
在yanzhengma.module.ts
里面将模块导出,内容如下:
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
,然后就可以直接调用其方法
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
方法返回的信息了
中间件
创建中间件模版
nest g mi logger // 最后一个是模块名
模版内容如下:
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
里面进行引入
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
里面更换为如下代码:
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();
响应拦截器
创建响应拦截器模块
nest g interceptor yanzhengma
在src/yanzhengma/yanzhengma.interceptor.ts
文件内加入以下代码
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
进行引入
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结构都是统一结构
异常过滤器
创建异常过滤器模块
nest g filter yanzhengma
在src/yanzhengma/yanzhengma.filter.ts
文件内加入以下代码
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
进行引入
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也能被捕获到返给前端
自定义抛出异常信息
import { HttpException } from '@nestjs/common';
// 使用如下:
throw new HttpException('未传入用户id,删除失败', 200);
管道验证
安装管道验证模块
// 以下两个是关于管道验证的模块
yarn add class-validator class-transformer
yanzhengma.controller.ts
需要验证的接口如下:
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
文件内加入验证字段
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
进行注册
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消息的话,就只能先把异常过滤器取消掉
守卫
创建守卫模块
nest g guard yanzhengma
全局使用
在main.ts
内导入注册
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
使用如下
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
加入以下内容判断权限
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
npm install @nestjs/swagger swagger-ui-express
在main.ts
里面进行配置
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
里面改造如下
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
内容如下
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认证
初始化结构
安装如下包
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
npm install @nestjs/passport
npm install md5
创建认证模块auth
,这个认证模块只用到了module
,service
和strategy
模块,第三个文件需要手动创建
nest g mo auth
nest g s auth
在上面创建完以后找到auth
文件夹,在下面创建jwt.strategy.ts
文件
创建登录模块login
,用于登录返回token
nest g res login
创建用户模块user
,需要携带token才能访问
nest g res user
上述全部都选择REST风格,然后输入yes
认证模块实现
auth.service.ts
这里面主要写一个方法返回token,主要给登陆模块返回一个token
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信息是否存在和过期,内容如下
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
内容如下:
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
里面代码如下:
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
就不能用了
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
里面的接口规则如下
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
文件,内容如下
XIAOJITEST = "你好哈哈"
NUMBERTEST = 123456
BOOLEANTEST = true
安装模块
yarn add @nestjs/config -D
在app.module.ts
进行全局引入插件
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
无需引入,此常量在全局引入过了
console.log('process.env',process.env.XIAOJITEST);
console.log('process.env',process.env.NUMBERTEST);
console.log('process.env',process.env.BOOLEANTEST);
使用Redis
安装如下模块包
npm i @nestjs/microservices ioredis -S
安装redis模块
nest g mo redis
nest g s redis
在生成的模块redis/redis.module.ts
里面做如下配置
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
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
为例
新建登录的模块
nest g res login
// 回车后选择REST API
// 再输入yes
// 再次回车即可
在login/login.module.ts
引入redis
模块
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
模块
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
发请求测试即可
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();
}
}
单设备登陆思路
利用上面学习的守卫
,jwt
,redis
实现单设备登陆
生成jwt时将jwt存入redis
在auth.service.ts
里面生成token,生成完毕存入redis
如下
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
创建一个中间件模块
nest g mi logger
在logger.middleware.ts
里面放入以下代码
主要是在jwt.strategy.ts
进行token验证时必须是同步的,不然过不了验证,而取redis数据是异步操作,这里利用中间件在请求时去获取redis的对应的值,并把它携带到header头里面
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校验失败
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
注入中间件
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校验
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存的和用户携带的是否一致,不一致的话就校验失败,这样就实现了任意一方登陆某个账号,其他地方的账号就会退出
文件上传
安装模块
npm i -D @types/multer
使用模块
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';
}
}