Skip to content
目录

Nest实用手册

NodeJS对于前端工程师来说是必不可少的技能,掌握node可以提高自己对前端的视野、架构能力和部署能力;通常在服务端node都需要做一些相关的配置,不限于跨域、日志、路由、静态资源压缩、部署等等,这里就来谈谈常见的配置

跨域

浏览器端为提高资源请求的安全性提出了同源策略机制,即:非同源(域名、端口、协议)的请求会被视为跨域请求,将会被浏览器拦截。然后不同域名的请求越来越多,如何解决跨域请求

解决跨域可以使用代理服务器(如nginx)或服务端的支持,这里介绍下nest中如何配置跨域请求(nginx配置跨域传送门)。在nest中其实和express配置跨域是一致的,底层都是使用了cors第三方库,这里介绍使用中间件和nest提供的方法

基本配置

  • 自定义中间件:

    其实搞懂跨域本质配置跨域请求就很简单了,跨域会先进行options请求,浏览器根据相应的后Access-Control-Allow-Origin等头部信息进行判断是不是允许跨域。所以只要在options请求时快速返回且设置客户端的请求域名和端口时,浏览器就会判断已经允许跨域

    ts
    async 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();
      });
    }
  • nest的enableCors

    ts
    app.enableCors((req: Request, cb) =>
        cb(null, {
          origin: req.headers.origin,
          methods: ['PUT, POST, GET, DELETE, OPTIONS'],
          allowedHeaders: ['x-locale', 'authorization'],
        }),
    );

参考文档

https://docs.nestjs.com/security/cors

https://github.com/expressjs/cors#configuration-options

静态资源

nestjs默认底层框架使用express,可以使用useStaticAssets处理静态资源,也可以参考express的使用方式,其底层都是使用express/serve-static

基本使用

ts
app.useStaticAssets(join(__dirname, '..', 'public'));
// more...

使用expressAPI处理和这个是一样的:

ts
app.use(express.static(resolve(__dirname, '../public')));

此方法也提供了可选的参数:

ts
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; // 路径前缀
}

可根据实际情况进行配置

ts
app.useStaticAssets(resolve(__dirname, '../public'), {
  // ...
  setHeaders(res: Response, path: string) {
    if (/.*\.html?\??.*/i.test(path)) {
      res.setHeader('Cache-Control', 'public, max-age=0');
    }
  },
});

资源压缩

使用压缩可以更好的减小响应内容的体积,但在生产环境高并发、nginx代理情况下不要使用

使用compression提供资源压缩功能:

sh
 yarn add compression

使用:

ts
app.use(
  compression({
    filter: (req: Request, res: Response) => {
      // 这些文件跳过不需要压缩
      if (/\.(woff2|gz|robots\.txt?)/i.test(req.path)) return false;
      return compression.filter(req, res);
    },
  }),
);

Gzip

对于生产环境可以将资源打包成.gz格式的压缩文件,直接使用gz静态服务器,这样就可以减少压缩消耗的时间

生成gz压缩文件,这里介绍webpack中使用compression-webpack-plugin插件打包

ts
import CompressionWebpackPlugin from "compression-webpack-plugin";

// 使用插件
new CompressionWebpackPlugin({
  algorithm: "gzip",
  test: new RegExp("\\.(js|css)$"),
  minRatio: 0.8
});

在node端使用gzip静态资源服务器:

sh
 yarn add express-static-gzip

使用:

ts
app.use(
  expressStaticGzip(resolve(__dirname, '../public'), {
    index: false,
  }),
);

参考文档

文件上传与下载

nest提供了http传输的multipart/form-data文件进行获取的中间件,以及处理流内容的方式

流文件

通常服务端返回流内容都是直接返回文件或者使用流对象pipe到响应对象,使用nest的StreamableFile方法可以快速的对流内容响应

ts
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);
  }
}

以上代码使用原声写法如下:

ts
@Controller('file')
export class FileController {
  @Get()
  getFile(@Res() res: Response) {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    file.pipe(res);
  }
}

文件上传

nest提供了FileInterceptor对文件类型进行处理,以及@UploadedFile装饰器对文件参数的注入

安装模块类型文件:

sh
 npm i -D @types/multer

模拟场景使用:

  1. 假如前端使用formdata上传文件:

    ts
    const formData = new FormData();
    formData.append('file', file); // 假设这个是文件
    formData.append('name', 'sky.png'); // 文件名
    formData.append('type', 'image');
    // 上传
    fetch('/上传接口', { data: formData });
  2. 后端获取文件和其他信息:

ts
@Post('upload')
@UseInterceptors(FileInterceptor('file')) // 文件转换要对应传过来的key
uploadFile(@Body() body:any, @UploadedFile() file: Express.Multer.File) {
  console.log(file); // 这里可以获取到formdata中的 file字段的文件
  console.log(body); // 获取formdata中其他的字段
}

websocket

nest封装了websocket功能,使用@WebSocketGateway装饰的类就拥有了websocket能力,它和使用具体的库没有关系,如ws、socketio,每一种库都可以适配WebSocketGateway

这里使用ws作用底层的库使用

ts
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;
  }
}

部署

nodejs通常都是使用pm2进行托管,部署通常都会使用docker+pm2+k8s形式进行配置

配置pm2:

json
{
	"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"
			}
		}
	]
}

配置docker:

dockerfile
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"]

打包镜像:

ts
➜ 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

项目中一般都会有.gitlab.yml配置,当提交代码时就会自动触发打包机制,最终根据不同环境部署到k8s集群

🎍后续会不断补充相关知识...

若您在阅读过程中发现一些错误:如语句不通、文字、逻辑等错误,可以在评论区指出,我会及时调整修改,感谢您的阅读。同时您觉得这篇文章对您有帮助的话,可以打赏作者一笔作为鼓励,金额不限,感谢支持🤝。
支付宝捐赠微信支付捐赠

Released under the MIT License.