zoukankan      html  css  js  c++  java
  • restTemplate.postForObject上传文件中文乱码(???.xls)

    一、问题描述

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

    中文乱码

    二、话不多说, 解决方案

    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变量
    4.2.8版本FormHttpMessageConverter

    4.0.2版本FormHttpMessageConverter

    那么问题来了, 怎么解决, 升级版本? 想了想升级版本成本太高, 很有可能导致其他问题, 那既然是由于没有multipartCharset变量, 那就看这个变量到底干了啥能解决中文乱码问题, 跟踪代码发现
    4.2.8

    原来是获取文件名时用到了, 如果自定义了multipartCharset字符集, 则按照字符集进行转码, 否则直接返回文件名, 再来看下4.0.2版本getFilename方法怎么写的
    4.0.2

    对, 就这么简单, 没有任何转码, 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, 搞定, 测试如下
    上传成功

    四、总结

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

  • 相关阅读:
    C#开发微信门户及应用(7)-微信多客服功能及开发集成
    C#开发微信门户及应用(6)--微信门户菜单的管理操作
    使用Json.NET来序列化所需的数据
    Winform开发框架里面使用事务操作的原理及介绍
    C#开发微信门户及应用(5)--用户分组信息管理
    C#开发微信门户及应用(4)--关注用户列表及详细信息管理
    基于MVC4+EasyUI的Web开发框架经验总结(3)- 使用Json实体类构建菜单数据
    基于MVC4+EasyUI的Web开发框架经验总结(2)- 使用EasyUI的树控件构建Web界面
    基于MVC4+EasyUI的Web开发框架经验总结(1)-利用jQuery Tags Input 插件显示选择记录
    如何在应用系统中实现数据权限的控制功能
  • 原文地址:https://www.cnblogs.com/wangzaiplus/p/10740470.html
Copyright © 2011-2022 走看看