SpringBoot Multipart表单中文乱码解决方案

 更新时间:2026年06月15日 09:39:24   作者:霸道流氓气质  
这篇文章主要介绍了在使用multipart/form-data提交表单时,中文参数存入数据库后变成乱码的问题,并详细分析了问题产生的原因及解决方法,涵盖了HTTP请求编码传递链路、Tomcat解析请求体的默认编码、Spring的CharacterEncodingFilter解决方案等内容,需要的朋友可以参考下

一、问题现象

使用 multipart/form-data 提交表单时,文件上传正常,但文本字段中的中文存入数据库后变成乱码(如"张三"变成"å¼ ä¸‰“或”???")。

典型触发场景:

  • 前端通过 FormData 同时上传文件和中文参数
  • 后端用 @RequestParam 接收文本字段
  • 数据库连接已经配置了 UTF-8,表字段也是 utf8mb4

二、底层原理

2.1 HTTP 请求中编码的传递链路

浏览器/客户端                    Tomcat 容器               Spring MVC              业务代码
     │                              │                        │                      │
     │── HTTP请求 ──→               │                        │                      │
     │  Content-Type:               │                        │                      │
     │  multipart/form-data;        │                        │                      │
     │  boundary=----xxx            │                        │                      │
     │                              │── 解析请求体 ──→        │                      │
     │                              │  request.getParameter() │                      │
     │                              │  使用什么编码?          │                      │
     │                              │                        │── 绑定参数到方法 ──→   │
     │                              │                        │  @RequestParam        │
     │                              │                        │                      │── 使用参数

2.2 问题出在哪一步

关键在于 Tomcat 解析请求体时使用的字符编码

  1. 客户端(浏览器/Postman)以 UTF-8 编码中文字符,写入请求体
  2. Tomcat 收到请求,调用 request.getCharacterEncoding() 获取编码
  3. 如果 Content-Type 头中没有 charset 声明(multipart 请求通常不带),返回 null
  4. 返回 null 时,Tomcat 默认使用 ISO-8859-1 解码请求体
  5. UTF-8 编码的字节用 ISO-8859-1 解码 → 乱码

2.3 为什么 JSON 请求不会乱码

application/json 请求通常带有 Content-Type: application/json; charset=UTF-8,明确声明了编码。而 multipart/form-data 的 Content-Type 格式是:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

没有 charset 声明,所以 Tomcat 不知道该用什么编码,退回到默认的 ISO-8859-1。

2.4 编码转换过程图解

原始文本:"张三"

UTF-8 编码字节:     E5 BC A0  E4 B8 89  (6个字节)

用 ISO-8859-1 解码这6个字节:
  E5 → å
  BC → ¼(实际显示可能不同)
  A0 →  (不可见)
  E4 → ä
  B8 → ¸
  89 → ‰

结果:乱码字符串 "å¼ ä¸‰"

三、Servlet 规范中的编码机制

3.1 request.setCharacterEncoding()

Servlet 规范定义了 request.setCharacterEncoding(String encoding) 方法,必须在第一次读取参数之前调用才有效。

// 正确:在读取参数前设置
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name"); // 用 UTF-8 解码
// 错误:在读取参数后设置(无效)
String name = request.getParameter("name"); // 已经用默认编码解码了
request.setCharacterEncoding("UTF-8");      // 为时已晚

3.2 CharacterEncodingFilter 的工作原理

Spring 提供的 CharacterEncodingFilter 就是在 Filter 链的最前面调用 request.setCharacterEncoding(),确保在任何参数读取之前设置编码。

源码核心逻辑(简化):

public class CharacterEncodingFilter extends OncePerRequestFilter {

    private String encoding;
    private boolean forceRequestEncoding = false;
    private boolean forceResponseEncoding = false;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
            HttpServletResponse response, FilterChain filterChain) {

        // 如果请求没有设置编码,或者强制覆盖
        if (this.encoding != null) {
            if (this.forceRequestEncoding || request.getCharacterEncoding() == null) {
                request.setCharacterEncoding(this.encoding);
            }
            if (this.forceResponseEncoding) {
                response.setCharacterEncoding(this.encoding);
            }
        }

        filterChain.doFilter(request, response);
    }
}

3.3 为什么 Filter 顺序很重要

