一、問題描述
項目中, 使用restTemplate上傳文件時, 文件名中文亂碼, 一串問號, 源文件名為: 測試中文亂碼哦哦哦.zip, 通過restTemplate.postForObject調用接口, 發現文件名變成了: ?????????.zip, 上傳失敗
二、話不多說, 解決方案
1、新建MyFormHttpMessageConverter類
package com.cn.pinliang.admin.Configure;
import javax.mail.internet.MimeUtility;
import org.springframework.http.converter.FormHttpMessageConverter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class MyFormHttpMessageConverter extends FormHttpMessageConverter {
@Override
protected String getFilename(Object part) {
String filename = super.getFilename(part);
Charset multipartCharset = StandardCharsets.UTF_8;
return MimeDelegate.encode(filename, multipartCharset.name());
}
private static class MimeDelegate {
private MimeDelegate() {
}
public static String encode(String value, String charset) {
try {
return MimeUtility.encodeText(value, charset, (String) null);
} catch (UnsupportedEncodingException var3) {
throw new IllegalStateException(var3);
}
}
}
}
2、新建RestTemplateConf類
package com.cn.pinliang.admin.Configure;
import org.springframework.http.MediaType;
import org.springframework.http.converter.*;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class RestTemplateConf {
public static RestTemplate restTemplate() {
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) {
MyFormHttpMessageConverter myConverter = new MyFormHttpMessageConverter();
myConverter.setCharset(StandardCharsets.UTF_8);
messageConverters.remove(i);
messageConverters.add(i, myConverter);
}
}
return restTemplate;
}
}
3、使用
RestTemplate restTemplate = RestTemplateConf.restTemplate();
restTemplate.postForObject... 巴拉巴拉
三、分析
本來之前遇到過同樣的問題, 是springboot項目, spring-web版本為4.2.8, 解決方案更簡單, 直接在Application啟動類中注入restTemplate bean
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().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 < restTemplate.getMessageConverters().size(); i++) {
HttpMessageConverter<?> converter = restTemplate.getMessageConverters().get(i);
if (converter instanceof StringHttpMessageConverter) {
restTemplate.getMessageConverters().remove(i);
restTemplate.getMessageConverters().add(i, stringHttpMessageConverter);
}
if(converter instanceof MappingJackson2HttpMessageConverter){
try{
((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
}catch(Exception e){
e.printStackTrace();
}
}
if (converter instanceof FormHttpMessageConverter) {
((FormHttpMessageConverter) converter).setCharset(StandardCharsets.UTF_8);
((FormHttpMessageConverter) converter).setMultipartCharset(StandardCharsets.UTF_8);
}
}
return restTemplate;
}
重點是這一行代碼
((FormHttpMessageConverter) converter).setMultipartCharset(StandardCharsets.UTF_8);
設置MultipartCharset字符集為UTF-8, 搞定
但是, 現在項目不是springboot項目, 直接copy這段代碼發現報錯, 沒有setMultipartCharset方法, 對比發現原因是spring-web版本不同, 現在是4.0.2, 沒有multipartCharset變量
那么問題來了, 怎么解決, 升級版本? 想了想升級版本成本太高, 很有可能導致其他問題, 那既然是由於沒有multipartCharset變量, 那就看這個變量到底干了啥能解決中文亂碼問題, 跟蹤代碼發現
原來是獲取文件名時用到了, 如果自定義了multipartCharset字符集, 則按照字符集進行轉碼, 否則直接返回文件名, 再來看下4.0.2版本getFilename方法怎么寫的
對, 就這么簡單, 沒有任何轉碼, OK, 既然我們無法通過構造參數指定編碼從而對文件名進行轉碼, 那為什么不重寫getFilename方法呢, 直接在方法里面指定字符集為UTF-8不就行了?
試一下, 新建MyFormHttpMessageConverter繼承FormHttpMessageConverter, 重寫getFilename
@Override
protected String getFilename(Object part) {
String filename = super.getFilename(part);
Charset multipartCharset = StandardCharsets.UTF_8;
return MimeDelegate.encode(filename, multipartCharset.name());
}
這一步搞定, 現在定義restTemplate, 最重要的是這一段代碼
if (converter instanceof FormHttpMessageConverter) {
MyFormHttpMessageConverter myConverter = new MyFormHttpMessageConverter();
myConverter.setCharset(StandardCharsets.UTF_8);
messageConverters.remove(i);
messageConverters.add(i, myConverter);
}
將原來的FormHttpMessageConverter替換為上面新建的MyFormHttpMessageConverter, 搞定, 測試如下
四、總結
解決bug是一個不斷摸索的過程, 尤其是碰到版本類似的問題, 很麻煩, 需要靜下心來定位問題, 分析問題, 找出解決方案, 然后不斷測試, 最后搞定, 本文沒有對RestTemplate的HttpMessageConverter里面的各種轉換器進行分析(我也不會, 哈哈), 更多的是一種解決問題的思路, 希望對小伙伴有一點幫助