前置環境#
JDK:8u423
Spring Cloud:Edgware.SR6(非常古いバージョンです)
Gateway:Zuul(spring-cloud-starter-netflix-zuul - 1.4.7.RELEASE)
起因#
以前、会社で開発していたときに、奇妙な問題に直面しました。ファイルをアップロードすると、中国語のファイル名(MultipartFile の 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 は約 100 文字しかなく、中国語に変換することはできません。この理由に基づいて、ソースコードをさらに分析すると、インラインクラス「MultipartHttpOutputMessage」がリクエストヘッダーを変換することがわかります。また、MultipartFile はファイル名(元のファイル名)を HTTP リクエストヘッダーに格納することが知られています(参考リンク)。これで説明がつきます。原因がわかったので、この問題を修正することができます。
修復#
ここでは簡単に修正します(Spring Framework のソースコードを修正することを指します)(逃げ、実際には Spring Framework のソースコードをコンパイルしたいだけです)。
実際には依存関係をアップグレードすることもできます(アップグレードが何をもたらすか分からない場合は、直接アップグレードすることはお勧めしません)。
実際、この問題を修正するのは非常に簡単です。こうしてああして最後にこうするだけ、このクラスのここを変更するだけです。
赤枠の部分を return name.getBytes("UTF-8"); に変更するだけです。
ヒント:実際には現在の主要ブランチのコードを見れば、すでにデフォルトで UTF-8 エンコーディングになっていることがわかります。(図のように)
その他の説明#
Spring Framework のコンパイル#
- JDK は 1.8 を使用
- コンパイルする前に必ず README.md を読むこと
- build.gradle のリポジトリアドレスを以下のように変更すること(いくつかは使用できないか、またはブロックされているため)、2 箇所を変更する必要があります。1 つは buildscript の下、もう 1 つは 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 を実行すれば完了です。