请求 → Filter1(编码) → Filter2(安全) → Filter3(日志) → DispatcherServlet → Controller

如果编码Filter不在第一个位置,安全Filter可能先读取了参数,
此时编码已经用默认ISO-8859-1解码了,后面再设置UTF-8也没用。

所以要设置 registration.setOrder(Integer.MIN_VALUE) 确保最先执行。

四、解决方案对比

方案实现方式作用范围推荐度
yml 配置 spring.servlet.encoding配置文件全局所有请求⭐⭐⭐⭐⭐
CharacterEncodingFilter + 指定 URLJava 配置类指定接口⭐⭐⭐⭐
Controller 中手动转码业务代码单个参数⭐⭐
前端显式设置 charset前端代码全局⭐⭐⭐

4.1 yml 全局配置

spring:
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

force: true 等同于 forceRequestEncoding=true + forceResponseEncoding=true

4.2 Java 配置(指定接口)

@Bean
public FilterRegistrationBean<Filter> encodingFilter() {
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("UTF-8");
    filter.setForceRequestEncoding(true);
    filter.setForceResponseEncoding(true);

    FilterRegistrationBean<Filter> reg = new FilterRegistrationBean<>();
    reg.setFilter(filter);
    reg.addUrlPatterns("/api/xxx/import");  // 只对特定接口
    reg.setOrder(Integer.MIN_VALUE);
    return reg;
}

4.3 Controller 手动转码

// 应急方案,不推荐长期使用
String name = new String(
    rawName.getBytes(StandardCharsets.ISO_8859_1), 
    StandardCharsets.UTF_8
);

4.4 前端显式设置

// 方式一:在 FormData 的 Blob 中指定 type
const formData = new FormData();
formData.append('file', file);
formData.append('name', new Blob([JSON.stringify('张三')], { type: 'text/plain; charset=UTF-8' }));

// 方式二:使用 fetch 时不需要额外设置,浏览器默认用 UTF-8 编码 FormData
fetch('/api/import', { method: 'POST', body: formData });

五、完整示例

5.1 场景描述

一个文件上传接口,同时接收文件和中文备注信息,确保中文不乱码。

5.2 Controller

package com.example.controller;

import com.example.dto.UploadResultDto;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * 文件上传接口.
 */
@RestController
@RequestMapping("/api/file")
public class FileUploadController {

  @PostMapping("/upload")
  public UploadResultDto upload(
      @RequestParam("file") MultipartFile file,
      @RequestParam("remark") String remark,
      @RequestParam("uploaderName") String uploaderName) {

    // 此时 remark 和 uploaderName 已经是正确的 UTF-8 字符串
    UploadResultDto result = new UploadResultDto();
    result.setFileName(file.getOriginalFilename());
    result.setFileSize(file.getSize());
    result.setRemark(remark);
    result.setUploaderName(uploaderName);
    return result;
  }
}

5.3 DTO

package com.example.dto;

import lombok.Data;

@Data
public class UploadResultDto {
  private String fileName;
  private Long fileSize;
  private String remark;
  private String uploaderName;
}

5.4 编码过滤器配置

package com.example.config;

import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * 字符编码过滤器,解决 multipart/form-data 中文乱码.
 */
@Configuration
public class EncodingConfig {

  @Bean
  public FilterRegistrationBean<Filter> uploadEncodingFilter() {
    // 1. 创建编码过滤器
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("UTF-8");
    filter.setForceRequestEncoding(true);   // 强制请求编码
    filter.setForceResponseEncoding(true);  // 强制响应编码

    // 2. 注册到 Servlet 容器
    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
    registration.setFilter(filter);

    // 3. 指定生效的 URL(只对上传接口)
    registration.addUrlPatterns("/api/file/upload");

    // 4. 设置最高优先级,确保在所有其他 Filter 之前执行
    registration.setOrder(Integer.MIN_VALUE);

    return registration;
  }
}

5.5 测试验证

cURL 测试

curl -X POST http://localhost:8080/api/file/upload \
  -F "file=@测试文件.xlsx" \
  -F "remark=这是一条中文备注" \
  -F "uploaderName=张三"

预期返回

{
  "fileName": "测试文件.xlsx",
  "fileSize": 12345,
  "remark": "这是一条中文备注",
  "uploaderName": "张三"
}

