Java Spring返回以BOM表以UTF-8编码的CSV文件

Java Spring returning CSV file encoded in UTF-8 with BOM

显然,对于excel来说,它很好地打开了CSV文件,它的开头应该有字节顺序标记。 CSV的下载是通过在控制器中写入HttpServletResponse的输出流来实现的,因为在请求期间会生成数据。 当我尝试写入BOM表字节-java.io.CharConversionException: Not an ISO 8859-1 character: []时出现异常(即使我指定的编码为UTF-8)。

有问题的控制器方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RequestMapping("/monthly/list")
public List<MonthlyDetailsItem> queryDetailsItems(
        MonthlyDetailsItemQuery query,
        @RequestParam(value ="format", required = false) String format,
        @RequestParam(value ="attachment", required = false, defaultValue="false") Boolean attachment,
        HttpServletResponse response) throws Exception
{  
    // load item list
    List<MonthlyDetailsItem> list = detailsSvc.queryMonthlyDetailsForList(query);
    // adjust format
    format = format != null ? format.toLowerCase() :"json";
    if (!Arrays.asList("json","csv").contains(format)) format ="json";

    // modify common response headers
    response.setCharacterEncoding("UTF-8");
    if (attachment)
        response.setHeader("Content-Disposition","attachment;filename=duomenys." + format);

    // build csv
    if ("csv".equals(format)) {
        response.setContentType("text/csv; charset=UTF-8");
        response.getOutputStream().print("\\ufeff");
        response.getOutputStream().write(buildMonthlyDetailsItemCsv(list).getBytes("UTF-8"));
        return null;
    }

    return list;
}


我刚刚遇到了同样的问题。对我有用的解决方案是从响应对象获取输出流,并按如下方式对其进行写入

1
2
3
4
5
6
7
8
9
10
11
12
    // first create an array for the Byte Order Mark
    final byte[] bom = new byte[] { (byte) 239, (byte) 187, (byte) 191 };
    try (OutputStream os = response.getOutputStream()) {
        os.write(bom);

        final PrintWriter w = new PrintWriter(new OutputStreamWriter(os,"UTF-8"));
        w.print(data);
        w.flush();
        w.close();
    } catch (IOException e) {
        // logit
    }

因此,在OutputStreamWriter上指定了UTF-8。

作为补充,我应该补充一点,同一应用程序需要允许用户上传文件,这些文件可能带有或不带有BOM。这可以通过使用类org.apache.commons.io.input.BOMInputStream来解决,然后使用该类来构造org.apache.commons.csv.CSVParser
BOMInputStream包括方法hasBOM(),用于检测文件是否具有BOM。
我首先陷入的一个陷阱是hasBOM()方法从基础流读取(显然!),因此处理此问题的方法是先标记流,然后在测试后是否没有BOM ,重置流。我为此使用的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try (InputStream is = uploadFile.getInputStream();
        BufferedInputStream buffIs = new BufferedInputStream(is);
        BOMInputStream bomIn = new BOMInputStream(buffIs);) {
      buffIs.mark(LOOKAHEAD_LENGTH);
      // this should allow us to deal with csv's with or without BOMs
      final boolean hasBOM = bomIn.hasBOM();
      final BufferedReader buffReadr = new BufferedReader(
          new InputStreamReader(hasBOM ? bomIn : buffIs, StandardCharsets.UTF_8));

      // if this stream does not have a BOM, then we must reset the stream as the test
      // for a BOM will have consumed some bytes
      if (!hasBOM) {
        buffIs.reset();
      }

      // collect the validated entity details
      final CSVParser parser = CSVParser.parse(buffReadr,
          CSVFormat.DEFAULT.withFirstRecordAsHeader());
      // Do stuff with the parser
      ...
  // Catch and clean up

希望这对某人有帮助。


这没有多大意义:BOM适用于UTF-16。 UTF-8没有字节顺序。您使用setCharacterEncoding设置的编码用于getWriter,而不用于getOutputStream。

更新:

OK,试试这个:

1
2
3
4
5
6
7
if ("csv".equals(format)) {
    response.setContentType("text/csv; charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.print("\\uFEFF");
    out.print(buildMonthlyDetailsItemCsv(list));
    return null;
}

我假设该方法buildMonthlyDetailsItemCsv返回一个字符串。