关于前端:写给初用Nestj做项目的你三篇-编写api-图片上传

9次阅读

共计 9505 个字符,预计需要花费 24 分钟才能阅读完成。

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

一. 模块

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

nest g module modules/users
nest g controller modules/users
nest 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/auth
nest g controller modules/auth
nest 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 操作数据库的实战篇, 会分享官网上没有展现的很多实在案例, 一些理论问题过后可是给我造成了不少困扰, 心愿和你一起提高。

正文完
 0