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
-
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
其他资料
-
解压缩:zip4j类。https://www.coder.work/article/6454309
-
批量上传文件:MultipartFile[] file https://www.jianshu.com/p/74802e8385cf
-
MultipartFile 和 CommonsMultipartFile的区别 https://www.cnblogs.com/jia0504/p/13991578.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)