NodeJS 如何从控制器返回PDF文件

aoyhnmkz  于 2022-12-18  发布在  Node.js
关注(0)|答案(7)|浏览(142)

我尝试使用NestJs从控制器端点返回PDF文件。当不设置Content-type头时,getDocumentFile返回的数据可以很好地返回给用户。但是,当我添加头时,返回的似乎是某种奇怪形式的GUID,响应总是如下所示:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,其中x是一个小写的十六进制字符。它似乎与处理函数的实际返回值完全无关,因为我甚至在根本没有返回任何东西的时候得到了这个奇怪的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
  }
}

我的文档服务是这样的:

@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
  }
}

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

7ajki6be

7ajki6be1#

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

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

dbf7pr2w

dbf7pr2w2#

你可以使用ready decorator @Res这是我的解决方案:
控制器(NestJ):

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

在我的例子中,流变量是由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);
  }
}
cgfeq70w

cgfeq70w3#

对我很有效。

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

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

yfjy0ee7

yfjy0ee74#

我知道这条旧线索。但它可能会帮助一些人。类似于@维克托

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

gywdnpxw5#

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

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

ifmq2ha2

ifmq2ha26#

试试这个可能会有帮助

@Get('openPdf/:filename')
  async openPdf(
    @Param('filename') filename: string,
    @Response({ passthrough: true }) res: Res,
  ):Promise<StreamableFile> {
    try {
     
      //if your pdf or file is other directory add this process.cwd()+'/parentfolder/childfolder' or else leave at it is like below
      const readableStream = fs.createReadStream(join(process.cwd(),`${filename}.pdf` ));
     //set application type as you need json,pdf etc
      res.set({
        'Content-Type': 'application/pdf',
        'Content-Disposition': `attachment; filename=${filename}.pdf`
      })
        // return readableStream.pipe(res)
      const streamdata= new StreamableFile(readableStream)
      return streamdata
    } catch (error) {
      return error.message
    }
  }
goucqfw6

goucqfw67#

遗憾的是,“StreamableFile”可以用于v8及以上版本,每次看NestJS都与NestJS的需求和版本不匹配,这是很可怕的事情......

相关问题