最近做項目跟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的事情,好久沒有接觸過這方面的開發了,感覺都忘了大部分,後面專門寫一篇文章來溫習溫習。