最近做项目跟excel打交道比较多,很多业务需求涉及到excel文件的上传、解析和下载,在这个过程中,我们难免会遇到一些棘手的问题。下面我将对这些问题做一个描述,提供出我的解决方案以及思考和总结。
一、斜线的问题
考虑到要用poi来开发如下带有斜线的表格,我之前也没遇过这样的需求,在网上也苦苦找寻了许久,一直没答案。后来突然来了灵感,为何不用读取模板的方式呢,我们可以直接在模板上把这个斜线给画好。一般情况下,斜线上下的文本也很少变动,于是我们也可以把“城市”和“手机品牌”一起画在模板上。
二、RestTemplate上传文件中文名乱码问题
一般情况下,我们用RestTemplate上传中文名的文件,用以下代码不会有文件名乱码问题。但是如果你有了spring-web5.0以下的jar包,你就会很郁闷,不管我们怎么设置编码格式,接收端收到的文件名都会乱码。
@Test
public void restTemplateTransferFile(){
final String filePath = "F:";
final String fileName = "我的文件.txt";
final String url = "http://localhost:8080/file/upload";
RestTemplate restTemplate = new RestTemplate();
//设置请求头
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("multipart/form-data");
headers.setContentType(type);
//设置请求体,注意是LinkedMultiValueMap
FileSystemResource fileSystemResource = new FileSystemResource(filePath+"/"+fileName);
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", fileSystemResource);
form.add("filename",fileName);
//用HttpEntity封装整个请求报文
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
String s = restTemplate.postForObject(url, files, String.class);
System.out.println(s);
}
这个问题阻碍了我很长时间,开始网上也找了几个解决方案,有些一看篇幅很长,我就觉得不就是编码格式问题嘛,干嘛要长篇大论一番,直接找到编码的位置设置下就好了。但是但是,这次还真不行了,在哪个地方设置编码,设置任何编码都不行。后来我也是debug进去找到了http表单的转换类,看到了以下代码。注意红色框框这里,编码竟然写死是ASCII码,我的妈呀,问题就在这里。学过编码的同学都知道,ASCII码就只有128个基本字符,根本编不了中文。
定位到问题后,我们就好办了。结合网上的搜索,得出两种解决方案。
第一是升级spring-web到5.0以上(我也是服了,spring竟然要到5.0以上才解决)。5.0以上我们看到没有硬编码
private byte[] getBytes(String name) {
return name.getBytes(this.charset);
}
第二是自定义一个表单的转换类,把spring-web默认的给替换了。由于我们项目是有统一的架构版本,不能私自升级spring-web包,所以最终只能用第二种方案。具体的方法如下:
- 复制FormHttpMessageConverter类下的所有代码,新建UploadFileFormHttpMessageConverter类
public class UploadFileUploadFileFormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
// 省略
}
- 修改getAsciiBytes方法,编码改为UTF-8
private byte[] getAsciiBytes(String name) {
try {
return name.getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException var3) {
throw new IllegalStateException(var3);
}
}
3.新建UploadRestTemplateUtil,替换新的converter
public class UploadRestTemplateUtil {
/**
* 获取上传文件的restTemplate
* @return
*/
public static RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.add(new MappingJackson2HttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
stringHttpMessageConverter.setWriteAcceptCharset(true);
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.ALL);
for (int i = 0; i < messageConverters.size(); i++) {
HttpMessageConverter<?> converter = messageConverters.get(i);
if (converter instanceof StringHttpMessageConverter) {
messageConverters.remove(i);
messageConverters.add(i, stringHttpMessageConverter);
}
if (converter instanceof MappingJackson2HttpMessageConverter) {
try {
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
} catch (Exception e) {
e.printStackTrace();
}
}
if (converter instanceof FormHttpMessageConverter) {
// 针对文件上传文件名乱码情况使用自定义的converter
UploadFileFormHttpMessageConverter myConverter = new UploadFileFormHttpMessageConverter();
myConverter.setCharset(StandardCharsets.UTF_8);
messageConverters.remove(i);
messageConverters.add(i, myConverter);
}
}
return restTemplate;
}
}
三、导出文件OOM问题
POI生成文件占用大内存的问题,相信很多开发同学都知道,如果在高并发场景下,容易遇到OOM问题。当时我们在开发环境压测下载excel的时候发现,服务很快就抛出了OOM,我们很快定位到是poi占用内存太大了。这个地方是我们代码疏忽了,漏加并发线程控制。当初我们设计文件下载的时候就有用@Async 注解做文件异步的处理。
后来我们也做了避免OOM这方面的讨论,得出结论是有两个方案,就看并发程度如何。
第一,文件下载并发低,我们直接做成异步调用,用@Async做并发控制,前端轮询文件生成结果。
第二,文件下载并发高,我们引进阿里的EasyExcel工具。这个工具是一行一行读取excel,特别省内存,我们在后面的开发中首先都是考虑采用这个工具生成excel。
关于EasyExcel的使用方法,可以查看官网。对于简单规整的excel,我们首先考虑使用这个工具。还有它的填充功能,我觉得还是特别赞的。但是对于一些单元格格式比较复杂的excel,我看EasyExcel没有很好的兼容支持,后面再持续关注这工具的发展。
四、输入输出流的实现
开发中有很多这样的需求:字节数组生成文件流,文件流生成文件,字符串写到文件等等。这都是输入输出流api的事情,好久没有接触过这方面的开发了,感觉都忘了大部分,后面专门写一篇文章来温习温习。