写给初用Nestj做我的项目的你(三篇: 编写api & 图片上传)

一. 模块

     比方咱们之前写的user的控制器(controller)与服务(service)它们其实应该算是一个整体, 比方某个中央要引入user相干的操作, 间接引入user的module即可, 咱们当初就来从头生成个module试试吧。

nest g module modules/usersnest g controller modules/usersnest g service modules/users

二. 各种申请形式

     获取不同的申请形式的参数须要用nest提供的不同的装璜器,比方get申请这种显式传参须要用@Query装璜器解决。

get
import { Controller, Get, Query} from '@nestjs/common';// ...@Get()getList(@Query() query) {    return query;}

post
import { Controller, Post, Body } from '@nestjs/common';// ...@Post('create')create(@Body() body) {    return body}

行间id
import { Controller, Get, Param } from '@nestjs/common';// ...@Get(':id')getUser(@Param('id') id): string {    return id;}

三. 公共门路

     设置api门路的前缀是很必要的, 咱们这里就设置为/api/v1, 在/share/src/main.ts

import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';async function bootstrap() {  const app = await NestFactory.create(AppModule);  app.setGlobalPrefix('api/v1'); // 这里这里  await app.listen(3000);}bootstrap();

四. 装璜器

     一些状况下咱们可能须要对参数做一下预处理, 比方申请列表的api参数, 必须蕴含pagepageSize并且最小为1。

     咱们在src文件加下创立decorator文件夹外面是paging.decorator.ts文件。

import { createParamDecorator, ExecutionContext } from '@nestjs/common';export const Paging = createParamDecorator(    (data: string, ctx: ExecutionContext) => {        const request = ctx.switchToHttp().getRequest();        const query = request.query;                if (!query.page || query.page < 1) {            query.page = 1        }        if (!query.pageSize || query.pageSize < 1) {            query.pageSize = 1        }        return query    },);

咱们革新原有的getList办法, 替换@Query() query@Paging() query并查看后果;

    @Get()    getList(@Paging() query) {        return query;    }

五. 管道

     管道将输出数据转换为所需的数据输入, 对输出数据进行验证, 如果验证胜利持续传递, 验证失败则抛出异样, 管道能够解决的事件比装璜器更宽泛, 比方下面说的装璜器更多是针对参数的, 而管道是整个申请。

     咱们用管道来实现一下对page与pageSize的校验,src上面新建pipe文件夹外面是paging.pipe.ts文件内容如下。

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';@Injectable()export class PagingPipe implements PipeTransform {    transform(query: any, metadata: ArgumentMetadata) {        if (metadata.type === 'query') {            if (!query.page || query.page < 1) {                query.page = 1            }            if (!query.pageSize || query.pageSize < 1) {                query.pageSize = 1            }        }        return query;    }}

这里须要用metadata判断一下是那种申请方再做解决, 他的应用形式如下:

import { Controller, Get, Query, UsePipes } from '@nestjs/common';@Controller('users')export class UsersController {    @Get()    @UsePipes(new PagingPipe())    getList(@Query() query) {        return query;    }}
  1. 须要依赖UsePipes装璜器。
  2. 作用在整体的申请上, 并不针对某个参数。
  3. 管道能够设置多个, 例如 @UsePipes(new PagingPipe(), new PagingPipe2())从左向右执行。

六. 中间件

     这个是老朋友了不多介绍了, 间接介绍配置办法吧, 在src目录下创立middleware文件夹, 上面是global.middleware.ts内容如下:

export default function (req, res, next) {    console.log(`全局函数式: 进入`);    next();    console.log(`全局函数式: 退出`);};
全局应用

main.ts外面间接应用

import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import globalMiddleware from './middleware/global.middleware';async function bootstrap() {  const app = await NestFactory.create(AppModule);  app.setGlobalPrefix('api/v1');  app.use(globalMiddleware)  await app.listen(3000);}bootstrap();
部分应用, 只有路由为/users才失效

     创立users.middleware.ts文件, 内容如下:

import { Injectable, NestMiddleware } from '@nestjs/common';@Injectable()export class UsersMiddleware implements NestMiddleware {  use(req: any, res: any, next: () => void) {    console.log('进入users中间件')    next();    console.log('走出users中间件')  }}

/share/src/app.module.ts文件外面做如下批改:

