Nest实用手册
NodeJS对于前端工程师来说是必不可少的技能,掌握node可以提高自己对前端的视野、架构能力和部署能力;通常在服务端node都需要做一些相关的配置,不限于跨域、日志、路由、静态资源压缩、部署等等,这里就来谈谈常见的配置
跨域
浏览器端为提高资源请求的安全性提出了同源策略机制,即:非同源(域名、端口、协议)的请求会被视为跨域请求,将会被浏览器拦截。然后不同域名的请求越来越多,如何解决跨域请求
解决跨域可以使用代理服务器(如nginx)或服务端的支持,这里介绍下nest中如何配置跨域请求(nginx配置跨域传送门)。在nest中其实和express配置跨域是一致的,底层都是使用了cors
第三方库,这里介绍使用中间件和nest提供的方法
基本配置
自定义中间件:
其实搞懂跨域本质配置跨域请求就很简单了,跨域会先进行
options
请求,浏览器根据相应的后Access-Control-Allow-Origin
等头部信息进行判断是不是允许跨域。所以只要在options请求时快速返回且设置客户端的请求域名和端口时,浏览器就会判断已经允许跨域tsasync function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); // 配置跨域 app.use((req: Request, res: Response, next) => { // 谁请求就设置 允许的源 res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // 设置请求允许的头 res.setHeader('Access-Control-Allow-Headers', 'X-Locale'); // 设置请求允许的方法 res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); // options请求快速返回 if (req.method?.toLowerCase() === 'options') { res.sendStatus(200); return; } next(); }); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19nest的enableCors
tsapp.enableCors((req: Request, cb) => cb(null, { origin: req.headers.origin, methods: ['PUT, POST, GET, DELETE, OPTIONS'], allowedHeaders: ['x-locale', 'authorization'], }), );
1
2
3
4
5
6
7
参考文档
https://docs.nestjs.com/security/cors
https://github.com/expressjs/cors#configuration-options
静态资源
nestjs默认底层框架使用express,可以使用useStaticAssets
处理静态资源,也可以参考express的使用方式,其底层都是使用express/serve-static
基本使用
app.useStaticAssets(join(__dirname, '..', 'public'));
// more...
2
使用expressAPI处理和这个是一样的:
app.use(express.static(resolve(__dirname, '../public')));
此方法也提供了可选的参数:
useStaticAssets(path: string, options?: ServeStaticOptions): this;
export interface ServeStaticOptions {
dotfiles?: string; // 点文件 ignore|deny|allow
etag?: boolean; // 默认true
extensions?: string[]; // 文件扩展
fallthrough?: boolean;
immutable?: boolean;
index?: boolean | string | string[]; // 路径访问 html
lastModified?: boolean;
maxAge?: number | string;
setHeaders?: (res: any, path: string, stat: any) => any; // 设置头信息
prefix?: string; // 路径前缀
}
2
3
4
5
6
7
8
9
10
11
12
13
可根据实际情况进行配置
app.useStaticAssets(resolve(__dirname, '../public'), {
// ...
setHeaders(res: Response, path: string) {
if (/.*\.html?\??.*/i.test(path)) {
res.setHeader('Cache-Control', 'public, max-age=0');
}
},
});
2
3
4
5
6
7
8
资源压缩
使用压缩可以更好的减小响应内容的体积,但在生产环境高并发、nginx代理情况下不要使用
使用compression
提供资源压缩功能:
➜ yarn add compression
使用:
app.use(
compression({
filter: (req: Request, res: Response) => {
// 这些文件跳过不需要压缩
if (/\.(woff2|gz|robots\.txt?)/i.test(req.path)) return false;
return compression.filter(req, res);
},
}),
);
2
3
4
5
6
7
8
9
Gzip
对于生产环境可以将资源打包成.gz
格式的压缩文件,直接使用gz静态服务器,这样就可以减少压缩消耗的时间
生成gz压缩文件,这里介绍webpack中使用compression-webpack-plugin
插件打包
import CompressionWebpackPlugin from "compression-webpack-plugin";
// 使用插件
new CompressionWebpackPlugin({
algorithm: "gzip",
test: new RegExp("\\.(js|css)$"),
minRatio: 0.8
});
2
3
4
5
6
7
8
在node端使用gzip静态资源服务器:
➜ yarn add express-static-gzip
使用:
app.use(
expressStaticGzip(resolve(__dirname, '../public'), {
index: false,
}),
);
2
3
4
5
参考文档
- https://docs.nestjs.com/techniques/mvc
- https://docs.nestjs.com/techniques/compression
- https://github.com/expressjs/compression
- https://github.com/expressjs/serve-static
- https://github.com/tkoenig89/express-static-gzip
文件上传与下载
nest提供了http传输的multipart/form-data
文件进行获取的中间件,以及处理流内容的方式
流文件
通常服务端返回流内容都是直接返回文件或者使用流对象pipe到响应对象,使用nest的StreamableFile方法可以快速的对流内容响应
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';
@Controller('file')
export class FileController {
@Get()
getFile(@Res() res: Response) {
const file = createReadStream(join(process.cwd(), 'package.json'));
return new StreamableFile(file);
}
}
2
3
4
5
6
7
8
9
10
11
12
以上代码使用原声写法如下:
@Controller('file')
export class FileController {
@Get()
getFile(@Res() res: Response) {
const file = createReadStream(join(process.cwd(), 'package.json'));
file.pipe(res);
}
}
2
3
4
5
6
7
8
文件上传
nest提供了FileInterceptor对文件类型进行处理,以及@UploadedFile
装饰器对文件参数的注入
安装模块类型文件:
➜ npm i -D @types/multer
模拟场景使用:
假如前端使用formdata上传文件:
tsconst formData = new FormData(); formData.append('file', file); // 假设这个是文件 formData.append('name', 'sky.png'); // 文件名 formData.append('type', 'image'); // 上传 fetch('/上传接口', { data: formData });
1
2
3
4
5
6后端获取文件和其他信息:
@Post('upload')
@UseInterceptors(FileInterceptor('file')) // 文件转换要对应传过来的key
uploadFile(@Body() body:any, @UploadedFile() file: Express.Multer.File) {
console.log(file); // 这里可以获取到formdata中的 file字段的文件
console.log(body); // 获取formdata中其他的字段
}
2
3
4
5
6
websocket
nest封装了websocket功能,使用@WebSocketGateway
装饰的类就拥有了websocket能力,它和使用具体的库没有关系,如ws、socketio,每一种库都可以适配WebSocketGateway
这里使用ws作用底层的库使用
import { Module } from '@nestjs/common';
import { SocketController } from './socket.controller';
@Module({
providers: [SocketController],
})
export class SocketModule {}
// 定义网关
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { IncomingMessage } from 'http';
// import { Server } from 'socket.io';
import { GlobalConfiguration } from 'src/config';
import { Server } from 'ws';
const globalConfig = GlobalConfiguration();
@WebSocketGateway(globalConfig.PORT)
export class SocketController {
@WebSocketServer()
server: Server;
// 连接
async handleConnection(client: WebSocket, request: IncomingMessage) {
// console.log(request.url);
}
@SubscribeMessage('people')
sendMessage() {
return {
name: 'socket',
date: +new Date(),
};
}
@SubscribeMessage('message')
async transferMessage(@MessageBody() body) {
console.log(1111, body);
// await new Promise((resolve) => setTimeout(resolve, 1500));
return 2;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
部署
nodejs通常都是使用pm2进行托管,部署通常都会使用docker+pm2+k8s形式进行配置
配置pm2:
{
"apps": [
{
"name": "nest-web",
"script": "dist/main.js",
"watch": false,
"instance": 2,
"autorestart": true,
"max_memory_restart": "1G",
"env": {
"NODE_ENV": "development"
},
"env_production": {
"NODE_ENV": "production"
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置docker:
FROM node:16-alpine3.18 AS base
ENV TZ=Asia/Shanghai
WORKDIR /app
# 需与配置文件中的SERVER_PORT对应
EXPOSE 9999
# COPY public .
# COPY src .
# COPY package.json .
# COPY pm2.json .
# COPY nest-cli.json /app/nest-cli.json
# COPY tsconfig.json /app/tsconfig.json
# COPY tsconfig.build.json /app/tsconfig.build.json
# RUN echo pwd
COPY . .
RUN yarn
RUN yarn build
RUN yarn global add pm2
ENTRYPOINT ["pm2-runtime", "start", "pm2.json", "--env", "production"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
打包镜像:
➜ general-mac nestjs-practive git:(dev) ✗ docker build -t nest-template .
[+] Building 177.5s (11/11) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 772B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 177B 0.0s
=> [internal] load metadata for docker.io/library/node:16-alpine3.18 17.6s
=> [1/6] FROM docker.io/library/node:16-alpine3.18@sha256:6c381d5dc2a11dcdb693f0301e8587e43f440c90cdb8933eaaaabb905d44cdb9 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 2.78kB 0.0s
=> CACHED [2/6] WORKDIR /app 0.0s
=> CACHED [3/6] COPY . . 0.0s
=> CACHED [4/6] RUN yarn 0.0s
=> CACHED [5/6] RUN yarn build 0.0s
=> [6/6] RUN yarn global add pm2 153.8s
=> exporting to image 6.0s
=> => exporting layers 6.0s
=> => writing image sha256:ffb09b4da10527b212eda87e2b239bc6ae36748e769f203b21e384a66b349ff8 0.0s
=> => naming to docker.io/library/nest-template
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
项目中一般都会有.gitlab.yml
配置,当提交代码时就会自动触发打包机制,最终根据不同环境部署到k8s集群
🎍后续会不断补充相关知识...