Как перенести временный PDF / CSV из Backend (Spring) в Frontend (Angular)? - PullRequest
0 голосов
/ 25 октября 2018

То, что я пытаюсь достичь: у меня есть служебный метод, который генерирует PDF/CSV на моем бэкэнде, и я хочу сохранить этот pdf, нажав кнопку на моем веб-интерфейсе.

Моей первой попыткой было создатьфайл и отправить целое PDF/CSV контроллером.

@PostMapping(value = "/export")
public File exportReport(
        @RequestParam(value = "format", defaultValue = "PDF") ExportFileFormat format,
        @RequestBody ExportBody exportBody) {
    if (format.equals(ExportFormat.CSV)) {
        return reportService.csvExportSummaryCustomerReport(exportBody);
    }
    if (format.equals(ExportFormat.PDF)) {
        return reportService.pdfExportSummaryCustomerReport(exportBody);
    }
    throw new InvalidWorkingTimeSyntaxException(String.format("Format:%s is invalid.", format));
}

Но это решение дало мне ошибки с

Доступ к XMLHttpRequest в 'file: /// C: / Users/UserFolder/AppData/Local/Temp/csv6677594787854925068.csv 'from origin' http://localhost:4200' заблокирован политикой CORS: запросы перекрестного источника поддерживаются только для схем протоколов: http, data, chrome, chrome-extension, https.

Ofc Я пытался установить новый заголовок ответа set с помощью 'Access-Control-Allow-Origin' : '*', но это не помогло.То же самое с chrome.exe --allow-file-access-from-files --disable-web-security.

. Поэтому я решил использовать другой подход, который заключается в передаче bytes[] и создании углового файла PDF/CSV.

@PostMapping(value = "/export")
public ResponseEntity<byte[]> exportReport(
        @RequestParam(value = "format", defaultValue = "pdf") ExportFileFormat format,
        @RequestBody ExportBody exportBody) {
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("Access-Control-Allow-Origin", "*");

    if (format.equals(ExportFileFormat.CSV)) {
        responseHeaders.setContentType(MediaType.valueOf("text/csv"));
        return new ResponseEntity<>(reportService.csvExportSummaryCustomerReport(exportBody),
                responseHeaders,
                HttpStatus.OK);
    }
    if (format.equals(ExportFileFormat.PDF)) {
        responseHeaders.setContentType(MediaType.APPLICATION_PDF);
        return new ResponseEntity<>(reportService.pdfExportSummaryCustomerReport(exportBody),
                responseHeaders,
                HttpStatus.OK);
    }
    throw new InvalidExportFileFormatException(String.format("Format:%s is invalid.", format));
}

Теперь я добавил заголовки и бэкэндкажется в порядке.После этого я создал сервис с фронтальной стороны:

exportReport(exportBody: ExportBody, format: String): Observable<Object> {
    const exportUrl = `${this.reportsUrl}/export?format=${format}`;

    if (format == "PDF") {
    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/pdf',
            'Accept': 'application/pdf'
        })
    };
      return this.http.post(exportUrl, exportBody, httpOptions);
    }

    if (format == "CSV") {
      const httpOptions = {
        headers: new HttpHeaders({
          'Content-Type': 'text/csv',
          'Accept': 'text/csv'
        })
      };
      return this.http.post(exportUrl, exportBody, httpOptions);
    }
}

Прямо сейчас я хотел использовать его только для печати результата.

downloadPdf() {
    this.hoursWorkedForCustomersService.exportReport(this.exportBody, "PDF").subscribe(
        result => {
            console.log(result);
            //saveAs(result, 'new.csv');  <- in the future.
        }
    );
}

Очевидно, что в будущем я хотел бы загрузить файл как PDF/CSV, например,

saveAs(result, 'new.pdf');

У меня ошибка 406. Ответ:

POST http://localhost:4200/export?format=PDF 406.

TypeError: Cannot read property 'message' of null
    at SafeSubscriber.next.handle.do.err [as _error] (error.interceptor.ts:25)
    at SafeSubscriber.__tryOrSetError (Subscriber.js:240)
    at SafeSubscriber.error (Subscriber.js:195)
    at Subscriber._error (Subscriber.js:125)
    at Subscriber.error (Subscriber.js:99)
    at DoSubscriber._error (tap.js:84)
    at DoSubscriber.error (Subscriber.js:99)
    at XMLHttpRequest.onLoad (http.js:1825)
    at ZoneDelegate.webpackJsonp../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:4006)

Есть идеи, что я делаю не так?

1 Ответ

0 голосов
/ 26 октября 2018

Попробуйте разделить ваш метод на два:

@PostMapping(value = "/export", params={"format=PDF"}, produces=MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<byte[]> generatePdf(){..}

@PostMapping(value = "/export", params={"format=CSV"}, produces="text/csv")
public ResponseEntity<byte[]> generateCsv(){..}

И, пожалуйста, исправьте Content-Type запроса: если вы отправляете JSON в UTF-8, это должно работать:

const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json;charset=UTF-8',
            'Accept': 'application/pdf'
        })
    };

Кстати: не обрабатывайте заголовки CORS в вашем контроллере следующим образом:

responseHeaders.set("Access-Control-Allow-Origin", "*");

Подумайте об использовании @CrossOrigin(origins = "http://localhost:{your frontend server port}") на уровне класса или метода на вашем контроллере или глобально, например:

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurerAdapter() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**").allowedOrigins("http://localhost:{your frontend server port}");
        }
    };
}

ура

...