前置环境

JDK:8u423

Spring Cloud:Edgware.SR6(可谓是很古老的版本了)

Gateway:Zuul(spring-cloud-starter-netflix-zuul - 1.4.7.RELEASE)

起因

之前在公司开发的时候,遇到了一个奇怪的问题,上传文件,中文的文件名(指通过MulipartFile的getOriginalFileName()的方法获取的文件名)会变成“???”乱码,起初,我以为是 Java环境和操作系统环境的编码不正确,经过一番检查,发现都是UTF-8的编码,跟怪啊,很怪。

排查

既然环境和系统都没问题,那就是代码的问题了,经过进一步的追查,发现在单体项目中文件名并没有乱码,只有在经过Zuul网关的时候才会出现乱码,在网上也尝试搜索了一下,基本上就给出2种解决方案:

  1. 在网关中加配置 application.yml

    zuul:
      servlet-path: /
  2. 在请求前面加 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

Spring Framework 4.3.x 源代码

这里为什么是ASCII编码呢?先粗略的分析一下,ASCII一共就那百来个字符,哪里会转换中文呢?根据这个原因,我们再继续分析这个源代码,我们知道这个内联类“MultipartHttpOutputMessage”会转换请求标头,然后我们又知道MultipartFile会把文件名(Original File Name)放在HTTP请求标头里面(参考链接);这样我们就说的通了,既然知道了大概原因,我们就可以尝试修复这个问题了。

修复

这里就简单粗暴的修一下(指修改Spring Framework源代码)(逃,其实想顺便编译一下Spring Framework的源代码

其实也可以升级依赖(如果不知道升级会带来什么后果的话,不建议直接升级依赖

其实修复这个问题很简单,只需要这样然后那样最后在这样,只需要改这个类的这里

Spring Framework 4.3.x 源代码

将红框处改为 return name.getBytes("UTF-8"); 即可。

Tips:其实直接看现在的主要分支的代码就可以知道,现在已经默认UTF-8编码了。(如图)
Spring Framework 主要分支源代码

其他说明

编译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 即可

摸🐟从未停止,努力从未开始。