角度6从 rest api 下载文件

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

“ JSON 中位置为0的意外令牌%”

”SyntaxError: JSON 中位置0 something at JSON.parse (的意外令牌%

这是我的终点

    @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);});
163504 次浏览

我决定如下:

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

我用这种方式解决了这个问题(请注意,我已经合并了多个解决方案发现堆栈溢出,但我无法找到参考。请随意在评论中添加它们)。

在我的服务中,我有:

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 中使用。然而,我真的不喜欢它,因为我的组件中充满了浏览器特有的东西,这些东西肯定会随着时间的推移而改变。

这也适用于 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);
});
}

你可以通过角度指令做到这一点:

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

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

首先,我需要访问请求的头部(注意 get method options 对象) :

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。

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

关于这段代码的几点注意事项:

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

  2. 一些浏览器将进一步清理文件名。我的 chrome 版本似乎用下划线代替了 :"。我相信还有其他人,但那已经超出范围了。

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

对于角度12 + ,我想到了这样的东西:

this.ApiService
.getFileFromApi()
.pipe(take(1))
.subscribe((response) => {
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(new Blob([response.body], { type: response.body.type }));


const contentDisposition = response.headers.get('content-disposition');
const fileName = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
downloadLink.download = fileName;
downloadLink.click();
});

订阅是在一个带有角形 HttpClient 的简单 get()上进行的。

// api-service.ts


getFileFromApi(url: string): Observable<HttpResponse<Blob>> {
return this.httpClient.get<Blob>(this.baseApiUrl + url, { observe: 'response', responseType: 'blob' as 'json'});
}