Springboot升级后@RequestBody封装出现乱码问题的解决

2022-04-26 18:48:12 浏览数 (1)

今天工作上遇到了一个比较奇葩的问题,这个问题有多方面的尴尬的原因产生。经过了一天的胡乱猜想,终于在公司大佬的指导下解决这个问题。由于公司的安全保密要求,不能直接用公司代码演示,我在自己的电脑上,把之前写的一个demo改造了一下,复现这个问题。废话少说,直接上代码。

问题产生:

用户突然发现系统(A),某个功能的列表中的数据中文有乱码了,之前还是好好的。然后把问题提交到开发这里,核查了一下发现,用户看到的数据是B系统推送进来的,这个功能开发后一直没动过,怎么突然不行了呢。

问题分析:

代码没有动过,但是由于近期产品方升级了一次springboot,所以问题可能产生在这里。乱码问题都是字符编码不统一造成的。A系统都是统一默认的UTF8编码,那问题出在B系统推送来的数据上。

然后让B系统的同事检查了下发送的数据,他们说用的数据编码正常,不过请求是用的GBK编码。

What?编码不一样确实会乱码,可是为什么乱码在这个时候出现。那既然这样,我们把request的请求的编码手动设置成UTF8的应该可以了。下面呢,我将分3个阶段,用代码演示一下效果。

刚开始没有问题阶段

demo是用springboot构建的,我忘了没升级前是多少版本了,就找一个比较早的1.5.5.RELEASE做为例子,编码为UTF8。

代码语言:javascript复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.5.RELEASE</version>
    <relativePath/> 
</parent>

代码示例1:

演示HelloController.java

代码语言:javascript复制
@Controller
@RequestMapping("/hello")
public class HelloController {
    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(@RequestBody UserDO user) {
        System.out.println(user.getUserId());
        System.out.println(user.getUserName());
        return user;
    }
}

UserDO.java

代码语言:javascript复制
@Data
public class UserDO {
    private Integer userId;
    private String userName;
}

POSTMAN 工具模拟请求

B系统的同事说请求使用的GBK发的,那就模拟当时的场景

在此请求头上加上gbk的编码,现在我们看结果是正常返回的。同样的代码,我们升级了下springboot到2.3.2.RELEASE。

代码语言:javascript复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/>
</parent>

同样的代码不动,发送请求看下结果:

现在是乱码了,这就是生产上出的问题。按照常规思路解决下,在我们的请求上对这个URL进行过滤,设置一下编码格式就ok,那接着加上过滤器:

代码语言:javascript复制

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/hello/test", filterName = "CharsetFilter")
public class CharsetFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        filterChain.doFilter(servletRequest, servletResponse);//交给下一个过滤器或servlet处理
    }

    public void destroy() {
        /*销毁时调用*/
    }
}

然后在run一下,发现仍然不可以,这个过滤器没有起作用。为什么springboot升级后就不可以了。问题就出在了这里,很明显,springboot升级后,会按照请求头设置的字符编码来对字节流解码,之前并没有这么做。而之前功能是正常的原因其实在B系统上,虽然他们在请求头加上了charset=gbk,但是传过来的是UTF8编码的字符,在springboot没有关注请求头的时候,是按照当前默认的字符解码,这是没问题的,两者都是相同编码。

问题找到了,那就很简单。我们把接收的字符用GBK解码后再用UTF8编码。

代码语言:javascript复制

@Controller
@RequestMapping("/hello")
public class HelloController {

    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(@RequestBody UserDO user) throws UnsupportedEncodingException {
        //转换编码
        String name = new String(user.getUserName().getBytes("GBK"), "UTF-8");
        user.setUserName(name);
        System.out.println(user.getUserId());
        System.out.println(user.getUserName());
        return user;
    }
}

现在看结果:

功能恢复正常了。

你以为这就结束了,完美修复了一个bug,太天真。下面看看我不叫“张三”,我叫“张三丰”,会有怎么样的结果呢?

哇塞,太神奇了,这一定是在逗我。

为什么会这样呢,这是跟字符编码有关系。感兴趣的同学可以搜一下,其实乱码的本质就是:读取二进制的时候采用的编码和最初将字符转换成二进制时的编码不一致。

所以这个问题原因就是:

GBK一个字符2个字节,UTF-8一个字符3个字节,当用GBK去读(解码)UTF-8编码后的内容,当UTF-8字符是奇数个的时候,GBK解码之后会多出一位字节,那只能用'?'字节(63)来替换,所以即使再转码也会出现最后一个中文字符是?的乱码问题

所以解决这个问题很简单了,直接改用inputStream直接读byte,之后再转为utf-8。

代码语言:javascript复制
@Controller
@RequestMapping("/hello")
public class HelloController {
    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(HttpServletRequest request) throws IOException {
        //读取字节流转成字符串
        String content = charReader(request);
        //json字符串转成java对象
        UserDO user = JSON.parseObject(content, UserDO.class);
        return user;
    }
    //字符串读取
    static String charReader(HttpServletRequest request) throws IOException {
        BufferedReader br = request.getReader();
        String str, wholeStr = "";
        while ((str = br.readLine()) != null) {
            wholeStr  = str;
        }
        return wholeStr;
    }
}

至此这个bug才是完全的修改好了,这个尴尬的问题是因为B系统请求头用的说用GBK编码,结果请求体确实UTF8,好比说现在考的中文听力,你给我放英语,然后我在一个字一个字用汉字把英语音洗出来,你说的library,我写的“来不弱瑞”,这怎么乱码呢?

我是马拉松程序员,可不止于代码。

0 人点赞