springboot如何直接从Request中解析上传的文件,前端上传和后端解析如何同步执行

3 篇文章 0 订阅
2 篇文章 0 订阅

一、情景描述

前段时间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;
                this.name = 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;
                this.name = name;
                break;
            }
        }
        if (this.originalFilename == null || this.size > 0) {
            // 文件获取失败,可以抛个异常或者打日志
        }
    }

    @Override
    public String getName() {
        return this.name;
    }

    @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 "文件为空";
    }
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以按照以下步骤将前端通过 Element-UI 上传文件传输到后端并存储到数据库: 1. 在前端使用 Element-UI 的 Upload 组件,设置好文件上传的相关参数,如文件类型、大小限制等。确保文件上传时能够获取到需要上传文件。 2. 前端通过 Ajax 或其他方式将文件发送到后端,确保请求包含上传文件数据。 3. 后端接收到前端发送的文件数据后,可以使用 Spring MVC 框架的 `@RequestParam` 注解来接收文件。示例代码如下: ```java @RequestMapping(value = "/uploadFile", method = RequestMethod.POST) public String uploadFile(@RequestParam("file") MultipartFile file) { // 处理文件上传逻辑 // 可以使用 file.getInputStream() 获取文件输入流,进行进一步操作 return "success"; } ``` 4. 在后端将接收到的文件数据存储到数据库。可以根据自己的需求选择适合的数据库操作方式,如使用 MyBatis 进行数据持久化操作。示例代码如下: ```java @Autowired private FileMapper fileMapper; public void saveFile(FileEntity fileEntity) { fileMapper.insert(fileEntity); // 将文件实体插入数据库表 } ``` 5. 在前端可以通过接口返回的数据进行相应的处理,如显示上传成功的提示信息或展示已上传文件列表等。 需要注意的是,为了保证文件上传的安全性和可靠性,你可能还需要进行一些文件校验、权限控制和异常处理等操作。具体实现方式可以根据你的项目需求和技术栈进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值