skip to content
寻找莉莉丝

Nest.js(三)

/ 5 min read / 次阅读

中间件

处理路由之前所调用的函数就是中间件,它能获取 requestresponse 对象,通过 next() 进入下一个循环。

中间件作用:

  • execute any code.
  • make changes to the request and the response objects.
  • end the request-response cycle.
  • call the next middleware function in the stack.
  • if the current middleware function does not end the - request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

部分路由插入中间件

logger.middleware.ts 放在 src/common/middleware 下。

import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response, NextFunction } from "express";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
	use(req: Request, res: Response, next: NextFunction) {
		// 中间件逻辑
		console.log("Request...");
		// 从请求中获取原始 Url 地址
		console.log(`Request URL:${req.originalUrl}`);
		// 获取请求的方法如 GET,POST 等
		console.log(`Request method:${req.method}`);
		// 获取请求的时间戳
		console.log(`Request Time:${Date.now()}`);
		// 针对 id 的路由返回路由内容
		console.log(`Request Parameter:${req.params.id}`);
		next();
	}
}

在 app.module.ts 中使用中间件

import { Module, MiddlewareConsumer } from "@nestjs/common";
import { LoggerMiddleware } from "./common/middleware/logger.middleware";
// ...

@Module({
	imports: [CatsModule, DogsModule, BirdsModule, UserModule],
	controllers: [AppController],
	providers: [AppService],
})
export class AppModule {
	configure(consumer: MiddlewareConsumer) {
		consumer
			.apply(LoggerMiddleware) // 应用 LoggerMiddleware 中间件
			.forRoutes("cats/:id"); // 指定中间件的应用路径 /cats/:id
		// .forRoutes("cats");

		// 指定包含中间件的请求方法
		// .forRoutes({ path: 'cats', method: RequestMethod.GET });

		// 使用通配符来匹配路径
		// .forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });

		// 针对不同控制器使用中间件,也可以传递一个由逗号分隔的控制器列表
		// .forRoutes(CatsController);

		// 通过 exclude 和路径方法来排除特定路径
		// .exclude(
		//   { path: 'cats', method: RequestMethod.GET },
		//   { path: 'cats', method: RequestMethod.POST }
		// )
	}
}

全局插入中间件

main.ts

const app = await NestFactory.create(AppModule);
// 全局插入中间件
app.use(logger);
await app.listen(3000);

像 express 那样的函数式中间件

// 导出
export function logger(req, res, next) {
	console.log(`Request...`);
	next();
}
// app.module.ts 中使用
consumer.apply(logger).forRoutes(CatsController);

调用多个中间件

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

express 中间件

  • cors
  • bodyParser
  • helmet
  • morgan
  • winston
  • express-jwt

文件上传(静态目录)

nest 自带了 multer,还要下一份声明文件。

pnpm add @types/multer -D

基础上传

upload.module.ts

// ...
import { MulterModule } from "@nestjs/platform-express";
import { UploadService } from "./upload.service";
import { UploadController } from "./upload.controller";
import { diskStorage } from "multer";
import { extname, join } from "path";

@Module({
	controllers: [UploadController],
	providers: [UploadService],
	imports: [
		// 注册 MulterModule 设置存储地点和文件名
		MulterModule.register({
			storage: diskStorage({
				destination: join(__dirname, "../images"),
				filename: (_, file, callback) => {
					const fileName = `${new Date().getTime() + extname(file.originalname)}`;
					return callback(null, fileName);
				},
			}),
		}),
	],
})
export class UploadModule {}
images

upload.controller.ts

@Post('album')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
	console.log('file', file);
	return {
		code: 200,
		message: '上传成功',
	};
}

main.ts

// ...
import * as session from "express-session";
import { join } from "path";
import { NestExpressApplication } from "@nestjs/platform-express";

async function bootstrap() {
	const app = await NestFactory.create<NestExpressApplication>(AppModule);
	app.useStaticAssets(join(__dirname, "images"), {
		// 加上前缀,通过链接可访问:http://localhost:3000/miaoosi/1698132859987.jpeg
		prefix: "/miaoosi",
	});
	await app.listen(3000);
}
bootstrap();
prefix link

多文件上传

大文件上传

是否允许上传

文件下载

常规方式下载

@Get('export')
download(@Res() res: Response) {
	const url = '/path/to/file';
	res.download(url);
}

注意:res 的类型 Response 需要从 express 中引入(import { Response } from 'express';),否则客户端进行下载是无效的。

文件流-方式1

@Get('stream')
async download3(@Res() res: Response) {
const url = '/path/to/file'; // 文件路径
const fileName = 'example.txt'; // 替换为实际文件名(如果是jpg下载下来就是jpg,如果是txt下载下来就是txt)

    res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
    res.setHeader('Content-Type', 'application/octet-stream');

    const fileStream = fs.createReadStream(url);
    fileStream.pipe(res);
}

前端:

fetch("/download", {
	method: "GET",
})
	.then((response) => {
		const contentDispositionHeader = response.headers.get("Content-Disposition");
		const fileName = contentDispositionHeader
			? contentDispositionHeader.split("filename=")[1]
			: "downloaded_file";

		response.blob().then((blob) => {
			// 创建一个临时的下载链接
			const downloadLink = document.createElement("a");
			const objectUrl = URL.createObjectURL(blob);
			downloadLink.href = objectUrl;
			downloadLink.download = fileName;

			// 模拟点击下载链接进行文件下载
			downloadLink.click();

			// 释放临时的下载链接
			URL.revokeObjectURL(objectUrl);
		});
	})
	.catch((error) => {
		console.error("文件下载失败:", error);
	});

文件流-方式2

zip 需要引入:import { zip } from 'compressing';

@Get('stream')
async download2(@Res() res: Response) {
	const url = '/path/to/file'; // 文件路径
	const fileName = '图片名字'; // 文件名称

	const tarStream = new zip.Stream();
	await tarStream.addEntry(url);

	// 设置内容类型为文件流
	res.setHeader('Content-Type', 'application/octet-stream');
	// 设置文件名
	res.setHeader('Content-Disposition', `attachment; filename=${fileName}`);

	// 通过管道 (pipe) 连接到响应对象 res 上
	tarStream.pipe(res);
}

前端:

const useFetch = async (url: string) => {
	const response = await fetch(url).then((res) => res.arrayBuffer());
	const contentDispositionHeader = response.headers.get("Content-Disposition");
	const fileName = contentDispositionHeader
		? contentDispositionHeader.split("filename=")[1]
		: "downloaded_file";

	const downloadLink = document.createElement("a");
	downloadLink.href = URL.createObjectURL(
		new Blob([response], {
			// type:"image/png"
		}),
	);
	downloadLink.download = fileName;
	// downloadLink.download = "filename.zip";
	downloadLink.click();
};

const download = () => {
	useFetch("http://localhost:3000/upload/stream");
};