文章目录
- 一、情景描述
- 二、问题解析
- 三、问题解决
- 1. 修改配置文件
- 2. 解析request
- 3. 用法
一、情景描述
前段时间QA提了个bug,说文件上传时,前端进度条走完了,但是还是得等一段时间才能上传完成。然后由于那时候我刚好事比较少,就让我来看了。
二、问题解析
从bug描述看来,应该是前端进度条只是前端上传文件到后端的时间(后端本地的临时文件),然后后面等的那段时间是后端上传文件到文件存储的时间。如果要解决这个问题,要么前端进度条改成整体流程的时间(治标不治本),要么后端就不存临时文件,由原本的接收整个文件改为接收文件流,这样就可以以流的形式上传,就可以实现前端一边传后端,后端一边传文件存储了。
三、问题解决
1. 修改配置文件
经查阅得知,可从Request中解析得到文件的流。但是想不缓存到本地,就得禁用掉spring的multipart操作,也就是在配置文件中,修改一下(本文用的是springboot,如果用的springMVC可以去配置中删除即可):
spring:
servlet:
multipart:
enabled: false
如果之后运行报文件大小超过多少多少的,可以再加几个参数:
spring:
servlet:
multipart:
enabled: false
max-file-size: 5120MB #最大文件大小
max-request-size: 5120MB #最大request大小
如果发现改完不起作用,检查一下代码中是否含有自定义的MultipartFile 配置类,可以注释掉(不删是为了防止出错),然后再试试。
2. 解析request
然后我自己写了个解析Request的类,实现了MultipartFile 接口,这样可以很大程度上保证业务逻辑代码可不变,只需controller代码修改一点即可,然后MultipartFile 的那些方法,可根据自己的需求去实现,当然也可以全部实现,前提是你有时间有心情(当然我没有。。。)
代码如下:
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
import org.apache.tomcat.util.http.fileupload.FileItemIterator;
import org.apache.tomcat.util.http.fileupload.FileItemStream;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* @author 琪丶琪
*/
public class MyMultipartFile implements MultipartFile {
// 文件的参数名称,比如uploadFile
private String name;
// 文件的实际名称,比如text.txt
private String originalFilename;
// 文件的类型
private String contentType;
// 文件的字节大小
private long size;
// 文件的输入流
private InputStream inputStream;
public MyMultipartFile(HttpServletRequest request) throws IOException, FileUploadException {
// 请求头中,需加上一个参数X-Multipart-Size(自定义参数,名称可随意,值为文件字节大小)
String sizeStr = request.getHeader("X-Multipart-Size");
// 原本是要求请求头必须传X-Multipart-Size信息的
// 后来考虑到系统兼容性,所以就加了请求头不含X-Multipart-Size信息的实现(也就是先暂存到本地)
if (StringUtils.isEmpty(sizeStr)) {
// 若请求头信息中,不包含 X-Multipart-Size 信息,就使用先暂存到本地的方式
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
//解析request请求
List<FileItem> items = upload.parseRequest(new ServletRequestContext(request));
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (item.isFormField()) {
//如果是表单域 ,就是非文件上传元素 必须要判断的
break;
}
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
long size = item.getSize();
InputStream inputStream = item.getInputStream();
this.originalFilename = fileName;
this.size = size;
this.inputStream = inputStream;
this.contentType = contentType;
= fieldName;
}
} else {
// 请求头包含 X-Multipart-Size 信息,则直接使用流的方式解析
final FileItemIterator iterStream = new ServletFileUpload().getItemIterator(request);
while (iterStream.hasNext()) {
FileItemStream item = iterStream.next();
if (item.isFormField()) {
break;
}
FileItemHeaders headers = item.getHeaders();
// // 查看Headers中信息
// for (Iterator<String> it = headers.getHeaderNames(); it.hasNext(); ) {
// String header = it.next();
// System.out.println(header + ": " + headers.getHeader(header));
// }
String content = headers.getHeader("content-disposition").substring(11);
String contentType = headers.getHeader("content-type");
String filename = content.substring(content.indexOf("filename=\"") + 10).split("\"")[0];
String name = content.substring(content.indexOf("name=\"") + 6).split("\"")[0];
long size = Long.parseLong(sizeStr);
InputStream input = item.openStream();
this.originalFilename = filename;
this.size = size;
this.inputStream = input;
this.contentType = contentType;
= name;
break;
}
}
if (this.originalFilename == null || this.size > 0) {
// 文件获取失败,可以抛个异常或者打日志
}
}
@Override
public String getName() {
return ;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return this.originalFilename == null || this.size > 0;
}
@Override
public long getSize() {
return this.size;
}
@Override
public byte[] getBytes() throws IOException {
// 这方法我用不到,就用个空实现,有需要可以写自己的实现
return new byte[0];
}
@Override
public InputStream getInputStream() throws IOException {
return this.inputStream;
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
// 这方法我用不到,就用个空实现,有需要可以写自己的实现
}
}
需要注意的是,只有请求头中包含X-Multipart-Size,用的才是不会先读取到本地的方式。如果请求头不包含X-Multipart-Size,则会依旧使用先读取到本地的方式。
为啥不直接定死一定得加请求头呢?因为这项目已经在最后收尾阶段了,不能确定所有涉及到的点,所以就做了个兼容。
3. 用法
大致用法如下,需要注意的是,参数中不需要加接收MultipartFile 的参数:
@ApiOperation(value = "上传文件")
@PostMapping("/addFile")
String addFile(HttpServletRequest request) {
MultipartFile file = null;
try {
file = new MyMultipartFile(request);
} catch (IOException | FileUploadException e) {
// 文件获取失败,可以抛个异常或者打日志
e.printStackTrace();
}
if (!file.isEmpty()) {
// 上传文件的逻辑代码,剩下的代码和之前直接接收MultipartFile一样
return "上传成功";
}
return "文件为空";
}