Spring - 如何将大型多部分文件上传到数据库而不存储在本地文件系统上

Spring - How to stream large multipart file uploads to database without storing on local file system

Spring 引导的默认 MultiPartResolver 接口通过将多部分文件存储在本地文件系统中来处理多部分文件的上传。在进入controller方法之前,整个multipart文件必须完成上传到服务器。

我们将所有上传的文件直接存储到数据库中,并且我们的服务器的磁盘配额非常小,因此如果上传大文件,我们会看到 IOExeption - Disk quota exceeded

在 Spring 的 MultiPartResolver 将文件存储在本地文件系统之前,有没有办法直接从客户端的传入请求中获取流,以便我们可以直接流式传输到我们的数据库?

其实这不是一件小事。如果您想将流从客户端直接写入数据库,则必须手动处理请求。有一些库可以简化此任务。其中之一是 "Apache Commons FileUpload"。下面是一个非常简单的例子,你如何处理这个库传入的 multipart/form-data 请求。

@Controller
public class Controller{

    @RequestMapping("/upload")
    public String upload(HttpServletRequest request){
        
        String boundary = extractBoundary(request);

        try {
            MultipartStream multipartStream = new MultipartStream(request.getInputStream(), 
                boundary.getBytes(), 1024, null);
            boolean nextPart = multipartStream.skipPreamble();
            while(nextPart) {
                String header = multipartStream.readHeaders();

                if(header.contains("filename")){
                    //if input is file
                    OutputStream output = createDbOutputStream();
                    multipartStream.readBodyData(output);
                    output.flush();
                    output.close();
                } else {
                    //if input is not file (text, checkbox etc)
                    ByteArrayOutputStream output = new ByteArrayOutputStream();
                    multipartStream.readBodyData(output);
                    String value = output.toString("utf-8");
                    //... do something with extracted value
                }
                nextPart = multipartStream.readBoundary();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }    
    }

    private String extractBoundary(HttpServletRequest request) {
        String boundaryHeader = "boundary=";
        int i = request.getContentType().indexOf(boundaryHeader)+
            boundaryHeader.length();
        return request.getContentType().substring(i);
    }    
}

Header 文件字段将如下所示:

Content-Disposition: form-data; name="fieldName"; filename="fileName.jpg"
Content-Type: image/jpeg

Header 对于简单字段将如下所示:

Content-Disposition: form-data; name="fieldName";

请注意,此代码段只是为您指明方向的简化示例。没有一些细节,例如:从 header 中提取字段名称、创建数据库输出流等。您可以自己实现所有这些东西。 您可以在 RFC1867. Information about multipart/form-data RFC2388.

中找到多部分请求字段 headers 的示例

您可以直接使用 apache,如此处所述https://commons.apache.org/proper/commons-fileupload/streaming.html

@Controller
public class UploadController {

    @RequestMapping("/upload")
    public String upload(HttpServletRequest request) throws IOException, FileUploadException {

        ServletFileUpload upload = new ServletFileUpload();

        FileItemIterator iterator = upload.getItemIterator(request);
        while (iterator.hasNext()) {
            FileItemStream item = iterator.next();

            if (!item.isFormField()) {
                InputStream inputStream = item.openStream();
                //...
            }
        }
    }
}

确保禁用 springs 多部分解析机制。

application.yml:

spring:
   http:
      multipart:
         enabled: false