import { Module, MiddlewareConsumer } from '@nestjs/common';import { AppController } from './app.controller';import { AppService } from './app.service';import { GitlabController } from './modules/gitlab/gitlab.controller';import { GitlabService } from './modules/gitlab/gitlab.service';import { UsersModule } from './modules/users/users.module';import { UsersMiddleware } from './middleware/users.middleware';@Module({  imports: [UsersModule],  controllers: [AppController, GitlabController],  providers: [AppService, GitlabService],})export class AppModule {  // 此处定义中间件  configure(consumer: MiddlewareConsumer) {    consumer      .apply(UsersMiddleware)      .forRoutes('/users*');  }}
  1. 如果在main.ts外面定义全局中间件, 只能是函数模式的中间件。
  2. consumer前面能够拼接多个.apply
  3. 如果.forRoutes('/users*');写成.forRoutes('users*');会报错。
  4. forRoutes外面间接配置forRoutes('*')就是全局两头键了, 并且不必必须函数式中间件。

七. 守卫

    守卫与两头键很像它也是在解决申请之前就能够执行, 守卫与两头键的区别是, 中间件调用next但并不知道接下来执行的是什么, 但守卫能够晓得接下来执行什么, 守卫个别被用来做权限的验证。

     接下来咱们就用jwd做一个简略易懂的校验, 不便了解所以做法是官网的简化版

装置依赖

yarn add @nestjs/jwt

生成模块

nest g module modules/authnest g controller modules/authnest g service modules/auth

share/src/modules/auth/auth.controller.ts, 配置生成token的接口。

import { Controller, Get, Response } from '@nestjs/common';import { AuthService } from './auth.service'@Controller('auth')export class AuthController {    constructor(        private readonly authService: AuthService    ) { }    @Get()    getToken(@Response() res) {        return this.authService.getToken(res);    }}

/share/src/modules/auth/auth.module.ts定义jwt的策略, 比方过期工夫等。

import { Module } from '@nestjs/common';import { AuthController } from './auth.controller';import { AuthService } from './auth.service';import { JwtModule } from '@nestjs/jwt';@Module({  imports: [    JwtModule.register({      secret: 'secretKey',      signOptions: { expiresIn: `${60 * 60 * 24 * 10}s` },    }),  ],  controllers: [AuthController],  providers: [AuthService],  exports: [AuthService], // 这里须要留神, 因为前面会全局应用所以要导出一下})export class AuthModule { }

/share/src/modules/auth/auth.service.ts定义生成token的办法。

import { Injectable } from '@nestjs/common';import { JwtService } from '@nestjs/jwt';@Injectable()export class AuthService {    constructor(        private readonly jwtService: JwtService    ) { }    getToken(res) {        res.setHeader("token", this.jwtService.sign({            id: 'dadscxciweneiu122',            name: "金毛"        }))        res.send()    }}
  1. 下面应用jwtService的sig办法生成了token并且最好不要间接返回在参数里, 而是放在header外面。
  2. sig外面的对象, 就是要加密的数据, 咱们能够把用户的某些id放外面。
  3. exports: [AuthService]很重要, 因为要被引入到守卫外面应用。
守卫的配置

/share/src/guard/token.guard.ts

import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';import { Observable } from 'rxjs';import { Reflector } from '@nestjs/core';import { Inject } from '@nestjs/common';@Injectable()export class TokenGuard implements CanActivate {  constructor(    private readonly reflector: Reflector,    @Inject('AuthService') private readonly authService,  ) { }  canActivate(    context: ExecutionContext,  ): boolean | Promise<boolean> | Observable<boolean> {    const request = context.switchToHttp().getRequest();    try {      const user = this.authService.testToken(request)      request.user = user;      return true    } catch (error) {      throw new HttpException({        status: 401,        error: '身份验证失败',      }, 401);    }  }}
  1. 这里的例子是当咱们解析完token后, 会依据token外面的userId去查找用户信息, 而后request上赋予用户信息, 这样每个操作都能够应用这个用户信息进行验证之类的操作了。
  2. HttpException一个谬误类型, 验证不通过这里咱们间接报401。

在全局应用守卫, /share/src/app.module.ts减少如下改变:

import { APP_GUARD } from '@nestjs/core';import { TokenGuard } from './guard/token.guard';// ...@Module({  imports: [UsersModule, AuthModule],  controllers: [AppController, GitlabController],  providers: [AppService, GitlabService,    {      provide: APP_GUARD,      useClass: TokenGuard,    }]})

八. 检测token

     已生成的token当然须要被校验, 也就是下面的testToken办法的实现/share/src/modules/auth/auth.service.ts:

testToken(req) {     const token = req.headers.token;     return this.jwtService.verify(token)     // 后续链接数据库后会查出user信息返回进来}

九. 设置无需验证token

     很多api并不必限度用户为登录态, 所以咱们要设置一个使申请无需校验的装璜器, 比方获取token的操作就不须要验证身份, 用法如下图。

/share/src/guard/noauth.ts

import { SetMetadata } from '@nestjs/common';export const NoAuth = () => SetMetadata('no-auth', true);
  1. SetMetadata设置元数据, 元数据就是用来形容数据的数据, 能够了解为解释这个申请数据是干啥的。
  2. 'no-auth' 设为 true。

/share/src/guard/token.guard.tscanActivate办法外面减少判断

import { CanActivate, ExecutionContext, Injectable, HttpException } from '@nestjs/common';import { Observable } from 'rxjs';import { Reflector } from '@nestjs/core';import { Inject } from '@nestjs/common';@Injectable()export class TokenGuard implements CanActivate {  constructor(    private readonly reflector: Reflector,    @Inject('AuthService') private readonly authService,  ) { }  canActivate(    context: ExecutionContext,  ): boolean | Promise<boolean> | Observable<boolean> {    const request = context.switchToHttp().getRequest();    // const headers = request.headers;    const noAuth =      this.reflector.get<boolean>('no-auth', context.getHandler());    if (noAuth) {      return true;    }    // 未受权    try {      const user = this.authService.testToken(request)      request.user = user;      return true    } catch (error) {      throw new HttpException({        status: 401,        error: '身份验证失败',      }, 401);    }  }}
  1. 反射取出no-auth
  2. context.getHandler()返回的是[Function: testToken]也就是咱们执行的服务里的办法。
  3. 还能够应用context.getClass()返回值是 [class AuthController]咱们以后的申请所在的控制器。
  4. 为了保险能够如下的写法:

     const noAuth =   this.reflector.get<boolean>('no-auth', context.getClass()) ||   this.reflector.get<boolean>('no-auth', context.getHandler());

十. 图片上传&展现

浏览图片

     建设文件夹/share/public外面放上一张图片。
     在main.ts文件外面减少如下代码:

import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { NestExpressApplication } from '@nestjs/platform-express'import globalMiddleware from './middleware/global.middleware';async function bootstrap() { // const app = await NestFactory.create(AppModule); // 这个改变了 const app = await NestFactory.create<NestExpressApplication>(AppModule); app.setGlobalPrefix('api/v1'); app.use(globalMiddleware); app.useStaticAssets('public', {   prefix: '/static' // 肯定不能够省略 '/' }); await app.listen(3000);}bootstrap();

咱们看下成果:

上传图片

     间接拿到文件, 而后按流的模式存储这里就不说了, 间接演示用装璜器实现接管图片上传性能。

import { Controller, BadRequestException, Post UploadedFile, UseInterceptors } from '@nestjs/common';import { FileInterceptor } from '@nestjs/platform-express';import { join } from 'path';import multer = require('multer');@Controller('user')export class UserController {    @Post('img:import')    @UseInterceptors(        FileInterceptor('file', {            storage: multer.diskStorage({                destination: function (req, file, cb) {                    // cb(null, join(process.cwd(), 'upload'));                    cb(null, join(process.cwd(), 'public'));                },                filename: function (req, file, cb) {                    const unique = `${Date.now()}${Math.round(Math.random() * 1e9)}`;                    const imgPath = `${unique}.${file.mimetype.split('/')[1]}`;                    cb(null, imgPath);                },            }),            limits: {                fileSize: 1024 * 1024,            },            fileFilter(req, file, cb) {                if (file.mimetype !== 'image/jpeg' && file.mimetype !== 'image/png') {                    throw new BadRequestException(`只反对jpg, png格局`);                }                cb(null, true);            },        }),    )    async coverImport(@UploadedFile() file) {        return { url: `/static/${file.filename}` };    }}
  1. destination外面设置贮存门路。
  2. filename这里为文件命名, 别忘了加上后缀。
  3. limits就是大小了。
  4. fileFilter做一些过滤操作与抛错。
  5. 最初把相对路径返回给客户端。
  6. 这种写法也并不美观, 所以并不是非要用这种形式。

end.

     下一篇是应用typeorm操作数据库的实战篇, 会分享官网上没有展现的很多实在案例, 一些理论问题过后可是给我造成了不少困扰, 心愿和你一起提高。