一次特殊的 乱码 问题的解决过程
作为码农,乱码 问题绝对是最让人抓狂的问题.遇到乱码后,往往不分青红皂白,一阵疯狂的google百度,尝试各种方法,希望人品爆发,能一举攻克乱码,不幸的是,结果常常事与愿违.最终只有痛定思痛,镇静的分析乱码产生的原因,对症下药才能有效的解决乱码问题,避免做无用功.
1. 乱码 背景
有两套系统,P系统和J系统.P系统负责生成和存储一段html代码片段,同时需要将html代码片段同步到J系统,J系统可以在页面上显示html代码.P系统通过发送HTTP请求方式,将html代码提交给J系统,J系统将请求参数中的html代码片段保存到后台数据库,然后在前台显示.P系统也可以在自己的前台显示html代码片段,并且P系统一切正常,存储html代码到数据库以及将数据库中html代码取出在前台显示一切正常.
2. 乱码 问题现象
J系统前台显示的来自P系统的html代码片段中所有的中文全部显示为?(问号),并且,其他语言只要是非ASCII字符,都显示成问号,比如日语,韩语等,都是问号了.查询J系统的数据库,select出来的中文也是问号.
3. 分析 乱码 产生原因
J2EE做WEB开发,遇到乱码问题时,一般情况下,只要保证系统的所有涉及到字符集和编码的地方都保持一种字符集编码即可解决,通常是全部设置为UTF8字符集编码.
3.1 所有涉及字符集编码的地方为:
(1)JSP文件的字符集编码,通过 <%@page contentType=”text/html” pageEncoding=”UTF-8″%> 设置
(2)java源文件字符集编码设置为UTF8,可通过编辑器设置源文件格式
(3)JVM 参数-Dfile.encoding = UTF8,注意,这个在编译java源文件为.class文件时 和 JVM启动是都要设置,且设置的字符集编码保持一致
(4)数据库的字符集编码要设置为UTF8
(5)tomcat中解析请求参数(query string)之前通过request.setCharacterEncoding(“UTF-8”);告诉tomcat请求参数的原始编码,以便正常解析参数.
(6)假如使用tomcat,然后使用get方式通过URL传递中文参数,还需要设置tomcat的URIEncoding=”UTF-8″,设置方法为,在server.xml文件中,将8080端口的<connector>标签设置成URIEncoding=”UTF-8″:<Connector port=”8080″ URIEncoding=”UTF-8″ />
3.2分析P系统
分析发现一切正常,都是UTF8的.分析J系统发现比较乱套:
(1)JSP页面有的pageEncoding是UTF-8,有的却是ISO-8859-1,而且JSP数量巨大,不太可能全部修改成一种编码,只能维持现状.
(2)分析Java源文件编码格式,都是UTF8的,源文件中没有中文,全是英文.
(3)分析J系统运行环境的file.encoding是ISO-8859-1,在编译的时候设置file.encoding=UTF8了,幸亏源代码中没有中文,否则在运行的时候肯定是乱码.
(4)数据库,使用的是SQL server, 执行exec sp_helpsort
返回的是:Latin1-General, case-insensitive, accent-sensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 52 on Code Page 1252 for non-Unicode Data
这显然是不支持中文的.于是 做了个测试:
drop table tab_test create table tab_test(ss text) insert into tab_test values('中文') select * from tab_test
这里需要说明的是,数据库属于DBA维护,开放人员是没有任何权限修改数据库的字符集等设置的,并且这种修改也是很危险的,于是,数据库也不能改.
另外,存储html代码片段的字段是text类型的.
(5)J系统没有设置request.setCharacterEncoding(“UTF-8”);于是,所有请求参数被默认为是使用ISO-8859-1编码的,因为种种原因,这里不能设置request.setCharacterEncoding(“UTF-8”);
(6) 这里不涉及URL方式传参,所以不涉及tomcat的URIEncoding,即便涉及到了,也没有权限修改tomcat配置,这里只有保持原状.
3.3 分析P系统和J系统的对接过程:
P系统发送HTTP请求POST方式传递数据到J系统,数据由UTF8格式编码发到J系统之后,tomcat使用了ISO-8859-1处理之后必定乱套.
4. 解决问题
首先需要解决分析P系统和J系统的对接过程中的请求参数字符集编码不一致问题.因为无论是UTF8还是ISO-8859-1它们在处理ASCII时都是一样的,也就是说,原来是UTF8的字符集编码的abc(ASCII字符,就是字符数字以及一些常用字符等,比如百分号),在使用ISO-8859-1字符集解码后还是abc.于是想到,在P系统发送HTTP的请求之前,将请求参数全部用URLEncoder.encode(“中文”,”UTF-8″)编码,这样传递给P系统的参数值就是:%E4%B8%AD%E6%96%87,%E4%B8%AD%E6%96%87在使用ISO-8859-1解码后还是%E4%B8%AD%E6%96%87,于是在J系统获取到参数值%E4%B8%AD%E6%96%87之后,再使用URLDecoder.decode(“%E4%B8%AD%E6%96%87″,”UTF-8”)解码,得到的仍然是UTF8字符集中的”中文”二字.
但是现在问题出现了,系统使用的是ISO-8859-1字符集编码,根本就不包含”中文”二字的编码,于是UTF8字符集编码的”中文”二字无法转换成ISO-8859-1字符集编码.
于是乱码问题似乎无解啊,就在快要绝望的时候,尝试了下J系统的其他页面,发现可以正常存储中文到数据库,并且也可以正常在网页上显示. 分析发现,存入数据库的”中文”二字其实变成了:中文浏览器在显示的时候,将中文显示成”中文”.于是,发现只要将所有中文变成中文 这种格式的存储数据库,然后再取出来就可以在网页上显示了啊.于是研究了下,”中文”二字是怎么变成”中文” 抓包发现,浏览器在发送请求的时候,传递的请求参数就已经是使用中文了,于是确定是在前端将”中文”二字变成”中文”的.搜索后没有发现有javascript代码将”中文”二字变成”中文”,这里推测是浏览器转换的(win7 64位英文版, chrome,IE都做了这种转换),这里仅是推测.如有误,指正.
这里需要说明的时,类似中文格式的被成为html实体,因为html代码是类似<p>中文</p>这样的,<p></p>是代码表示段落,中文是要显示的内容,但是如果内容要显示为<p>时,'<‘和’>’就需要转义成<和>于是,在后端,使用StringEscapeUtils.escapeHtml(“<p>中文</p>”);就将html代码片段”<p>中文</p>”转义为<p>中文</p>但是,这里虽然将”中文”转义成了”中文”这是我们系统的,但是将<p>转义成<p>以及</p>转义成</p>都不是我们期望的,于是采用下面的代码解决这个问题:
private String escapeNoneAsciiChar(String str) { String result = str; result = StringEscapeUtils.escapeHtml(str); //先转义所有html实体 result = result.replace("&#", "$^$_$^$"); //因为中文实体都是以&#开头的,先替换掉 result = StringEscapeUtils.unescapeHtml(result); // 将不需要转义的实体转义回来,保留中文的转义 result = result.replace("$^$_$^$", "&#"); //将中文转义实体替换出正确的格式 return result; }
至此,问题解决. 虽然不够圆满,因为数据库里面将中文存储为”中文”了,但是这里不影响功能,因为需求就是能正常存储,正常显示就行了.
5. 总结
这次乱码的解决思路主要是将中文存储和再展示转换成ASCII字符的存储和展示,这样即使是不包含中文字符的ISO-8859-1字符集也可以存储和展示中文,尽管只能使用浏览器来展示.
6. 参考文档
http://stackoverflow.com/questions/6876697/how-to-set-request-encoding-in-tomcat
赞 赏微信赞赏 支付宝赞赏