遇到一个需求,需要完成下载Excel表格的功能,表格名包含 “~” 符号,开发时是按照"~" 写的,但是运行的效果却是 “_”,因为项目无法在本地启动,无法 debug,需要重新搭一个简单demo 在本地调试。
使用的是 SpringBoot 框架,快速构建。
一、SpringBoot 构建 Restful Web 服务
先在pom中添加父节点:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
再导入 SpringBoot Web 依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
写个最简单的启动类,注意必须把类放置在包下:
package demo.restful;
@SpringBootApplication
@RestController
public class RestfulDemo {
public static void main(String[] args) {
SpringApplication.run(RestfulDemo.class,args);
}
@GetMapping("/hellodemo")
public String hello(){
return "hello";
}
}
访问 http://localhost:8080/hellodemo 即可。
二、使用 POI 生成 Excel
需求中点击数据看板的“下载”按钮后,执行下载功能,使用的是 HTTP的 GET 方法,而且需要用到 response 的 setHeader 方法。思路是使用 POI 接口,生成 Excel 文件,然后将其写入文件流。写个最简单的生成表格demo:
@SpringBootApplication
@RestController
public class RestfulDemo {
public static void main(String[] args) {
SpringApplication.run(RestfulDemo.class, args);
}
@GetMapping("/hellodemo")
public String hello(HttpServletResponse response) throws IOException {
HSSFWorkbook wb = new HSSFWorkbook();//创建HSSFWorkbook对象
HSSFSheet sheet = wb.createSheet("成绩表");//建立sheet对象
HSSFRow row1 = sheet.createRow(0); //在sheet里创建第一行,参数为行索引
HSSFCell cell = row1.createCell(0); //创建单元格
cell.setCellValue("学生成绩表"); //设置单元格内容
//合并单元格 CellRangeAddress构造参数依次表示起始行,截止行,起始列, 截止列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
//在sheet里创建第二行
HSSFRow row2 = sheet.createRow(1);
//创建单元格并设置单元格内容
row2.createCell(0).setCellValue("姓名");
row2.createCell(1).setCellValue("班级");
row2.createCell(2).setCellValue("语文成绩");
row2.createCell(3).setCellValue("数学成绩");
row2.createCell(4).setCellValue("英语成绩");
//在sheet里创建第三行
HSSFRow row3 = sheet.createRow(2);
row3.createCell(0).setCellValue("小明");
row3.createCell(1).setCellValue("1班");
row3.createCell(2).setCellValue(80);
row3.createCell(3).setCellValue(75);
row3.createCell(4).setCellValue(88);
HSSFRow row4 = sheet.createRow(3);
row4.createCell(0).setCellValue("小红");
row4.createCell(1).setCellValue("1班");
row4.createCell(2).setCellValue(82);
row4.createCell(3).setCellValue(70);
row4.createCell(4).setCellValue(90);
//输出Excel文件
OutputStream output = response.getOutputStream();
response.reset();
//设置响应头
response.setHeader("Content-disposition", "attachment; filename=Student~1.xls");
response.setContentType("application/msexcel");
wb.write(output);
output.close();
return "hello";
}
}
(这里先设置了表格的列名和数据,再获取响应的输出流,然后设置响应头、然后对它进行写操作,)
可以看到,表名应该是 Student~1.xls,运行代码,效果如下:
问题出现了❗ 响应头里设置成了 “~”,但是实际下载的却是"_"。
对响应的设置主要是:
response.setContentType(MIME) 的作用是使客户端浏览器,区分不同种类的数据,并根据不同的 MIME 调用浏览器内不同的程序嵌入模块来处理相应的数据。
Excel 文件对应的是 application/msexcel,没什么问题。
主要是响应头的设置,换些形式:
String fileName = "起始日期~终止日期";
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("GBK"), "ISO-8859-1") + ".xls");
结果:
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1") + ".xls");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"UTF-8")+ ".xls");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"GBK")+ ".xls");
连文件名也变得离谱了起来。
response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + URLEncoder.encode(fileName,"UTF-8")+ ".xls");
整了一圈,还是显示失效。
看了一下 RFC 关于字符集和语言编码的说明 https://www.rfc-editor.org/rfc/rfc5987.txt,说是 header 不能携带 ISO-8859-1 字符集之外的类型。
但是前面为什么不直接使用 “中文文件名”.getBytes(“ISO8859-1”); 这样的代码呢?因为在 ISO8859-1 的编码表中,根本就没有汉字字符,所以应该先通过 “中文文件名”.getBytes(“utf-8”) 获取其 byte[] 字节,让其按照字节来编码,即再使用 new String(“中文文件名”.getBytes(“utf-8”), “ISO8859-1”) 将其重新组成一个字符串:
response.setHeader("Content-Disposition", "attachment;filename=" + new String((tableName + "~" + ".xlsx").getBytes(), StandardCharsets.ISO_8859_1));
然而还是显示不来波浪线,有待解决。