不加 Filter 时的错误返回

{
  "fileName": "测试文件.xlsx",
  "fileSize": 12345,
  "remark": "è¿æ¯ä¸æ¡ä¸­æå¤æ³¨",
  "uploaderName": "å¼ ä¸"
}

六、排查清单

如果配置了 Filter 仍然乱码,按以下顺序排查:

检查点排查方法
1. Filter 是否生效在 Filter 中加日志 log.info("encoding filter executed")
2. Filter 顺序是否正确确认 order 是否为最小值,没有其他 Filter 先读取了参数
3. URL Pattern 是否匹配打印请求路径,确认和 addUrlPatterns 一致
4. 数据库连接编码确认 JDBC URL 有 characterEncoding=UTF-8
5. 数据库表/列编码SHOW CREATE TABLE xxx 确认是 utf8mb4
6. 客户端发送编码用 Wireshark 或 Charles 抓包,确认请求体是 UTF-8 字节
7. 响应编码确认 Response 的 Content-Type 包含 charset=UTF-8

七、总结

问题本质:
  multipart/form-data 请求的 Content-Type 不携带 charset
  → Tomcat 用默认 ISO-8859-1 解码
  → UTF-8 字节被错误解码为乱码

解决本质:
  在 Tomcat 读取参数之前,告诉它"请用 UTF-8 解码"
  → CharacterEncodingFilter 在 Filter 链最前面调用 request.setCharacterEncoding("UTF-8")
  → 后续所有参数读取都使用 UTF-8

以上就是SpringBoot Multipart表单中文乱码解决方案的详细内容,更多关于SpringBoot Multipart表单中文乱码的资料请关注脚本之家其它相关文章!

相关文章

  • Java通过MyBatis框架对MySQL数据进行增删查改的基本方法

    Java通过MyBatis框架对MySQL数据进行增删查改的基本方法

    MyBatis框架由Java的JDBC API进一步封装而来,在操作数据库方面效果拔群,接下来我们就一起来看看Java通过MyBatis框架对MySQL数据进行增删查改的基本方法:
    2016-06-06
  • Java双向链表按照顺序添加节点的方法实例

    Java双向链表按照顺序添加节点的方法实例

    这篇文章主要给大家介绍了关于Java双向链表按照顺序添加节点的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 手把手教你在eclipse创建第一个java web项目并运行

    手把手教你在eclipse创建第一个java web项目并运行

    Eclipse是用来做开发的自由集成开发环境,这也是很多java程序员会使用的开发环境,所以可以使用eclipse创建javaweb项目,下面这篇文章主要给大家介绍了关于如何在eclipse创建第一个java web项目并运行的相关资料,需要的朋友可以参考下
    2023-02-02
  • springboot应用服务启动事件的监听实现

    springboot应用服务启动事件的监听实现

    本文主要介绍了springboot应用服务启动事件的监听实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • MongoDB整合Spring实例详细讲解(含代码)

    MongoDB整合Spring实例详细讲解(含代码)

    这篇文章主要介绍了MongoDB整合Spring实例详细讲解(含代码),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Spring Cache 集成 Caffeine实现项目缓存的示例

    Spring Cache 集成 Caffeine实现项目缓存的示例

    本文主要介绍了Spring Cache 集成 Caffeine实现项目缓存的示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • 10个Java文件操作必备技巧分享

    10个Java文件操作必备技巧分享

    在我们日常的开发中,文件操作是一个非常重要的主题。文件读写、文件复制、任意位置读写、缓存等技巧都是我们必须要掌握的。本文为大家整理了10个实用的文件操作技巧,希望对大家有所帮助
    2023-04-04
  • Java中char数组(字符数组)与字符串String类型的转换方法

    Java中char数组(字符数组)与字符串String类型的转换方法

    这篇文章主要介绍了Java中char数组(字符数组)与字符串String类型的转换方法,涉及Java中toCharArray与valueOf方法的使用技巧,需要的朋友可以参考下
    2015-12-12
  • 如何将Set直接转成数组

    如何将Set直接转成数组

    这篇文章主要介绍了如何将Set直接转成数组,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • java 替换docx文件中的字符串方法实现

    java 替换docx文件中的字符串方法实现

    这篇文章主要介绍了java 替换docx文件中的字符串方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02

最新评论