02 文件上传

本文所有内容都是流式上传(数据通过body传输)对于本地文件上传(传入本地路径,服务端根据路径读取文件,比较简单)不做讨论。文件上传可以分为普通上传、表单上传、分片上传。

一、普通上传

直接在body传输文件的字节流,可以在header中传递业务参数。

  • 仅支持单文件上传

  • Content-Type为文件类型,如audio/mpeg

  • 复制代码
    // GPT生成,仅供参考
    import
    java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/upload") public class FileUploadServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取请求头中的参数 String fileName = request.getHeader("filename"); // 获取请求体中的文件内容 InputStream inputStream = request.getInputStream(); // 保存文件到指定路径 String uploadPath = "path/to/upload/directory"; File uploadDir = new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdirs(); } String filePath = uploadPath + File.separator + fileName; try (OutputStream outputStream = new FileOutputStream(filePath)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } // 返回成功响应 response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("上传成功"); } }
    复制代码

二、表单上传(推荐✅)

使用multipart/form-data类型上传文件

  • 支持单文件、多文件

  • 后端处理方式可以为:原生HttpServletRequest、MultipartFile、FileUpload

  • Content-Type为multipart/form-data

  • 支持同时传递参数、文件(不宜过大)

  • 复制代码
    // 使用原生HttpServletRequest处理上传文件
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.file.Files;
    import java.nio.file.StandardCopyOption;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.MultipartConfig;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.Part;
    
    @WebServlet("/upload")
    @MultipartConfig
    public class FileUploadServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 获取上传的文件
            Part filePart = request.getPart("file");
            String fileName = getFileName(filePart);
    
            // 保存文件到指定路径
            String uploadPath = "path/to/upload/directory";
            File uploadDir = new File(uploadPath);
            if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }
    
            String filePath = uploadPath + File.separator + fileName;
            try (InputStream inputStream = filePart.getInputStream()) {
                Files.copy(inputStream, new File(filePath).toPath(), StandardCopyOption.REPLACE_EXISTING);
            }
    
            // 返回成功响应
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().write("上传成功");
        }
    
        private String getFileName(Part part) {
            String contentDisp = part.getHeader("content-disposition");
            String[] tokens = contentDisp.split(";");
            for (String token : tokens) {
                if (token.trim().startsWith("filename")) {
                    return token.substring(token.indexOf("=") + 2, token.length() - 1);
                }
            }
            return "";
        }
    }
    复制代码
  • 复制代码
    // 使用了MultipartFile工具类之后,我们对文件上传的操作就简便许多了。
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    @RestController
    public class BatchUploadApplication {
    
        @PostMapping("/upload")
        public List<String> uploadFiles(@RequestParam("files") MultipartFile[] files,
                                                                @RequestParam("param1") String param1,
                                                               @RequestParam("param2") String param2) {
            List<String> fileUrls = new ArrayList<>();
    
            // 检查文件数组是否为空
            if (files == null || files.length == 0) {
                throw new IllegalArgumentException("No files to upload");
            }
    
            // 遍历文件数组并保存文件
            for (MultipartFile file : files) {
                if (!file.isEmpty()) {
                    String fileName = StringUtils.cleanPath(file.getOriginalFilename());
                    try {
                        // 保存文件到指定路径
                        String filePath = "/path/to/save/" + fileName;
                        File dest = new File(filePath);
                         // 将MultipartFile类型的文件转化为File类型的文件
                        file.transferTo(dest);
                        fileUrls.add(filePath);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
          
           // 在这里可以对参数进行业务逻辑处理
    
            return fileUrls;
        }
    }
    复制代码
  • 复制代码
    //  使用FileUpload: Apache commons下面的一个子项目,用来实现文件上传功能,主要使用 FileItem类、ServletFileUpload 类、DiskFileItemFactory类
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileItemFactory;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    
    @WebServlet("/upload")
    public class FileUploadServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // 检查请求是否为multipart/form-data类型
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (!isMultipart) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "请求不是multipart/form-data类型");
                return;
            }
    
            // 创建文件项工厂
            FileItemFactory factory = new DiskFileItemFactory();
    
            // 创建文件上传处理器
            ServletFileUpload upload = new ServletFileUpload(factory);
    
            try {
                // 解析请求,获取文件项列表
                List<FileItem> items = upload.parseRequest(request);
    
                // 遍历文件项列表
                for (FileItem item : items) {
                    // 检查是否为文件项
                    if (!item.isFormField()) {
                        // 获取上传文件的文件名
                        String fileName = item.getName();
    
                        // 创建目标文件
                        String uploadPath = "path/to/upload/directory";
                        File uploadDir = new File(uploadPath);
                        if (!uploadDir.exists()) {
                            uploadDir.mkdirs();
                        }
                        File file = new File(uploadPath + File.separator + fileName);
    
                        // 将文件内容以流的形式写入目标文件
                        try (InputStream inputStream = item.getInputStream();
                             OutputStream outputStream = new FileOutputStream(file)) {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, bytesRead);
                            }
                        }
                    }
                }
    
                // 返回成功响应
                response.setStatus(HttpServletResponse.SC_OK);
                response.getWriter().write("上传成功");
            } catch (Exception e) {
                e.printStackTrace();
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "上传失败");
            }
        }
    }

    // 高级方式:使用FileItemIterator直接操作每个文件流
    public ResponseResult dataUpload(HttpServletRequest request) {
    ServletFileUpload upload = new ServletFileUpload();
    FileItemIterator iter = upload.getItemIterator(request);
    String sessionId = "", tempFilePath = "", sessionIdMd5 = "";
    while (iter.hasNext()) {
    FileItemStream item = iter.next();
    String name = item.getFieldName();
    InputStream stream = item.openStream();
    if (item.isFormField()) {
    if (name.equals("session_id")) {
    sessionId = Streams.asString(stream);
    sessionIdMd5 = DigestUtils.md5DigestAsHex(sessionId.getBytes());
    }
    } else {
    if (StringUtils.isEmpty(sessionId)) {
    return ResponseResult.error(ErrorConstant.REQUEST_PARAMETERS_EXCEPTION);
    }
    redisCacheService.setUploadFileProcess(sessionId, FileUploadProcess.PROCESSING_UPLOAD_TRANSCRIPTION_SERVICE);
    tempFilePath = tempLocalDir + sessionIdMd5;
    OutputStream out = new FileOutputStream(tempFilePath);
    IOUtils.copy(stream, out);
    stream.close();
    out.close();
    }
    }
    return uploadService.dataUpload(sessionId, sessionIdMd5, tempFilePath);
    }
    复制代码

