前置環境#
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 即可