【问题标题】:How to transfer temporary PDF/CSV from Backend(Spring) to Frontend(Angular)?如何将临时 PDF/CSV 从后端(Spring)传输到前端(Angular)?
【发布时间】:2019-03-30 13:06:13
【问题描述】:

我试图达到的目标: 我有在后端生成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' 来自原点“http://localhost:4200”已被 CORS 策略阻止: 跨源请求仅支持协议方案:http, 数据,铬,铬扩展,https。

我尝试使用'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)

任何想法我做错了什么?

【问题讨论】:

  • 406 表示您的后端无法生成请求内容类型的响应(Accept-Header in request)。让我感到奇怪的是,在同一个请求中,您声明您发送的正文与您期望的相同。 ExportBody 的正确 Content-Type 是什么?

标签: spring angular csv pdf io


【解决方案1】:

尝试将后端方法一分为二:

@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:如果您以 UTF-8 格式发送 JSON,这应该可以:

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

干杯

【讨论】:

  • "请修复请求的 Content-Type:如果您以 UTF-8 格式发送 JSON,这应该可以:"当我在后端设置为 produces=MediaType.APPLICATION_PDF_VALUEtext/csv 将其更改为 application/pdftext/csv 这就是为什么我不能设置 'Content-Type': 'application/json;charset=UTF-8' 或者我错了吗?更改后我有一个错误:error: SyntaxError: Unexpected token F in JSON at position 0 at JSON.parse
  • WebMvcConfigurerAdapter() 也被弃用了。
  • 这是在您的前端吗:错误:SyntaxError:JSON.parse 中位置 0 处的 JSON 中的意外令牌 F?
  • 为了确保您的后端按预期工作:尝试 postman 或 curl 在没有前端的情况下发布帖子。专注于后端,如果一切都按预期运行,请注意前端。
  • 是的,这是在前端。好吧,我认为后端很好,因为我测试了我的响应,它返回 200 和正确的正文(如果我将它作为“text/csv”和“application/pdf”发送,或者两者都使用“application/json”发送,实际上没有区别)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-27
  • 1970-01-01
  • 1970-01-01
  • 2020-07-14
相关资源
最近更新 更多