Springboot上传踩坑
Springboot上传踩坑
原因是公司部分架构换成Springboot,为了兼容老项目的业务逻辑,就把上传模块做迁移处理,结果版本兼容踩了好多坑.
SpringBoot 上传文件
配置application.yml文件
spring:
# 文件上传
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 1024MB
单文件上传
@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "上传失败,请选择文件";
}
String fileName = file.getOriginalFilename();
String filePath = "D:/upload/";
File dest = new File(filePath + fileName);
try {
file.transferTo(dest);
log.info("上传成功");
return "上传成功";
} catch (IOException e) {
log.error(e.toString(), e);
}
return "上传失败!";
}
这种情况是只有一个表单,选择文件, form 的 enctype 为 multipart/form-data
多文件上传
@PostMapping(value = "upload", consumes = "multipart/form-data")
@ResponseBody
public UnifyInfo uploadTest(@RequestParam("file") MultipartFile[] files) throws Exception {
UnifyInfo unifyInfo = new UnifyInfo();
File filePath = new File("D:/upload/");
if (!filePath.exists()) {
filePath.mkdirs();
}
if (files == null) {
unifyInfo.setCode(ErrorCode.FAILURE.getCode());
return unifyInfo;
}
for (MultipartFile file : files) {
if (file != null) {
try {
file.transferTo(new File(filePath + File.separator + file.getOriginalFilename()));
} catch (IOException e) {
e.printStackTrace();
unifyInfo.setCode(ErrorCode.FAILURE.getCode());
return unifyInfo;
}
}
}
unifyInfo.setCode(ErrorCode.SUCCESS.getCode());
return unifyInfo;
}
项目中使用的是Apache的fileupload组件上传的,想着和Springboot的上传做兼容的,因为参数MultipartFile也有对应FormField.结果使用 ServletFileUpload上传文件失败,upload.parseRequest(request)为空
尝试强转 CommonsMultipartFile 来获取 FileItem ,因为 SpringBoot 的 MultipartFile 的默认实现是 StandardMultipartFile ,不满足一些业务使用的判断规则.
StandardMultipartFile 不能转换为 CommonsMultipartFile 的问题
使用此方式获取
需要配置下Bean
/**
* @author Allen
* @Date: 2019/05/22 14:50
* @Description: BeanConfig
* @Version 1.0
**/
@Configuration
public class BeanConfig {
/**
* Description : 配置上传控件为CommonsMultipartFile解析器
* @date 2019/5/22 14:53
* @param
* @return org.springframework.web.multipart.commons.CommonsMultipartResolver
*/
@Bean(name = "multipartResolver")
public CommonsMultipartResolver getCommonsMultipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1073741824L);
multipartResolver.setMaxInMemorySize(1048576);
return multipartResolver;
}
}
CommonsMultipartFile cmf = (CommonsMultipartFile) file;
FileItem item = cmf.getFileItem();
但是此 FileItem 查看源码虽然也是使用的Apache的组件里的 FileItem 类,可是转换过来后,发现字段都没有设置上, FieldName 对应不了表单传的值, isFormField 明明是表单也为false, String也都获取不到.
SpringBoot获取request的几种方式
- 通过静态方法获取
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
- 通过参数直接获取,只要在你的方法上加上参数,Springboot就会帮你绑定,你可以直接使用。如果你的方法有其他参数,把这两个加到后面即可。
@GetMapping(value = "")
public String center(HttpServletRequest request,HttpServletResponse response) {
//...
}
- 注入到类,这样就不用每个方法都写了
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
调试时发现 request 中有值,遂通过 request 来获取 MultipartFile 值
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
List<MultipartFile> fileList = multipartRequest.getFiles("file");
// 简易写法
List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
行不通,最后还是想着使用原来的上传方式吧.但是要面对前面的 parseRequest 转换为空的问题 ,即 List<FileItem> items = upload.parseRequest(request);
得到的 size=0 ,也就是根本没有得到文件数据。
网上一般有两种说法
(1) 原因在于 Spring 的配置文件中已经配置了MultipartResolver ,导致文件上传请求已经被预处理过了,所以此处解析文件列表为空,对应的做法是删除该段配置.
(2) 认为是structs的过滤器导致请求已被预处理,所以也要修改对应过滤器的配置.
还有说查看源代码说是框架对 request 已经进行过预转了,就是下面的.
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
但是,我自己查看源码发现不一样,是正常的封装返回
FileItemIterator iter = getItemIterator(ctx);
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
final FileItemStream item = iter.next();
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(format("Processing of %s request failed. %s",
MULTIPART_FORM_DATA, e.getMessage()), e);
}
final FileItemHeaders fih = item.getHeaders();
fileItem.setHeaders(fih);
}
还是不行,最后忽然想到 SpringBoot 默认的上传组件会不会影响这个转换呢?
修改配置,
SpringBoot 2.x 使用 servlet 配置
servlet:
multipart:
enabled: false
SpringBoot 1.x 使用 http 配置
http:
multipart:
enabled: false
发现终于成功获取到了.
最后贴下 Apache 上传组件代码使用的部分逻辑
@RequestMapping(value = "/test/file")
@ResponseBody
public String file (HttpServletRequest request,HttpServletResponse response) throws IOException{
response.setContentType("application/json;charset=utf-8");
try{
//使用Apache文件上传组件处理文件上传步骤:
//1、创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB
/*//设置上传时生成的临时文件的保存目录
factory.setRepository(tmpFile);*/
//2、创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//监听文件上传进度
upload.setProgressListener(new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int arg2) {
System.out.println("文件大小为:" + pContentLength + ",当前已处理:" + pBytesRead);
}
});
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//3、判断提交上来的数据是否是上传表单的数据
if(!ServletFileUpload.isMultipartContent(request)){
//按照传统方式获取数据
return;
}
//设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
upload.setFileSizeMax(1024*1024*10);
//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB
upload.setSizeMax(1024*1024*30);
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> list = upload.parseRequest(request);
for(FileItem item : list){
//如果fileitem中封装的是普通输入项的数据
if(item.isFormField()){
String name = item.getFieldName();
//解决普通输入项的数据的中文乱码问题
String value = item.getString("UTF-8");
//value = new String(value.getBytes("iso8859-1"),"UTF-8");
System.out.println(name + "=" + value);
}else{//如果fileitem中封装的是上传文件
//得到上传的文件名称,
String filename = item.getName();
System.out.println(filename);
if(filename==null || filename.trim().equals("")){
continue;
}
//注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
//处理获取到的上传文件的文件名的路径部分,只保留文件名部分
filename = filename.substring(filename.lastIndexOf("\\")+1);
//得到上传文件的扩展名
String fileExtName = filename.substring(filename.lastIndexOf(".")+1);
filename = filename.substring(0,filename.lastIndexOf("."));
//如果需要限制上传的文件类型,那么可以通过文件的扩展名来判断上传的文件类型是否合法
System.out.println("上传的文件的扩展名是:"+fileExtName);
//保存文件
FileManager fileManager = new FileManager();
if(FileManager.ERROR.equals(fileManager.save(filename,fileExtName,"song",item.getInputStream()))){
return JsonUtil.statusResponse(1,"上传文件失败",null);
}
}
}
}catch (FileUploadBase.FileSizeLimitExceededException e) {
e.printStackTrace();
return JsonUtil.statusResponse(1,"单个文件超出最大值!!!",null);
}catch (FileUploadBase.SizeLimitExceededException e) {
e.printStackTrace();
return JsonUtil.statusResponse(1,"上传文件的总的大小超出限制的最大值!!!",null);
}catch (Exception e) {
e.printStackTrace();
return JsonUtil.statusResponse(1,"其他异常,上传失败!!!",null);
}
return JsonUtil.statusResponse(0,"上传文件成功",fileManager.getFileURI(filename,fileExtName));
}
为何不学cc++,汇编,计算机原理,这是基础,是源头
spring boot入门挺难的。