前置环境#
JDK:8u423
Spring Cloud:Edgware.SR6(可谓是很古老的版本了)
Gateway:Zuul(spring-cloud-starter-netflix-zuul - 1.4.7.RELEASE)
起因#
之前在公司开发的时候,遇到了一个奇怪的问题,上传文件,中文的文件名(指通过 MulipartFile 的 getOriginalFileName () 的方法获取的文件名)会变成 “???” 乱码,起初,我以为是 Java 环境和操作系统环境的编码不正确,经过一番检查,发现都是 UTF-8 的编码,跟怪啊,很怪。
排查#
既然环境和系统都没问题,那就是代码的问题了,经过进一步的追查,发现在单体项目中文件名并没有乱码,只有在经过 Zuul 网关的时候才会出现乱码,在网上也尝试搜索了一下,基本上就给出 2 种解决方案:
- 在网关中加配置 application.yml
zuul:
servlet-path: /
- 在请求前面加 zuul
例如,实际的 api 是 /upload-file 乱码
改成 /zuul/upload-file 解决
但是上面并不能解决大部分情况,比如我使用了方法 1,POST 请求直接无响应,方法 2 会 404,又经过了一段时间的排查,我们将目光对准 “FormHttpMessageConverter”(表单 HTTP 消息转换器)这个类,这个类很可疑。已知上传文件使用的是 Form Data,又可以根据相关文档可以知道,当我通过网关上传文件的时候会经过这个类,当我们阅读这个类的源代码的时候,我发现了一个可疑的地方:
FormHttpMessageConverter 类的作用是可以将 "application/x-www-form-urlencoded" 媒体类型读取并写入为 MultiValueMap<String, String>,也可以将 "multipart/form-data" 和 "multipart/mixed" 媒体类型写入为 MultiValueMap<String, Object>(但不能读取)。
原文 Java doc
这里为什么是 ASCII 编码呢?先粗略的分析一下,ASCII 一共就那百来个字符,哪里会转换中文呢?根据这个原因,我们再继续分析这个源代码,我们知道这个内联类 “MultipartHttpOutputMessage” 会转换请求标头,然后我们又知道 MultipartFile 会把文件名(Original File Name)放在 HTTP 请求标头里面(参考链接);这样我们就说的通了,既然知道了大概原因,我们就可以尝试修复这个问题了。
修复#
这里就简单粗暴的修一下(指修改 Spring Framework 源代码)(逃,其实想顺便编译一下 Spring Framework 的源代码
其实也可以升级依赖(如果不知道升级会带来什么后果的话,不建议直接升级依赖
其实修复这个问题很简单,只需要这样然后那样最后在这样,只需要改这个类的这里
将红框处改为 return name.getBytes("UTF-8"); 即可。
Tips:其实直接看现在的主要分支的代码就可以知道,现在已经默认 UTF-8 编码了。(如图)
其他说明#
编译 Spring Framework#
- JDK 用 1.8
- 编译的时候一定要先读 README.md
- 把 build.gradle 的仓库地址改成如下的样子(因为有些用不了,或者被墙了),要改 2 处,一个是 buildscript 下面的,还有一个 configure (allprojects) 下面的
repositories {
// mavenCentral()
// gradlePluginPortal()
// jcenter()
maven { url 'https://repo.spring.io/snapshot' }
// maven { url "https://repo.spring.io/plugins-release" }
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
//阿里云新库
maven { url "https://maven.aliyun.com/repository/central" }
maven { url "https://maven.aliyun.com/repository/google" }
maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven { url "https://maven.aliyun.com/repository/spring" }
maven { url "https://maven.aliyun.com/repository/spring-plugin" }
maven { url "https://maven.aliyun.com/repository/public" }
maven { url "https://maven.aliyun.com/repository/releases" }
maven { url "https://maven.aliyun.com/repository/snapshots" }
maven { url "https://maven.aliyun.com/repository/grails-core" }
maven { url "https://maven.aliyun.com/repository/mapr-public" }
maven { url "http://repo.springsource.org/plugins-release" }
}
- 正常情况下执行的命令:
./gradlew cleanIdea :spring-oxm:compileTestJava ./gradlew install ./gradlew build -x test
- 如果想上传到私有仓库,可以在项目根目录下的 gradle/publish-maven.gradle 文件中编写如下代码(把代码中的中文换成你需要的内容):
uploadArchives { onlyIf { project.file('build/libs').exists() } repositories { mavenDeployer { customizePom(pom, project) repository(url: "这里写你的发布仓库地址") { authentication(userName: 仓库用户名, password: 仓库密码) } snapshotRepository(url: "这里写你的快照仓库地址") { authentication(userName: 仓库用户名, password: 仓库密码) } pom.version = version } } }
- 然后再执行 ./gradlew uploadArchives 即可