0. 问题背景
最近做一个解析数据的小工具,本地运行时都正常,发布到服务器上后在导出文件数据时发现中文全部变成了问号,特此记录下问题解决的思路和过程
1. 环境
java 1.8
springboot 2.6.13
额外引入了fastjson
,commons-csv
等依赖
2. 解决过程
思路1:后端响应体及前端blob对象声明编码格式为utf8
1、首先该问题在本地未发现,服务器上出现了,因此可以明确的是环境编码方式不一致导致的
2、一开始我以为在生成导出数据时出现的问题,项目涉及将导入的文本文件数据解析后导出为csv格式的excel数据
于是首先在导出时,在响应体中声明编码格式为utf8
response.setContentType("application/octet-stream;charset=utf-8");
同时我前端的处理,是将文件流数据转换为blob对象,然后进行文件下载,于是将生成blob对象的地方也声明编码格式
/**
* 文件流转换为blob对象进行下载
* @param data 后端返回的文件流数据
* @param filename 文件名
*/
function exportBlob(data, filename) {
let blob = new Blob([data], {type: "application/octet-stream,charset=UTF-8"});
// 创建一个下载链接
const url = window.URL.createObjectURL(blob);
// 创建一个隐藏的<a>元素,并设置其href属性为下载链接
const a = document.createElement('a');
a.href = url;
a.download = filename;
// 将<a>元素添加到页面中,并模拟点击
document.body.appendChild(a);
a.click();
// 完成下载后,移除<a>元素和下载链接
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
3、该方式本地运行正常,发布服务器后仍然有中文为问号的问题。于是继续分析
思路2:application.properties中设置全局编码格式
1、前面已经明确的是:服务器编码格式不一致导致的,于是通过locale
指令查询服务器编码格式
如上图,发现服务器默认配置的编码格式是utf8
2、但结合实际情况,说明服务器编码格式肯定不是utf8,有时因为安装了一些额外的插件会导致服务器的编码格式被更改
同时怀疑数据在输入时就已经乱码了,于是在后端接口,接收输入参数时打印了数据,发现果然解析的中文就是问号,这就说明问题是出在前端传输给后端的数据中文乱码了,而不是后端返回给前端的数据乱码了。
3、怀疑是前后端的传输编码格式不统一导致的,于是在前端ajax请求中声明数据编码格式UTF-8
$.ajax({
url: 'json/export',
type: 'POST',
contentType: "application/x-www-form-urlencoded;charset=UTF-8",
data: {json: json},
success: function (data, textStatus, xhr) {
layer.close(load);
if(data.msg != null){
layer.msg(data.msg, {icon: 5});
}else{
exportBlob(data, "json转换输出数据.csv");
layer.msg("导出成功");
}
},
error: function (xhr, status, error) {
layer.msg("导出失败");
layer.close(load);
console.log(xhr);
}
});
4、同时在后端声明编码格式,因为是springboot项目,所以直接在application.properties
中配置:
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8
因为我这里springboot版本是2.3+,低版本中应该是
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
该配置可以让服务端在接收和发送数据时使用UTF-8编码
5、再次在服务器上运行,发现还是有中文乱码问题
6、怀疑该配置没生效,于是手写了一个配置类,用于声明编码格式
@Configuration
public class WebConfig implements WebMvcConfigurer
{
@Bean
public FilterRegistrationBean characterEncodingFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(new CharacterEncodingFilter());
registration.addInitParameter("encoding", "UTF-8");
registration.addInitParameter("forceEncoding", "true");
registration.addUrlPatterns("/*");
return registration;
}
private static class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
chain.doFilter(request, response);
}
}
}
7、服务器上运行,问题还是没解决,说明不是该问题导致
思路3:重新编码传输数据
1、如上我们已经将服务端编码格式设置为utf8了,解析仍然有问题,于是尝试将传入数据二次编码
导入文件解析:
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1);
字符串参数解析:
json = new String(json.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
2、然后本地运行,发现就出现问题了,本地运行也出现中文乱码了,那么说明编码格式ISO_8859_1肯定不行
3、于是尝试根据环境本身的编码格式去设置
打印了下服务器上的编码格式
log.error("编码:"+ Charset.defaultCharset().name());
服务器打印结果
这里就可以看到服务器上编码果然不是utf8,于是重新设置编码:
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.defaultCharset());
json = new String(json.getBytes(Charset.defaultCharset()), StandardCharsets.UTF_8);
4、服务器上运行,依然出现中文乱码
思路4:jvm启动脚本中添加-Dfile.encoding=utf-8
1、目前我们能确定的是服务器的编码格式不是utf8,而是US-ASCII。但在代码中二次编码的形式行不通,这里思路有些阻塞了。可以明确的就是环境编码格式导致的,但是怎么定义环境的编码格式呢? 从项目内部定义的形式似乎都行不通
2、自己思路阻塞的时候一般有两种解决办法:
(1)放一放,过几个小时或者第二天再来看,这是思路清空,相对受之前惯性思维的影响较小,一般这时再来看有奇效
(2)与其他人讨论,融合新的思路
3、这里因为想当天把这个问题解决了,于是采取了第二种与同事进行了交流,刚好同事给我说他之前遇到过一种中文乱码的情况,通过在启动脚本里设置-Dfile.encoding=utf-8
解决的
于是进行了尝试,在java项目的启动脚本的添加了该配置
JAVA_OPTS="-server -Xms1G -Xmx2G -Xmn256m -Xss1m \
-Dfile.encoding=utf-8 \
-XX:SurvivorRatio=4 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection \
-XX:CMSInitiatingOccupancyFraction=60 -XX:+PrintGCDateStamps \
-XX:+PrintGCDetails -Xloggc:$LOGS_DIR/gc.log"
4、重启项目,发现中文显示正常了,同时打印的编码格式也变成utf8了。问题解决
5、这里再回过头来看时,发现确实是自己思路陷入局限了,明明知道是环境的问题,却一直期望通过项目内配置解决。
实际上java通过jvm运行,其环境编码问题,自然应该从jvm配置着手。
-Dfile.encoding=utf-8
的作用就是设置jvm虚拟机的编码格式,java项目默认字符集是在java虚拟机启动时决定的,依赖于java虚拟机所在的操作系统的区域以及字符集
实际上该配置应该作为我们项目的常用配置,此次也是因为临时做个新工具,忽略了该配置的书写
总结
如上,就是我针对java项目出现中文乱码的问题的解决思路及过程,对比了网上说明的解决方法,发现也基本上涵盖了大多数情况,大家可以参考