提问者:小点点

创建CSV并作为字节[]返回,以便在Spring控制器中下载


总结:<代码>响应

我正在为下载按钮编写一个控制器,该按钮将使用从另一个依赖项检索的数据生成CSV。

    @PostMapping(BASE_ROUTE + "/download")
    public ResponseEntity<byte[]> downloadCases(@RequestBody RequestType request, final HttpServletResponse response) throws IOException {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
                PrintWriter writer = new PrintWriter(outputStreamWriter, true)) {
            String nextPageToken = null;
            do {
                ServiceResponse serviceResponse = makeServiceCall(request.getParam());
                // write a String of comma separated values
                writer.println(serviceResponse.getLine());
                // eventually null
                nextPageToken = serviceResponse.getToken(); 
            } while (nextPageToken != null);

            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_TYPE, "text/csv")
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.csv\"")
                    .body(outputStream.toByteArray());
        }

作为测试,我还尝试将主体设置为<代码>。正文(“案例ID,受让人”。getBytes(StandardCharsets.UTF\u 8))。这应等同于https://stackoverflow.com/a/34508533/10327093

在这两种情况下,我得到的回应都是Base64。示例(缩短):Q2FzZSBJRCxBc3NpZ25lZQ==

它看起来实际上不是Base64。使用. body(Base64.getDecoder(). decode(outputStream.toByteArray()))给我java.lang.IllegalArgumentException:非法的base 64字符

如果我返回val并将outputStream复制到response.getOutputStream()和IOUtils.write(outputStream.toByteArray(),response.getOutputStream());,下载的文件是正确的(CSV)。

然而,我想避免呼叫响应。直接获取getOutputStream()。如果之后出现任何异常,我会得到一个错误,该错误已经在异常处理程序(ExceptionHandler)中为此响应调用了

编辑:Base64解码响应给我正确的值

System.out.println(new String(Base64.getDecoder().decode("Q2FzZSBJRCxBc3NpZ25lZQ==")));
// Case ID,Assignee

似乎字节[]在返回ResponseEntity和客户端之间进行了Base64编码(使用Postman和browser进行了尝试)。


共2个答案

匿名用户

TL;DR:不要以字节[]的形式给出数据,而是以字符串的形式给出数据。

使用ResponseEntity时,Spring使用注册的HttpMessageConverter。

如果指定了内容类型为应用程序/八位字节流,主体类型为字节[],Spring将使用ByteArrayHttpMessageConverter,并按原样发送字节。

如果您指定的内容类型为文本,例如您正在指定的文本,正文类型为字符串,Spring将使用StringHttpMessageConverter对文本进行适当编码并发送。最好明确指定字符集,例如使用内容类型文本/csv;字符集=UTF-8,因此客户端知道服务器使用的字符集。

由于您指定的内容类型为文本/csv,但正文类型为字节[],Spring正在使用其他的HttpMessageConverter,可能是一个ObjecttoStringttpMessageConverter,带有一个将字节[]编码为字符串的转换服务,作为基64。

旁注:为了更好的性能,不要自动刷新每一行输出。关闭自动刷新,完成后只需执行最后的flush()

长话短说,既然您想要文本,就不要转换为字节[]。使用StringWriter将所有内容保留为文本:

@PostMapping(path = BASE_ROUTE + "/download", produces = "text/csv; charset=UTF-8")
public ResponseEntity<String> downloadCases(@RequestBody RequestType request) throws IOException {
    try (StringWriter stringWriter = new StringWriter();
            PrintWriter writer = new PrintWriter(stringWriter)) {
        String nextPageToken = null;
        do {
            ServiceResponse serviceResponse = makeServiceCall(request.getParam());
            // write a String of comma separated values
            writer.println(serviceResponse.getLine());
            // eventually null
            nextPageToken = serviceResponse.getToken();
        } while (nextPageToken != null);
        writer.flush();

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_TYPE, "text/csv; charset=UTF-8")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.csv\"")
                .body(stringWriter.toString());
    }
}

由于从未使用过响应参数,因此已将其删除。

匿名用户

不幸的是,我不能发表评论,所以我将作为答案发布,但这确实只是一些建议:

您是否尝试使用“应用程序/csv”而不是“文本”/csv?

不久前,我不得不做一些非常类似的事情,通过查看您的代码,我看不出有任何问题。

这是我所做的一个例子:

@GetMapping("/" , produces="application/vnd.ms-excel")
public ResponseEntity<Object> myMethod() {
  String filename = "filename.xlsx";
  byte[] bytes = getBytes();
  HttpHeaders headers = new HttpHeaders();
  headers.set("Content-Type", "application/vnd.ms-excel;");
  headers.setContentDispositionFormData(filename, filename);
  return new ResponseEntity<Object>(bytes, headers, HttpStatus.OK);
}