【问题标题】:Angular 6 Downloading file from rest apiAngular 6从rest api下载文件
【发布时间】:2019-02-08 19:31:05
【问题描述】:

我有我的 REST API,我将我的 pdf 文件放在其中,现在我希望我的 angular 应用程序通过我的网络浏览器点击下载它,但我得到了 HttpErrorResponse

“位置 0 处 JSON 中的意外令牌百分比”

"SyntaxError: JSON.parse 中位置 0↵ 处的 JSON 中的意外标记 % (

这是我的终点

    @GetMapping("/help/pdf2")
public ResponseEntity<InputStreamResource> getPdf2(){

    Resource resource = new ClassPathResource("/pdf-sample.pdf");
    long r = 0;
    InputStream is=null;

    try {
        is = resource.getInputStream();
        r = resource.contentLength();
    } catch (IOException e) {
        e.printStackTrace();
    }

        return ResponseEntity.ok().contentLength(r)
                .contentType(MediaType.parseMediaType("application/pdf"))
                .body(new InputStreamResource(is));

}

这是我的服务

  getPdf() {

this.authKey = localStorage.getItem('jwt_token');

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type':  'application/pdf',
    'Authorization' : this.authKey,
    responseType : 'blob',
    Accept : 'application/pdf',
    observe : 'response'
  })
};
return this.http
  .get("http://localhost:9989/api/download/help/pdf2", httpOptions);

}

和调用

this.downloadService.getPdf()
  .subscribe((resultBlob: Blob) => {
  var downloadURL = URL.createObjectURL(resultBlob);
  window.open(downloadURL);});

