写给初用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参数, 必须蕴含page
与pageSize
并且最小为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;
}
}
- 须要依赖
UsePipes
装璜器。 - 作用在整体的申请上, 并不针对某个参数。
- 管道能够设置多个, 例如
@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*');
}
}
- 如果在
main.ts
外面定义全局中间件, 只能是函数模式的中间件。 consumer
前面能够拼接多个.apply
。- 如果
.forRoutes('/users*');
写成.forRoutes('users*');
会报错。 - 在
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()
}
}
- 下面应用
jwtService
的sig办法生成了token并且最好不要间接返回在参数里, 而是放在header外面。 - sig外面的对象, 就是要加密的数据, 咱们能够把用户的某些id放外面。
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);
}
}
}
- 这里的例子是当咱们解析完token后, 会依据token外面的userId去查找用户信息, 而后request上赋予用户信息, 这样每个操作都能够应用这个用户信息进行验证之类的操作了。
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);
SetMetadata
设置元数据
, 元数据就是用来形容数据的数据, 能够了解为解释这个申请数据是干啥的。- ‘no-auth’ 设为 true。
/share/src/guard/token.guard.ts
的canActivate
办法外面减少判断
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);
}
}
}
- 反射取出
no-auth
。 context.getHandler()
返回的是[Function: testToken]
也就是咱们执行的服务里的办法。- 还能够应用
context.getClass()
返回值是[class AuthController]
咱们以后的申请所在的控制器。 -
为了保险能够如下的写法:
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}` };
}
}
destination
外面设置贮存门路。filename
这里为文件命名, 别忘了加上后缀。limits
就是大小了。fileFilter
做一些过滤操作与抛错。- 最初把相对路径返回给客户端。
- 这种写法也并不美观, 所以并不是非要用这种形式。
end.
下一篇是应用typeorm
操作数据库的实战篇, 会分享官网上没有展现的很多实在案例, 一些理论问题过后可是给我造成了不少困扰, 心愿和你一起提高。
发表回复