在开发接口中,遇到了需要同时接收参数和文件的情况,可以有多种方式实现文件+参数的接收,这里基于spring boot 3 + vue 3 + axios,做一个简单的代码演示。
参数较少时,比较方便,直接参数接受即可
@RestController
@RequestMapping("/param")
@Validated
public class FileParamController extends BaseController {
/**
* 简单参数
*
* @param test1
* @param test2
* @param file
* @return
*/
@PostMapping("/file-simple-param")
public Map<String, Object> fileAndSimpleParam(@RequestParam @NotBlank String test1,
@RequestParam @NotBlank String test2,
@RequestParam MultipartFile file) {
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("test1", test1);
objectMap.put("test2", test2);
objectMap.put("fileName", file.getOriginalFilename());
return objectMap;
}
}
const fileAndSimpleParamFuc = (methodParam: { file: string | Blob }) => {
let formData = new FormData()
formData.append('test1', 'test1')
formData.append('test2', 'test2')
formData.append('file', methodParam.file)
fileAndSimpleParam(formData).then(resp => {
console.log(resp)
})
}
fileAndSimpleParam 为封装的api请求方法,可查看下文的 param.ts
将各个参数封装到一个JavaBean中接收,同时接收文件参数,此时JavaBean参数不加任何注解,不支持接收List参数
@RestController
@RequestMapping("/param")
@Validated
public class FileParamController extends BaseController {
/**
* 简单参数转JavaBean接收,不支持如:LIst 这样的属性
*
* @param bean
* @param file
* @return
*/
@PostMapping("/file-simple-bean")
public SimpleBean fileAndSimpleJavaBean(@Validated SimpleBean bean, @RequestParam MultipartFile file) {
bean.setFileName(file.getOriginalFilename());
return bean;
}
}
@Data
public class SimpleBean {
@NotBlank
private String test1;
@NotBlank
private String test2;
@Null(message = "The fileName is not support to be used")
private String fileName;
}
const fileAndSimpleJavaBeanFuc = (methodParam: { file: string | Blob }) => {
let formData = new FormData()
formData.append('test1', 'test1')
formData.append('test2', 'test2')
formData.append('file', methodParam.file)
fileAndSimpleJavaBean(formData).then(resp => {
console.log(resp)
})
}
fileAndSimpleJavaBean 为封装的api请求方法,可查看下文的 param.ts
在这种接收方式中,使用String来接收参数,在使用工具(如fastjson)手动转为JavaBean,支持接收List参数,但是需要自行校验参数是否满足要求
@RestController
@RequestMapping("/param")
@Validated
public class FileParamController extends BaseController {
/**
* 参数转字符串接收,支持复杂参数属性,如:List
*
* @param bean
* @param file
* @return
*/
@PostMapping("/file-and-json")
public SimpleBean fileAndJsonJavaBean(String bean, @RequestParam MultipartFile file) {
SimpleBean simpleBean = JSON.parseObject(bean, SimpleBean.class);
//自定义参数校验
List<String> valid = ValidatorUtils.validFast(simpleBean);
if (!valid.isEmpty()) {
throw new FailException(String.join(",", valid));
}
simpleBean.setFileName(file.getOriginalFilename());
return simpleBean;
}
}
@Data
public class SimpleBean {
@NotBlank
private String test1;
@NotBlank
private String test2;
private List<ParamBO> params;
@Null(message = "The fileName is not support to be used")
private String fileName;
}
借助spring的参数校验做自定义调用
public class ValidatorUtils {
private static final Validator VALIDATOR_FAST = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
private static final Validator VALIDATOR_ALL = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
private ValidatorUtils(){}
/**
* 参数校验,遇到第一个不合法的字段直接返回不合法字段,后续字段不再校验
*
* @param object
* @return
* @param
*/
public static <T> List<String> validFast(T object, Class<?>... groups){
return valid(VALIDATOR_FAST, object, groups);
}
/**
* 校验所有字段并返回不合法字段
*
* @param object
* @return
* @param
*/
public static <T> List<String> validAll(T object, Class<?>... groups){
return valid(VALIDATOR_ALL, object, groups);
}
private static <T> List<String> valid(Validator validator, T object, Class<?>... groups){
Set<ConstraintViolation<T>> errors = validator.validate(object, groups);
return errors.stream().map(ConstraintViolation::getMessage).toList();
}
}
const fileAndJsonStringToJavaBeanFuc = (methodParam: { file: string | Blob }) => {
let paramList = []
for (let i = 0; i < 3; i++) {
let param = { username: '参数' + i, data: '参数值' + i }
paramList.push(param)
}
let data = {
test1: 'test1',
test2: 'test2',
params: paramList
}
let formData = new FormData()
formData.append('bean', JSON.stringify(data))
formData.append('file', methodParam.file)
fileAndJsonStringToJavaBean(formData).then(resp => {
console.log(resp)
})
}
fileAndJsonStringToJavaBean 为封装的api请求方法,可查看下文的 param.ts
将文件作为JavaBean的属性,随参数一起接收
@RestController
@RequestMapping("/param")
@Validated
public class FileParamController extends BaseController {
/**
* 文件放入JavaBean一起提交
*
* @param bean
* @return
*/
@PostMapping(value = "/file-in-bean")
public String fileInJavaBean(@Validated FileInBeanBO bean) {
bean.setFileName(bean.getFile().getOriginalFilename());
return bean.getFile().getOriginalFilename();
}
}
@Data
public class SimpleBean {
@NotBlank
private String test1;
@NotBlank
private String test2;
@Null(message = "The fileName is not support to be used")
private String fileName;
private MultipartFile file;
}
此处需要特别注意,需要修改Content-Type
const fileInJavaBeanFuc = (methodParam: { file: string | Blob }) => {
let formData = new FormData()
formData.append('test1', 'test1')
formData.append('test2', 'test2')
formData.append('file', methodParam.file)
fileInJavaBean(formData).then(resp => {
console.log(resp)
})
}
fileInJavaBean 为封装的api请求方法,可查看下文的 param.ts
使用@RequestPart注解,实现参数与文件分别接收,应该来说是最优解,优先推荐
@RestController
@RequestMapping("/param")
@Validated
public class FileParamController extends BaseController {
/**
* 参数与文件分开接收,支持复杂参数属性,如:List
*
* @param bean
* @param file
* @return
*/
@PostMapping("/file-and-bean")
public FileBeanBO fileAndJavaBean(@RequestPart @Validated FileBeanBO bean, @RequestPart MultipartFile[] file) {
String collect = Arrays.stream(file).map(MultipartFile::getOriginalFilename).collect(Collectors.joining(","));
bean.setFileName(collect);
return bean;
}
}
@Data
public class SimpleBean {
@NotBlank
private String username;
@Valid
@NotEmpty
private List<ParamBO> params;
@Null(message = "The fileName is not support to be used")
private String fileName;
@Data
public class ParamBO {
private String username;
private Object data;
}
}
此处需要特别注意,需要手动指定JavaBean的类型 Content-Type
const fileAndJavaBeanFuc = (methodParam: { file: string | Blob }) => {
let formData = new FormData()
let paramList = []
for (let i = 0; i < 3; i++) {
let param = { username: '参数' + i, data: '参数值' + i }
paramList.push(param)
}
let data = {
username: '张三',
params: paramList
}
formData.append('bean', new Blob([JSON.stringify(data)], { type: 'application/json' }))
formData.append('file', methodParam.file)
fileAndJavaBean(formData).then(resp => {
console.log(resp)
})
}
fileAndJavaBean 为封装的api请求方法,可查看下文的 param.ts
export const fileAndSimpleParam = (params: any) => {
return axios.post('/param/file-simple-param', params)
}
export const fileAndSimpleJavaBean = (params: any) => {
return axios.post('/param/file-simple-bean', params)
}
export const fileAndJsonStringToJavaBean = (params: any) => {
return axios.post('/param/file-and-json', params)
}
export const fileInJavaBean = (params: any) => {
return axios.post('/param/file-in-bean', params, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
export const fileAndJavaBean = (params: any) => {
return axios.post('/param/file-and-bean', params)
}
以上5种方式,都能实现参数与文件同时接受,至于使用哪一种,看具体需求,参数太多的时候,可以优先考虑@RequestPart注解。