【问题讨论】:

    标签: rest typescript angular6


    【解决方案1】:

    我通过这种方式解决了这个问题(请注意,我已经合并了在堆栈溢出时找到的多个解决方案,但我找不到引用。请随意将它们添加到 cmets 中)。

    在我的服务中,我有:

    public getPDF(): Observable<Blob> {   
    //const options = { responseType: 'blob' }; there is no use of this
        let uri = '/my/uri';
        // this.http refers to HttpClient. Note here that you cannot use the generic get<Blob> as it does not compile: instead you "choose" the appropriate API in this way.
        return this.http.get(uri, { responseType: 'blob' });
    }
    

    在组件中,我有(这是从多个答案合并的部分):

    public showPDF(fileName: string): void {
        this.myService.getPDF()
            .subscribe(x => {
                // It is necessary to create a new blob object with mime-type explicitly set
                // otherwise only Chrome works like it should
                var newBlob = new Blob([x], { type: "application/pdf" });
                
                // IE doesn't allow using a blob object directly as link href
                // instead it is necessary to use msSaveOrOpenBlob
                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                    window.navigator.msSaveOrOpenBlob(newBlob, fileName);
                    return;
                }
                
                // For other browsers: 
                // Create a link pointing to the ObjectURL containing the blob.
                const data = window.URL.createObjectURL(newBlob);
                
                var link = document.createElement('a');
                link.href = data;
                link.download = fileName;
                // this is necessary as link.click() does not work on the latest firefox
                link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
                
                setTimeout(function () {
                    // For Firefox it is necessary to delay revoking the ObjectURL
                    window.URL.revokeObjectURL(data);
                    link.remove();
                }, 100);
            });
    }
    

    以上代码适用于 IE、Edge、Chrome 和 Firefox。但是,我不太喜欢它,因为我的组件是用浏览器特定的东西拉出来的,这些东西肯定会随着时间的推移而改变。

    【讨论】:

    • 像魅力一样工作,我评论了“const options = { responseType: 'blob' };”这条线因为没用。
    • 谢谢!:) 这是我原始代码的剩余部分。
    • 你不需要以某种方式从 DOM 中删除链接吗?
    • window.URL.createObjectURL 已被弃用并且已经有一段时间了这个答案已经过时并且不再有效。
    • 这个答案仍然适用于 Chrome:78.0.3904.97 和 Firefox:70.0.1。但是,我找不到解决此问题的可靠方法。你会如何解决它?
    【解决方案2】:

    我是这样解决的:

    // header.component.ts
    this.downloadService.getPdf().subscribe((data) => {
    
      this.blob = new Blob([data], {type: 'application/pdf'});
    
      var downloadURL = window.URL.createObjectURL(data);
      var link = document.createElement('a');
      link.href = downloadURL;
      link.download = "help.pdf";
      link.click();
    
    });
    
    
    
    //download.service.ts
    getPdf() {
    
      const httpOptions = {
        responseType: 'blob' as 'json')
      };
    
      return this.http.get(`${this.BASE_URL}/help/pdf`, httpOptions);
    }
    

    【讨论】:

    • 我讨厌发布简单的感谢 cmets,因为它们通常没有附加值……但是我不得不为此做。今天我已经为此苦苦挣扎了好几个小时。我一直在寻找你从this.bloblink.click() 的代码,你刚刚救了我,我认为这将是一个通宵达旦的事情!谢谢!!!!!!
    • 谢谢,无需任何插件等即可工作。节省了我的一天,为你点赞。
    • window.URL.createObjectURL 已弃用。这是错误的。
    • 您应该将 this.blob 传递给 createObjectURL,而不是数据。 var downloadURL = window.URL.createObjectURL(this.blob);
    • 使用 URL.createObjectURL,而不是 window.URL.createObjectURL,显然较新的浏览器认为 window.URL.createObjectURL 存在安全风险
    【解决方案3】:

    您可以使用角度指令来做到这一点:

    @Directive({
        selector: '[downloadInvoice]',
        exportAs: 'downloadInvoice',
    })
    export class DownloadInvoiceDirective implements OnDestroy {
        @Input() orderNumber: string;
        private destroy$: Subject<void> = new Subject<void>();
        _loading = false;
    
        constructor(private ref: ElementRef, private api: Api) {}
    
        @HostListener('click')
        onClick(): void {
            this._loading = true;
            this.api.downloadInvoice(this.orderNumber)
                .pipe(
                    takeUntil(this.destroy$),
                    map(response => new Blob([response], { type: 'application/pdf' })),
                )
                .subscribe((pdf: Blob) => {
                    this.ref.nativeElement.href = window.URL.createObjectURL(pdf);
                    this.ref.nativeElement.click();
                });
        }
        
        // your loading custom class
        @HostBinding('class.btn-loading') get loading() {
            return this._loading;
        }
    
        ngOnDestroy(): void {
            this.destroy$.next();
            this.destroy$.complete();
        }
    }
    

    在模板中:

    <a
          downloadInvoice
          [orderNumber]="order.number"
          class="btn-show-invoice"
      >
         Show invoice
      </a>

    【讨论】:

      【解决方案4】:

      我的答案是基于@Yennefer 的,但我想使用来自服务器的文件名,因为我的 FE 中没有它。我使用 Content-Disposition 标头来传输它,因为这是浏览器用于直接下载的。

      首先,我需要访问请求的标头(注意 get 方法选项对象):

      public getFile(): Observable<HttpResponse<Blob>> {   
          let uri = '/my/uri';
          return this.http.get(uri, { responseType: 'blob', observe: 'response' });
      }
      

      接下来,我需要从标题中提取文件名。

      public getFileName(res: HttpResponse<any>): string {
          const disposition = res.headers.get('Content-Disposition');
          if (!disposition) {
              // either the disposition was not sent, or is not accessible
              //  (see CORS Access-Control-Expose-Headers)
              return null;
          }
          const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; |$)/;
          const asciiFilenameRegex = /filename=(["'])(.*?[^\\])\1(?:; |$)/;
      
          let fileName: string = null;
          if (utf8FilenameRegex.test(disposition)) {
            fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
          } else {
            const matches = asciiFilenameRegex.exec(disposition);
            if (matches != null && matches[2]) {
              fileName = matches[2];
            }
          }
          return fileName;
      }
      

      此方法检查 ascii 和 utf-8 编码的文件名,首选 utf-8。

      一旦我有了文件名,我就可以更新链接对象的下载属性(在@Yennifer 的回答中,即link.download = 'FileName.ext'window.navigator.msSaveOrOpenBlob(newBlob, 'FileName.ext'); 行)

      关于这段代码的几点说明:

      1. Content-Disposition 不在默认的 CORS 白名单中,因此根据您的服务器配置,可能无法从响应对象访问它。如果是这种情况,请在响应服务器中将标头 Access-Control-Expose-Headers 设置为包含 Content-Disposition

      2. 一些浏览器会进一步清理文件名。我的 chrome 版本似乎用下划线替换了 :"。我敢肯定还有其他人,但这超出了范围。

      【讨论】:

        【解决方案5】:
        //Step: 1
        //Base Service
        this.getPDF() {
         return this.http.get(environment.baseUrl + apiUrl, {
              responseType: 'blob',
              headers: new HttpHeaders({
                'Access-Control-Allow-Origin': '*',
                'Authorization': localStorage.getItem('AccessToken') || ''
              })
            });
        }
        
        //Step: 2
        //downloadService
        getReceipt() {
            return new Promise((resolve, reject) => {
              try {
                // {
                const apiName = 'js/getReceipt/type/10/id/2';
                this.getPDF(apiName).subscribe((data) => {
                  if (data !== null && data !== undefined) {
                    resolve(data);
                  } else {
                    reject();
                  }
                }, (error) => {
                  console.log('ERROR STATUS', error.status);
                  reject(error);
                });
              } catch (error) {
                reject(error);
              }
            });
          }
        
        
        //Step 3:
        //Component 
        getReceipt().subscribe((respect: any) => {
          var downloadURL = window.URL.createObjectURL(data);
          var link = document.createElement(‘a’);
          link.href = downloadURL;
          link.download = “sample.pdf";
          link.click();
        });
        

        【讨论】:

          【解决方案6】:

          这也适用于 IE 和 Chrome,几乎相同的答案仅适用于其他浏览器答案有点短。

          getPdf(url: string): void {
              this.invoiceService.getPdf(url).subscribe(response => {
          
                // It is necessary to create a new blob object with mime-type explicitly set
                // otherwise only Chrome works like it should
                const newBlob = new Blob([(response)], { type: 'application/pdf' });
          
                // IE doesn't allow using a blob object directly as link href
                // instead it is necessary to use msSaveOrOpenBlob
                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                    window.navigator.msSaveOrOpenBlob(newBlob);
                    return;
                }
          
                // For other browsers:
                // Create a link pointing to the ObjectURL containing the blob.
                const downloadURL = URL.createObjectURL(newBlob);
                  window.open(downloadURL);
              });
            } 
          

          【讨论】:

          • window.URL.createObjectURL 已弃用。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-09-01
          • 2019-01-08
          • 1970-01-01
          • 2019-10-06
          • 1970-01-01
          • 2019-06-05
          • 2019-01-18
          相关资源
          最近更新 更多