创建一个 CSV 并返回为 byte[] 以在 Spring Controller 中下载
Posted
技术标签:
【中文标题】创建一个 CSV 并返回为 byte[] 以在 Spring Controller 中下载【英文标题】:Create a CSV and return as byte[] for download in a Spring Controller 【发布时间】:2020-02-02 07:00:05 【问题描述】:总结:ResponseEntity<byte[]>
包含 CSV 不会返回为 CSV
我正在为下载按钮编写一个控制器,它将生成一个 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());
作为测试,我还尝试将正文设置为.body("Case ID,Assignee".getBytes(StandardCharsets.UTF_8))
。这应该等同于https://***.com/a/34508533/10327093
在这两种情况下,我得到的响应都类似于 Base64。示例(缩短):Q2FzZSBJRCxBc3NpZ25lZQ==
它似乎实际上不是 Base64。使用.body(Base64.getDecoder().decode(outputStream.toByteArray()))
给我java.lang.IllegalArgumentException: Illegal base64 character
如果我返回 void
并使用 IOUtils.write(outputStream.toByteArray(), response.getOutputStream());
将 outputStream 复制到 response.getOutputStream(),则下载的文件是正确的 (CSV)。
但是我想避免直接调用 response.getOutputStream()。如果之后我得到任何异常,我会在@ExceptionHandler
中得到错误getOutputStream() has already been called for this response
编辑:Base64 解码响应给我正确的值
System.out.println(new String(Base64.getDecoder().decode("Q2FzZSBJRCxBc3NpZ25lZQ==")));
// Case ID,Assignee
似乎 byte[] 在返回 ResponseEntity 和客户端之间进行了 Base64 编码(使用 Postman 和浏览器进行了尝试)。
【问题讨论】:
【参考方案1】:很遗憾,我无法发表评论,所以我将发布作为答案,但这只是一些建议:
你试过用“application/csv”代替“text”/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);
【讨论】:
我试过了,但仍然从浏览器和 Postman 获得 Base64 也来自邮递员?您如何尝试将文件保存在 Postman 上?您是否点击“发送和下载”(***.com/questions/38975718/…)? 如果将“produces”属性添加到 GetMapping 会发生什么?我编辑了我的答案。【参考方案2】:TL;DR:不要以byte[]
的形式提供数据,而是以String
的形式提供数据。
当使用ResponseEntity
时,Spring 使用已注册的HttpMessageConverter
。
如果您指定内容类型为application/octet-stream
,正文类型为byte[]
,Spring 将使用ByteArrayHttpMessageConverter
并按原样发送字节。
如果您指定 text/*
的内容类型,例如您指定的text/csv
,具有String
的主体类型,Spring 将使用StringHttpMessageConverter
,它将适当地编码文本并发送它。最好明确指定charset
,例如使用内容类型text/csv; charset=UTF-8
,因此客户端知道服务器使用的字符集。
由于您指定了 text/csv
的内容类型,但正文类型为 byte[]
,因此 Spring 正在使用其他一些 HttpMessageConverter
,可能是 ObjectToStringHttpMessageConverter
,以及将 byte[]
编码为的 ConversionService
String
作为 Base-64。
旁注:为了获得更好的性能,不要自动刷新每一行输出。关闭自动刷新并在完成后执行最终的flush()
。
长话短说,既然您需要文字,请不要转换为byte[]
。使用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());
由于您从未使用过 response
参数,因此已将其删除。
【讨论】:
我尝试了application/octet-stream
,但仍然得到了 Base64。我开始怀疑它是否与使用 POST 而不是 GET 有关?更改为 String 有效。也就是说,我在 *** 上看到的大多数示例都使用 byte[]。在 byte[] 上使用 String 有什么缺点吗?还是仅仅是因为大多数示例都是从可能不是文本的文件中读取的?以上是关于创建一个 CSV 并返回为 byte[] 以在 Spring Controller 中下载的主要内容,如果未能解决你的问题,请参考以下文章
ParserError: NULL byte detected. This byte cannot be processed in Python‘s native csv library(
Linq to CSV:将数据转换为 .CSV 文件并从操作中返回文件,而不在服务器上保存文件
python 格式化单个值以在符合RFC4180的CSV文件中使用。使用逗号将这个函数返回的多个值连接到crea