SpringBoot 同时接收表单数据(后端以实体类接收)和文件

问题背景

请求体

我们都知道,POST 请求可以经参数放入请求体中:

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;;

@RestController
@RequiredArgsConstructor
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @RequestBody @Valid WhiteListReq) {
        // 功能正常
    }

实体类如下:

package com.ageovb.beans.vo.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.ageovb.common.annotation.CheckTimeInterval;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

/**
 * 白名单表(WhiteList)请求类
 *
 * @author : ageovb
 * @date : 2022-03-09 10:47:39
 */
@Data
@ApiModel(value = "白名单表请求类")
// 自定义的时区区间校验注解
@CheckTimeInterval(beginTime = "startDate", endTime = "endDate")
public class WhiteListReq {
    @ApiModelProperty(value = "白名单组别 ID", required = true, example = "1002")
    @NotNull(message = "白名单组别 ID 不能为空")
    private Long groupId;

    @ApiModelProperty(value = "品牌", required = true, example = "GE")
    @NotBlank(message = "品牌不能为空")
    @Length(max = 32, message = "品牌长度上限为 32")
    private String brand;

    @ApiModelProperty(value = "组织代码", required = true, example = "D000002304")
    @NotBlank(message = "组织代码不能为空")
    @Length(max = 20, message = "组织代码长度上限为 20")
    private String orgCode;

    @ApiModelProperty(value = "开始日期", required = true, example = "2020-10-01 00:00:00")
    @NotNull(message = "开始日期不能为空")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startDate;

    @ApiModelProperty(value = "结束日期", required = true, example = "2023-10-01 00:00:00")
    @NotNull(message = "结束日期不能为空")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endDate;

    @ApiModelProperty(value = "用户 ID", required = true, example = "1B3FB2DC-BFB8-4F61-8E9A-44983685EAB6")
    @NotBlank(message = "用户 ID 不能为空")
    @Length(max = 50, message = "用户 ID 长度上限为 50")
    private String userId;

    @ApiModelProperty(value = "用户账号", required = true, example = "2283164982@qq.com")
    @NotBlank(message = "用户账号不能为空")
    @Length(max = 200, message = "用户账号长度上限为 200")
    private String userAccount;
}

这样的话,调用接口的时候 SpringBoot 会自动将请求体中的 JSON 映射到实体 WhiteListReq 中。

上传文件

需要上传文件的时候,添加一个 MultipartFile 类型的请求参数即可:

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;;

@RestController
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @RequestParam("file") MultipartFile file) {
        // 代码略
    }

同时使用请求体和上传文件

但是,当我们同时需要使用请求体和上传文件时,运行程序就会报错:

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;;

@RestController
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @RequestBody @Valid WhiteListReq,
        @RequestParam("file") MultipartFile file) {
        // 代码略
    }

问题分析

SpringBoot 是使用 HttpMessageConverter 来转换数据的:

  • 请求体需要将 Content-Type 设置为 application/json;
  • 文件需要将 Content-Type 设置为 multipart/form-data。

但是同时存在上述两种情况时,就出问题了,HTTP 请求的 Content-Type 不可能既是 application/json 又是 multipart/form-data。

解决方案

方案一:将请求实体拆为请求参数

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;;

@RestController
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @RequestParam("file") MultipartFile file,
        @RequestParam("groupId") Long groupId, @RequestParam("brand") String brand) {
        // 代码略
    }

参数如果太多,写起来不方便,而且如果需要校验字段,也不方便。

方案二:使用 @RequestPart

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;;

@RestController
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @RequestPart @Valid WhiteListReq,
        @RequestPart MultipartFile file) {
        // 代码略
    }

但是这种方式,需要将 WhiteListReq 放入 JSON 文件中,作为一个文件上传,一般不会使用。

方案三:去掉 @RequestBody

package com.aveovb.api.controller;

import com.aveovb.beans.vo.request.WhiteListReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;;

@RestController
@Validated
@Api(tags = "白名单")
@RequestMapping(value = "/white/list")
public class WhiteListController {
    @ApiOperation(value = "批量导入", notes = "添加数据库校验功能")
    @PostMapping(value = "/upload")
    public Result<Void> batchInsert(HttpServletResponse response, @Valid WhiteListReq,
        @RequestParam("file") MultipartFile file) {
        // 代码略
    }

这种方式最简单,也不改变使用方式,推荐使用。

posted @ 2022-09-06 17:37  ageovb  阅读(3655)  评论(0编辑  收藏  举报