一、问题描述

项目中, 使用restTemplate上传文件时, 文件名中文乱码, 一串问号, 源文件名为: 测试中文乱码哦哦哦.zip, 通过restTemplate.postForObject调用接口, 发现文件名变成了: ?????????.zip, 上传失败
restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第1张

restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第2张

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

二、话不多说, 解决方案

1、新建MyFormHttpMessageConverter类

package com.cn.pinliang.admin.Configure;

import javax.mail.internet.MimeUtility;
import org.springframework.http.converter.FormHttpMessageConverter;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class MyFormHttpMessageConverter extends FormHttpMessageConverter {

    @Override
    protected String getFilename(Object part) {
        String filename = super.getFilename(part);
        Charset multipartCharset = StandardCharsets.UTF_8;
        return MimeDelegate.encode(filename, multipartCharset.name());
    }

    private static class MimeDelegate {
        private MimeDelegate() {
        }

        public static String encode(String value, String charset) {
            try {
                return MimeUtility.encodeText(value, charset, (String) null);
            } catch (UnsupportedEncodingException var3) {
                throw new IllegalStateException(var3);
            }
        }
    }
}

2、新建RestTemplateConf类

package com.cn.pinliang.admin.Configure;

import org.springframework.http.MediaType;
import org.springframework.http.converter.*;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class RestTemplateConf {

    public static RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        messageConverters.add(new MappingJackson2HttpMessageConverter());

        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        stringHttpMessageConverter.setWriteAcceptCharset(true);

        List<MediaType> mediaTypeList = new ArrayList<>();
        mediaTypeList.add(MediaType.ALL);

        for (int i = 0; i < messageConverters.size(); i++) {
            HttpMessageConverter<?> converter = messageConverters.get(i);
            if (converter instanceof StringHttpMessageConverter) {
                messageConverters.remove(i);
                messageConverters.add(i, stringHttpMessageConverter);
            }
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                try {
                    ((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (converter instanceof FormHttpMessageConverter) {
                MyFormHttpMessageConverter myConverter = new MyFormHttpMessageConverter();
                myConverter.setCharset(StandardCharsets.UTF_8);

                messageConverters.remove(i);
                messageConverters.add(i, myConverter);
            }
        }
        return restTemplate;
    }

}

3、使用

RestTemplate restTemplate = RestTemplateConf.restTemplate();
restTemplate.postForObject... 巴拉巴拉

三、分析

本来之前遇到过同样的问题, 是springboot项目, spring-web版本为4.2.8, 解决方案更简单, 直接在Application启动类中注入restTemplate bean

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        stringHttpMessageConverter.setWriteAcceptCharset(true);

        List<MediaType> mediaTypeList = new ArrayList<>();
        mediaTypeList.add(MediaType.ALL);

        for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
            HttpMessageConverter<?> converter = restTemplate.getMessageConverters().get(i);
            if (converter instanceof StringHttpMessageConverter) {
                restTemplate.getMessageConverters().remove(i);
                restTemplate.getMessageConverters().add(i, stringHttpMessageConverter);
            }
            if(converter instanceof MappingJackson2HttpMessageConverter){
                try{
                    ((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if (converter instanceof FormHttpMessageConverter) {
                ((FormHttpMessageConverter) converter).setCharset(StandardCharsets.UTF_8);
                ((FormHttpMessageConverter) converter).setMultipartCharset(StandardCharsets.UTF_8);
            }
        }
        return restTemplate;
    }

重点是这一行代码

((FormHttpMessageConverter) converter).setMultipartCharset(StandardCharsets.UTF_8);

设置MultipartCharset字符集为UTF-8, 搞定

但是, 现在项目不是springboot项目, 直接copy这段代码发现报错, 没有setMultipartCharset方法, 对比发现原因是spring-web版本不同, 现在是4.0.2, 没有multipartCharset变量
restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第3张

restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第4张

那么问题来了, 怎么解决, 升级版本? 想了想升级版本成本太高, 很有可能导致其他问题, 那既然是由于没有multipartCharset变量, 那就看这个变量到底干了啥能解决中文乱码问题, 跟踪代码发现
restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第5张

原来是获取文件名时用到了, 如果自定义了multipartCharset字符集, 则按照字符集进行转码, 否则直接返回文件名, 再来看下4.0.2版本getFilename方法怎么写的
restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第6张

对, 就这么简单, 没有任何转码, OK, 既然我们无法通过构造参数指定编码从而对文件名进行转码, 那为什么不重写getFilename方法呢, 直接在方法里面指定字符集为UTF-8不就行了?

试一下, 新建MyFormHttpMessageConverter继承FormHttpMessageConverter, 重写getFilename

@Override
    protected String getFilename(Object part) {
        String filename = super.getFilename(part);
        Charset multipartCharset = StandardCharsets.UTF_8;
        return MimeDelegate.encode(filename, multipartCharset.name());
    }

这一步搞定, 现在定义restTemplate, 最重要的是这一段代码

            if (converter instanceof FormHttpMessageConverter) {
                MyFormHttpMessageConverter myConverter = new MyFormHttpMessageConverter();
                myConverter.setCharset(StandardCharsets.UTF_8);

                messageConverters.remove(i);
                messageConverters.add(i, myConverter);
            }

将原来的FormHttpMessageConverter替换为上面新建的MyFormHttpMessageConverter, 搞定, 测试如下
restTemplate.postForObject上传文件中文乱码(???.xls) 随笔 第7张

四、总结

解决bug是一个不断摸索的过程, 尤其是碰到版本类似的问题, 很麻烦, 需要静下心来定位问题, 分析问题, 找出解决方案, 然后不断测试, 最后搞定, 本文没有对RestTemplate的HttpMessageConverter里面的各种转换器进行分析(我也不会, 哈哈), 更多的是一种解决问题的思路, 希望对小伙伴有一点帮助

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