如何从控制器返回 PDF 文件

Tom*_*ler 7 javascript node.js nestjs

我尝试使用 NestJs 从控制器端点返回 PDF 文件。当不设置Content-type标题时,返回的数据getDocumentFile会很好地返回给用户。然而,当我添加标题时,我得到的返回似乎是某种奇怪的 GUID 形式,响应总是如下所示:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxwherex是一个小写的十六进制字符。它似乎也与处理程序函数的实际返回值完全无关,因为我什至在根本不返回任何内容时得到了这个奇怪的 GUID。

不设置时Content-type: application/pdf,该函数返回缓冲区的数据就好了,但是我需要设置标题以使浏览器将响应识别为 PDF 文件,这对我的用例很重要。

控制器看起来像这样:

@Controller('documents')
export class DocumentsController {
  constructor(private documentsService: DocumentsService) {}

  @Get(':id/file')
  @Header('Content-type', 'application/pdf')
  async getDocumentFile(@Param('id') id: string): Promise<Buffer> {
    const document = await this.documentsService.byId(id)
    const pdf = await this.documentsService.getFile(document)

    // using ReadableStreamBuffer as suggested by contributor
    const stream = new ReadableStreamBuffer({
      frequency: 10,
      chunkSize: 2048,
    })
    stream.put(pdf)
    return stream
  }
}
Run Code Online (Sandbox Code Playgroud)

和我的 DocumentsService 是这样的:

@Injectable()
export class DocumentsService {
  async getAll(): Promise<Array<DocumentDocument>> {
    return DocumentModel.find({})
  }

  async byId(id: string): Promise<DocumentDocument> {
    return DocumentModel.findOne({ _id: id })
  }

  async getFile(document: DocumentDocument): Promise<Buffer> {
    const filename = document.filename
    const filepath = path.join(__dirname, '..', '..', '..', '..', '..', 'pdf-generator', 'dist', filename)

    const pdf = await new Promise<Buffer>((resolve, reject) => {
      fs.readFile(filepath, {}, (err, data) => {
        if (err) reject(err)
        else resolve(data)
      })
    })
    return pdf
  }
}
Run Code Online (Sandbox Code Playgroud)

我最初只是返回缓冲区 ( return pdf),但这带来了与上述尝试相同的结果。在 NestJs 的存储库上,有用户建议使用上述方法,这显然对我也不起作用。请参阅此处的 GitHub 线程。

小智 18

2021 年更新:

从现在开始,在 Nest 版本 8 中,您可以使用该类StreamableFile

import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('file')
export class FileController {
  @Get()
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file);
  }
}
Run Code Online (Sandbox Code Playgroud)

官方 Nest 文档中的更多信息:https ://docs.nestjs.com/techniques/streaming-files


muh*_*adi 6

我知道这个旧线程。但它可能会帮助某人。类似于@Victor

  @Get('pdf')
  @HttpCode(201)
  @Header('Content-Type', 'image/pdf')
  @Header('Content-Disposition', 'attachment; filename=test.pdf')
  public pdf() {
    return fs.createReadStream('./test.pdf');
  }
Run Code Online (Sandbox Code Playgroud)


Vic*_*tor 5

这个对我有用。

@Get('pdf')
@HttpCode(HttpStatus.OK)
@Header('Content-Type', 'application/pdf')
@Header('Content-Disposition', 'attachment; filename=test.pdf')
pdf() {
    return createReadStream('./nodejs.pdf');
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我认为应该更好地使用Stream替代readFile。因为它将文件的所有内容加载到 RAM 中。


小智 5

您可以使用现成的装饰器 @Res 这是我的工作解决方案:

控制器(NestJs):

async getNewsPdfById(@Param() getNewsParams: GetNewsPdfParams, @Req() request: Request, @Res() response: Response): Promise<void> {
  const stream = await this.newsService.getNewsPdfById(getNewsParams.newsId, request.user.ownerId);

  response.set({
    'Content-Type': 'image/pdf',
  });

  stream.pipe(response);
}
Run Code Online (Sandbox Code Playgroud)

在我的情况下,流变量只是由 html-pdf 库创建的就绪流,因为我通过 html https://www.npmjs.com/package/html-pdf创建 pdf,但您如何创建流并不重要。问题是您应该使用 @Res 装饰器并将其管道化,因为它是原生的 NestJs 解决方案。

这里也是如何在客户端声明文件的代码:https : //gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743

无论如何,让我们在您的情况下尝试这个:

@Controller('documents')
export class DocumentsController {
  constructor(private documentsService: DocumentsService) {}

  @Get(':id/file')
  async getDocumentFile(@Param('id') id: string, @Res res: Response): Promise<Buffer> {
    const document = await this.documentsService.byId(id)
    const pdf = await this.documentsService.getFile(document)


    const stream = new ReadableStreamBuffer({
      frequency: 10,
      chunkSize: 2048,
    })

    res.set({
      'Content-Type': 'image/pdf',
    });

    stream.pipe(res);
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

也在 2021 年更新:

我更喜欢这种方式,因为我不需要后控制器拦截器逻辑。我们可以控制文件的名称并使其内联或下载文件。

@Get()
  download(@Res() res) {
    const filename = '123.pdf';
    // make it to be inline other than downloading
    // res.setHeader('Content-disposition', 'inline; filename=' + filename);
    res.setHeader('Content-disposition', 'attachment; filename=' + filename);
    const filestream = createReadStream('files/' + filename);
    filestream.pipe(res);
  }
Run Code Online (Sandbox Code Playgroud)

官方 Nest 文档中的更多信息:https ://docs.nestjs.com/techniques/streaming-files