中间件
处理路由之前所调用的函数就是中间件,它能获取 request 和 response 对象,通过 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 {}
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();
多文件上传
大文件上传
是否允许上传
文件下载
常规方式下载
@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");
};