三、分片上传

当文件较大时,需要分片上传

//todo

四、其他

4.1 前端处理方式

浏览器如果想传输文件,通过表单方式(multipart/form-data)

  • 文件上传为什么要用 multipart/form-data?

    因为application/x-www-form-urlencoded类型不适合用于传输大型二进制数据或者包含非ASCII字符的数据。平常我们使用这个类型都是把表单数据使用url编码后传送给后端,二进制文件当然没办法一起编码进去了。所以multipart/form-data就诞生了,专门用于有效的传输文件。https://zhuanlan.zhihu.com/p/120834588

 4.2 MultipartFile

  • 常用方法

    • transferTo(dest)方法:transferTo方法

      使用transferTo(dest)方法将MultipartFile文件写到指定的File文件(服务器上的临时文件);

      MultipartFile中的transferTo(dest)方法只能使用一次;并且使用transferTo方法之后不可以在使用getInputStream()方法(原因文件流只可以接收读取一次,传输完毕则关闭流)

      注:transferTo的方式将文件存储到堆外内存

4.3 上传大文件

上传大文件时,要注意OOM问题。

1、传统的在内存中读取

读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法:

Files.readLines(new File(path), Charsets.UTF_8); FileUtils.readLines(new File(path));

这种方法带来的问题是文件的所有行都被存放在内存中,当文件足够大时很快就会导致程序抛出OutOfMemoryError 异常。

2、文件流

现在让我们看下这种解决方案——我们将使用java.util.Scanner类扫描文件的内容,一行一行连续地读取:

这种方案将会遍历文件中的所有行——允许对每一行进行处理,而不保持对它的引用。总之没有把它们存放在内存中

3、Apache Commons IO流

同样也可以使用Commons IO库实现,利用该库提供的自定义LineIterator

其他资料

posted @   zhegeMaw  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示